feat: docs
This commit is contained in:
+144
-5
@@ -80,9 +80,22 @@ namespace At {
|
||||
type At<
|
||||
T extends readonly unknown[],
|
||||
K extends keyof T & number,
|
||||
> = `${K}` extends `-${infer N extends number}`
|
||||
? At.Select<T, At.Dec<N>>
|
||||
: T[K];
|
||||
> = number extends T["length"]
|
||||
? T[K] | undefined
|
||||
: `${K}` extends `-${infer N extends number}`
|
||||
? At.Select<T, At.Dec<N>>
|
||||
: T[K];
|
||||
|
||||
type Repeat<
|
||||
T extends readonly unknown[],
|
||||
N extends number,
|
||||
Acc extends null[] = [],
|
||||
TOut extends readonly T[number][] = [],
|
||||
> = Acc["length"] extends N
|
||||
? [...TOut, ...T]
|
||||
: Acc["length"] extends MaxDepth
|
||||
? T[number][]
|
||||
: Repeat<T, N, [...Acc, null], [...TOut, ...T]>;
|
||||
|
||||
namespace Flat {
|
||||
type Inc<
|
||||
@@ -124,18 +137,112 @@ export interface Array extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? T extends readonly (infer Item)[]
|
||||
? {
|
||||
/**
|
||||
* Index the array using the specified zero-based index.
|
||||
* Negative indexes start at the end of the array.
|
||||
* @param index Zero-based index
|
||||
* @example
|
||||
* const array = ["first", "middle", "last"] as const;
|
||||
*
|
||||
* const first = $(array).at(0).value;
|
||||
* const middle = $(array).at(1).value;
|
||||
* const last = $(array).at(-1).value;
|
||||
*
|
||||
* expect(first).toBe("first");
|
||||
* expect(middle).toBe("middle");
|
||||
* expect(last).toBe("last");
|
||||
*
|
||||
* const oob = $(array).at(10).value;
|
||||
* expect(oob).toBe(undefined);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
at: <const K extends keyof T & number>(
|
||||
index: K,
|
||||
) => Return<At<T, K>, typeof t>;
|
||||
/**
|
||||
* Interospect each item of the array using the specified
|
||||
* callback
|
||||
* @param callback Callback taking item, index, and the array for each item.
|
||||
* @example
|
||||
* const array = [4, 5, 6] as const;
|
||||
*
|
||||
* let sum = 0;
|
||||
* const v = $(array).each((n) => { sum += n }).value;
|
||||
*
|
||||
* expect(sum).toBe(15);
|
||||
* expect(v).toMatchObject(array);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
each: (
|
||||
callback: (...args: IterArgs<T>) => void,
|
||||
) => Return<T, typeof t>;
|
||||
/**
|
||||
* Transform each item of the array using the specified callback
|
||||
* @param callback Transformative callback taking item, index, and array; returns the new item
|
||||
* @example
|
||||
* const chars = ["h", "e", "l", "l", "o"] as const;
|
||||
*
|
||||
* const v = $(chars).map(ch => ch.toUpperCase()).value;
|
||||
* expect(v).toMatchObject(["H", "E", "L", "L", "O"]);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
map: <U>(
|
||||
callback: (...args: IterArgs<T>) => U,
|
||||
) => Return<Map<T, U>, typeof t>;
|
||||
filter: (
|
||||
/**
|
||||
* Extend the array by repeating its current contents n times
|
||||
* @param count The number of repetitions to add
|
||||
* @example
|
||||
* const array = [1, 2];
|
||||
*
|
||||
* const v = $(array).extend(2).value;
|
||||
* expect(v).toMatchObject([1, 2, 1, 2, 1, 2]);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
extend: <const N extends number>(
|
||||
count: N,
|
||||
) => Return<Repeat<T, N>, typeof t>;
|
||||
/**
|
||||
* Filter the array to contain only items satisfying the
|
||||
* specified callback conditional
|
||||
* @param callback Callback getting item, index, and array for each item and determining if it should be filtered out or not
|
||||
* @example
|
||||
* const dirty = [-2, 4, 1, -5, -6] as const;
|
||||
*
|
||||
* const v = $(dirty).filter(v => v >= 0).value;
|
||||
* expect(v).toMatchObject([4, 1]);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
filter: ((
|
||||
callback: (...args: IterArgs<T>) => boolean,
|
||||
) => Return<Unordered<T>, typeof t>;
|
||||
) => Return<Unordered<T>, typeof t>) & {
|
||||
/**
|
||||
* Filter the array to only contain items which are not
|
||||
* `null` or `undefined`.
|
||||
* @example
|
||||
* const array = new Array(5);
|
||||
*
|
||||
* array[0] = 1;
|
||||
* array[1] = 5;
|
||||
* array[2] = 2;
|
||||
*
|
||||
* const v = $(array).filter.some().value;
|
||||
* expect(v).toMatchObject([1, 5, 2]);
|
||||
* @from {@link Array `Array`}
|
||||
*/
|
||||
some: () => Return<
|
||||
T extends unknown[]
|
||||
? Exclude<
|
||||
T[number],
|
||||
null | undefined
|
||||
>[]
|
||||
: readonly Exclude<
|
||||
T[number],
|
||||
null | undefined
|
||||
>[],
|
||||
typeof t
|
||||
>;
|
||||
} & HidePrototype;
|
||||
reduce: (<U>(
|
||||
initial: U,
|
||||
callback: (
|
||||
@@ -217,6 +324,14 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
return fluent(value);
|
||||
};
|
||||
|
||||
$.extend = (count: number) => {
|
||||
assert(count >= 0);
|
||||
if (count === 0) return fluent(value);
|
||||
return fluent(
|
||||
value.concat(...new globalThis.Array(count).fill(value)),
|
||||
);
|
||||
};
|
||||
|
||||
$.map = (callback: (...args: IterArgs) => unknown) => {
|
||||
return fluent(value.map(callback));
|
||||
};
|
||||
@@ -224,6 +339,14 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
$.filter = (callback: (...args: IterArgs) => boolean) => {
|
||||
return fluent(value.filter(callback));
|
||||
};
|
||||
assertType<object>($.filter);
|
||||
Object.assign($.filter, {
|
||||
some: () => {
|
||||
return fluent(
|
||||
value.filter((v) => v !== null && v !== undefined),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
$.reduce = (
|
||||
initial: unknown,
|
||||
@@ -326,6 +449,17 @@ if (import.meta.vitest) {
|
||||
);
|
||||
});
|
||||
|
||||
test("extend()", () => {
|
||||
const arr = [1, 2] as const;
|
||||
|
||||
const copies = 2;
|
||||
const expected: number[] = new globalThis.Array(copies + 1)
|
||||
.fill(arr)
|
||||
.flat(1);
|
||||
|
||||
expect($(arr).extend(copies).value).toMatchObject(expected);
|
||||
});
|
||||
|
||||
test("map()", () => {
|
||||
const arr = [1, 2, 3, 4, 5] as const;
|
||||
const isEven = vi.fn((v: number) => v % 2 === 0);
|
||||
@@ -358,6 +492,11 @@ if (import.meta.vitest) {
|
||||
i,
|
||||
arr,
|
||||
);
|
||||
|
||||
const dirty = [1, null, 6, undefined, 2] as const;
|
||||
expect($(dirty).filter.some().value).toMatchObject(
|
||||
dirty.filter((v) => v !== null && v !== undefined),
|
||||
);
|
||||
});
|
||||
|
||||
test("reduce()", () => {
|
||||
|
||||
+26
-1
@@ -5,9 +5,34 @@ import { Mixin, type Input, type Return } from "../base/mixin";
|
||||
export interface Base extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? {
|
||||
/**
|
||||
* Interospect value using the specified `callback` without
|
||||
* modifying the value.
|
||||
* @param callback The interospective callback
|
||||
* @see `transform` to modify
|
||||
* @example
|
||||
* let x;
|
||||
* const value = $(10).tap(v => { x = ++v }).value;
|
||||
*
|
||||
* expect(x).toBe(11);
|
||||
* expect(value).toBe(10);
|
||||
* @from {@link Base `Base`}
|
||||
*/
|
||||
tap(
|
||||
callback: (value: T) => void,
|
||||
callback: (value: Readonly<T>) => void,
|
||||
): Return<T, typeof t>;
|
||||
/**
|
||||
* Put value through or pipe value through the specified
|
||||
* `callback` using the outputted return value as a new value.
|
||||
* A.K.A., _transform_ the current value using a callback
|
||||
* @param callback The transformative callback
|
||||
* @example
|
||||
* const value = $("Hello")
|
||||
* .transform(v => v.toUpperCase())
|
||||
* .value;
|
||||
* expect(value).toBe("HELLO");
|
||||
* @from {@link Base `Base`}
|
||||
*/
|
||||
transform<U>(
|
||||
callback: (value: T) => U,
|
||||
): Return<U, typeof t>;
|
||||
|
||||
+137
-4
@@ -6,7 +6,7 @@ import {
|
||||
type Props,
|
||||
type Return,
|
||||
} from "../base/mixin";
|
||||
import { assertType, type HidePrototype } from "../internal";
|
||||
import { assert, assertType, type HidePrototype } from "../internal";
|
||||
|
||||
type NoneSentinel = null | undefined;
|
||||
type None<T> = Extract<T, NoneSentinel>;
|
||||
@@ -17,6 +17,23 @@ function isNone<T>(v: T): v is None<T> {
|
||||
}
|
||||
|
||||
interface Where<T, t extends Props> {
|
||||
/**
|
||||
* Set a value to a fallback if it does not conform to some conditional
|
||||
* @param callback Callback returning a boolean, `false` sets the value to the fallback
|
||||
* @param fallback The fallback value, `null` by default
|
||||
* @example
|
||||
* const parse = () => { version: "1.2.4" } as unknown;
|
||||
*
|
||||
* const version = $(parse())
|
||||
* .where(v => typeof v === 'object' &&
|
||||
* v !== null &&
|
||||
* 'version' in v)
|
||||
* .and(v => v.version)
|
||||
* .or("1.0.0")
|
||||
* .value;
|
||||
* expect(version).toBe("1.2.4");
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
where: <U = null>(
|
||||
callback: (v: T) => boolean,
|
||||
fallback?: U,
|
||||
@@ -28,16 +45,111 @@ export interface Optional extends Mixin.HKT {
|
||||
? None<T> extends never
|
||||
? Where<T, typeof t>
|
||||
: {
|
||||
/**
|
||||
* Transform a value via callback if it is not `null` or
|
||||
* `undefined`
|
||||
*
|
||||
* If the value is equal to `null` or `undefined`, it
|
||||
* remains unchanged and the callback is not called.
|
||||
* @param callback Function for a non-null value
|
||||
* @example
|
||||
* const none = () => null as number | null;
|
||||
* const some = () => 10 as number | null;
|
||||
*
|
||||
* const callback = (v: number) => v + 5;
|
||||
*
|
||||
* const a = $(none()).and(callback).value;
|
||||
* expect(a).toBe(null)
|
||||
*
|
||||
* const b = $(some()).and(callback).value;
|
||||
* expect(b).toBe(15);
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
and: <U>(
|
||||
callback: (v: Some<T>) => U,
|
||||
) => Return<None<T> | U, typeof t>;
|
||||
/**
|
||||
* Set value to a fallback if it is `null` or `undefined`.
|
||||
* @param fallback The fallback value to use. To defer
|
||||
* computing this fallback value, you can use `.or.else()`
|
||||
* @example
|
||||
* const none = () => null as number | null;
|
||||
* const some = () => 10 as number | null;
|
||||
*
|
||||
* const fallback = -1;
|
||||
*
|
||||
* const a = $(none()).or(fallback).value;
|
||||
* expect(a).toBe(fallback);
|
||||
*
|
||||
* const b = $(some()).or(fallback).value;
|
||||
* expect(b).toBe(10);
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
or: (<U>(
|
||||
fallback: U,
|
||||
) => Return<Some<T> | U, typeof t>) & {
|
||||
/**
|
||||
* Set value to the result of `callback` if it is `null`
|
||||
* or `undefined`. Unlike the normal `.or()`, this method
|
||||
* only computes the fallback if the value is `null` or
|
||||
* `undefined`
|
||||
* @param callback
|
||||
* @example
|
||||
* const none = () => null as number | null;
|
||||
* const some = () => 8 as number | null;
|
||||
*
|
||||
* const fallback = () => 22; // mocked
|
||||
*
|
||||
* const a = $(none()).or.else(fallback).value;
|
||||
* expect(a).toBe(22);
|
||||
*
|
||||
* const b = $(some()).or.else(fallback).value;
|
||||
* expect(b).toBe(8);
|
||||
*
|
||||
* expect(fallback).toHaveBeenCalledOnce()
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
else: <U>(
|
||||
callback: (v: None<T>) => U,
|
||||
) => Return<Some<T> | U, typeof t>;
|
||||
} & HidePrototype;
|
||||
/**
|
||||
* Assert that value is not `null` or `undefined`
|
||||
* @param msg Reasoning to attach to the `AssertionError`
|
||||
* @example
|
||||
* const array = [1, 2, 3];
|
||||
* const element: number | undefined = array[1];
|
||||
*
|
||||
* const v = $(element).assert("index within bounds").value;
|
||||
* expect(v).toBe(2);
|
||||
*
|
||||
* expect(() => {
|
||||
* $(array[6]).assert()
|
||||
* }).toThrow()
|
||||
* @see `.assert.none()` for the inverse assertion
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
assert: ((
|
||||
msg?: string,
|
||||
) => Return<Some<T>, typeof t>) & {
|
||||
/**
|
||||
* Assert that the value is either `null` or `undefined`
|
||||
* @param msg Reasoning to attach to the `AssertionError`
|
||||
* @example
|
||||
* const array = [1, 2, 3];
|
||||
* const element: number | undefined = array[5];
|
||||
*
|
||||
* const v = $(element).assert.none("index out of bounds").value;
|
||||
* expect(v).toBe(undefined);
|
||||
*
|
||||
* expect(() => {
|
||||
* $(array[1]).assert.none()
|
||||
* }).toThrow()
|
||||
*/
|
||||
none: (
|
||||
msg?: string,
|
||||
) => Return<None<T>, typeof t>;
|
||||
} & HidePrototype;
|
||||
} & Where<T, typeof t>
|
||||
: never;
|
||||
}
|
||||
@@ -51,13 +163,22 @@ export const Optional = Mixin<Optional>((value, $, fluent) => {
|
||||
$.or = (fallback: unknown) => {
|
||||
return fluent(fallback);
|
||||
};
|
||||
|
||||
assertType<object>($.or);
|
||||
Object.assign($.or, {
|
||||
else: (callback: (v: unknown) => unknown) => {
|
||||
return fluent(callback(value));
|
||||
},
|
||||
});
|
||||
|
||||
$.assert = () => {
|
||||
return fluent(value);
|
||||
};
|
||||
assertType<object>($.assert);
|
||||
Object.assign($.assert, {
|
||||
none: (msg?: string) => {
|
||||
assert(false, msg);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$.and = (callback: (v: unknown) => unknown) => {
|
||||
return fluent(callback(value));
|
||||
@@ -73,6 +194,16 @@ export const Optional = Mixin<Optional>((value, $, fluent) => {
|
||||
return fluent(value);
|
||||
},
|
||||
});
|
||||
|
||||
$.assert = (msg?: string) => {
|
||||
assert(false, msg);
|
||||
};
|
||||
assertType<object>($.assert);
|
||||
Object.assign($.assert, {
|
||||
none: (msg?: string) => {
|
||||
return fluent(value);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$.where = (
|
||||
@@ -85,7 +216,7 @@ export const Optional = Mixin<Optional>((value, $, fluent) => {
|
||||
});
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect } = import.meta.vitest;
|
||||
const { test, expect, vi } = import.meta.vitest;
|
||||
|
||||
const registry = [Optional] as const;
|
||||
const $ = makeFluent(registry);
|
||||
@@ -112,10 +243,12 @@ if (import.meta.vitest) {
|
||||
expect($(some).or(fallback).value).toBe(some);
|
||||
expect($(none).or(fallback).value).toBe(fallback);
|
||||
|
||||
const callback = () => fallback;
|
||||
const callback = vi.fn(() => fallback);
|
||||
|
||||
expect($(some).or.else(callback).value).toBe(some);
|
||||
expect($(none).or.else(callback).value).toBe(fallback);
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("where()", () => {
|
||||
|
||||
Reference in New Issue
Block a user