feat: more math & refactor
This commit is contained in:
@@ -46,6 +46,7 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-namespace": "off",
|
"@typescript-eslint/no-namespace": "off",
|
||||||
|
"@typescript-eslint/no-empty-object-type": "off",
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
"jiti": "^2.7.0",
|
"jiti": "^2.7.0",
|
||||||
"madge": "^8.0.0",
|
"madge": "^8.0.0",
|
||||||
"prettier": "^3.8.4",
|
"prettier": "^3.8.4",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "6",
|
||||||
"typescript-eslint": "^8.59.2",
|
"typescript-eslint": "^8.59.2",
|
||||||
"vitest": "^4.1.9"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1
-1
@@ -39,7 +39,7 @@ importers:
|
|||||||
specifier: ^3.8.4
|
specifier: ^3.8.4
|
||||||
version: 3.8.4
|
version: 3.8.4
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ~6.0.2
|
specifier: '6'
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^8.59.2
|
specifier: ^8.59.2
|
||||||
|
|||||||
+4
-3
@@ -1,18 +1,19 @@
|
|||||||
export interface HKT<In = unknown, Out = unknown> {
|
export interface HKT<In = unknown, Out = unknown> {
|
||||||
readonly _meta: In;
|
readonly _in: In;
|
||||||
|
readonly _out: Out;
|
||||||
readonly _t: unknown;
|
readonly _t: unknown;
|
||||||
new: (t: never) => Out;
|
new: (t: never) => Out;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Param<
|
type Param<
|
||||||
This extends HKT,
|
This extends HKT,
|
||||||
U = This["_meta"],
|
U = This["_in"],
|
||||||
> = This["_t"] extends infer T ? (T extends U ? T : U) : never;
|
> = This["_t"] extends infer T ? (T extends U ? T : U) : never;
|
||||||
|
|
||||||
export namespace HKT {
|
export namespace HKT {
|
||||||
export type T<
|
export type T<
|
||||||
This extends HKT,
|
This extends HKT,
|
||||||
T = This extends { _meta: infer I } ? I : unknown,
|
T = This["_in"], //This extends { _in: infer I } ? I : unknown,
|
||||||
> = Param<This, T>;
|
> = Param<This, T>;
|
||||||
export type Apply<T extends HKT, t extends T["_t"]> = ReturnType<
|
export type Apply<T extends HKT, t extends T["_t"]> = ReturnType<
|
||||||
(T & { _t: t })["new"]
|
(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>;
|
const f = { value } as unknown as Fluent<T, Reg>;
|
||||||
|
|
||||||
for (const mixin of registry) {
|
for (const mixin of registry) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
mixin.fn(value, f, fluent);
|
||||||
mixin.fn(value, f, fluent as (value: unknown) => never);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
|
|||||||
+26
-10
@@ -1,6 +1,7 @@
|
|||||||
import type { Fluent } from ".";
|
import type { Fluent } from ".";
|
||||||
import { never } from "../internal";
|
import { never } from "../internal";
|
||||||
import type { Registry } from "../registry";
|
import type { Registry } from "../registry";
|
||||||
|
import type { RecPartial } from "../utility";
|
||||||
import type { HKT } from "./hkt";
|
import type { HKT } from "./hkt";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -8,28 +9,43 @@ export interface Props {
|
|||||||
readonly meta: { registry: Registry };
|
readonly meta: { registry: Registry };
|
||||||
}
|
}
|
||||||
|
|
||||||
type MixinHKT = HKT<Props>;
|
type MixinHKT<I> = HKT<Props, RecPartial<I>>;
|
||||||
type MixinFn = (
|
type MixinFn<I> = <T>(
|
||||||
value: unknown,
|
value: T,
|
||||||
fluent: Record<PropertyKey, unknown>,
|
fluent: object & RecPartial<I>,
|
||||||
callback: (value: unknown) => never,
|
callback: <U>(value: U) => Fluent<U, Registry>,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export interface Mixin<T extends MixinHKT = MixinHKT> {
|
export interface Mixin<
|
||||||
|
I = unknown,
|
||||||
|
T extends MixinHKT<I> = MixinHKT<I>,
|
||||||
|
> {
|
||||||
|
interface: I;
|
||||||
hkt: T;
|
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 {
|
return {
|
||||||
|
interface: never,
|
||||||
hkt: never,
|
hkt: never,
|
||||||
fn,
|
fn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Mixin {
|
export namespace Mixin {
|
||||||
export type HKT = MixinHKT;
|
export type HKT<I> = MixinHKT<I>;
|
||||||
export type Function = MixinFn;
|
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;
|
export declare const shim: unique symbol;
|
||||||
|
|||||||
+9
-3
@@ -7,6 +7,10 @@ export interface Identity extends HKT {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
export const never = undefined as never;
|
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 {
|
export class AssertionError extends Error {
|
||||||
public constructor(msg?: string) {
|
public constructor(msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
@@ -26,9 +30,11 @@ export function assert(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters, @typescript-eslint/no-unused-vars
|
export namespace assert {
|
||||||
export function assertType<T>(_v: unknown): asserts _v is T {
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
/* empty */
|
export function type<T>(_v: unknown): asserts _v is NoInfer<T> {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
|||||||
+378
-382
File diff suppressed because it is too large
Load Diff
+75
-64
@@ -1,7 +1,13 @@
|
|||||||
import { makeFluent } from "../base";
|
import { makeFluent } from "../base";
|
||||||
import type { HKT } from "../base/hkt";
|
import type { HKT } from "../base/hkt";
|
||||||
import { Mixin, shim, type Input, type Return } from "../base/mixin";
|
import {
|
||||||
import { assert, assertType, never } from "../internal";
|
Mixin,
|
||||||
|
shim,
|
||||||
|
type Input,
|
||||||
|
type Props,
|
||||||
|
type Return,
|
||||||
|
} from "../base/mixin";
|
||||||
|
import { assert, never } from "../internal";
|
||||||
import { Base } from "./base";
|
import { Base } from "./base";
|
||||||
|
|
||||||
interface AwaitedIdentitity extends HKT {
|
interface AwaitedIdentitity extends HKT {
|
||||||
@@ -23,20 +29,23 @@ class Awaited<T extends Promise<unknown>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AsyncMixin extends Mixin.HKT {
|
interface IAsync<
|
||||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
T extends Promise<unknown> = Promise<unknown>,
|
||||||
|
t extends Props = Props,
|
||||||
|
> {
|
||||||
|
readonly awaited: Return<Awaited<T>, t>;
|
||||||
|
then: <U>(
|
||||||
|
fn: (value: T extends Promise<infer O> ? O : never) => U,
|
||||||
|
) => Return<Promise<U>, t>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AsyncMixin extends Mixin.HKT<IAsync> {
|
||||||
|
new: (
|
||||||
|
t: HKT.T<this>,
|
||||||
|
) => Input<typeof t> extends infer T
|
||||||
? T extends Promise<unknown>
|
? T extends Promise<unknown>
|
||||||
? {
|
? IAsync<T, typeof t>
|
||||||
readonly awaited: Return<Awaited<T>, typeof t>;
|
: {}
|
||||||
then: <U>(
|
|
||||||
callback: (
|
|
||||||
value: T extends Promise<infer T>
|
|
||||||
? T
|
|
||||||
: never,
|
|
||||||
) => U,
|
|
||||||
) => Return<Promise<U>, typeof t>;
|
|
||||||
}
|
|
||||||
: unknown
|
|
||||||
: never;
|
: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,62 +54,64 @@ interface BaseFluent<T> {
|
|||||||
[K: PropertyKey]: unknown;
|
[K: PropertyKey]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AsyncMixin = Mixin<AsyncMixin>((value, $, fluent) => {
|
export const AsyncMixin = Mixin<IAsync, AsyncMixin>(
|
||||||
if (!(value instanceof Promise)) return;
|
(value, $, fluent) => {
|
||||||
|
if (!(value instanceof Promise)) return;
|
||||||
|
|
||||||
$.then = (callback: (value: unknown) => unknown) => {
|
$.then = (callback: (value: unknown) => unknown) => {
|
||||||
return fluent(value.then(callback));
|
return fluent(value.then(callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty($, "awaited", {
|
Object.defineProperty($, "awaited", {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get() {
|
get() {
|
||||||
let v: Promise<BaseFluent<unknown>> = value.then((v) =>
|
let v: Promise<BaseFluent<unknown>> = value.then(
|
||||||
fluent(v),
|
(v) => fluent(v),
|
||||||
);
|
);
|
||||||
|
|
||||||
const path: PropertyKey[] = [];
|
const path: PropertyKey[] = [];
|
||||||
// eslint-disable-next-line no-empty-pattern
|
// eslint-disable-next-line no-empty-pattern
|
||||||
const proxy = new Proxy((...[]: unknown[]) => proxy, {
|
const proxy = new Proxy((...[]: unknown[]) => proxy, {
|
||||||
get: (_, key) => {
|
get: (_, key) => {
|
||||||
if (key === "value")
|
if (key === "value")
|
||||||
return v.then((v) => v.value);
|
return v.then((v) => v.value);
|
||||||
path.push(key);
|
path.push(key);
|
||||||
return proxy;
|
return proxy;
|
||||||
},
|
},
|
||||||
apply: (target, thisArg, args: unknown[]) => {
|
apply: (target, thisArg, args: unknown[]) => {
|
||||||
v = v.then((v) => {
|
v = v.then((v) => {
|
||||||
let obj: unknown = v;
|
let obj: unknown = v;
|
||||||
for (const node of path) {
|
for (const node of path) {
|
||||||
assert(
|
assert(
|
||||||
typeof obj === "object" &&
|
typeof obj === "object" &&
|
||||||
obj !== null &&
|
obj !== null &&
|
||||||
node in obj,
|
node in obj,
|
||||||
);
|
);
|
||||||
assertType<Record<PropertyKey, unknown>>(
|
assert.type<
|
||||||
obj,
|
Record<PropertyKey, unknown>
|
||||||
);
|
>(obj);
|
||||||
|
|
||||||
obj = obj[node];
|
obj = obj[node];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(typeof obj === "function");
|
assert(typeof obj === "function");
|
||||||
assertType<
|
assert.type<
|
||||||
(
|
(
|
||||||
...args: unknown[]
|
...args: unknown[]
|
||||||
) => BaseFluent<unknown>
|
) => BaseFluent<unknown>
|
||||||
>(obj);
|
>(obj);
|
||||||
|
|
||||||
return obj(...args);
|
return obj(...args);
|
||||||
});
|
});
|
||||||
return target.apply(thisArg, args);
|
return target.apply(thisArg, args);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (import.meta.vitest) {
|
if (import.meta.vitest) {
|
||||||
const { test, expect } = import.meta.vitest;
|
const { test, expect } = import.meta.vitest;
|
||||||
|
|||||||
+80
-42
@@ -1,50 +1,71 @@
|
|||||||
import { makeFluent } from "../base";
|
import { makeFluent } from "../base";
|
||||||
import type { HKT } from "../base/hkt";
|
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 {
|
interface IBase<T = unknown, t extends Props = Props> {
|
||||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
/**
|
||||||
? {
|
* Interospect value using the specified `callback` without
|
||||||
/**
|
* modifying the value.
|
||||||
* Interospect value using the specified `callback` without
|
* @param callback The interospective callback
|
||||||
* modifying the value.
|
* @see `transform` to modify
|
||||||
* @param callback The interospective callback
|
* @from {@link Base `Base`}
|
||||||
* @see `transform` to modify
|
* @example
|
||||||
* @from {@link Base `Base`}
|
* ```ts
|
||||||
* @example
|
* let x;
|
||||||
* ```ts
|
* const value = $(10).tap(v => { x = ++v }).value;
|
||||||
* let x;
|
*
|
||||||
* const value = $(10).tap(v => { x = ++v }).value;
|
* expect(x).toBe(11);
|
||||||
*
|
* expect(value).toBe(10);
|
||||||
* expect(x).toBe(11);
|
* ```
|
||||||
* expect(value).toBe(10);
|
*/
|
||||||
* ```
|
tap: (callback: (value: Readonly<T>) => void) => Return<T, t>;
|
||||||
*/
|
/**
|
||||||
tap(
|
* Put value through or pipe value through the specified
|
||||||
callback: (value: Readonly<T>) => void,
|
* `callback` using the outputted return value as a new value.
|
||||||
): Return<T, typeof t>;
|
* A.K.A., _transform_ the current value using a callback
|
||||||
/**
|
* @param callback The transformative callback
|
||||||
* Put value through or pipe value through the specified
|
* @from {@link Base `Base`}
|
||||||
* `callback` using the outputted return value as a new value.
|
* @example
|
||||||
* A.K.A., _transform_ the current value using a callback
|
* ```ts
|
||||||
* @param callback The transformative callback
|
* const value = $("Hello")
|
||||||
* @from {@link Base `Base`}
|
* .transform(v => v.toUpperCase())
|
||||||
* @example
|
* .value;
|
||||||
* ```ts
|
* expect(value).toBe("HELLO");
|
||||||
* const value = $("Hello")
|
* ```
|
||||||
* .transform(v => v.toUpperCase())
|
*/
|
||||||
* .value;
|
transform: <U>(callback: (value: T) => U) => Return<U, t>;
|
||||||
* expect(value).toBe("HELLO");
|
/**
|
||||||
* ```
|
* Set value to a fallback if the specified callback returns `false`
|
||||||
*/
|
* @param callback Guarding callback returning `true` to keep the value
|
||||||
transform<U>(
|
* @param fallback The fallback value, `null` by default
|
||||||
callback: (value: T) => U,
|
* @from {@link Base `Base`}
|
||||||
): Return<U, typeof t>;
|
* @example
|
||||||
}
|
* const isObject = (value: unknown) => typeof value === 'object';
|
||||||
: never;
|
*
|
||||||
|
* 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) => {
|
$.tap = (callback: (value: unknown) => void) => {
|
||||||
callback(value);
|
callback(value);
|
||||||
return fluent(value);
|
return fluent(value);
|
||||||
@@ -53,6 +74,11 @@ export const Base = Mixin<Base>((value, $, fluent) => {
|
|||||||
$.transform = (callback: (value: unknown) => unknown) => {
|
$.transform = (callback: (value: unknown) => unknown) => {
|
||||||
return fluent(callback(value));
|
return fluent(callback(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.where = (
|
||||||
|
callback: (value: unknown) => boolean,
|
||||||
|
fallback: unknown = null,
|
||||||
|
) => fluent(callback(value) ? value : fallback);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (import.meta.vitest) {
|
if (import.meta.vitest) {
|
||||||
@@ -83,4 +109,16 @@ if (import.meta.vitest) {
|
|||||||
$(value).transform(increment).transform(increment).value,
|
$(value).transform(increment).transform(increment).value,
|
||||||
).toBe(increment(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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-717
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
+91
-61
@@ -1,5 +1,5 @@
|
|||||||
import { makeFluent } from "../../base";
|
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 { Math } from "../math";
|
||||||
import type { Vec, Dec, Inc as __Inc } from "../../utility";
|
import type { Vec, Dec, Inc as __Inc } from "../../utility";
|
||||||
|
|
||||||
@@ -98,11 +98,13 @@ type Primative<
|
|||||||
: never
|
: never
|
||||||
: TOut;
|
: TOut;
|
||||||
|
|
||||||
|
interface Swizzle4D extends SwizzlePermutations<4> {}
|
||||||
|
|
||||||
type SwizzleCache = [
|
type SwizzleCache = [
|
||||||
{ x: [0] },
|
{ x: [0] },
|
||||||
SwizzlePermutations<2>,
|
SwizzlePermutations<2>,
|
||||||
SwizzlePermutations<3>,
|
SwizzlePermutations<3>,
|
||||||
SwizzlePermutations<4>,
|
Swizzle4D,
|
||||||
];
|
];
|
||||||
|
|
||||||
export type Swizzle<
|
export type Swizzle<
|
||||||
@@ -110,38 +112,59 @@ export type Swizzle<
|
|||||||
t extends Props,
|
t extends Props,
|
||||||
> = number extends T["length"]
|
> = number extends T["length"]
|
||||||
? unknown
|
? unknown
|
||||||
: (
|
: T extends readonly []
|
||||||
Dec<T["length"]> extends infer K extends 0 | 1 | 2 | 3
|
? unknown
|
||||||
? { k: K; c: SwizzleCache[K] }
|
: (
|
||||||
: SwizzleCache extends [...unknown[], infer Last]
|
Dec<T["length"]> extends infer K extends
|
||||||
? { k: Dec<SwizzleCache["length"]>; c: Last }
|
| 0
|
||||||
: never
|
| 1
|
||||||
) extends {
|
| 2
|
||||||
k: infer K extends number;
|
| 3
|
||||||
c: infer C extends Record<
|
? { k: K; c: SwizzleCache[K] }
|
||||||
PropertyKey,
|
: SwizzleCache extends [
|
||||||
(number | undefined)[]
|
...unknown[],
|
||||||
>;
|
infer Last,
|
||||||
}
|
]
|
||||||
? Omit<
|
? {
|
||||||
{
|
k: Dec<SwizzleCache["length"]>;
|
||||||
readonly [K in keyof C]: Return<
|
c: Last;
|
||||||
Sequence<T, C[K]>,
|
}
|
||||||
t
|
: never
|
||||||
|
) extends {
|
||||||
|
k: infer K extends number;
|
||||||
|
c: infer C extends Record<
|
||||||
|
PropertyKey,
|
||||||
|
(number | undefined)[]
|
||||||
>;
|
>;
|
||||||
},
|
}
|
||||||
Axis[number]
|
? Omit<
|
||||||
> &
|
{
|
||||||
Primative<T, t>[K]
|
readonly [K in keyof C]: Return<
|
||||||
: never;
|
Sequence<T, C[K]>,
|
||||||
|
t
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
Axis[number]
|
||||||
|
> &
|
||||||
|
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;
|
const axis = ["x", "y", "z", "w"] as const;
|
||||||
|
|
||||||
export function applySwizzle(
|
export const applySwizzle = Mixin.partial<
|
||||||
value: number[],
|
ISwizzle,
|
||||||
$: object,
|
readonly unknown[]
|
||||||
fluent: (value: unknown) => never,
|
>((value, $, fluent) => {
|
||||||
) {
|
|
||||||
const length = globalThis.Math.min(value.length, axis.length);
|
const length = globalThis.Math.min(value.length, axis.length);
|
||||||
const state = new Array<number>(length).fill(-1);
|
const state = new Array<number>(length).fill(-1);
|
||||||
|
|
||||||
@@ -197,10 +220,11 @@ export function applySwizzle(
|
|||||||
return fluent(value[3]);
|
return fluent(value[3]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
if (import.meta.vitest) {
|
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 registry = [Math] as const;
|
||||||
const $ = makeFluent(registry);
|
const $ = makeFluent(registry);
|
||||||
@@ -263,43 +287,49 @@ if (import.meta.vitest) {
|
|||||||
expectTypeOf(v).toEqualTypeOf(w);
|
expectTypeOf(v).toEqualTypeOf(w);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Next", () => {
|
describe("types", () => {
|
||||||
type N = 3;
|
test("Next", () => {
|
||||||
|
type N = 3;
|
||||||
|
|
||||||
expectTypeOf<
|
expectTypeOf<
|
||||||
Next<N, [undefined, undefined, undefined]>
|
Next<N, [undefined, undefined, undefined]>
|
||||||
>().toEqualTypeOf<[0, undefined, undefined]>();
|
>().toEqualTypeOf<[0, undefined, undefined]>();
|
||||||
|
|
||||||
expectTypeOf<
|
expectTypeOf<
|
||||||
Next<N, [2, undefined, undefined]>
|
Next<N, [2, undefined, undefined]>
|
||||||
>().toEqualTypeOf<[undefined, 0, undefined]>();
|
>().toEqualTypeOf<[undefined, 0, undefined]>();
|
||||||
|
|
||||||
expectTypeOf<Next<N, [2, 2, 2]>>().toEqualTypeOf<null>();
|
expectTypeOf<Next<N, [2, 2, 2]>>().toEqualTypeOf<null>();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Key", () => {
|
test("Key", () => {
|
||||||
expectTypeOf<Key<[1, 2, undefined]>>().toEqualTypeOf<"yz">();
|
expectTypeOf<
|
||||||
expectTypeOf<Key<[1, 1, 1]>>().toEqualTypeOf<"yyy">();
|
Key<[1, 2, undefined]>
|
||||||
});
|
>().toEqualTypeOf<"yz">();
|
||||||
|
expectTypeOf<Key<[1, 1, 1]>>().toEqualTypeOf<"yyy">();
|
||||||
|
});
|
||||||
|
|
||||||
test("Sequence", () => {
|
test("Sequence", () => {
|
||||||
type In = [1, 2, 3, 4, 5];
|
type In = [1, 2, 3, 4, 5];
|
||||||
type Out = [5, 4, 3, 2, 1];
|
type Out = [5, 4, 3, 2, 1];
|
||||||
type Reverse = [4, 3, 2, 1, 0];
|
type Reverse = [4, 3, 2, 1, 0];
|
||||||
|
|
||||||
expectTypeOf<Sequence<In, Reverse>>().toEqualTypeOf<Out>();
|
expectTypeOf<
|
||||||
});
|
Sequence<In, Reverse>
|
||||||
|
>().toEqualTypeOf<Out>();
|
||||||
|
});
|
||||||
|
|
||||||
test("Primative", () => {
|
test("Primative", () => {
|
||||||
interface Props {
|
interface Props {
|
||||||
value: [number, number];
|
value: [number, number];
|
||||||
meta: { registry: [] };
|
meta: { registry: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
type V = Primative<[number, number], Props>[1];
|
type V = Primative<[number, number], Props>[1];
|
||||||
|
|
||||||
expectTypeOf<V>().toHaveProperty("x");
|
expectTypeOf<V>().toHaveProperty("x");
|
||||||
expectTypeOf<V>().toHaveProperty("y");
|
expectTypeOf<V>().toHaveProperty("y");
|
||||||
expectTypeOf<V>().not.toHaveProperty("z");
|
expectTypeOf<V>().not.toHaveProperty("z");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+161
-204
@@ -6,7 +6,7 @@ import {
|
|||||||
type Props,
|
type Props,
|
||||||
type Return,
|
type Return,
|
||||||
} from "../base/mixin";
|
} from "../base/mixin";
|
||||||
import { assert, assertType, type HidePrototype } from "../internal";
|
import { assert, type HidePrototype } from "../internal";
|
||||||
|
|
||||||
type NoneSentinel = null | undefined;
|
type NoneSentinel = null | undefined;
|
||||||
type None<T> = Extract<T, NoneSentinel>;
|
type None<T> = Extract<T, NoneSentinel>;
|
||||||
@@ -16,211 +16,180 @@ function isNone<T>(v: T): v is None<T> {
|
|||||||
return v === null || v === undefined;
|
return v === null || v === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Where<T, t extends Props> {
|
interface IOptional<T = unknown, t extends Props = Props> {
|
||||||
/**
|
/**
|
||||||
* Set a value to a fallback if it does not conform to some conditional
|
* Transform a value via callback if it is not `null` or
|
||||||
* @param callback Callback returning a boolean, `false` sets the value to the fallback
|
* `undefined`
|
||||||
* @param fallback The fallback value, `null` by default
|
|
||||||
* @example
|
|
||||||
* const parse = () => { version: "1.2.4" } as unknown;
|
|
||||||
*
|
*
|
||||||
* const version = $(parse())
|
* If the value is equal to `null` or `undefined`, it
|
||||||
* .where(v => typeof v === 'object' &&
|
* remains unchanged and the callback is not called.
|
||||||
* v !== null &&
|
* @param callback Function for a non-null value
|
||||||
* 'version' in v)
|
* @example
|
||||||
* .and(v => v.version)
|
* const none = () => null as number | null;
|
||||||
* .or("1.0.0")
|
* const some = () => 10 as number | null;
|
||||||
* .value;
|
*
|
||||||
* expect(version).toBe("1.2.4");
|
* 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`}
|
* @from {@link Optional `Optional`}
|
||||||
*/
|
*/
|
||||||
where: <const U = null>(
|
and: <U>(callback: (v: Some<T>) => U) => Return<None<T> | U, t>;
|
||||||
callback: (v: T) => boolean,
|
/**
|
||||||
fallback?: U,
|
* Set value to a fallback if it is `null` or `undefined`.
|
||||||
) => Return<T | U, t>;
|
* @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: (<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
|
||||||
|
* only computes the fallback if the value is `null` or
|
||||||
|
* `undefined`
|
||||||
|
* @param callback
|
||||||
|
* @from {@link Optional `Optional`}
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const none = () => null as number | null;
|
||||||
|
* const some = () => 8 as number | null;
|
||||||
|
*
|
||||||
|
* const fallback = vi.fn(() => 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()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
else: <U>(
|
||||||
|
callback: (v: None<T>) => U,
|
||||||
|
) => Return<Some<T> | U, t>;
|
||||||
|
} & HidePrototype;
|
||||||
|
/**
|
||||||
|
* Assert that value is not `null` or `undefined`
|
||||||
|
* @param msg Reasoning to attach to the `AssertionError`
|
||||||
|
* @see `.assert.none()` for the inverse assertion
|
||||||
|
* @from {@link Optional `Optional`}
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const array = [1, 2, 3] as const;
|
||||||
|
* const element = array.at(1);
|
||||||
|
*
|
||||||
|
* const v = $(element).assert("index within bounds").value;
|
||||||
|
* expect(v).toBe(2);
|
||||||
|
*
|
||||||
|
* expect(() => {
|
||||||
|
* $(array.at(6)).assert()
|
||||||
|
* }).toThrow()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
assert: ((msg?: string) => Return<Some<T>, t>) & {
|
||||||
|
/**
|
||||||
|
* Assert that the value is either `null` or `undefined`
|
||||||
|
* @param msg Reasoning to attach to the `AssertionError`
|
||||||
|
* @from {@link Optional `Optional`}
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const array = [1, 2, 3] as const;
|
||||||
|
* const element = array.at(5);
|
||||||
|
*
|
||||||
|
* const v = $(element).assert.none("index out of bounds").value;
|
||||||
|
* expect(v).toBe(undefined);
|
||||||
|
*
|
||||||
|
* expect(() => {
|
||||||
|
* $(array.at(1)).assert.none()
|
||||||
|
* }).toThrow()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
none: (msg?: string) => Return<None<T>, t>;
|
||||||
|
} & HidePrototype;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Optional extends Mixin.HKT {
|
export interface Optional extends Mixin.HKT<IOptional> {
|
||||||
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
new: (
|
||||||
|
t: HKT.T<this>,
|
||||||
|
) => Input<typeof t> extends infer T
|
||||||
? None<T> extends never
|
? None<T> extends never
|
||||||
? Where<T, typeof t>
|
? {}
|
||||||
: {
|
: IOptional<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: (<const 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
|
|
||||||
* @from {@link Optional `Optional`}
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const none = () => null as number | null;
|
|
||||||
* const some = () => 8 as number | null;
|
|
||||||
*
|
|
||||||
* const fallback = vi.fn(() => 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()
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
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`
|
|
||||||
* @see `.assert.none()` for the inverse assertion
|
|
||||||
* @from {@link Optional `Optional`}
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const array = [1, 2, 3] as const;
|
|
||||||
* const element = array.at(1);
|
|
||||||
*
|
|
||||||
* const v = $(element).assert("index within bounds").value;
|
|
||||||
* expect(v).toBe(2);
|
|
||||||
*
|
|
||||||
* expect(() => {
|
|
||||||
* $(array.at(6)).assert()
|
|
||||||
* }).toThrow()
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
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`
|
|
||||||
* @from {@link Optional `Optional`}
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const array = [1, 2, 3] as const;
|
|
||||||
* const element = array.at(5);
|
|
||||||
*
|
|
||||||
* const v = $(element).assert.none("index out of bounds").value;
|
|
||||||
* expect(v).toBe(undefined);
|
|
||||||
*
|
|
||||||
* expect(() => {
|
|
||||||
* $(array.at(1)).assert.none()
|
|
||||||
* }).toThrow()
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
none: (
|
|
||||||
msg?: string,
|
|
||||||
) => Return<None<T>, typeof t>;
|
|
||||||
} & HidePrototype;
|
|
||||||
} & Where<T, typeof t>
|
|
||||||
: never;
|
: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Optional = Mixin<Optional>((value, $, fluent) => {
|
export const Optional = Mixin<IOptional, Optional>(
|
||||||
if (isNone(value)) {
|
(value, $, fluent) => {
|
||||||
$.and = () => {
|
if (isNone(value)) {
|
||||||
return fluent(value);
|
$.and = () => {
|
||||||
};
|
return fluent(value);
|
||||||
|
};
|
||||||
|
|
||||||
$.or = (fallback: unknown) => {
|
$.or = Object.assign(
|
||||||
return fluent(fallback);
|
(fallback: unknown) => {
|
||||||
};
|
return fluent(fallback);
|
||||||
assertType<object>($.or);
|
},
|
||||||
Object.assign($.or, {
|
{
|
||||||
else: (callback: (v: unknown) => unknown) => {
|
else: (callback: (v: unknown) => unknown) => {
|
||||||
|
return fluent(callback(value));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
$.assert = Object.assign(
|
||||||
|
(msg?: string) => {
|
||||||
|
assert(false, msg);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
none: () => {
|
||||||
|
return fluent(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$.and = (callback: (v: unknown) => unknown) => {
|
||||||
return fluent(callback(value));
|
return fluent(callback(value));
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
|
||||||
$.assert = (msg?: string) => {
|
$.or = Object.assign(
|
||||||
assert(false, msg);
|
() => {
|
||||||
};
|
return fluent(value);
|
||||||
assertType<object>($.assert);
|
},
|
||||||
Object.assign($.assert, {
|
{
|
||||||
none: () => {
|
else: () => {
|
||||||
return fluent(value);
|
return fluent(value);
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
} else {
|
);
|
||||||
$.and = (callback: (v: unknown) => unknown) => {
|
|
||||||
return fluent(callback(value));
|
|
||||||
};
|
|
||||||
|
|
||||||
$.or = () => {
|
$.assert = Object.assign(
|
||||||
return fluent(value);
|
() => {
|
||||||
};
|
return fluent(value);
|
||||||
|
},
|
||||||
assertType<object>($.or);
|
{
|
||||||
Object.assign($.or, {
|
none: (msg?: string) => {
|
||||||
else: () => {
|
assert(false, msg);
|
||||||
return fluent(value);
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
}
|
||||||
$.assert = () => {
|
},
|
||||||
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) {
|
if (import.meta.vitest) {
|
||||||
const { test, expect, expectTypeOf, vi } = import.meta.vitest;
|
const { test, expect, expectTypeOf, vi } = import.meta.vitest;
|
||||||
@@ -272,16 +241,4 @@ if (import.meta.vitest) {
|
|||||||
|
|
||||||
expect(callback).toHaveBeenCalledOnce();
|
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 MaxDepth = 50;
|
||||||
|
|
||||||
export type Pretty<T> = { [K in keyof T]: T[K] };
|
export type Pretty<T> = { [K in keyof T]: T[K] };
|
||||||
export type Constrain<T, U> = T extends U ? T : never;
|
export type Constrain<T, U> = T extends U ? T : never;
|
||||||
|
|
||||||
|
export type IsReadonly<T extends readonly unknown[]> =
|
||||||
|
T extends unknown[] ? true : false;
|
||||||
|
|
||||||
namespace Vec {
|
namespace Vec {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type Lookup<T, N extends number> = N extends 0
|
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 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) {
|
if (import.meta.vitest) {
|
||||||
const { test, expectTypeOf } = import.meta.vitest;
|
const { test, expectTypeOf } = import.meta.vitest;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
/* Linting */
|
/* Linting */
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUncheckedIndexedAccess": false,
|
"noUncheckedIndexedAccess": false,
|
||||||
"erasableSyntaxOnly": true,
|
|
||||||
},
|
},
|
||||||
"include": ["src", "tests"]
|
"include": ["src", "tests"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
"checkJs": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
|||||||
Reference in New Issue
Block a user