feat: more math & refactor
This commit is contained in:
@@ -46,6 +46,7 @@ export default defineConfig([
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"jiti": "^2.7.0",
|
||||
"madge": "^8.0.0",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript": "6",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -39,7 +39,7 @@ importers:
|
||||
specifier: ^3.8.4
|
||||
version: 3.8.4
|
||||
typescript:
|
||||
specifier: ~6.0.2
|
||||
specifier: '6'
|
||||
version: 6.0.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.59.2
|
||||
|
||||
+4
-3
@@ -1,18 +1,19 @@
|
||||
export interface HKT<In = unknown, Out = unknown> {
|
||||
readonly _meta: In;
|
||||
readonly _in: In;
|
||||
readonly _out: Out;
|
||||
readonly _t: unknown;
|
||||
new: (t: never) => Out;
|
||||
}
|
||||
|
||||
type Param<
|
||||
This extends HKT,
|
||||
U = This["_meta"],
|
||||
U = This["_in"],
|
||||
> = This["_t"] extends infer T ? (T extends U ? T : U) : never;
|
||||
|
||||
export namespace HKT {
|
||||
export type T<
|
||||
This extends HKT,
|
||||
T = This extends { _meta: infer I } ? I : unknown,
|
||||
T = This["_in"], //This extends { _in: infer I } ? I : unknown,
|
||||
> = Param<This, T>;
|
||||
export type Apply<T extends HKT, t extends T["_t"]> = ReturnType<
|
||||
(T & { _t: t })["new"]
|
||||
|
||||
+1
-2
@@ -33,8 +33,7 @@ export function makeFluent<const Reg extends Registry>(
|
||||
const f = { value } as unknown as Fluent<T, Reg>;
|
||||
|
||||
for (const mixin of registry) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
mixin.fn(value, f, fluent as (value: unknown) => never);
|
||||
mixin.fn(value, f, fluent);
|
||||
}
|
||||
|
||||
return f;
|
||||
|
||||
+26
-10
@@ -1,6 +1,7 @@
|
||||
import type { Fluent } from ".";
|
||||
import { never } from "../internal";
|
||||
import type { Registry } from "../registry";
|
||||
import type { RecPartial } from "../utility";
|
||||
import type { HKT } from "./hkt";
|
||||
|
||||
export interface Props {
|
||||
@@ -8,28 +9,43 @@ export interface Props {
|
||||
readonly meta: { registry: Registry };
|
||||
}
|
||||
|
||||
type MixinHKT = HKT<Props>;
|
||||
type MixinFn = (
|
||||
value: unknown,
|
||||
fluent: Record<PropertyKey, unknown>,
|
||||
callback: (value: unknown) => never,
|
||||
type MixinHKT<I> = HKT<Props, RecPartial<I>>;
|
||||
type MixinFn<I> = <T>(
|
||||
value: T,
|
||||
fluent: object & RecPartial<I>,
|
||||
callback: <U>(value: U) => Fluent<U, Registry>,
|
||||
) => void;
|
||||
|
||||
export interface Mixin<T extends MixinHKT = MixinHKT> {
|
||||
export interface Mixin<
|
||||
I = unknown,
|
||||
T extends MixinHKT<I> = MixinHKT<I>,
|
||||
> {
|
||||
interface: I;
|
||||
hkt: T;
|
||||
fn: MixinFn;
|
||||
fn: MixinFn<I>;
|
||||
}
|
||||
|
||||
export function Mixin<T extends MixinHKT>(fn: MixinFn): Mixin<T> {
|
||||
export function Mixin<I, T extends MixinHKT<I>>(
|
||||
fn: MixinFn<I>,
|
||||
): Mixin<I, T> {
|
||||
return {
|
||||
interface: never,
|
||||
hkt: never,
|
||||
fn,
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Mixin {
|
||||
export type HKT = MixinHKT;
|
||||
export type Function = MixinFn;
|
||||
export type HKT<I> = MixinHKT<I>;
|
||||
export function partial<I, T>(
|
||||
callback: (
|
||||
value: T,
|
||||
fluent: object & RecPartial<I>,
|
||||
callback: <P>(value: P) => Fluent<P, Registry>,
|
||||
) => void,
|
||||
): typeof callback {
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
|
||||
export declare const shim: unique symbol;
|
||||
|
||||
+8
-2
@@ -7,6 +7,10 @@ export interface Identity extends HKT {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
export const never = undefined as never;
|
||||
|
||||
export function isArray(v: unknown): v is unknown[] {
|
||||
return typeof v === "object" && v !== null && Array.isArray(v);
|
||||
}
|
||||
|
||||
export class AssertionError extends Error {
|
||||
public constructor(msg?: string) {
|
||||
super(msg);
|
||||
@@ -26,10 +30,12 @@ export function assert(
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters, @typescript-eslint/no-unused-vars
|
||||
export function assertType<T>(_v: unknown): asserts _v is T {
|
||||
export namespace assert {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function type<T>(_v: unknown): asserts _v is NoInfer<T> {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
type FunctionProto = Function;
|
||||
|
||||
+83
-87
@@ -1,7 +1,12 @@
|
||||
import { makeFluent } from "../base";
|
||||
import type { HKT } from "../base/hkt";
|
||||
import { Mixin, type Input, type Return } from "../base/mixin";
|
||||
import { assert, assertType, type HidePrototype } from "../internal";
|
||||
import {
|
||||
Mixin,
|
||||
type Input,
|
||||
type Props,
|
||||
type Return,
|
||||
} from "../base/mixin";
|
||||
import { assert, type HidePrototype } from "../internal";
|
||||
import type { MaxDepth, At } from "../utility";
|
||||
|
||||
type IterArgs<T extends readonly unknown[] = unknown[]> = [
|
||||
@@ -50,10 +55,10 @@ function isArray(v: unknown): v is readonly unknown[] {
|
||||
return typeof v === "object" && globalThis.Array.isArray(v);
|
||||
}
|
||||
|
||||
export interface Array extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? T extends readonly (infer Item)[]
|
||||
? {
|
||||
interface IArray<
|
||||
T extends readonly unknown[] = readonly unknown[],
|
||||
t extends Props = Props,
|
||||
> {
|
||||
/**
|
||||
* Index the array using the specified zero-based index.
|
||||
* Negative indexes start at the end of the array.
|
||||
@@ -78,7 +83,7 @@ export interface Array extends Mixin.HKT {
|
||||
at: <const K extends keyof T & number>(
|
||||
/** Zero-based index */
|
||||
index: K,
|
||||
) => Return<At<T, K>, typeof t>;
|
||||
) => Return<At<T, K>, t>;
|
||||
/**
|
||||
* Interospect each item of the array using the specified
|
||||
* callback
|
||||
@@ -103,7 +108,7 @@ export interface Array extends Mixin.HKT {
|
||||
* @param array The array being iterated
|
||||
*/
|
||||
callback: (...args: IterArgs<T>) => void,
|
||||
) => Return<T, typeof t>;
|
||||
) => Return<T, t>;
|
||||
/**
|
||||
* Transform each item of the array using the specified callback
|
||||
* @param callback Predicate to compute each new value with
|
||||
@@ -125,7 +130,7 @@ export interface Array extends Mixin.HKT {
|
||||
* @return The new value to use for this element
|
||||
*/
|
||||
callback: (...args: IterArgs<T>) => U,
|
||||
) => Return<Map<T, U>, typeof t>;
|
||||
) => Return<Map<T, U>, t>;
|
||||
/**
|
||||
* Extend the array by repeating its current contents n times
|
||||
* @param count The amount of repetitions to extend the array by
|
||||
@@ -141,7 +146,7 @@ export interface Array extends Mixin.HKT {
|
||||
extend: <const N extends number>(
|
||||
/** The amount of repetitions to extend the array by */
|
||||
count: N,
|
||||
) => Return<Repeat<T, N>, typeof t>;
|
||||
) => Return<Repeat<T, N>, t>;
|
||||
/**
|
||||
* Filter the array to contain only items satisfying the
|
||||
* specified callback conditional
|
||||
@@ -164,7 +169,7 @@ export interface Array extends Mixin.HKT {
|
||||
* @return `true` if the value should be kept and `false` if it should not
|
||||
*/
|
||||
callback: (...args: IterArgs<T>) => boolean,
|
||||
) => Return<Unordered<T>, typeof t>) & {
|
||||
) => Return<Unordered<T>, t>) & {
|
||||
/**
|
||||
* Filter the array to only contain items which are not
|
||||
* `null` or `undefined`.
|
||||
@@ -183,15 +188,9 @@ export interface Array extends Mixin.HKT {
|
||||
*/
|
||||
some: () => Return<
|
||||
T extends unknown[]
|
||||
? Exclude<
|
||||
T[number],
|
||||
null | undefined
|
||||
>[]
|
||||
: readonly Exclude<
|
||||
T[number],
|
||||
null | undefined
|
||||
>[],
|
||||
typeof t
|
||||
? Exclude<T[number], null | undefined>[]
|
||||
: readonly Exclude<T[number], null | undefined>[],
|
||||
t
|
||||
>;
|
||||
} & HidePrototype;
|
||||
/**
|
||||
@@ -221,11 +220,8 @@ export interface Array extends Mixin.HKT {
|
||||
* @param index The index of the current element in the array
|
||||
* @param array The array being iterated
|
||||
*/
|
||||
callback: (
|
||||
value: U,
|
||||
...args: IterArgs<T>
|
||||
) => U,
|
||||
) => Return<U, typeof t>) & {
|
||||
callback: (value: U, ...args: IterArgs<T>) => U,
|
||||
) => Return<U, t>) & {
|
||||
/**
|
||||
* Collapse the array using the specified accumulator function, starting from the right.
|
||||
* @param initial The initial value of the accumulator
|
||||
@@ -253,11 +249,8 @@ export interface Array extends Mixin.HKT {
|
||||
* @param index The index of the current element in the array
|
||||
* @param array The array being iterated
|
||||
*/
|
||||
callback: (
|
||||
value: U,
|
||||
...args: IterArgs<T>
|
||||
) => U,
|
||||
) => Return<U, typeof t>;
|
||||
callback: (value: U, ...args: IterArgs<T>) => U,
|
||||
) => Return<U, t>;
|
||||
} & HidePrototype;
|
||||
/**
|
||||
* Get the number of items in the array
|
||||
@@ -269,7 +262,7 @@ export interface Array extends Mixin.HKT {
|
||||
* expect(count).toBe(3);
|
||||
* ```
|
||||
*/
|
||||
length: () => Return<T["length"], typeof t>;
|
||||
length: () => Return<T["length"], t>;
|
||||
|
||||
/**
|
||||
* Rearrange the items in the array in accordance to the
|
||||
@@ -299,12 +292,12 @@ export interface Array extends Mixin.HKT {
|
||||
* and `0` if `a` and `b` are considered the same.
|
||||
*/
|
||||
callback: (
|
||||
a: Item,
|
||||
b: Item,
|
||||
a: T[number],
|
||||
b: T[number],
|
||||
arr: Readonly<T>,
|
||||
) => number,
|
||||
) => Return<Unordered<T>, typeof t>) &
|
||||
([Item] extends [string]
|
||||
) => Return<Unordered<T>, t>) &
|
||||
([T[number]] extends [string]
|
||||
? {
|
||||
/**
|
||||
* @param via A callback to compute a
|
||||
@@ -316,11 +309,8 @@ export interface Array extends Mixin.HKT {
|
||||
* @param v The current element
|
||||
* @return A string representation of `v`
|
||||
*/
|
||||
via?: (v: Item) => string,
|
||||
) => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
via?: (v: T[number]) => string,
|
||||
) => Return<Unordered<T>, t>;
|
||||
}
|
||||
: {
|
||||
/**
|
||||
@@ -333,36 +323,21 @@ export interface Array extends Mixin.HKT {
|
||||
* @param v The current element
|
||||
* @return The string representation of `v`
|
||||
*/
|
||||
via: (v: Item) => string,
|
||||
) => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
via: (v: T[number]) => string,
|
||||
) => Return<Unordered<T>, t>;
|
||||
}) &
|
||||
([Item] extends [number]
|
||||
([T[number]] extends [number]
|
||||
? {
|
||||
ascending: () => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
descending: () => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
ascending: () => Return<Unordered<T>, t>;
|
||||
descending: () => Return<Unordered<T>, t>;
|
||||
}
|
||||
: {
|
||||
ascending: (
|
||||
via: (v: Item) => number,
|
||||
) => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
via: (v: T[number]) => number,
|
||||
) => Return<Unordered<T>, t>;
|
||||
descending: (
|
||||
via: (v: Item) => number,
|
||||
) => Return<
|
||||
Unordered<T>,
|
||||
typeof t
|
||||
>;
|
||||
via: (v: T[number]) => number,
|
||||
) => Return<Unordered<T>, t>;
|
||||
}) & {
|
||||
/**
|
||||
* Sort alphabetically (A-Z) by Unicode code points
|
||||
@@ -376,12 +351,19 @@ export interface Array extends Mixin.HKT {
|
||||
*/
|
||||
alpha: unknown;
|
||||
} & HidePrototype;
|
||||
reverse: () => Return<Reverse<T>, typeof t>;
|
||||
reverse: () => Return<Reverse<T>, t>;
|
||||
}
|
||||
: unknown
|
||||
|
||||
export interface Array extends Mixin.HKT<IArray> {
|
||||
new: (
|
||||
t: HKT.T<this>,
|
||||
) => Input<typeof t> extends infer T
|
||||
? T extends readonly unknown[]
|
||||
? IArray<T, typeof t>
|
||||
: {}
|
||||
: never;
|
||||
}
|
||||
export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
export const Array = Mixin<IArray, Array>((value, $, fluent) => {
|
||||
if (!isArray(value)) return;
|
||||
|
||||
$.at = (index: number) => {
|
||||
@@ -407,39 +389,47 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
return fluent(value.map(callback));
|
||||
};
|
||||
|
||||
$.filter = (callback: (...args: IterArgs) => boolean) => {
|
||||
$.filter = Object.assign(
|
||||
(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),
|
||||
value.filter(
|
||||
(v) => v !== null && v !== undefined,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$.reduce = (
|
||||
$.reduce = Object.assign(
|
||||
(
|
||||
initial: unknown,
|
||||
callback: (value: unknown, ...args: IterArgs) => unknown,
|
||||
) => {
|
||||
return fluent(value.reduce(callback, initial));
|
||||
};
|
||||
assertType<object>($.reduce);
|
||||
Object.assign($.reduce, {
|
||||
},
|
||||
{
|
||||
right: (
|
||||
initial: unknown,
|
||||
callback: (value: unknown, ...args: IterArgs) => unknown,
|
||||
callback: (
|
||||
value: unknown,
|
||||
...args: IterArgs
|
||||
) => unknown,
|
||||
) => {
|
||||
return fluent(value.reduceRight(callback, initial));
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$.length = () => {
|
||||
return fluent(value.length);
|
||||
};
|
||||
|
||||
$.sort = (
|
||||
$.sort = Object.assign(
|
||||
(
|
||||
callback: (
|
||||
a: unknown,
|
||||
b: unknown,
|
||||
@@ -449,9 +439,8 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
return fluent(
|
||||
value.toSorted((a, b) => callback(a, b, value)),
|
||||
);
|
||||
};
|
||||
assertType<object>($.sort);
|
||||
Object.assign($.sort, {
|
||||
},
|
||||
{
|
||||
alpha: (
|
||||
via: (v: unknown) => string = (v) => (
|
||||
assert(typeof v === "string"),
|
||||
@@ -459,7 +448,9 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
),
|
||||
) => {
|
||||
return fluent(
|
||||
value.toSorted((a, b) => (via(a) < via(b) ? -1 : 1)),
|
||||
value.toSorted((a, b) =>
|
||||
via(a) < via(b) ? -1 : 1,
|
||||
),
|
||||
);
|
||||
},
|
||||
ascending: (
|
||||
@@ -468,7 +459,9 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
v
|
||||
),
|
||||
) => {
|
||||
return fluent(value.toSorted((a, b) => via(a) - via(b)));
|
||||
return fluent(
|
||||
value.toSorted((a, b) => via(a) - via(b)),
|
||||
);
|
||||
},
|
||||
descending: (
|
||||
via: (v: unknown) => number = (v) => (
|
||||
@@ -476,9 +469,12 @@ export const Array = Mixin<Array>((value, $, fluent) => {
|
||||
v
|
||||
),
|
||||
) => {
|
||||
return fluent(value.toSorted((a, b) => via(b) - via(a)));
|
||||
return fluent(
|
||||
value.toSorted((a, b) => via(b) - via(a)),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$.reverse = () => {
|
||||
return fluent(value.toReversed());
|
||||
|
||||
+33
-22
@@ -1,7 +1,13 @@
|
||||
import { makeFluent } from "../base";
|
||||
import type { HKT } from "../base/hkt";
|
||||
import { Mixin, shim, type Input, type Return } from "../base/mixin";
|
||||
import { assert, assertType, never } from "../internal";
|
||||
import {
|
||||
Mixin,
|
||||
shim,
|
||||
type Input,
|
||||
type Props,
|
||||
type Return,
|
||||
} from "../base/mixin";
|
||||
import { assert, never } from "../internal";
|
||||
import { Base } from "./base";
|
||||
|
||||
interface AwaitedIdentitity extends HKT {
|
||||
@@ -23,20 +29,23 @@ class Awaited<T extends Promise<unknown>> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsyncMixin extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? T extends Promise<unknown>
|
||||
? {
|
||||
readonly awaited: Return<Awaited<T>, typeof t>;
|
||||
interface IAsync<
|
||||
T extends Promise<unknown> = Promise<unknown>,
|
||||
t extends Props = Props,
|
||||
> {
|
||||
readonly awaited: Return<Awaited<T>, t>;
|
||||
then: <U>(
|
||||
callback: (
|
||||
value: T extends Promise<infer T>
|
||||
? T
|
||||
: never,
|
||||
) => U,
|
||||
) => Return<Promise<U>, typeof t>;
|
||||
fn: (value: T extends Promise<infer O> ? O : never) => U,
|
||||
) => Return<Promise<U>, t>;
|
||||
}
|
||||
: unknown
|
||||
|
||||
export interface AsyncMixin extends Mixin.HKT<IAsync> {
|
||||
new: (
|
||||
t: HKT.T<this>,
|
||||
) => Input<typeof t> extends infer T
|
||||
? T extends Promise<unknown>
|
||||
? IAsync<T, typeof t>
|
||||
: {}
|
||||
: never;
|
||||
}
|
||||
|
||||
@@ -45,7 +54,8 @@ interface BaseFluent<T> {
|
||||
[K: PropertyKey]: unknown;
|
||||
}
|
||||
|
||||
export const AsyncMixin = Mixin<AsyncMixin>((value, $, fluent) => {
|
||||
export const AsyncMixin = Mixin<IAsync, AsyncMixin>(
|
||||
(value, $, fluent) => {
|
||||
if (!(value instanceof Promise)) return;
|
||||
|
||||
$.then = (callback: (value: unknown) => unknown) => {
|
||||
@@ -55,8 +65,8 @@ export const AsyncMixin = Mixin<AsyncMixin>((value, $, fluent) => {
|
||||
Object.defineProperty($, "awaited", {
|
||||
enumerable: true,
|
||||
get() {
|
||||
let v: Promise<BaseFluent<unknown>> = value.then((v) =>
|
||||
fluent(v),
|
||||
let v: Promise<BaseFluent<unknown>> = value.then(
|
||||
(v) => fluent(v),
|
||||
);
|
||||
|
||||
const path: PropertyKey[] = [];
|
||||
@@ -77,15 +87,15 @@ export const AsyncMixin = Mixin<AsyncMixin>((value, $, fluent) => {
|
||||
obj !== null &&
|
||||
node in obj,
|
||||
);
|
||||
assertType<Record<PropertyKey, unknown>>(
|
||||
obj,
|
||||
);
|
||||
assert.type<
|
||||
Record<PropertyKey, unknown>
|
||||
>(obj);
|
||||
|
||||
obj = obj[node];
|
||||
}
|
||||
|
||||
assert(typeof obj === "function");
|
||||
assertType<
|
||||
assert.type<
|
||||
(
|
||||
...args: unknown[]
|
||||
) => BaseFluent<unknown>
|
||||
@@ -100,7 +110,8 @@ export const AsyncMixin = Mixin<AsyncMixin>((value, $, fluent) => {
|
||||
return proxy;
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect } = import.meta.vitest;
|
||||
|
||||
+51
-13
@@ -1,10 +1,13 @@
|
||||
import { makeFluent } from "../base";
|
||||
import type { HKT } from "../base/hkt";
|
||||
import { Mixin, type Input, type Return } from "../base/mixin";
|
||||
import {
|
||||
Mixin,
|
||||
type Input,
|
||||
type Props,
|
||||
type Return,
|
||||
} from "../base/mixin";
|
||||
|
||||
export interface Base extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? {
|
||||
interface IBase<T = unknown, t extends Props = Props> {
|
||||
/**
|
||||
* Interospect value using the specified `callback` without
|
||||
* modifying the value.
|
||||
@@ -20,9 +23,7 @@ export interface Base extends Mixin.HKT {
|
||||
* expect(value).toBe(10);
|
||||
* ```
|
||||
*/
|
||||
tap(
|
||||
callback: (value: Readonly<T>) => void,
|
||||
): Return<T, typeof t>;
|
||||
tap: (callback: (value: Readonly<T>) => void) => Return<T, t>;
|
||||
/**
|
||||
* Put value through or pipe value through the specified
|
||||
* `callback` using the outputted return value as a new value.
|
||||
@@ -37,14 +38,34 @@ export interface Base extends Mixin.HKT {
|
||||
* expect(value).toBe("HELLO");
|
||||
* ```
|
||||
*/
|
||||
transform<U>(
|
||||
callback: (value: T) => U,
|
||||
): Return<U, typeof t>;
|
||||
}
|
||||
: never;
|
||||
transform: <U>(callback: (value: T) => U) => Return<U, t>;
|
||||
/**
|
||||
* Set value to a fallback if the specified callback returns `false`
|
||||
* @param callback Guarding callback returning `true` to keep the value
|
||||
* @param fallback The fallback value, `null` by default
|
||||
* @from {@link Base `Base`}
|
||||
* @example
|
||||
* const isObject = (value: unknown) => typeof value === 'object';
|
||||
*
|
||||
* const a = $({ version: '1.0.0' }).where(isObject).value;
|
||||
* const b = $(42).where(isObject).value;
|
||||
* const c = $(42).where(isObject, { version: '0.1.0' }).value;
|
||||
*
|
||||
* expect(a).toMatchObject({ version: '1.0.0' });
|
||||
* expect(b).toBe(null);
|
||||
* expect(c).toMatchObject({ version: '0.1.0' });
|
||||
*/
|
||||
where: <const U = null>(
|
||||
callback: (v: T) => boolean,
|
||||
fallback?: U,
|
||||
) => Return<T | U, t>;
|
||||
}
|
||||
|
||||
export const Base = Mixin<Base>((value, $, fluent) => {
|
||||
export interface Base extends Mixin.HKT<IBase> {
|
||||
new: (t: HKT.T<this>) => IBase<Input<typeof t>, typeof t>;
|
||||
}
|
||||
|
||||
export const Base = Mixin<IBase, Base>((value, $, fluent) => {
|
||||
$.tap = (callback: (value: unknown) => void) => {
|
||||
callback(value);
|
||||
return fluent(value);
|
||||
@@ -53,6 +74,11 @@ export const Base = Mixin<Base>((value, $, fluent) => {
|
||||
$.transform = (callback: (value: unknown) => unknown) => {
|
||||
return fluent(callback(value));
|
||||
};
|
||||
|
||||
$.where = (
|
||||
callback: (value: unknown) => boolean,
|
||||
fallback: unknown = null,
|
||||
) => fluent(callback(value) ? value : fallback);
|
||||
});
|
||||
|
||||
if (import.meta.vitest) {
|
||||
@@ -83,4 +109,16 @@ if (import.meta.vitest) {
|
||||
$(value).transform(increment).transform(increment).value,
|
||||
).toBe(increment(increment(value)));
|
||||
});
|
||||
|
||||
test("where()", () => {
|
||||
const even = 4 as const;
|
||||
const odd = 7 as const;
|
||||
|
||||
const isEven = (v: number) => v % 2 === 0;
|
||||
|
||||
expect($(even).where(isEven).value).toBe(even);
|
||||
expect($(odd).where(isEven).value).toBe(null);
|
||||
|
||||
expect($(odd).where(isEven, -1).value).toBe(-1);
|
||||
});
|
||||
}
|
||||
|
||||
+29
-712
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
||||
import type { Inc, Vec } from "../../utility";
|
||||
|
||||
export type Floor<T extends number> =
|
||||
`${T}` extends `${infer N extends number}.${number}` ? N : T;
|
||||
export type Ceil<T extends number> =
|
||||
`${T}` extends `${infer N extends number}.${number}` ? Inc<N> : T;
|
||||
export type Round<T extends number> =
|
||||
`${T}` extends `${infer N extends number}.${0 | 1 | 2 | 3 | 4}${number | ""}`
|
||||
? N
|
||||
: Ceil<T>;
|
||||
|
||||
export type Sign<T extends number> = T extends 0
|
||||
? 0
|
||||
: `${T}` extends `-${number}`
|
||||
? -1
|
||||
: 1;
|
||||
|
||||
export type Abs<T extends number> =
|
||||
`${T}` extends `-${infer N extends number}` ? N : T;
|
||||
|
||||
export type Negate<T extends number> = T extends 0
|
||||
? 0
|
||||
: `${T}` extends `-${infer N extends number}`
|
||||
? N
|
||||
: `-${T}` extends `${infer N extends number}`
|
||||
? N
|
||||
: number;
|
||||
|
||||
type Fn<
|
||||
N extends number = typeof Infinity,
|
||||
Max extends boolean = false,
|
||||
> = (
|
||||
...args: [...Vec<never, N>, ...(Max extends true ? [] : never[])]
|
||||
) => unknown;
|
||||
|
||||
interface IBase {
|
||||
/**
|
||||
* `x + y`
|
||||
* @param other The second term of the addition (`y`)
|
||||
*/
|
||||
add: Fn<1>;
|
||||
/**
|
||||
* `x - y`
|
||||
* @param other The second term of the subtraction (`y`)
|
||||
*/
|
||||
subtract: Fn<1>;
|
||||
/**
|
||||
* `x * y`
|
||||
* @param factor Factor term of the multiplication (`y`)
|
||||
*/
|
||||
multiply: Fn<1>;
|
||||
/**
|
||||
* `x ** y`
|
||||
* @param exponent Exponent term of the power (`y`)
|
||||
*/
|
||||
pow: Fn<1, true>;
|
||||
/**
|
||||
* `Math.sqrt(x)` (or `Math.log2(x)`)
|
||||
*/
|
||||
sqrt: Fn<0, true>;
|
||||
/**
|
||||
* `x / y`
|
||||
* @param divisor Divisor term of the division (`y`)
|
||||
*/
|
||||
divide: Fn<1, true>;
|
||||
/**
|
||||
* `x mod y`, not to be confused with `x % y` (remainder operation)
|
||||
* @param divisor Divisor term of the modulo operation (`y`)
|
||||
* @see `.rem()` for the remainder operation
|
||||
*/
|
||||
mod: Fn<1, true>;
|
||||
/**
|
||||
* `x % y` remainder operation, not to be confused with `x mod y` (modulo)
|
||||
* @param divisor Divisor term of the remainder operation (`y`)
|
||||
* @see `.mod()` for the true modulo operation
|
||||
*/
|
||||
rem: Fn<1, true>;
|
||||
/**
|
||||
* `ln(x)`, `log(x, base)`, or alternatively `Math.log(x) / Math.log(base)`
|
||||
* @param base Base of the logarithm (`base`). Defaults to `Math.E` for a natural logarithm function
|
||||
*/
|
||||
log: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.floor(x)`
|
||||
*/
|
||||
floor: Fn<0, true>;
|
||||
/**
|
||||
* `Math.ceil(x)`
|
||||
*/
|
||||
ceil: Fn<0, true>;
|
||||
/**
|
||||
* `Math.round(x)` or `Math.round(x / multiple) * multiple` to round to the
|
||||
* specified multiple
|
||||
* @param multiple Multiple to round to, 1 if unspecified
|
||||
*/
|
||||
round: Fn<1, true>;
|
||||
/**
|
||||
* `Math.fround(x)`, round to the nearest 32-bit float
|
||||
* approximation of value
|
||||
*/
|
||||
fround: Fn<0, true>;
|
||||
/**
|
||||
* `Math.f16round(x)`, round to the nearest 16-bit float
|
||||
* approximation of value
|
||||
*/
|
||||
ffround: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.abs(x)`, calculate the absolute value (distance
|
||||
* from 0).
|
||||
*/
|
||||
abs: Fn<0, true>;
|
||||
/**
|
||||
* `Math.sign(x)`, returns the sign (1 or -1) of the value or zero
|
||||
*/
|
||||
sign: Fn<0, true>;
|
||||
/**
|
||||
* `-x` or alternatively `x * -1`
|
||||
*/
|
||||
negate: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* `Math.min(x, ...values)`
|
||||
* @param values Other canidates
|
||||
*/
|
||||
min: Fn<0>;
|
||||
/**
|
||||
* `Math.min(x, ...values)`
|
||||
* @param values Other canidates
|
||||
*/
|
||||
max: Fn<0>;
|
||||
|
||||
/**
|
||||
* `Math.min(Math.max(x, min), max)`
|
||||
* @param min Smallest allowed value to clamp to
|
||||
* @param max Largest allowed value to clamp to
|
||||
*/
|
||||
clamp: Fn<2, true>;
|
||||
/**
|
||||
* Clamps value into the range [0.0, 1.0]
|
||||
* @see `.clamp()` for a customizable range
|
||||
*/
|
||||
saturate: Fn<0, true>;
|
||||
|
||||
/**
|
||||
* Calculate the value at the specified point `t` on the line from the
|
||||
* current value (`a`) corresponding to 0.0 to the target value (`b`)
|
||||
* corresponding to 1.0. Linear interpolation from `a` to `b` at `t`.
|
||||
*
|
||||
* `t` is not constrained or clamped to the range [0.0, 1.0], values beyond
|
||||
* that range extrapolate linearly beyond `a` and `b`.
|
||||
*
|
||||
* Equivalent to `lerp(a, b, t)`, `a * (1 - t) + b * t`, and `a + (b - a) *
|
||||
* t`
|
||||
* @param b Target value to interpolate to
|
||||
* @param t Interpolation factor, the point on the line where 0.0 is `a` and 1.0 is `b`
|
||||
* @see `.inverseLerp()` for the inverse operation (to calculate `t`)
|
||||
*/
|
||||
lerp: Fn<2, true>;
|
||||
/**
|
||||
* Normalize the value to the interpolation factor between the specified `a`
|
||||
* and `b` range. This effectively computes the interpolation factor (`t`)
|
||||
* of the linear interpolation `lerp(a, b, t)`.
|
||||
*
|
||||
* If the value is outside the bounds of the specified range, it will not be
|
||||
* constrained or clamped to the range [0.0, 1.0].
|
||||
*
|
||||
* Equivalent to a range normalization or `(v - a) / (b - a)`
|
||||
* @param a The 0.0 point of the range
|
||||
* @param b The 1.0 point of the range
|
||||
* @see `.lerp()` for the inverse operation
|
||||
*/
|
||||
inverseLerp: Fn<2, true>;
|
||||
}
|
||||
|
||||
export type Base<
|
||||
I extends IBase,
|
||||
OmitDocKeys extends keyof IBase = never,
|
||||
> = Omit<{ [K in keyof IBase]: unknown }, OmitDocKeys> & I;
|
||||
|
||||
export namespace Base {
|
||||
export interface Interface extends IBase {}
|
||||
}
|
||||
|
||||
export function log(x: number, base: number) {
|
||||
if (base === 2) return Math.log2(x);
|
||||
if (base === 10) return Math.log10(x);
|
||||
if (base === Math.E) return Math.log(x);
|
||||
return Math.log(x) / Math.log(base);
|
||||
}
|
||||
|
||||
export function clamp(x: number, min: number, max: number) {
|
||||
return Math.min(Math.max(x, min), max);
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import { makeFluent } from "../../base";
|
||||
import type { HKT } from "../../base/hkt";
|
||||
import { Mixin, type Props, type Return } from "../../base/mixin";
|
||||
import { assert, type HidePrototype } from "../../internal";
|
||||
import type { Apply, Vec } from "../../utility";
|
||||
import { Math as M } from "../math";
|
||||
import {
|
||||
clamp,
|
||||
log,
|
||||
type Abs,
|
||||
type Base,
|
||||
type Ceil,
|
||||
type Floor,
|
||||
type Negate,
|
||||
type Round,
|
||||
type Sign,
|
||||
} from "./base";
|
||||
|
||||
type Arg<T extends readonly number[]> = Vec<number, T["length"]>;
|
||||
|
||||
interface FloorHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Floor<typeof t>;
|
||||
}
|
||||
interface CeilHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Ceil<typeof t>;
|
||||
}
|
||||
interface RoundHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Round<typeof t>;
|
||||
}
|
||||
|
||||
interface AbsHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Abs<typeof t>;
|
||||
}
|
||||
interface SignHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Sign<typeof t>;
|
||||
}
|
||||
interface NegateHkt extends HKT<number, number> {
|
||||
new: (t: HKT.T<this>) => Negate<typeof t>;
|
||||
}
|
||||
|
||||
namespace IList {
|
||||
export type BaseImpl<
|
||||
T extends readonly number[],
|
||||
t extends Props,
|
||||
> = Base<{
|
||||
add: (
|
||||
other: number extends T["length"]
|
||||
? number
|
||||
: number | Arg<T>,
|
||||
) => Return<Arg<T>, t>;
|
||||
subtract: (
|
||||
other: number extends T["length"]
|
||||
? number
|
||||
: number | Arg<T>,
|
||||
) => Return<Arg<T>, t>;
|
||||
multiply: (factor: number) => Return<Arg<T>, t>;
|
||||
pow: (exponent: number) => Return<Arg<T>, t>;
|
||||
sqrt: () => Return<Arg<T>, t>;
|
||||
divide: (divisor: number) => Return<Arg<T>, t>;
|
||||
mod: (divisor: number) => Return<Arg<T>, t>;
|
||||
rem: (divisor: number) => Return<Arg<T>, t>;
|
||||
log: (base?: number) => Return<Arg<T>, t>;
|
||||
|
||||
floor: () => Return<Apply<FloorHkt, T>, t>;
|
||||
ceil: () => Return<Apply<CeilHkt, T>, t>;
|
||||
round: <N extends readonly [number] | readonly []>(
|
||||
...multiple: N
|
||||
) => Return<
|
||||
N extends readonly [number] ? Arg<T> : Apply<RoundHkt, T>,
|
||||
t
|
||||
>;
|
||||
fround: () => Return<Arg<T>, t>;
|
||||
ffround: () => Return<Arg<T>, t>;
|
||||
|
||||
abs: () => Return<Apply<AbsHkt, T>, t>;
|
||||
sign: () => Return<Apply<SignHkt, T>, t>;
|
||||
negate: () => Return<Apply<NegateHkt, T>, t>;
|
||||
|
||||
min: <U extends number[]>(
|
||||
...others: U
|
||||
) => Return<T[number] | U[number], t>;
|
||||
max: <U extends number[]>(
|
||||
...others: U
|
||||
) => Return<T[number] | U[number], t>;
|
||||
clamp: <Min extends number, Max extends number>(
|
||||
min: Min,
|
||||
max: Max,
|
||||
) => Return<Apply<Apply.Union<Min | Max>, T>, t>;
|
||||
saturate: () => Return<Arg<T>, t>;
|
||||
|
||||
lerp: (b: Arg<T>, t: number) => Return<Arg<T>, t>;
|
||||
inverseLerp: (a: Arg<T>, b: Arg<T>) => Return<Arg<T>, t>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type IList<
|
||||
T extends readonly number[] = readonly number[],
|
||||
t extends Props = Props,
|
||||
> = T extends readonly [] | readonly [number]
|
||||
? {}
|
||||
: IList.BaseImpl<T, t> & {
|
||||
sum: () => Return<number, t>;
|
||||
product: () => Return<number, t>;
|
||||
|
||||
findMin: () => Return<keyof T, t>;
|
||||
findMax: () => Return<keyof T, t>;
|
||||
|
||||
average: () => Return<number, t>;
|
||||
median: () => Return<number, t>;
|
||||
mode: () => Return<number[], t>;
|
||||
range: () => Return<number, t>;
|
||||
|
||||
variance: (() => Return<number, t>) & {
|
||||
sample: () => Return<number, t>;
|
||||
} & HidePrototype;
|
||||
deviation: (() => Return<number, t>) & {
|
||||
sample: () => Return<number, t>;
|
||||
} & HidePrototype;
|
||||
};
|
||||
|
||||
export const applyList = Mixin.partial<IList, readonly number[]>(
|
||||
(value, $, fluent) => {
|
||||
$.add = (other: number | number[]) => {
|
||||
if (typeof other === "object") {
|
||||
assert(
|
||||
value.length === other.length,
|
||||
"List.add() parameters must be of equal length",
|
||||
);
|
||||
return fluent(value.map((x, idx) => x + other[idx]));
|
||||
}
|
||||
return fluent(value.map((x) => x + other));
|
||||
};
|
||||
$.subtract = (other: number | number[]) => {
|
||||
if (typeof other === "object") {
|
||||
assert(
|
||||
value.length === other.length,
|
||||
"List.subtract() parameters must be of equal length",
|
||||
);
|
||||
return fluent(value.map((x, idx) => x - other[idx]));
|
||||
}
|
||||
return fluent(value.map((x) => x - other));
|
||||
};
|
||||
$.multiply = (factor: number) =>
|
||||
fluent(value.map((x) => x * factor));
|
||||
$.pow = (exponent: number) =>
|
||||
fluent(value.map((x) => x ** exponent));
|
||||
$.sqrt = () => fluent(value.map(Math.sqrt));
|
||||
$.divide = (divisor: number) =>
|
||||
fluent(value.map((x) => x / divisor));
|
||||
$.mod = (divisor: number) =>
|
||||
fluent(
|
||||
value.map((x) => ((x % divisor) + divisor) % divisor),
|
||||
);
|
||||
$.rem = (divisor: number) =>
|
||||
fluent(value.map((x) => x % divisor));
|
||||
$.log = (base: number = Math.E) =>
|
||||
fluent(value.map((x) => log(x, base)));
|
||||
|
||||
$.floor = () => fluent(value.map(Math.floor));
|
||||
$.ceil = () => fluent(value.map(Math.ceil));
|
||||
$.round = (multiple?: number) =>
|
||||
fluent(
|
||||
multiple !== undefined
|
||||
? value.map(
|
||||
(x) =>
|
||||
Math.round(x / multiple) * multiple,
|
||||
)
|
||||
: value.map(Math.floor),
|
||||
);
|
||||
$.fround = () => fluent(value.map(Math.fround));
|
||||
$.ffround = () => fluent(value.map(Math.f16round));
|
||||
|
||||
$.abs = () => fluent(value.map(Math.abs));
|
||||
$.sign = () => fluent(value.map(Math.sign));
|
||||
$.negate = () => fluent(value.map((x) => -x));
|
||||
|
||||
$.min = (...others: number[]) =>
|
||||
fluent(Math.min(...value, ...others));
|
||||
$.max = (...others: number[]) =>
|
||||
fluent(Math.max(...value, ...others));
|
||||
$.clamp = (min: number, max: number) =>
|
||||
fluent(value.map((x) => clamp(x, min, max)));
|
||||
$.saturate = () => fluent(value.map((x) => clamp(x, 0, 1)));
|
||||
|
||||
$.lerp = (b: number[], t: number) => {
|
||||
assert(
|
||||
value.length === b.length,
|
||||
"List.lerp() a and b parameters must be of equal length",
|
||||
);
|
||||
return fluent(
|
||||
value.map((x, idx) => x + (b[idx] - x) * t),
|
||||
);
|
||||
};
|
||||
$.inverseLerp = (a: number[], b: number[]) => {
|
||||
assert(
|
||||
value.length === a.length && a.length === b.length,
|
||||
"List.inverseLerp() a and b parameters must be of equal length",
|
||||
);
|
||||
return fluent(
|
||||
value.map(
|
||||
(x, idx) => (x - a[idx]) / (b[idx] - a[idx]),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
$.sum = () => fluent(value.reduce((a, b) => a + b, 0));
|
||||
$.product = () => fluent(value.reduce((a, b) => a * b, 1));
|
||||
|
||||
$.findMin = () => fluent(value.indexOf(Math.min(...value)));
|
||||
$.findMax = () => fluent(value.indexOf(Math.max(...value)));
|
||||
|
||||
$.average = () =>
|
||||
fluent(value.reduce((a, b) => a + b, 0) / value.length);
|
||||
$.median = () => {
|
||||
const sorted = value.toSorted((a, b) => a - b);
|
||||
const middle = (sorted.length - 1) / 2;
|
||||
|
||||
if (sorted.length % 2 === 1) return sorted[middle];
|
||||
|
||||
const a = sorted[Math.floor(middle)];
|
||||
const b = sorted[Math.ceil(middle)];
|
||||
|
||||
return (a + b) / 2;
|
||||
};
|
||||
$.mode = () => {
|
||||
const occurances = new Map<number, number>();
|
||||
let max = 0;
|
||||
|
||||
for (const v of value) {
|
||||
const count = (occurances.get(v) ?? 0) + 1;
|
||||
max = Math.max(max, count);
|
||||
occurances.set(v, count);
|
||||
}
|
||||
|
||||
const output: number[] = [];
|
||||
occurances.forEach((count, value) => {
|
||||
if (count === max) output.push(value);
|
||||
});
|
||||
|
||||
return fluent(output);
|
||||
};
|
||||
|
||||
$.range = () =>
|
||||
fluent(Math.max(...value) - Math.min(...value));
|
||||
|
||||
const variance = (sample: boolean) => {
|
||||
if (value.length === 0) return NaN;
|
||||
if (value.length === 1) return sample ? NaN : 0;
|
||||
|
||||
const mean =
|
||||
value.reduce((a, b) => a + b, 0) / value.length;
|
||||
const deviations = value.map((x) => (x - mean) ** 2);
|
||||
|
||||
const sum = deviations.reduce((a, b) => a + b, 0);
|
||||
|
||||
if (sample) return sum / (deviations.length - 1);
|
||||
return sum / deviations.length;
|
||||
};
|
||||
$.variance = Object.assign(() => fluent(variance(false)), {
|
||||
sample: () => fluent(variance(true)),
|
||||
});
|
||||
$.deviation = Object.assign(
|
||||
() => fluent(Math.sqrt(variance(false))),
|
||||
{
|
||||
sample: () => fluent(Math.sqrt(variance(true))),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect, expectTypeOf, describe } = import.meta
|
||||
.vitest;
|
||||
|
||||
const registry = [M] as const;
|
||||
const $ = makeFluent(registry);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+44
-14
@@ -1,5 +1,5 @@
|
||||
import { makeFluent } from "../../base";
|
||||
import type { Props, Return } from "../../base/mixin";
|
||||
import { Mixin, type Props, type Return } from "../../base/mixin";
|
||||
import { Math } from "../math";
|
||||
import type { Vec, Dec, Inc as __Inc } from "../../utility";
|
||||
|
||||
@@ -98,23 +98,37 @@ type Primative<
|
||||
: never
|
||||
: TOut;
|
||||
|
||||
interface Swizzle4D extends SwizzlePermutations<4> {}
|
||||
|
||||
type SwizzleCache = [
|
||||
{ x: [0] },
|
||||
SwizzlePermutations<2>,
|
||||
SwizzlePermutations<3>,
|
||||
SwizzlePermutations<4>,
|
||||
Swizzle4D,
|
||||
];
|
||||
|
||||
export type Swizzle<
|
||||
T extends readonly unknown[],
|
||||
t extends Props,
|
||||
> = number extends T["length"]
|
||||
? unknown
|
||||
: T extends readonly []
|
||||
? unknown
|
||||
: (
|
||||
Dec<T["length"]> extends infer K extends 0 | 1 | 2 | 3
|
||||
Dec<T["length"]> extends infer K extends
|
||||
| 0
|
||||
| 1
|
||||
| 2
|
||||
| 3
|
||||
? { k: K; c: SwizzleCache[K] }
|
||||
: SwizzleCache extends [...unknown[], infer Last]
|
||||
? { k: Dec<SwizzleCache["length"]>; c: Last }
|
||||
: SwizzleCache extends [
|
||||
...unknown[],
|
||||
infer Last,
|
||||
]
|
||||
? {
|
||||
k: Dec<SwizzleCache["length"]>;
|
||||
c: Last;
|
||||
}
|
||||
: never
|
||||
) extends {
|
||||
k: infer K extends number;
|
||||
@@ -135,13 +149,22 @@ export type Swizzle<
|
||||
Primative<T, t>[K]
|
||||
: never;
|
||||
|
||||
export type ISwizzle<
|
||||
T extends readonly unknown[] = readonly unknown[],
|
||||
t extends Props = Props,
|
||||
> = {
|
||||
readonly [K in keyof Swizzle4D]: Return<
|
||||
Sequence<T, Swizzle4D[K]>,
|
||||
t
|
||||
>;
|
||||
} & Primative<T, t>[3];
|
||||
|
||||
const axis = ["x", "y", "z", "w"] as const;
|
||||
|
||||
export function applySwizzle(
|
||||
value: number[],
|
||||
$: object,
|
||||
fluent: (value: unknown) => never,
|
||||
) {
|
||||
export const applySwizzle = Mixin.partial<
|
||||
ISwizzle,
|
||||
readonly unknown[]
|
||||
>((value, $, fluent) => {
|
||||
const length = globalThis.Math.min(value.length, axis.length);
|
||||
const state = new Array<number>(length).fill(-1);
|
||||
|
||||
@@ -197,10 +220,11 @@ export function applySwizzle(
|
||||
return fluent(value[3]);
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect, expectTypeOf } = import.meta.vitest;
|
||||
const { test, describe, expect, expectTypeOf } = import.meta
|
||||
.vitest;
|
||||
|
||||
const registry = [Math] as const;
|
||||
const $ = makeFluent(registry);
|
||||
@@ -263,6 +287,7 @@ if (import.meta.vitest) {
|
||||
expectTypeOf(v).toEqualTypeOf(w);
|
||||
});
|
||||
|
||||
describe("types", () => {
|
||||
test("Next", () => {
|
||||
type N = 3;
|
||||
|
||||
@@ -278,7 +303,9 @@ if (import.meta.vitest) {
|
||||
});
|
||||
|
||||
test("Key", () => {
|
||||
expectTypeOf<Key<[1, 2, undefined]>>().toEqualTypeOf<"yz">();
|
||||
expectTypeOf<
|
||||
Key<[1, 2, undefined]>
|
||||
>().toEqualTypeOf<"yz">();
|
||||
expectTypeOf<Key<[1, 1, 1]>>().toEqualTypeOf<"yyy">();
|
||||
});
|
||||
|
||||
@@ -287,7 +314,9 @@ if (import.meta.vitest) {
|
||||
type Out = [5, 4, 3, 2, 1];
|
||||
type Reverse = [4, 3, 2, 1, 0];
|
||||
|
||||
expectTypeOf<Sequence<In, Reverse>>().toEqualTypeOf<Out>();
|
||||
expectTypeOf<
|
||||
Sequence<In, Reverse>
|
||||
>().toEqualTypeOf<Out>();
|
||||
});
|
||||
|
||||
test("Primative", () => {
|
||||
@@ -302,4 +331,5 @@ if (import.meta.vitest) {
|
||||
expectTypeOf<V>().toHaveProperty("y");
|
||||
expectTypeOf<V>().not.toHaveProperty("z");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+44
-87
@@ -6,7 +6,7 @@ import {
|
||||
type Props,
|
||||
type Return,
|
||||
} from "../base/mixin";
|
||||
import { assert, assertType, type HidePrototype } from "../internal";
|
||||
import { assert, type HidePrototype } from "../internal";
|
||||
|
||||
type NoneSentinel = null | undefined;
|
||||
type None<T> = Extract<T, NoneSentinel>;
|
||||
@@ -16,35 +16,7 @@ function isNone<T>(v: T): v is None<T> {
|
||||
return v === null || v === undefined;
|
||||
}
|
||||
|
||||
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: <const U = null>(
|
||||
callback: (v: T) => boolean,
|
||||
fallback?: U,
|
||||
) => Return<T | U, t>;
|
||||
}
|
||||
|
||||
export interface Optional extends Mixin.HKT {
|
||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||
? None<T> extends never
|
||||
? Where<T, typeof t>
|
||||
: {
|
||||
interface IOptional<T = unknown, t extends Props = Props> {
|
||||
/**
|
||||
* Transform a value via callback if it is not `null` or
|
||||
* `undefined`
|
||||
@@ -65,9 +37,7 @@ export interface Optional extends Mixin.HKT {
|
||||
* expect(b).toBe(15);
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
and: <U>(
|
||||
callback: (v: Some<T>) => U,
|
||||
) => Return<None<T> | U, typeof t>;
|
||||
and: <U>(callback: (v: Some<T>) => U) => Return<None<T> | U, t>;
|
||||
/**
|
||||
* Set value to a fallback if it is `null` or `undefined`.
|
||||
* @param fallback The fallback value to use. To defer
|
||||
@@ -85,9 +55,7 @@ export interface Optional extends Mixin.HKT {
|
||||
* expect(b).toBe(10);
|
||||
* @from {@link Optional `Optional`}
|
||||
*/
|
||||
or: (<const U>(
|
||||
fallback: U,
|
||||
) => Return<Some<T> | U, typeof t>) & {
|
||||
or: (<const U>(fallback: U) => Return<Some<T> | U, t>) & {
|
||||
/**
|
||||
* Set value to the result of `callback` if it is `null`
|
||||
* or `undefined`. Unlike the normal `.or()`, this method
|
||||
@@ -113,7 +81,7 @@ export interface Optional extends Mixin.HKT {
|
||||
*/
|
||||
else: <U>(
|
||||
callback: (v: None<T>) => U,
|
||||
) => Return<Some<T> | U, typeof t>;
|
||||
) => Return<Some<T> | U, t>;
|
||||
} & HidePrototype;
|
||||
/**
|
||||
* Assert that value is not `null` or `undefined`
|
||||
@@ -133,9 +101,7 @@ export interface Optional extends Mixin.HKT {
|
||||
* }).toThrow()
|
||||
* ```
|
||||
*/
|
||||
assert: ((
|
||||
msg?: string,
|
||||
) => Return<Some<T>, typeof t>) & {
|
||||
assert: ((msg?: string) => Return<Some<T>, t>) & {
|
||||
/**
|
||||
* Assert that the value is either `null` or `undefined`
|
||||
* @param msg Reasoning to attach to the `AssertionError`
|
||||
@@ -153,74 +119,77 @@ export interface Optional extends Mixin.HKT {
|
||||
* }).toThrow()
|
||||
* ```
|
||||
*/
|
||||
none: (
|
||||
msg?: string,
|
||||
) => Return<None<T>, typeof t>;
|
||||
none: (msg?: string) => Return<None<T>, t>;
|
||||
} & HidePrototype;
|
||||
} & Where<T, typeof t>
|
||||
}
|
||||
|
||||
export interface Optional extends Mixin.HKT<IOptional> {
|
||||
new: (
|
||||
t: HKT.T<this>,
|
||||
) => Input<typeof t> extends infer T
|
||||
? None<T> extends never
|
||||
? {}
|
||||
: IOptional<T, typeof t>
|
||||
: never;
|
||||
}
|
||||
|
||||
export const Optional = Mixin<Optional>((value, $, fluent) => {
|
||||
export const Optional = Mixin<IOptional, Optional>(
|
||||
(value, $, fluent) => {
|
||||
if (isNone(value)) {
|
||||
$.and = () => {
|
||||
return fluent(value);
|
||||
};
|
||||
|
||||
$.or = (fallback: unknown) => {
|
||||
$.or = Object.assign(
|
||||
(fallback: unknown) => {
|
||||
return fluent(fallback);
|
||||
};
|
||||
assertType<object>($.or);
|
||||
Object.assign($.or, {
|
||||
},
|
||||
{
|
||||
else: (callback: (v: unknown) => unknown) => {
|
||||
return fluent(callback(value));
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$.assert = (msg?: string) => {
|
||||
$.assert = Object.assign(
|
||||
(msg?: string) => {
|
||||
assert(false, msg);
|
||||
};
|
||||
assertType<object>($.assert);
|
||||
Object.assign($.assert, {
|
||||
},
|
||||
{
|
||||
none: () => {
|
||||
return fluent(value);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
$.and = (callback: (v: unknown) => unknown) => {
|
||||
return fluent(callback(value));
|
||||
};
|
||||
|
||||
$.or = () => {
|
||||
$.or = Object.assign(
|
||||
() => {
|
||||
return fluent(value);
|
||||
};
|
||||
|
||||
assertType<object>($.or);
|
||||
Object.assign($.or, {
|
||||
},
|
||||
{
|
||||
else: () => {
|
||||
return fluent(value);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
$.assert = () => {
|
||||
$.assert = Object.assign(
|
||||
() => {
|
||||
return fluent(value);
|
||||
};
|
||||
assertType<object>($.assert);
|
||||
Object.assign($.assert, {
|
||||
},
|
||||
{
|
||||
none: (msg?: string) => {
|
||||
assert(false, msg);
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
$.where = (
|
||||
callback: (v: unknown) => boolean,
|
||||
fallback = null,
|
||||
) => {
|
||||
if (callback(value)) return fluent(value);
|
||||
return fluent(fallback);
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expect, expectTypeOf, vi } = import.meta.vitest;
|
||||
@@ -272,16 +241,4 @@ if (import.meta.vitest) {
|
||||
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("where()", () => {
|
||||
const even = 4 as const;
|
||||
const odd = 7 as const;
|
||||
|
||||
const isEven = (v: number) => v % 2 === 0;
|
||||
|
||||
expect($(even).where(isEven).value).toBe(even);
|
||||
expect($(odd).where(isEven).value).toBe(null);
|
||||
|
||||
expect($(odd).where(isEven, -1).value).toBe(-1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { HKT } from "./base/hkt";
|
||||
|
||||
export type MaxDepth = 50;
|
||||
|
||||
export type Pretty<T> = { [K in keyof T]: T[K] };
|
||||
export type Constrain<T, U> = T extends U ? T : never;
|
||||
|
||||
export type IsReadonly<T extends readonly unknown[]> =
|
||||
T extends unknown[] ? true : false;
|
||||
|
||||
namespace Vec {
|
||||
// prettier-ignore
|
||||
type Lookup<T, N extends number> = N extends 0
|
||||
@@ -181,6 +186,35 @@ namespace Flat {
|
||||
}
|
||||
export type Flat<T extends readonly unknown[]> = Flat.Flat<T>;
|
||||
|
||||
export type RecPartial<T> = {
|
||||
[K in keyof T]?: RecPartial<T[K]>;
|
||||
};
|
||||
|
||||
export namespace Apply {
|
||||
export interface Union<U> extends HKT {
|
||||
new: (t: HKT.T<this>) => typeof t | U;
|
||||
}
|
||||
|
||||
export interface Intersection<U> extends HKT {
|
||||
new: (t: HKT.T<this>) => typeof t & U;
|
||||
}
|
||||
}
|
||||
|
||||
export type Apply<
|
||||
Hkt extends HKT,
|
||||
T extends readonly Hkt["_in"][],
|
||||
TOut extends Hkt["_out"][] = [],
|
||||
> = number extends T["length"]
|
||||
? Hkt["_out"][]
|
||||
: TOut["length"] extends MaxDepth
|
||||
? (TOut[number] | Hkt["_out"])[]
|
||||
: T extends readonly [
|
||||
infer Curr extends Hkt["_in"],
|
||||
...infer Rest extends Hkt["_in"][],
|
||||
]
|
||||
? Apply<Hkt, Rest, [...TOut, HKT.Apply<Hkt, Curr>]>
|
||||
: TOut;
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { test, expectTypeOf } = import.meta.vitest;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
/* Linting */
|
||||
"noUnusedLocals": false,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"erasableSyntaxOnly": true,
|
||||
},
|
||||
"include": ["src", "tests"]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
|
||||
Reference in New Issue
Block a user