diff --git a/src/bytes/buffer.ts b/src/bytes/buffer.ts index a2c458e..9d3eee8 100644 --- a/src/bytes/buffer.ts +++ b/src/bytes/buffer.ts @@ -1,16 +1,49 @@ +/** + * @module + * Buffer provides an in-memory byte buffer that implements Reader and Writer interfaces. + */ + import { EOFError } from "../io/error.ts"; import { Reader } from "../io/reader.ts"; import { Writer } from "../io/writer.ts"; +// Re-export io types that Buffer implements +export type { Reader, Writer }; + +/** + * MinRead is the minimum read size for Buffer.readFrom operations. + */ export const MinRead = 512; +/** + * TooLargeError indicates that a buffer operation would exceed the maximum buffer size. + */ export class TooLargeError extends Error { + /** + * Creates a new TooLargeError. + * + * @param message - The error message, defaults to "bytes buffer: too large" + */ constructor(message = "bytes buffer: too large") { super(message); this.name = "TooLargeError"; } } +/** + * Buffer is an in-memory byte buffer that implements Reader and Writer interfaces. + * Similar to Go's bytes.Buffer, it provides efficient read and write operations + * with automatic growth and UTF-8 rune handling. + * + * @example + * ```ts + * import { bytes } from "@okudai/golikejs"; + * + * const buf = bytes.Buffer.make(128); + * await buf.write(new TextEncoder().encode("hello")); + * const data = buf.bytes(); + * ``` + */ export class Buffer implements Reader, Writer { #buf: Uint8Array; #off: number; // read offset @@ -18,6 +51,11 @@ export class Buffer implements Reader, Writer { #lastReadOp: "byte" | "rune" | "read" | null; #lastReadSize: number; + /** + * Creates a new Buffer backed by the given memory. + * + * @param memory - The ArrayBufferLike to use as backing storage + */ constructor(memory: ArrayBufferLike) { this.#buf = new Uint8Array(memory); this.#off = 0; @@ -26,41 +64,85 @@ export class Buffer implements Reader, Writer { this.#lastReadSize = 0; } + /** + * Creates a new Buffer with the specified capacity. + * + * @param capacity - The initial capacity in bytes + * @returns A new Buffer instance + */ static make(capacity: number): Buffer { const buf = new Uint8Array(capacity); return new Buffer(buf.buffer); } + /** + * Returns a view of the unread portion of the buffer. + * + * @returns A Uint8Array view of the unread data + */ bytes(): Uint8Array { return this.#buf.subarray(this.#off, this.#len); } + /** + * Returns the number of unread bytes in the buffer. + */ get size(): number { return this.#len - this.#off; } + /** + * Returns the buffer's capacity. + */ get capacity(): number { return this.#buf.length; } - // Len and Cap helpers (Go-like names) + /** + * Returns the number of unread bytes (Go-style method name). + * + * @returns The number of unread bytes + */ len(): number { return this.size; } + /** + * Returns the buffer's capacity (Go-style method name). + * + * @returns The buffer capacity + */ cap(): number { return this.capacity; } + /** + * Converts the unread portion of the buffer to a UTF-8 string. + * + * @returns A string decoded from the unread bytes + */ toString(): string { return new TextDecoder().decode(this.bytes()); } + /** + * Writes a UTF-8 encoded string to the buffer. + * + * @param s - The string to write + * @returns A tuple of [bytes written, error] + */ async writeString(s: string): Promise<[number, Error | undefined]> { const data = new TextEncoder().encode(s); return await this.write(data); } + /** + * Returns the next n bytes from the buffer and advances the read position. + * + * @param n - The number of bytes to return + * @returns A Uint8Array containing the next n bytes (or fewer if EOF) + * @throws {Error} If n is negative + */ next(n: number): Uint8Array { if (n < 0) throw new Error("bytes buffer: negative next length"); const avail = this.size; @@ -73,6 +155,12 @@ export class Buffer implements Reader, Writer { return res; } + /** + * Discards all but the first n unread bytes from the buffer. + * + * @param n - The number of bytes to keep + * @throws {Error} If n is negative or greater than buffer size + */ truncate(n: number) { if (n < 0 || n > this.size) { throw new Error("bytes buffer: truncate out of range"); @@ -81,6 +169,11 @@ export class Buffer implements Reader, Writer { // if (this.#off === this.#len) this.reset(); } + /** + * Unreads the last byte read by readByte. + * + * @returns An error if the last operation was not readByte, undefined otherwise + */ unreadByte(): Error | undefined { if (this.#lastReadOp !== "byte" || this.#lastReadSize !== 1) { return new Error("bytes buffer: cannot unread byte"); @@ -198,6 +291,11 @@ export class Buffer implements Reader, Writer { return [code, size, undefined]; } + /** + * Unreads the last rune read by readRune. + * + * @returns An error if the last operation was not readRune, undefined otherwise + */ unreadRune(): Error | undefined { if (this.#lastReadOp !== "rune" || this.#lastReadSize <= 0) { return new Error("bytes buffer: cannot unread rune"); @@ -261,17 +359,33 @@ export class Buffer implements Reader, Writer { return [new TextDecoder().decode(b), undefined]; } + /** + * Writes a single Unicode code point to the buffer. + * + * @param r - The Unicode code point to write + * @returns A tuple of [bytes written, error] + */ async writeRune(r: number): Promise<[number, Error | undefined]> { const s = String.fromCodePoint(r); const data = new TextEncoder().encode(s); return await this.write(data); } + /** + * Resets the buffer to be empty. + */ reset() { this.#off = 0; this.#len = 0; } + /** + * Reads data into the provided buffer. + * Implements the Reader interface. + * + * @param buf - The buffer to read into + * @returns A tuple of [bytes read, error] + */ async read(buf: Uint8Array): Promise<[number, Error | undefined]> { const bytesAvailable = this.size; const bytesToRead = Math.min(buf.length, bytesAvailable); @@ -288,6 +402,11 @@ export class Buffer implements Reader, Writer { return [bytesToRead, undefined]; } + /** + * Reads and returns the next byte from the buffer. + * + * @returns A tuple of [byte value, error] + */ readByte(): [number, Error | undefined] { if (this.size < 1) { return [0, new EOFError()]; @@ -302,6 +421,13 @@ export class Buffer implements Reader, Writer { return [value, undefined]; } + /** + * Writes data to the buffer. + * Implements the Writer interface. + * + * @param data - The data to write + * @returns A tuple of [bytes written, error] + */ async write(data: Uint8Array): Promise<[number, Error | undefined]> { this.grow(data.length); this.#buf.set(data, this.#len); @@ -311,6 +437,12 @@ export class Buffer implements Reader, Writer { this.#lastReadSize = 0; return [data.length, undefined]; } + + /** + * Writes a single byte to the buffer. + * + * @param value - The byte value to write + */ writeByte(value: number): void { this.grow(1); this.#buf[this.#len] = value; @@ -319,6 +451,12 @@ export class Buffer implements Reader, Writer { this.#lastReadSize = 0; } + /** + * Grows the buffer to guarantee space for n more bytes. + * + * @param n - The number of additional bytes needed + * @throws {Error} If n is negative + */ grow(n: number) { if (n < 0) { throw new Error("Cannot grow buffer by a negative size"); diff --git a/src/bytes/search.ts b/src/bytes/search.ts index 9dd98ed..74f61bf 100644 --- a/src/bytes/search.ts +++ b/src/bytes/search.ts @@ -1,5 +1,15 @@ -// compare returns an integer comparing two byte slices lexicographically. -// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. +/** + * @module + * Search and comparison functions for byte slices. + */ + +/** + * Compares two byte slices lexicographically. + * + * @param a - The first byte slice + * @param b - The second byte slice + * @returns 0 if a==b, -1 if a < b, and +1 if a > b + */ export function compare(a: Uint8Array, b: Uint8Array): number { const len = Math.min(a.length, b.length); for (let i = 0; i < len; i++) { @@ -16,19 +26,36 @@ export function compare(a: Uint8Array, b: Uint8Array): number { return 0; } -// clone returns a copy of the given byte slice. +/** + * Returns a copy of the given byte slice. + * + * @param src - The source byte slice to clone + * @returns A new byte slice containing a copy of src + */ export function clone(src: Uint8Array): Uint8Array { const dst = new Uint8Array(src.length); dst.set(src); return dst; } -// contains reports whether subslice is within b. +/** + * Reports whether subslice is within b. + * + * @param b - The byte slice to search in + * @param subslice - The byte slice to search for + * @returns true if subslice is found in b, false otherwise + */ export function contains(b: Uint8Array, subslice: Uint8Array): boolean { return index(b, subslice) >= 0; } -// containsAny reports whether any of the UTF-8-encoded code points in chars are within b. +/** + * Reports whether any of the UTF-8-encoded code points in chars are within b. + * + * @param b - The byte slice to search in + * @param chars - String containing characters to search for + * @returns true if any character from chars is found in b + */ export function containsAny(b: Uint8Array, chars: string): boolean { const charBytes = new TextEncoder().encode(chars); for (const char of charBytes) { @@ -39,8 +66,14 @@ export function containsAny(b: Uint8Array, chars: string): boolean { return false; } -// count counts the number of non-overlapping instances of sep in s. -// If sep is an empty slice, count returns 1 + the number of UTF-8-encoded code points in s. +/** + * Counts the number of non-overlapping instances of sep in s. + * If sep is an empty slice, count returns 1 + the number of UTF-8-encoded code points in s. + * + * @param s - The byte slice to search in + * @param sep - The separator to count + * @returns The number of occurrences of sep in s + */ export function count(s: Uint8Array, sep: Uint8Array): number { if (sep.length === 0) { return new TextDecoder().decode(s).length + 1; @@ -54,8 +87,13 @@ export function count(s: Uint8Array, sep: Uint8Array): number { return count; } -// equal reports whether a and b are the same length and contain the same bytes. -// A nil argument is equivalent to an empty slice. +/** + * Reports whether a and b are the same length and contain the same bytes. + * + * @param a - The first byte slice + * @param b - The second byte slice + * @returns true if a and b are equal + */ export function equal(a: Uint8Array, b: Uint8Array): boolean { if (a.length !== b.length) { return false; @@ -68,14 +106,26 @@ export function equal(a: Uint8Array, b: Uint8Array): boolean { return true; } -// equalFold reports whether s and t, interpreted as UTF-8 strings, are equal under Unicode case-folding. +/** + * Reports whether s and t, interpreted as UTF-8 strings, are equal under Unicode case-folding. + * + * @param s - The first byte slice + * @param t - The second byte slice + * @returns true if s and t are equal ignoring case + */ export function equalFold(s: Uint8Array, t: Uint8Array): boolean { const strS = new TextDecoder().decode(s).toLowerCase(); const strT = new TextDecoder().decode(t).toLowerCase(); return strS === strT; } -// hasPrefix tests whether the byte slice s begins with prefix. +/** + * Tests whether the byte slice s begins with prefix. + * + * @param s - The byte slice to test + * @param prefix - The prefix to check for + * @returns true if s starts with prefix + */ export function hasPrefix(s: Uint8Array, prefix: Uint8Array): boolean { if (prefix.length > s.length) { return false; @@ -88,7 +138,13 @@ export function hasPrefix(s: Uint8Array, prefix: Uint8Array): boolean { return true; } -// hasSuffix tests whether the byte slice s ends with suffix. +/** + * Tests whether the byte slice s ends with suffix. + * + * @param s - The byte slice to test + * @param suffix - The suffix to check for + * @returns true if s ends with suffix + */ export function hasSuffix(s: Uint8Array, suffix: Uint8Array): boolean { if (suffix.length > s.length) { return false; @@ -102,7 +158,13 @@ export function hasSuffix(s: Uint8Array, suffix: Uint8Array): boolean { return true; } -// index returns the index of the first instance of sep in s, or -1 if sep is not present in s. +/** + * Returns the index of the first instance of sep in s, or -1 if sep is not present in s. + * + * @param s - The byte slice to search in + * @param sep - The separator to search for + * @returns The index of the first occurrence, or -1 if not found + */ export function index(s: Uint8Array, sep: Uint8Array): number { if (sep.length === 0) { return 0; @@ -125,9 +187,14 @@ export function index(s: Uint8Array, sep: Uint8Array): number { return -1; } -// indexAny interprets s as a sequence of UTF-8-encoded Unicode code points. -// It returns the byte index of the first occurrence in s of any of the Unicode code points in chars. -// It returns -1 if chars is empty or if there is no code point in common. +/** + * Interprets s as a sequence of UTF-8-encoded Unicode code points. + * Returns the byte index of the first occurrence in s of any of the Unicode code points in chars. + * + * @param s - The byte slice to search in + * @param chars - String containing characters to search for + * @returns The byte index of the first match, or -1 if no match found + */ export function indexAny(s: Uint8Array, chars: string): number { const charSet = new Set(chars.split("").map((c) => c.charCodeAt(0))); const str = new TextDecoder().decode(s); @@ -139,7 +206,13 @@ export function indexAny(s: Uint8Array, chars: string): number { return -1; } -// indexByte returns the index of the first instance of c in s, or -1 if c is not present in s. +/** + * Returns the index of the first instance of c in s, or -1 if c is not present in s. + * + * @param s - The byte slice to search in + * @param c - The byte value to search for + * @returns The index of the first occurrence, or -1 if not found + */ export function indexByte(s: Uint8Array, c: number): number { for (let i = 0; i < s.length; i++) { if (s[i] === c) { @@ -149,8 +222,14 @@ export function indexByte(s: Uint8Array, c: number): number { return -1; } -// indexFunc interprets s as a sequence of UTF-8-encoded code points. -// It returns the byte index in s of the first Unicode code point satisfying f(c), or -1 if none do. +/** + * Interprets s as a sequence of UTF-8-encoded code points. + * Returns the byte index in s of the first Unicode code point satisfying f(c), or -1 if none do. + * + * @param s - The byte slice to search in + * @param f - Predicate function to test each code point + * @returns The byte index of the first match, or -1 if no match found + */ export function indexFunc(s: Uint8Array, f: (r: number) => boolean): number { const str = new TextDecoder().decode(s); for (let i = 0; i < str.length; i++) { @@ -161,8 +240,14 @@ export function indexFunc(s: Uint8Array, f: (r: number) => boolean): number { return -1; } -// indexRune interprets s as a sequence of UTF-8-encoded Unicode code points. -// It returns the byte index of the first occurrence in s of the given rune, or -1 if rune is not present in s. +/** + * Interprets s as a sequence of UTF-8-encoded Unicode code points. + * Returns the byte index of the first occurrence in s of the given rune, or -1 if rune is not present in s. + * + * @param s - The byte slice to search in + * @param r - The rune (code point) to search for + * @returns The byte index of the first occurrence, or -1 if not found + */ export function indexRune(s: Uint8Array, r: number): number { const str = new TextDecoder().decode(s); const runeStr = String.fromCharCode(r); @@ -173,7 +258,13 @@ export function indexRune(s: Uint8Array, r: number): number { return new TextEncoder().encode(str.slice(0, index)).length; } -// lastIndex returns the index of the last instance of sep in s, or -1 if sep is not present in s. +/** + * Returns the index of the last instance of sep in s, or -1 if sep is not present in s. + * + * @param s - The byte slice to search in + * @param sep - The separator to search for + * @returns The index of the last occurrence, or -1 if not found + */ export function lastIndex(s: Uint8Array, sep: Uint8Array): number { if (sep.length === 0) { return s.length; @@ -196,9 +287,14 @@ export function lastIndex(s: Uint8Array, sep: Uint8Array): number { return -1; } -// lastIndexAny interprets s as a sequence of UTF-8-encoded Unicode code points. -// It returns the byte index of the last occurrence in s of any of the Unicode code points in chars. -// It returns -1 if chars is empty or if there is no code point in common. +/** + * Interprets s as a sequence of UTF-8-encoded Unicode code points. + * Returns the byte index of the last occurrence in s of any of the Unicode code points in chars. + * + * @param s - The byte slice to search in + * @param chars - String containing characters to search for + * @returns The byte index of the last match, or -1 if no match found + */ export function lastIndexAny(s: Uint8Array, chars: string): number { const charSet = new Set(chars.split("").map((c) => c.charCodeAt(0))); const str = new TextDecoder().decode(s); @@ -210,7 +306,13 @@ export function lastIndexAny(s: Uint8Array, chars: string): number { return -1; } -// lastIndexByte returns the index of the last instance of c in s, or -1 if c is not present in s. +/** + * Returns the index of the last instance of c in s, or -1 if c is not present in s. + * + * @param s - The byte slice to search in + * @param c - The byte value to search for + * @returns The index of the last occurrence, or -1 if not found + */ export function lastIndexByte(s: Uint8Array, c: number): number { for (let i = s.length - 1; i >= 0; i--) { if (s[i] === c) { @@ -220,8 +322,14 @@ export function lastIndexByte(s: Uint8Array, c: number): number { return -1; } -// lastIndexFunc interprets s as a sequence of UTF-8-encoded code points. -// It returns the byte index in s of the last Unicode code point satisfying f(c), or -1 if none do. +/** + * Interprets s as a sequence of UTF-8-encoded code points. + * Returns the byte index in s of the last Unicode code point satisfying f(c), or -1 if none do. + * + * @param s - The byte slice to search in + * @param f - Predicate function to test each code point + * @returns The byte index of the last match, or -1 if no match found + */ export function lastIndexFunc(s: Uint8Array, f: (r: number) => boolean): number { const str = new TextDecoder().decode(s); for (let i = str.length - 1; i >= 0; i--) { diff --git a/src/bytes/split_join.ts b/src/bytes/split_join.ts index 579ed88..43c4aaa 100644 --- a/src/bytes/split_join.ts +++ b/src/bytes/split_join.ts @@ -1,6 +1,17 @@ -// cut slices s around the first instance of sep, returning the text before and after sep. -// The found result reports whether sep appears in s. -// If sep does not appear in s, cut returns s, empty slice, and false. +/** + * @module + * Functions for splitting and joining byte slices. + */ + +/** + * Slices s around the first instance of sep, returning the text before and after sep. + * The found result reports whether sep appears in s. + * If sep does not appear in s, cut returns s, empty slice, and false. + * + * @param s - The byte slice to cut + * @param sep - The separator to cut around + * @returns A tuple of [before, after, found] + */ export function cut(s: Uint8Array, sep: Uint8Array): [Uint8Array, Uint8Array, boolean] { const idx = index(s, sep); if (idx < 0) { @@ -9,9 +20,14 @@ export function cut(s: Uint8Array, sep: Uint8Array): [Uint8Array, Uint8Array, bo return [s.subarray(0, idx), s.subarray(idx + sep.length), true]; } -// cutPrefix returns s without the provided leading prefix slice and reports whether it found the prefix. -// If s doesn't start with prefix, cutPrefix returns s, false. -// If prefix is empty, cutPrefix returns s, true. +/** + * Returns s without the provided leading prefix slice and reports whether it found the prefix. + * If s doesn't start with prefix, returns s, false. If prefix is empty, returns s, true. + * + * @param s - The byte slice to process + * @param prefix - The prefix to cut + * @returns A tuple of [result, found] + */ export function cutPrefix(s: Uint8Array, prefix: Uint8Array): [Uint8Array, boolean] { if (prefix.length === 0) { return [s, true]; @@ -22,9 +38,14 @@ export function cutPrefix(s: Uint8Array, prefix: Uint8Array): [Uint8Array, boole return [s, false]; } -// cutSuffix returns s without the provided ending suffix slice and reports whether it found the suffix. -// If s doesn't end with suffix, cutSuffix returns s, false. -// If suffix is empty, cutSuffix returns s, true. +/** + * Returns s without the provided ending suffix slice and reports whether it found the suffix. + * If s doesn't end with suffix, returns s, false. If suffix is empty, returns s, true. + * + * @param s - The byte slice to process + * @param suffix - The suffix to cut + * @returns A tuple of [result, found] + */ export function cutSuffix(s: Uint8Array, suffix: Uint8Array): [Uint8Array, boolean] { if (suffix.length === 0) { return [s, true]; @@ -35,17 +56,27 @@ export function cutSuffix(s: Uint8Array, suffix: Uint8Array): [Uint8Array, boole return [s, false]; } -// fields interprets s as a sequence of UTF-8-encoded code points. -// It splits the slice s around each instance of one or more consecutive white space characters, as defined by unicode.IsSpace, returning a slice of subslices of s or an empty slice if s contains only white space. +/** + * Interprets s as a sequence of UTF-8-encoded code points. + * Splits the slice s around each instance of one or more consecutive white space characters. + * + * @param s - The byte slice to split + * @returns An array of byte slices representing fields + */ export function fields(s: Uint8Array): Uint8Array[] { const str = new TextDecoder().decode(s); const fields = str.split(/\s+/).filter((f) => f.length > 0); return fields.map((f) => new TextEncoder().encode(f)); } -// fieldsFunc interprets s as a sequence of UTF-8-encoded code points. -// It splits the slice s into subslices according to the predicate f. -// The subslices do not overlap and do not share underlying memory. +/** + * Interprets s as a sequence of UTF-8-encoded code points. + * Splits the slice s into subslices according to the predicate f. + * + * @param s - The byte slice to split + * @param f - Predicate function to determine split points + * @returns An array of byte slices + */ export function fieldsFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array[] { const result: Uint8Array[] = []; let start = 0; @@ -64,7 +95,14 @@ export function fieldsFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array return result; } -// join concatenates the elements of s to create a new byte slice. The separator sep is placed between elements in the result. +/** + * Concatenates the elements of s to create a new byte slice. + * The separator sep is placed between elements in the result. + * + * @param s - Array of byte slices to join + * @param sep - The separator to place between elements + * @returns A new byte slice with joined elements + */ export function join(s: Uint8Array[], sep: Uint8Array): Uint8Array { if (s.length === 0) { return new Uint8Array(0); @@ -86,12 +124,15 @@ export function join(s: Uint8Array[], sep: Uint8Array): Uint8Array { return result; } -// split slices s into all subslices separated by sep and returns a slice of the subslices between those separators. -// If sep is empty, split splits after each UTF-8 sequence. -// The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +/** + * Slices s into all subslices separated by sep and returns a slice of the subslices between those separators. + * If sep is empty, split splits after each UTF-8 sequence. + * + * @param s - The byte slice to split + * @param sep - The separator + * @param n - Maximum number of splits (n > 0: at most n subslices; n == 0: empty result; n < 0: all subslices) + * @returns An array of byte slices + */ export function split(s: Uint8Array, sep: Uint8Array, n: number): Uint8Array[] { if (n === 0) { return []; @@ -132,12 +173,15 @@ export function split(s: Uint8Array, sep: Uint8Array, n: number): Uint8Array[] { return result; } -// splitAfter slices s into all subslices after each instance of sep and returns a slice of those subslices. -// If sep is empty, splitAfter splits after each UTF-8 sequence. -// The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +/** + * Slices s into all subslices after each instance of sep and returns a slice of those subslices. + * If sep is empty, splitAfter splits after each UTF-8 sequence. + * + * @param s - The byte slice to split + * @param sep - The separator (included at end of each slice) + * @param n - Maximum number of splits (n > 0: at most n subslices; n == 0: empty result; n < 0: all subslices) + * @returns An array of byte slices + */ export function splitAfter(s: Uint8Array, sep: Uint8Array, n: number): Uint8Array[] { if (n === 0) { return []; @@ -178,22 +222,28 @@ export function splitAfter(s: Uint8Array, sep: Uint8Array, n: number): Uint8Arra return result; } -// splitAfterN slices s into subslices after each instance of sep and returns a slice of those subslices. -// If sep is empty, splitAfterN splits after each UTF-8 sequence. -// The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +/** + * Slices s into subslices after each instance of sep and returns a slice of those subslices. + * This is an alias for splitAfter with explicit count parameter. + * + * @param s - The byte slice to split + * @param sep - The separator (included at end of each slice) + * @param n - Maximum number of splits + * @returns An array of byte slices + */ export function splitAfterN(s: Uint8Array, sep: Uint8Array, n: number): Uint8Array[] { return splitAfter(s, sep, n); } -// splitN slices s into subslices separated by sep and returns a slice of the subslices between those separators. -// If sep is empty, splitN splits after each UTF-8 sequence. -// The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +/** + * Slices s into subslices separated by sep and returns a slice of the subslices between those separators. + * This is an alias for split with explicit count parameter. + * + * @param s - The byte slice to split + * @param sep - The separator + * @param n - Maximum number of splits + * @returns An array of byte slices + */ export function splitN(s: Uint8Array, sep: Uint8Array, n: number): Uint8Array[] { return split(s, sep, n); } diff --git a/src/bytes/transform.ts b/src/bytes/transform.ts index 0cf37b7..a5e2e5f 100644 --- a/src/bytes/transform.ts +++ b/src/bytes/transform.ts @@ -1,5 +1,16 @@ -// map returns a copy of the byte slice s with all its characters modified according to the mapping function. -// If mapping returns a negative value, the character is dropped from the string with no replacement. +/** + * @module + * Transformation functions for byte slices. + */ + +/** + * Returns a copy of the byte slice s with all its characters modified according to the mapping function. + * If mapping returns a negative value, the character is dropped from the string with no replacement. + * + * @param mapping - Function that maps each rune to a new rune (or negative to drop) + * @param s - The byte slice to transform + * @returns A new byte slice with mapped characters + */ export function map(mapping: (r: number) => number, s: Uint8Array): Uint8Array { const str = new TextDecoder().decode(s); let result = ""; @@ -12,7 +23,14 @@ export function map(mapping: (r: number) => number, s: Uint8Array): Uint8Array { return new TextEncoder().encode(result); } -// repeat returns a new byte slice consisting of count copies of b. +/** + * Returns a new byte slice consisting of count copies of b. + * + * @param b - The byte slice to repeat + * @param count - The number of times to repeat b + * @returns A new byte slice with b repeated count times + * @throws {Error} If count is negative + */ export function repeat(b: Uint8Array, count: number): Uint8Array { if (count === 0) { return new Uint8Array(0); @@ -30,9 +48,17 @@ export function repeat(b: Uint8Array, count: number): Uint8Array { return result; } -// replace returns a copy of the slice s with the first n non-overlapping instances of old replaced by new. -// If old is empty, it matches at the beginning of the slice and after each UTF-8 sequence, yielding up to k+1 replacements for a k-rune string. -// If n < 0, there is no limit on the number of replacements. +/** + * Returns a copy of the slice s with the first n non-overlapping instances of old replaced by new. + * If old is empty, it matches at the beginning of the slice and after each UTF-8 sequence. + * If n < 0, there is no limit on the number of replacements. + * + * @param s - The byte slice to search in + * @param old - The byte slice to replace + * @param new_ - The replacement byte slice + * @param n - Maximum number of replacements (-1 for unlimited) + * @returns A new byte slice with replacements made + */ export function replace(s: Uint8Array, old: Uint8Array, new_: Uint8Array, n: number): Uint8Array { if (old.length === 0) { const str = new TextDecoder().decode(s); @@ -64,14 +90,28 @@ export function replace(s: Uint8Array, old: Uint8Array, new_: Uint8Array, n: num return concat(...parts); } -// replaceAll returns a copy of the slice s with all non-overlapping instances of old replaced by new. -// If old is empty, it matches at the beginning of the slice and after each UTF-8 sequence, yielding up to k+1 replacements for a k-rune string. +/** + * Returns a copy of the slice s with all non-overlapping instances of old replaced by new. + * If old is empty, it matches at the beginning of the slice and after each UTF-8 sequence. + * + * @param s - The byte slice to search in + * @param old - The byte slice to replace + * @param new_ - The replacement byte slice + * @returns A new byte slice with all replacements made + */ export function replaceAll(s: Uint8Array, old: Uint8Array, new_: Uint8Array): Uint8Array { return replace(s, old, new_, -1); } -// runWhile returns a subslice of s, starting at index i and extending as long as r(i) returns true for each element. -// It panics if i is out of bounds. +/** + * Returns a subslice of s, starting at index i and extending as long as r(i) returns true for each element. + * + * @param s - The byte slice to process + * @param i - The starting index + * @param r - Predicate function to test each byte + * @returns A subslice while the predicate is true + * @throws {Error} If i is out of bounds + */ export function runWhile(s: Uint8Array, i: number, r: (b: number) => boolean): Uint8Array { if (i < 0 || i > s.length) { throw new Error("bytes: index out of bounds"); @@ -83,26 +123,47 @@ export function runWhile(s: Uint8Array, i: number, r: (b: number) => boolean): U return s.subarray(i, end); } -// toLower returns a copy of the byte slice s with all Unicode letters mapped to their lower case. +/** + * Returns a copy of the byte slice s with all Unicode letters mapped to their lower case. + * + * @param s - The byte slice to convert + * @returns A new byte slice with lowercase letters + */ export function toLower(s: Uint8Array): Uint8Array { const str = new TextDecoder().decode(s); return new TextEncoder().encode(str.toLowerCase()); } -// toTitle returns a copy of the byte slice s with all Unicode letters mapped to their title case. +/** + * Returns a copy of the byte slice s with all Unicode letters mapped to their title case. + * + * @param s - The byte slice to convert + * @returns A new byte slice with uppercase letters + */ export function toTitle(s: Uint8Array): Uint8Array { const str = new TextDecoder().decode(s); return new TextEncoder().encode(str.toUpperCase()); } -// toUpper returns a copy of the byte slice s with all Unicode letters mapped to their upper case. +/** + * Returns a copy of the byte slice s with all Unicode letters mapped to their upper case. + * + * @param s - The byte slice to convert + * @returns A new byte slice with uppercase letters + */ export function toUpper(s: Uint8Array): Uint8Array { const str = new TextDecoder().decode(s); return new TextEncoder().encode(str.toUpperCase()); } -// toValidUTF8 returns a copy of the byte slice s with each run of invalid UTF-8 byte sequences replaced by the replacement slice r. -// If r is nil or empty, invalid UTF-8 byte sequences are replaced by U+FFFD. +/** + * Returns a copy of the byte slice s with each run of invalid UTF-8 byte sequences replaced by the replacement slice r. + * If r is empty, invalid UTF-8 byte sequences are replaced by U+FFFD. + * + * @param s - The byte slice to validate + * @param r - The replacement byte slice for invalid sequences + * @returns A new byte slice with valid UTF-8 + */ export function toValidUTF8(s: Uint8Array, r: Uint8Array): Uint8Array { const decoder = new TextDecoder("utf-8", { fatal: false }); const str = decoder.decode(s); diff --git a/src/bytes/trim.ts b/src/bytes/trim.ts index 174c553..9c6f890 100644 --- a/src/bytes/trim.ts +++ b/src/bytes/trim.ts @@ -1,4 +1,15 @@ -// trim returns a subslice of s by slicing off all leading and trailing UTF-8-encoded code points contained in cutset. +/** + * @module + * Functions for trimming byte slices. + */ + +/** + * Returns a subslice of s by slicing off all leading and trailing UTF-8-encoded code points contained in cutset. + * + * @param s - The byte slice to trim + * @param cutset - Byte slice containing characters to remove + * @returns A trimmed byte slice + */ export function trim(s: Uint8Array, cutset: Uint8Array): Uint8Array { let start = 0; let end = s.length; @@ -19,7 +30,13 @@ export function trim(s: Uint8Array, cutset: Uint8Array): Uint8Array { return s.subarray(start, end); } -// trimFunc returns a subslice of s by slicing off all leading and trailing Unicode code points c satisfying f(c). +/** + * Returns a subslice of s by slicing off all leading and trailing Unicode code points c satisfying f(c). + * + * @param s - The byte slice to trim + * @param f - Predicate function to test each code point + * @returns A trimmed byte slice + */ export function trimFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array { const str = new TextDecoder().decode(s); let start = 0; @@ -33,7 +50,13 @@ export function trimFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array { return new TextEncoder().encode(str.slice(start, end)); } -// trimLeft returns a subslice of s by slicing off all leading UTF-8-encoded code points contained in cutset. +/** + * Returns a subslice of s by slicing off all leading UTF-8-encoded code points contained in cutset. + * + * @param s - The byte slice to trim + * @param cutset - Byte slice containing characters to remove from the left + * @returns A left-trimmed byte slice + */ export function trimLeft(s: Uint8Array, cutset: Uint8Array): Uint8Array { let start = 0; const cutsetSet = new Set(cutset); @@ -45,7 +68,13 @@ export function trimLeft(s: Uint8Array, cutset: Uint8Array): Uint8Array { return s.subarray(start); } -// trimLeftFunc returns a subslice of s by slicing off all leading Unicode code points c satisfying f(c). +/** + * Returns a subslice of s by slicing off all leading Unicode code points c satisfying f(c). + * + * @param s - The byte slice to trim + * @param f - Predicate function to test each code point + * @returns A left-trimmed byte slice + */ export function trimLeftFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array { const str = new TextDecoder().decode(s); let start = 0; @@ -55,8 +84,14 @@ export function trimLeftFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Arr return new TextEncoder().encode(str.slice(start)); } -// trimPrefix returns s without the provided leading prefix slice. -// If s doesn't start with prefix, trimPrefix returns s unchanged. +/** + * Returns s without the provided leading prefix slice. + * If s doesn't start with prefix, returns s unchanged. + * + * @param s - The byte slice to trim + * @param prefix - The prefix to remove + * @returns A byte slice without the prefix + */ export function trimPrefix(s: Uint8Array, prefix: Uint8Array): Uint8Array { if (hasPrefix(s, prefix)) { return s.subarray(prefix.length); @@ -64,7 +99,13 @@ export function trimPrefix(s: Uint8Array, prefix: Uint8Array): Uint8Array { return s; } -// trimRight returns a subslice of s by slicing off all trailing UTF-8-encoded code points contained in cutset. +/** + * Returns a subslice of s by slicing off all trailing UTF-8-encoded code points contained in cutset. + * + * @param s - The byte slice to trim + * @param cutset - Byte slice containing characters to remove from the right + * @returns A right-trimmed byte slice + */ export function trimRight(s: Uint8Array, cutset: Uint8Array): Uint8Array { let end = s.length; const cutsetSet = new Set(cutset); @@ -76,7 +117,13 @@ export function trimRight(s: Uint8Array, cutset: Uint8Array): Uint8Array { return s.subarray(0, end); } -// trimRightFunc returns a subslice of s by slicing off all trailing Unicode code points c satisfying f(c). +/** + * Returns a subslice of s by slicing off all trailing Unicode code points c satisfying f(c). + * + * @param s - The byte slice to trim + * @param f - Predicate function to test each code point + * @returns A right-trimmed byte slice + */ export function trimRightFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Array { const str = new TextDecoder().decode(s); let end = str.length; @@ -86,15 +133,26 @@ export function trimRightFunc(s: Uint8Array, f: (r: number) => boolean): Uint8Ar return new TextEncoder().encode(str.slice(0, end)); } -// trimSpace returns a subslice of s by slicing off all leading and trailing white space, as defined by Unicode. +/** + * Returns a subslice of s by slicing off all leading and trailing white space, as defined by Unicode. + * + * @param s - The byte slice to trim + * @returns A byte slice with whitespace removed from both ends + */ export function trimSpace(s: Uint8Array): Uint8Array { const str = new TextDecoder().decode(s); const trimmed = str.trim(); return new TextEncoder().encode(trimmed); } -// trimSuffix returns s without the provided ending suffix slice. -// If s doesn't end with suffix, trimSuffix returns s unchanged. +/** + * Returns s without the provided ending suffix slice. + * If s doesn't end with suffix, returns s unchanged. + * + * @param s - The byte slice to trim + * @param suffix - The suffix to remove + * @returns A byte slice without the suffix + */ export function trimSuffix(s: Uint8Array, suffix: Uint8Array): Uint8Array { if (hasSuffix(s, suffix)) { return s.subarray(0, s.length - suffix.length); diff --git a/src/channel.ts b/src/channel.ts index 773aeda..16f02a4 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -245,7 +245,10 @@ export class Channel { } /** - * Select cases for channel operations + * ReceiveCase represents a channel receive operation in a select statement. + * When selected, receives a value from the channel and executes the action. + * + * @template T - The type of value to receive */ export interface ReceiveCase { /** Channel to receive from */ @@ -254,19 +257,35 @@ export interface ReceiveCase { action: (value: T | undefined, ok: boolean) => void; } +/** + * SendCase represents a channel send operation in a select statement. + * When selected, sends a value to the channel and executes the action. + * + * @template T - The type of value to send + */ export interface SendCase { - /** Channel and value to send */ + /** Channel to send to */ channel: Channel; + /** Value to send to the channel */ value: T; /** Action to execute when this case is selected */ action: () => void; } +/** + * DefaultCase represents the default branch in a select statement. + * Executes when no other cases are ready immediately. + */ export interface DefaultCase { /** Default action when no other cases are ready */ default: () => void; } +/** + * SelectCase is a union type of all possible select cases. + * + * @template T - The type of channel values + */ export type SelectCase = ReceiveCase | SendCase | DefaultCase; /** @@ -349,9 +368,22 @@ export async function select(cases: SelectCase[]): Promise { } /** - * Helper function to create a receive case for select() + * Creates a receive case for use with select(). + * Fluent API that returns a builder with a `then` method to specify the action. + * + * @template T - The type of value to receive + * @param channel - The channel to receive from + * @returns A builder object with a `then` method + * * @example - * receive(channel).then((value, ok) => console.log('received:', value)) + * ```ts + * import { Channel, select, receive } from "@okudai/golikejs"; + * + * const ch = new Channel(); + * await select([ + * receive(ch).then((value, ok) => console.log('received:', value)) + * ]); + * ``` */ export function receive(channel: Channel): { then(action: (value: T | undefined, ok: boolean) => void): ReceiveCase; @@ -364,9 +396,23 @@ export function receive(channel: Channel): { } /** - * Helper function to create a send case for select() + * Creates a send case for use with select(). + * Fluent API that returns a builder with a `then` method to specify the action. + * + * @template T - The type of value to send + * @param channel - The channel to send to + * @param value - The value to send + * @returns A builder object with a `then` method + * * @example - * send(channel, value).then(() => console.log('sent')) + * ```ts + * import { Channel, select, send } from "@okudai/golikejs"; + * + * const ch = new Channel(); + * await select([ + * send(ch, "hello").then(() => console.log('sent')) + * ]); + * ``` */ export function send(channel: Channel, value: T): { then(action: () => void): SendCase; @@ -379,9 +425,22 @@ export function send(channel: Channel, value: T): { } /** - * Helper function to create a default case for select() + * Creates a default case for use with select(). + * The default case executes when no other cases are immediately ready. + * + * @param action - The function to execute when no other cases are ready + * @returns A DefaultCase object + * * @example - * default_(() => console.log('no operation ready')) + * ```ts + * import { Channel, select, receive, default_ } from "@okudai/golikejs"; + * + * const ch = new Channel(); + * await select([ + * receive(ch).then((value, ok) => console.log('received:', value)), + * default_(() => console.log('no data available')) + * ]); + * ``` */ export function default_(action: () => void): DefaultCase { return { default: action }; diff --git a/src/context/context.ts b/src/context/context.ts index 46cd6db..d162587 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -1,3 +1,20 @@ +/** + * Context carries cancellation signals and deadlines across API boundaries. + * Similar to Go's context.Context, it enables propagating cancellation and timeouts + * through asynchronous operation chains. + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const [ctx, cancel] = context.withCancel(context.background()); + * try { + * await doWork(ctx); + * } finally { + * cancel(); + * } + * ``` + */ export interface Context { /** * Returns a promise that resolves when the context is finished (cancelled, timed out, etc). @@ -10,10 +27,27 @@ export interface Context { err(): Error | undefined; } +/** + * CancelFunc is a function that cancels a Context. + * Calling a CancelFunc cancels the associated context and all derived contexts. + */ export type CancelFunc = () => void; + +/** + * CancelCauseFunc is a function that cancels a Context with a specific error. + * Allows providing a custom error or undefined to indicate clean completion. + */ export type CancelCauseFunc = (err: Error | undefined) => void; +/** + * ContextCancelledError indicates that a Context was explicitly cancelled. + */ export class ContextCancelledError extends Error { + /** + * Creates a new ContextCancelledError. + * + * @param message - The error message, defaults to "Context cancelled" + */ constructor(message = "Context cancelled") { super(message); this.name = "ContextCancelledError"; @@ -21,7 +55,15 @@ export class ContextCancelledError extends Error { } } +/** + * ContextTimeoutError indicates that a Context exceeded its deadline or timeout. + */ export class ContextTimeoutError extends Error { + /** + * Creates a new ContextTimeoutError. + * + * @param message - The error message, defaults to "context deadline exceeded" + */ constructor(message = "context deadline exceeded") { super(message); this.name = "ContextTimeoutError"; @@ -108,15 +150,33 @@ const backgroundContext: Context = (() => { })(); // Public API functions + +/** + * Returns a background Context that is never cancelled. + * Similar to Go's context.Background(), this is typically used as the root context. + * + * @returns A background Context + */ export function background(): Context { return backgroundContext; } /** - * Watch an external AbortSignal and cancel the returned Context when the signal aborts. - * This mirrors the behavior of the previous `withSignal` but is named to emphasize - * that the signal is being observed rather than produced. Returns a Context derived - * from `parent` which will be cancelled when `signal` aborts. + * Creates a Context that cancels when the provided AbortSignal aborts. + * Useful for integrating with fetch and other Web APIs that use AbortSignal. + * + * @param parent - The parent Context + * @param signal - The AbortSignal to watch + * @returns A new Context that cancels when the signal aborts + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const ac = new AbortController(); + * const ctx = context.watchSignal(context.background(), ac.signal); + * ac.abort(); // cancels ctx + * ``` */ export function watchSignal(parent: Context, signal: AbortSignal): Context { const context = new DefaultContext(parent); @@ -142,11 +202,20 @@ export function watchSignal(parent: Context, signal: AbortSignal): Context { // (No backwards-compatible aliases — package is not yet published.) /** - * Go-style helper: returns a new child Context and an AbortController paired to it. - * - Aborting the returned controller will cancel the Context (with controller.reason if provided). - * - Cancelling the Context will abort the controller's signal (with the context's error as reason when possible). - * This provides a two-way bridge between AbortController-based APIs and this Context implementation, - * and follows the Go idiom where a create function returns both the derived Context and a cancel handle. + * Creates a Context with an AbortController that are synchronized. + * Aborting the controller cancels the Context, and vice versa. + * Similar to Go's context.WithCancel, returns both the Context and a cancel mechanism. + * + * @param parent - The parent Context + * @returns A tuple of [Context, AbortController] + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const [ctx, ac] = context.withAbort(context.background()); + * ac.abort(); // cancels ctx + * ``` */ export function withAbort(parent: Context): [Context, AbortController] { const context = new DefaultContext(parent); @@ -181,16 +250,55 @@ export function withAbort(parent: Context): [Context, AbortController] { return [context, ac]; } +/** + * Creates a cancellable Context derived from parent. + * Similar to Go's context.WithCancel. + * + * @param parent - The parent Context + * @returns A tuple of [Context, CancelFunc] + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const [ctx, cancel] = context.withCancel(context.background()); + * // ... do work + * cancel(); // cancels ctx + * ``` + */ export function withCancel(parent: Context): [Context, CancelFunc] { const context = new DefaultContext(parent); return [context, () => context.cancel(new ContextCancelledError())]; } +/** + * Creates a cancellable Context with a cancel function that accepts a custom error. + * Similar to Go's context.WithCancelCause. + * + * @param parent - The parent Context + * @returns A tuple of [Context, CancelCauseFunc] + */ export function withCancelCause(parent: Context): [Context, CancelCauseFunc] { const context = new DefaultContext(parent); return [context, (err: Error | undefined) => context.cancel(err)]; } +/** + * Creates a Context that automatically cancels after the specified timeout. + * Similar to Go's context.WithTimeout. + * + * @param parent - The parent Context + * @param timeoutMs - The timeout in milliseconds + * @returns A new Context that cancels after the timeout + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const ctx = context.withTimeout(context.background(), 5000); + * await ctx.done(); // resolves after 5 seconds + * ``` + */ export function withTimeout(parent: Context, timeoutMs: number): Context { const context = new DefaultContext(parent); const id = setTimeout(() => { @@ -200,6 +308,15 @@ export function withTimeout(parent: Context, timeoutMs: number): Context { return context; } +/** + * Creates a Context that completes when the provided Promise settles. + * Resolves with undefined on success, or with the rejection error on failure. + * + * @template T - The Promise result type + * @param parent - The parent Context + * @param promise - The Promise to watch + * @returns A new Context that completes with the Promise + */ export function watchPromise(parent: Context, promise: Promise): Context { const context = new DefaultContext(parent); promise.then( @@ -212,6 +329,10 @@ export function watchPromise(parent: Context, promise: Promise): Context { return context; } +/** + * AfterFuncContext is an internal Context that executes a function when its parent is cancelled. + * @internal + */ class AfterFuncContext extends DefaultContext { #fn?: () => void | Promise; #executed = false; @@ -254,13 +375,45 @@ class AfterFuncContext extends DefaultContext { } } +/** + * Registers a function to execute when the parent Context is cancelled. + * Returns a stop function that can prevent the function from executing if called before cancellation. + * Similar to Go's context.AfterFunc. + * + * @param parent - The parent Context + * @param fn - The function to execute on cancellation + * @returns A stop function that returns true if it prevented execution, false otherwise + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const [ctx, cancel] = context.withCancel(context.background()); + * const stop = context.afterFunc(ctx, () => console.log("cancelled")); + * cancel(); // prints "cancelled" + * ``` + */ export function afterFunc(parent: Context, fn: () => void | Promise): () => boolean { const ctx = new AfterFuncContext(parent, fn); return () => ctx.stop(); } + /** - * Convert a Context into an AbortSignal for integration with fetch / Web APIs. + * Converts a Context into an AbortSignal for integration with fetch and other Web APIs. + * The returned signal will abort when the Context is cancelled. + * + * @param ctx - The Context to convert + * @returns An AbortSignal that aborts when the Context is cancelled + * + * @example + * ```ts + * import { context } from "@okudai/golikejs"; + * + * const [ctx, cancel] = context.withCancel(context.background()); + * const signal = context.toAbortSignal(ctx); + * fetch("/api/data", { signal }); + * ``` */ export function toAbortSignal(ctx: Context): AbortSignal { const ac = new AbortController(); diff --git a/src/defer.ts b/src/defer.ts index e76b914..72616fa 100644 --- a/src/defer.ts +++ b/src/defer.ts @@ -1,7 +1,44 @@ +/** + * DeferredSyncFunc represents a synchronous function that can be deferred. + * Functions of this type will be executed in LIFO order when the enclosing scope exits. + */ export type DeferredSyncFunc = () => void; + +/** + * DeferredAsyncFunc represents an asynchronous function that can be deferred. + * Functions of this type will be executed in LIFO order when the enclosing scope exits. + */ export type DeferredAsyncFunc = () => Promise; + +/** + * DeferFunc is a function that registers cleanup handlers to be called on scope exit. + * Similar to Go's defer statement, deferred functions execute in LIFO order. + * + * @param fn - The synchronous or asynchronous function to defer + */ export type DeferFunc = (fn: DeferredSyncFunc | DeferredAsyncFunc) => void; +/** + * Executes a function with defer support, running all deferred functions on exit. + * Similar to Go's defer statement, cleanup functions are executed in LIFO order + * regardless of whether the main function completes normally or throws an error. + * + * @param fn - The function to execute with defer support. Receives a defer function to register cleanup handlers. + * @returns A promise that resolves when the function and all deferred cleanup complete. + * + * @example + * ```ts + * import { scope } from "@okudai/golikejs"; + * + * await scope(async (defer) => { + * const file = await openFile("data.txt"); + * defer(() => file.close()); + * + * // Work with file - close() will be called automatically + * const data = await file.read(); + * }); + * ``` + */ export async function scope(fn: (defer: DeferFunc) => void | Promise): Promise { const task: (DeferredSyncFunc | DeferredAsyncFunc)[] = []; diff --git a/src/io/closer.ts b/src/io/closer.ts index 4751f17..81e6a30 100644 --- a/src/io/closer.ts +++ b/src/io/closer.ts @@ -3,5 +3,10 @@ * Close closes the stream. */ export interface Closer { + /** + * Closes the stream. + * + * @returns A promise that resolves with an error if closing failed, or undefined on success + */ close(): Promise; } diff --git a/src/io/reader.ts b/src/io/reader.ts index 697651e..4599650 100644 --- a/src/io/reader.ts +++ b/src/io/reader.ts @@ -9,6 +9,12 @@ import { Closer } from "./closer.ts"; * When Read encounters an end-of-file condition, it returns n, EOF. */ export interface Reader { + /** + * Reads up to len(p) bytes into p. + * + * @param p - The buffer to read into + * @returns A tuple of [bytes read, error] + */ read(p: Uint8Array): Promise<[number, Error | undefined]>; } @@ -18,6 +24,12 @@ export interface Reader { * optional error. */ export interface ReaderFrom { + /** + * Reads from r until EOF and appends to the buffer. + * + * @param r - The Reader to read from + * @returns A tuple of [bytes read, error] + */ readFrom(r: Reader): Promise<[number, Error | undefined]>; } diff --git a/src/io/writer.ts b/src/io/writer.ts index 4d29123..8185805 100644 --- a/src/io/writer.ts +++ b/src/io/writer.ts @@ -7,6 +7,12 @@ import { Closer } from "./closer.ts"; * Write must return a non-nil error if it returns n < len(p). */ export interface Writer { + /** + * Writes len(p) bytes from p to the underlying data stream. + * + * @param p - The buffer to write from + * @returns A tuple of [bytes written, error] + */ write(p: Uint8Array): Promise<[number, Error | undefined]>; } @@ -15,6 +21,12 @@ export interface Writer { * method returns the number of bytes written and an optional error. */ export interface WriterTo { + /** + * Writes contents to w. + * + * @param w - The Writer to write to + * @returns A tuple of [bytes written, error] + */ writeTo(w: Writer): Promise<[number, Error | undefined]>; } diff --git a/src/slice.ts b/src/slice.ts index 3698175..a5f26e5 100644 --- a/src/slice.ts +++ b/src/slice.ts @@ -1,7 +1,13 @@ -// A minimal Go-like slice implementation for JS/TS. -// Supports backing by plain Array or by TypedArray constructors (Uint8Array, etc.). +/** + * @module + * A minimal Go-like slice implementation for JS/TS. + * Supports backing by plain Array or by TypedArray constructors (Uint8Array, etc.). + */ -type TypedArray = +/** + * TypedArray represents all JavaScript typed array types. + */ +export type TypedArray = | Uint8Array | Int8Array | Uint16Array @@ -10,15 +16,61 @@ type TypedArray = | Int32Array | Float32Array | Float64Array; -type TypedArrayConstructor = { new (length: number): TypedArray; BYTES_PER_ELEMENT?: number }; +/** + * TypedArrayConstructor represents constructors for typed arrays. + */ +export type TypedArrayConstructor = { new (length: number): TypedArray; BYTES_PER_ELEMENT?: number }; + +/** + * Slice implements a Go-like slice with support for plain arrays and typed arrays. + * Similar to Go slices, a Slice is a view into an underlying array with length and capacity. + * + * @template T - The type of elements in the slice + * + * @example + * ```ts + * import { Slice, make } from "@okudai/golikejs"; + * + * const s = make(Array, 5, 10); + * s.set(0, "hello"); + * const value = s.get(0); + * ``` + */ export class Slice implements Iterable { + /** + * The underlying array storage (Array or TypedArray). + */ backing: any; // Array or TypedArray + + /** + * The typed array constructor, present for typed array slices. + */ ctor?: TypedArrayConstructor; // present for typed arrays + + /** + * The starting index in the backing array. + */ start: number; + + /** + * The length of the slice. + */ len: number; + + /** + * The capacity of the slice. + */ cap: number; + /** + * Creates a new Slice instance. + * + * @param backing - The underlying array storage + * @param start - Starting index in the backing array + * @param len - Length of the slice + * @param cap - Capacity of the slice + */ constructor(backing: any, start: number, len: number, cap: number) { this.backing = backing; this.start = start; @@ -34,18 +86,39 @@ export class Slice implements Iterable { } } - // index access + /** + * Gets the element at the specified index. + * + * @param i - The index to access + * @returns The element at index i + * @throws {RangeError} If index is out of range + */ get(i: number): T { if (i < 0 || i >= this.len) throw new RangeError("index out of range"); return this.backing[this.start + i]; } + /** + * Sets the element at the specified index. + * + * @param i - The index to set + * @param v - The value to set + * @throws {RangeError} If index is out of range + */ set(i: number, v: T): void { if (i < 0 || i >= this.len) throw new RangeError("index out of range"); this.backing[this.start + i] = v; } - // slice returns a new Slice that shares the same backing (like Go) + /** + * Returns a new Slice that shares the same backing array. + * Similar to Go's slice operation, the new slice is a view into the same underlying data. + * + * @param a - Start index (inclusive), defaults to 0 + * @param b - End index (exclusive), defaults to slice length + * @returns A new Slice sharing the same backing array + * @throws {RangeError} If indices are out of range + */ slice(a = 0, b?: number): Slice { if (a < 0) throw new RangeError("slice start out of range"); const bb = b === undefined ? this.len : b; @@ -56,7 +129,12 @@ export class Slice implements Iterable { return new Slice(this.backing, newStart, newLen, newCap); } - // convert to a standard JS array or typed-array view of the current length + /** + * Converts the slice to a standard JavaScript array or typed array view. + * For typed arrays, returns a subarray view. For regular arrays, returns a copy. + * + * @returns A JavaScript array or typed array containing the slice elements + */ toArray(): T[] | TypedArray { if (this.ctor) { // typed array: return subarray view @@ -65,6 +143,12 @@ export class Slice implements Iterable { return this.backing.slice(this.start, this.start + this.len); } + /** + * Returns an iterator for the slice elements. + * Enables use of for...of loops and other iterable protocols. + * + * @returns An iterator over the slice elements + */ [Symbol.iterator](): Iterator { let i = 0; return { @@ -80,12 +164,30 @@ export class Slice implements Iterable { } } -// make function: emulate Go's make for slices and for typed arrays when a constructor is provided. -// Usage patterns implemented: -// - make(Array, len, cap?) -> Slice -// - make(Uint8Array, len, cap?) -> Slice backed by Uint8Array -// - make(Map, initialCapacity?) -> Map -// Notes: Channels are not implemented here. +/** + * Creates a new slice or map, similar to Go's make builtin. + * Supports creating slices backed by arrays or typed arrays, and creating maps. + * + * @template T - The element type + * @param ctor - The constructor (Array, TypedArray constructor, or Map) + * @param length - The initial length (for maps, this is ignored) + * @param capacity - The capacity (defaults to length) + * @returns A new Slice or Map + * + * @example + * ```ts + * import { make } from "@okudai/golikejs"; + * + * // Create a slice of length 5, capacity 10 + * const s = make(Array, 5, 10); + * + * // Create a typed array slice + * const bytes = make(Uint8Array, 100); + * + * // Create a map + * const m = make(Map, 0); + * ``` + */ export function make(ctor: any, length: number, capacity?: number): Slice | Map { if (ctor === Map) { // initialCapacity is ignored for JS Map @@ -116,7 +218,23 @@ export function make(ctor: any, length: number, capacity?: number): Slice return new Slice(backing, 0, length, cap); } -// append function: emulate Go's append(slice, ...elements) +/** + * Appends elements to a slice and returns the resulting slice. + * Similar to Go's append builtin, may reallocate if capacity is exceeded. + * + * @template T - The element type + * @param s - The slice to append to + * @param items - The elements to append + * @returns A new Slice containing the original elements plus the appended items + * + * @example + * ```ts + * import { make, append } from "@okudai/golikejs"; + * + * let s = make(Array, 0, 5); + * s = append(s, 1, 2, 3); + * ``` + */ export function append(s: Slice, ...items: T[]): Slice { const need = s.len + items.length; if (need > s.cap) { diff --git a/src/sync/cond.ts b/src/sync/cond.ts index 24d005f..0bc1c5d 100644 --- a/src/sync/cond.ts +++ b/src/sync/cond.ts @@ -1,9 +1,39 @@ /** - * Cond implements condition variables for coordinating access to shared resources + * @module + * Cond implements condition variables for coordinating access to shared resources. */ import { Mutex } from "./mutex.ts"; +/** + * Cond implements condition variables for coordinating access to shared resources. + * A condition variable enables threads to wait for certain conditions to be met, + * similar to Go's sync.Cond. + * + * @example + * ```ts + * import { sync } from "@okudai/golikejs"; + * + * const mu = new sync.Mutex(); + * const cond = new sync.Cond(mu); + * + * // In a waiter + * await mu.lock(); + * try { + * await cond.wait(); + * } finally { + * mu.unlock(); + * } + * + * // In a signaler + * await mu.lock(); + * try { + * cond.signal(); + * } finally { + * mu.unlock(); + * } + * ``` + */ export class Cond { #mutex: Mutex; // waiters are parameterless callbacks invoked when signaled