From 9809627acd88e10c68c29baa8644632c14e2f1b5 Mon Sep 17 00:00:00 2001 From: SepehrGanji Date: Sat, 7 Oct 2023 18:18:56 -0600 Subject: [PATCH 1/4] add collection docs --- packages/common/src/models/collection.ts | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/common/src/models/collection.ts b/packages/common/src/models/collection.ts index 1ea44899..78d28e1e 100644 --- a/packages/common/src/models/collection.ts +++ b/packages/common/src/models/collection.ts @@ -3,6 +3,21 @@ import { isDefined } from "../utils"; export type CollectionAddOptions = { index?: number }; +/** + * Collection abstract model + * + * @example + * Define a new collection class with internal type `number` and external type `string` + * ``` + * class TestCollection extends Collection { + * protected _map(item: string | number): number { + * return Number(item); + * } + * // Some other methods + * } + * ``` + * + */ export abstract class Collection implements Iterable { protected readonly _items: InternalType[]; @@ -27,14 +42,24 @@ export abstract class Collection implements Iterable }; } + /** + * Number of items in the collection + */ public get length(): number { return this._items.length; } + /** + * True if the collection is empty + */ public get isEmpty(): boolean { return this.length === 0; } + /** + * Get item at index, if index is out of bounds, throws RangeError + * @param index + */ public at(index: number): InternalType { if (this._isIndexOutOfBounds(index)) { throw new RangeError(`Index '${index}' is out of range.`); @@ -43,12 +68,23 @@ export abstract class Collection implements Iterable return this._items[index]; } + /** + * Add item to the collection + * @param items + * @param options + * @returns The new length of the collection + */ public add(items: OneOrMore, options?: CollectionAddOptions): number { return this._addOneOrMore(items, options); } abstract remove(item: unknown): number; + /** + * Map external type to internal type + * @param item + * @protected + */ protected abstract _map(item: ExternalType | InternalType): InternalType; protected _addOne(item: InternalType | ExternalType, options?: CollectionAddOptions): number { @@ -89,6 +125,9 @@ export abstract class Collection implements Iterable return this.length; } + /** + * Get the collection as an array + */ public toArray(): InternalType[] { return [...this._items]; } From d952fcb8ea6d3aed5f6dca1dc46a4f6615f24e7b Mon Sep 17 00:00:00 2001 From: SepehrGanji Date: Tue, 10 Oct 2023 11:29:36 -0600 Subject: [PATCH 2/4] add utils tsdoc --- packages/common/src/utils/array.ts | 139 ++++++++++++++++++++++++++++ packages/common/src/utils/bigInt.ts | 68 ++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/packages/common/src/utils/array.ts b/packages/common/src/utils/array.ts index 52d4bd1c..ff917fc8 100644 --- a/packages/common/src/utils/array.ts +++ b/packages/common/src/utils/array.ts @@ -3,6 +3,10 @@ import { assert, isEmpty } from "./assertions"; type ObjectSelector = (item: T) => T[keyof T]; +/** + * Returns the first element of an array. Throws an error if the array is empty. + * @param array + */ export function first(array: undefined): undefined; export function first(array: ArrayLike): T; export function first(array: ArrayLike | undefined): T | number | undefined { @@ -12,6 +16,10 @@ export function first(array: ArrayLike | undefined): T | number | undefine return array[0]; } +/** + * Returns the last element of an array. Throws an error if the array is empty. + * @param array + */ export function last(array: undefined): undefined; export function last(array: ArrayLike): T; export function last(array: ArrayLike | undefined): T | undefined { @@ -21,6 +29,11 @@ export function last(array: ArrayLike | undefined): T | undefined { return at(array, -1); } +/** + * Returns the element at the specified index. Negative indices are counted from the end of the array. + * @param array + * @param index + */ export function at(array: undefined, index: number): undefined; export function at(array: ArrayLike, index: number): T; export function at(array: ArrayLike | undefined, index: number): T | undefined { @@ -52,6 +65,19 @@ export function hasDuplicatesBy(array: T[], selector: ObjectSelector): boo }); } +/** + * Turns an array into chunks of the specified size + * @param array + * @param size + * + * @example + * ``` + * const array = [1, 2, 3, 4, 5]; + * const chunks = chunk(array, 2); + * console.log(chunks); + * // [[1, 2], [3, 4], [5]] + * ``` + */ export function chunk(array: T[], size: number): T[][] { if (array.length <= size) { return [array]; @@ -65,6 +91,20 @@ export function chunk(array: T[], size: number): T[][] { return chunks; } +/** + * Sorts an array of objects by the specified property + * @param array + * @param iteratee + * @param order + * + * @example + * ``` + * const array = [{ name: "John", age: 25 }, { name: "Jane", age: 30 }]; + * const sorted = orderBy(array, (item) => item.age, "desc"); + * console.log(sorted); + * // [{ name: "Jane", age: 30 }, { name: "John", age: 25 }] + * ``` + */ export function orderBy( array: T[], iteratee: SortingSelector, @@ -81,6 +121,22 @@ export function orderBy( }); } +/** + * Returns true if arrays are equal + * @param array1 + * @param array2 + * + * @example + * ``` + * const array1 = [1, 2, 3]; + * const array2 = [1, 2, 3]; + * const array3 = [1, 2, 4]; + * const array4 = [1, 2, 3, 4]; + * areEqual(array1, array2); // true + * areEqual(array1, array3); // false + * areEqual(array1, array4); // false + * ``` + */ export function areEqual(array1: ArrayLike, array2: ArrayLike): boolean { if (array1 === array2) { return true; @@ -99,6 +155,22 @@ export function areEqual(array1: ArrayLike, array2: ArrayLike): boolean return true; } +/** + * Returns true if arrays are equal by the specified property + * @param array1 + * @param array2 + * @param selector + * + * @example + * ``` + * const array1 = [{ name: "John", age: 25 }, { name: "Jane", age: 30 }]; + * const array2 = [{ name: "John", age: 25 }, { name: "Jane", age: 30 }]; + * const array3 = [{ name: "John", age: 25 }, { name: "Jane", age: 31 }]; + * + * areEqualBy(array1, array2, (item) => item.age); // true + * areEqualBy(array1, array3, (item) => item.age); // false + * ``` + */ export function areEqualBy( array1: ArrayLike, array2: ArrayLike, @@ -121,6 +193,21 @@ export function areEqualBy( return true; } +/** + * Returns true if the array starts with the specified target + * @param array + * @param target + * + * @example + * ``` + * const array = [1, 2, 3, 4, 5]; + * const target1 = [1, 2]; + * const target2 = [1, 3]; + * + * startsWith(array, target1); // true + * startsWith(array, target2); // false + * ``` + */ export function startsWith(array: ArrayLike, target: ArrayLike): boolean { if (array === target) { return true; @@ -139,6 +226,21 @@ export function startsWith(array: ArrayLike, target: ArrayLike): boolea return true; } +/** + * Returns true if the array ends with the specified target + * @param array + * @param target + * + * @example + * ``` + * const array = [1, 2, 3, 4, 5]; + * const target1 = [4, 5]; + * const target2 = [3, 5]; + * + * endsWith(array, target1); // true + * endsWith(array, target2); // false + * ``` + */ export function endsWith(array: ArrayLike, target: ArrayLike): boolean { if (array === target) { return true; @@ -159,6 +261,18 @@ export function endsWith(array: ArrayLike, target: ArrayLike): boolean return true; } +/** + * Makes an array unique by removing duplicate elements + * @param array + * + * @example + * ``` + * const array = [1, 2, 3, 3, 4, 5, 5]; + * const unique = uniq(array); + * console.log(unique); + * // [1, 2, 3, 4, 5] + * ``` + */ export function uniq(array: Array): Array { if (isEmpty(array)) { return array; @@ -167,6 +281,20 @@ export function uniq(array: Array): Array { return Array.from(new Set(array)); } +/** + * Makes an array unique by removing duplicate elements using the specified property + * @param array + * @param selector + * @param selection + * + * @example + * ``` + * const array = [{ name: "John", age: 25 }, { name: "Jane", age: 30 }, { name: "John", age: 30 }]; + * const unique = uniqBy(array, (item) => item.name); + * console.log(unique); + * // [{ name: "John", age: 25 }, { name: "Jane", age: 30 }] + * ``` + */ export function uniqBy( array: Array, selector: ObjectSelector, @@ -191,6 +319,17 @@ export function uniqBy( ); } +/** + * Returns the depth of an array + * @param array + * + * @example + * ``` + * const array = [1, 2, 3, [4, 5, [6, 7]]]; + * const depth = depthOf(array); + * console.log(depth); + * // 3 + */ export function depthOf(array: unknown | unknown[]): number { return Array.isArray(array) ? 1 + Math.max(0, ...array.map(depthOf)) : 0; } diff --git a/packages/common/src/utils/bigInt.ts b/packages/common/src/utils/bigInt.ts index 3ecdb300..8cf6c073 100644 --- a/packages/common/src/utils/bigInt.ts +++ b/packages/common/src/utils/bigInt.ts @@ -12,6 +12,10 @@ export const _63n = BigInt(63); export const _127n = BigInt(127); export const _128n = BigInt(128); +/** + * Ensure that the given value is a bigint + * @param number + */ export function ensureBigInt(number: NumberLike): bigint { return typeof number === "bigint" ? number : BigInt(number); } @@ -29,6 +33,16 @@ type ParsingOptions = { decimalMark?: string; }; +/** + * Parse a decimal string into a bigint with options + * @param decimalStr + * @param options + * + * @example + * undecimalize("129.8379183", { decimals: 9 }) // 129837918300n + * undecimalize("1", { decimals: 2 }) // 100n + * undecimalize("1", 2) // 100n + */ export function undecimalize(decimalStr: string, options?: ParsingOptions | number): bigint { if (!decimalStr) { return _0n; @@ -60,6 +74,10 @@ export function undecimalize(decimalStr: string, options?: ParsingOptions | numb return BigInt(negative + _stripNonDigits(integer + decimal)); } +/** + * Strip all non-digits from a string + * @param value + */ function _stripNonDigits(value: string): string { return value.replace(/\D/g, ""); } @@ -82,6 +100,15 @@ type FormattingOptions = { decimalMark?: string; }; +/** + * Format a bigint into a decimal string with options + * @param value + * @param options + * + * @example + * decimalize(129837918300n, { decimals: 9 }) // "129.8379183" + * decimalize(100n, { decimals: 2 }) // "1" + */ export function decimalize(value: Amount, options?: FormattingOptions | number): string { value = ensureBigInt(value); if (!options) { @@ -99,6 +126,20 @@ export function decimalize(value: Amount, options?: FormattingOptions | number): return _buildFormattedDecimal(integer.toString(10), decimal.toString(10), options); } +/** + * Format a bigint percentage into a decimal string with options + * @param value + * @param percentage + * @param precision + * + * @example + * ``` + * percent(3498n, 1n) // 34n(1%) + * percent(3498n, 2n) // 69n(2%) + * percent(3498n, 10n) // 349n(10%) + * ``` + * + */ export function percent(value: bigint, percentage: bigint, precision = 2n) { return (value * percentage) / 10n ** precision; } @@ -142,6 +183,25 @@ function _removeLeadingZeros(value: string): string { return value.replace(/^0+\.?/, ""); } +/** + * Sum a collection of numbers by a given iteratee + * @param collection + * @param iteratee + * @param condition + * + * @example + * ``` + * const values = [ + * { key: 1, value: 100n }, + * { key: 2, value: 200n }, + * { key: 3, value: 300n }, + * { key: 4, value: 400n }, + * ]; + * + * sumBy(values, x => x.value) // 1000n + * sumBy(values, x => x.value, x => x.key < 0) // 0n + * sumBy(values, x => x.value, x => x.key % 2 === 0) // 600n + */ export function sumBy( collection: readonly T[], iteratee: (value: T) => bigint, @@ -161,6 +221,10 @@ export function sumBy( return acc; } +/** + * Get the minimum value from a collection of numbers + * @param numbers + */ export function min(...numbers: T[]): T { let min = first(numbers); @@ -173,6 +237,10 @@ export function min(...numbers: T[]): T { return min; } +/** + * Get the maximum value from a collection of numbers + * @param numbers + */ export function max(...numbers: T[]): T { let max = first(numbers); From efa241692c9b5ccabc96268eed2fa161b80b698d Mon Sep 17 00:00:00 2001 From: SepehrGanji Date: Thu, 12 Oct 2023 11:29:51 -0600 Subject: [PATCH 3/4] complete common tsdocs --- packages/common/src/utils/object.ts | 24 +++++++++++ packages/common/src/utils/utxo.ts | 64 +++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/packages/common/src/utils/object.ts b/packages/common/src/utils/object.ts index 7c1fe47c..9dc2f569 100644 --- a/packages/common/src/utils/object.ts +++ b/packages/common/src/utils/object.ts @@ -1,5 +1,16 @@ import { isEmpty, isUndefined } from "./assertions"; +/** + * Remove undefined values from an object + * @param value + * + * @example + * ``` + * const obj = { a: 1, b: undefined }; + * const result = clearUndefined(obj); + * console.log(result); // { a: 1 } + * ``` + */ export function clearUndefined(value: Record) { const result: Record = {}; for (const key in value) { @@ -12,6 +23,19 @@ export function clearUndefined(value: Record) { return result; } +/** + * Ensure that the options object has all the default values + * @param options + * @param defaults + * + * @example + * ``` + * const options = { a: 1 }; + * const defaults = { a: 2, b: 3 }; + * const result = ensureDefaults(options, defaults); + * console.log(result); // { a: 1, b: 3 } + * ``` + */ export function ensureDefaults( options: T | undefined, defaults: R diff --git a/packages/common/src/utils/utxo.ts b/packages/common/src/utils/utxo.ts index bf984d3e..746f8205 100644 --- a/packages/common/src/utils/utxo.ts +++ b/packages/common/src/utils/utxo.ts @@ -13,6 +13,27 @@ import { _0n } from "./bigInt"; const NANOERGS_TOKEN_ID = "nanoErgs"; +/** + * Calculates the sum of all nanoErgs and tokens in the given boxes. + * @param boxes + * + * @example + * ``` + * const boxes = [ + * { + * value: 10, + * assets: [{ tokenId: "test", amount: 20 }] + * }, { + * value: 20, + * assets: [{ tokenId: "test", amount: 30 }] + * } + * ]; + * const sum = utxoSum(boxes); + * console.log(sum); + * // { nanoErgs: 30n, tokens: [{ tokenId: "test", amount: 50n }] } + * ``` + * + */ export function utxoSum(boxes: MinimalBoxAmounts): BoxSummary; export function utxoSum(boxes: MinimalBoxAmounts, tokenId: TokenId): bigint; export function utxoSum(boxes: MinimalBoxAmounts, tokenId?: TokenId): BoxSummary | bigint { @@ -46,6 +67,25 @@ export function utxoSum(boxes: MinimalBoxAmounts, tokenId?: TokenId): BoxSummary }; } +/** + * Calculates the difference between two utxos or utxo sets. + * @param minuend + * @param subtrahend + * + * @example + * ``` + * const minuend = { + * nanoErgs: 30n, + * tokens: [{ tokenId: "test", amount: 50n }] + * }; + * const subtrahend = { + * nanoErgs: 10n, + * tokens: [{ tokenId: "test", amount: 20n }] + * }; + * const diff = utxoDiff(minuend, subtrahend); + * console.log(diff); + * // { nanoErgs: 20n, tokens: [{ tokenId: "test", amount: 30n }] } + */ export function utxoDiff( minuend: BoxSummary | Box[], subtrahend: BoxSummary | Box[] @@ -76,6 +116,21 @@ export function utxoDiff( const MIN_NON_MANDATORY_REGISTER_INDEX = 4; const MAX_NON_MANDATORY_REGISTER_INDEX = 9; +/** + * Checks if the given registers are densely packed. + * @param registers + * + * @example + * ``` + * const registers = { + * R4: "0x0000000000", + * R6: "0x0000000000", + * R7: "0x0000000000", + * }; + * const result = areRegistersDenselyPacked(registers); + * console.log(result); + * // false + */ export function areRegistersDenselyPacked(registers: NonMandatoryRegisters): boolean { let lastIndex = 0; for (let i = MIN_NON_MANDATORY_REGISTER_INDEX; i <= MAX_NON_MANDATORY_REGISTER_INDEX; i++) { @@ -97,6 +152,11 @@ export function areRegistersDenselyPacked(registers: NonMandatoryRegisters): boo return true; } +/** + * Filters the given utxos by the given filter parameters. + * @param utxos + * @param filterParams + */ export function utxoFilter( utxos: Box[], filterParams: UTxOFilterParams @@ -167,6 +227,10 @@ export type MinimalBoxAmounts = readonly { assets: TokenAmount[]; }[]; +/** + * Ensures that the given box candidate has big integer values. + * @param box + */ export function ensureUTxOBigInt(box: Box): Box; export function ensureUTxOBigInt(candidate: BoxCandidate): BoxCandidate; export function ensureUTxOBigInt( From 4a2562e09aefa89abdf0f529ce65eb5025c78c6e Mon Sep 17 00:00:00 2001 From: SepehrGanji Date: Wed, 18 Oct 2023 11:22:30 -0600 Subject: [PATCH 4/4] resolve comments --- packages/common/src/models/collection.ts | 3 ++- packages/common/src/utils/array.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/common/src/models/collection.ts b/packages/common/src/models/collection.ts index 78d28e1e..d66f46b4 100644 --- a/packages/common/src/models/collection.ts +++ b/packages/common/src/models/collection.ts @@ -57,8 +57,9 @@ export abstract class Collection implements Iterable } /** - * Get item at index, if index is out of bounds, throws RangeError + * Get item at index * @param index + * @throws RangeError if index is out of bounds */ public at(index: number): InternalType { if (this._isIndexOutOfBounds(index)) { diff --git a/packages/common/src/utils/array.ts b/packages/common/src/utils/array.ts index ff917fc8..db632462 100644 --- a/packages/common/src/utils/array.ts +++ b/packages/common/src/utils/array.ts @@ -4,8 +4,9 @@ import { assert, isEmpty } from "./assertions"; type ObjectSelector = (item: T) => T[keyof T]; /** - * Returns the first element of an array. Throws an error if the array is empty. + * Returns the first element of an array. * @param array + * @throws an error if the array is empty. */ export function first(array: undefined): undefined; export function first(array: ArrayLike): T; @@ -17,8 +18,9 @@ export function first(array: ArrayLike | undefined): T | number | undefine } /** - * Returns the last element of an array. Throws an error if the array is empty. + * Returns the last element of an array. * @param array + * @throws an error if the array is empty. */ export function last(array: undefined): undefined; export function last(array: ArrayLike): T; @@ -122,7 +124,7 @@ export function orderBy( } /** - * Returns true if arrays are equal + * Checks if arrays are equal * @param array1 * @param array2 * @@ -156,7 +158,7 @@ export function areEqual(array1: ArrayLike, array2: ArrayLike): boolean } /** - * Returns true if arrays are equal by the specified property + * Checks if arrays are equal by the specified property * @param array1 * @param array2 * @param selector @@ -194,7 +196,7 @@ export function areEqualBy( } /** - * Returns true if the array starts with the specified target + * Checks if the array starts with the specified target * @param array * @param target * @@ -227,7 +229,7 @@ export function startsWith(array: ArrayLike, target: ArrayLike): boolea } /** - * Returns true if the array ends with the specified target + * Checks if the array ends with the specified target * @param array * @param target *