wip: wtf
This commit is contained in:
@@ -45,6 +45,7 @@ export default defineConfig([
|
|||||||
reportUsedIgnorePattern: true,
|
reportUsedIgnorePattern: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"@typescript-eslint/no-namespace": "off",
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"esbuild": "^0.28.1",
|
"esbuild": "^0.28.1",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"jiti": "^2.7.0",
|
"jiti": "^2.7.0",
|
||||||
|
"madge": "^8.0.0",
|
||||||
"prettier": "^3.8.4",
|
"prettier": "^3.8.4",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"typescript-eslint": "^8.59.2",
|
"typescript-eslint": "^8.59.2",
|
||||||
|
|||||||
Generated
+948
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
|||||||
|
export interface HKT<In = unknown, Out = unknown> {
|
||||||
|
readonly _meta: In;
|
||||||
|
readonly _t: In;
|
||||||
|
new: (t: never) => Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Param<
|
||||||
|
This extends HKT,
|
||||||
|
U = This["_meta"],
|
||||||
|
> = This["_t"] extends infer T ? (T extends U ? T : U) : never;
|
||||||
|
|
||||||
|
export namespace HKT {
|
||||||
|
export type T<
|
||||||
|
This extends HKT,
|
||||||
|
T = This extends { _t: infer I } ? I : unknown,
|
||||||
|
> = Param<This, T>;
|
||||||
|
export type Apply<T extends HKT, t extends T["_t"]> = ReturnType<
|
||||||
|
(T & { _t: t })["new"]
|
||||||
|
>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { DefaultRegistry, Methods, Registry } from "../registry";
|
||||||
|
|
||||||
|
export interface Base<T> {
|
||||||
|
readonly value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Fluent<
|
||||||
|
T,
|
||||||
|
Reg extends Registry = DefaultRegistry,
|
||||||
|
> = Base<T> & Methods<T, Reg> & { readonly __registry: Reg };
|
||||||
|
|
||||||
|
export function makeFluent<const Reg extends Registry>(
|
||||||
|
registry: Reg,
|
||||||
|
) {
|
||||||
|
const fluent = <T>(value: T) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
const f = { value } as never 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
|
||||||
|
return fluent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import type { Fluent } from ".";
|
||||||
|
import { never } from "../internal";
|
||||||
|
import type { Registry } from "../registry";
|
||||||
|
import type { HKT } from "./hkt";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
readonly value: unknown;
|
||||||
|
readonly meta: { registry: Registry };
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnionToIntersection<U> = (
|
||||||
|
U extends unknown ? (x: U) => void : never
|
||||||
|
) extends (x: infer I) => void
|
||||||
|
? I
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type MixinHKT = HKT<Props>;
|
||||||
|
type MixinFn<T extends MixinHKT> = (
|
||||||
|
value: unknown,
|
||||||
|
fluent: Partial<
|
||||||
|
UnionToIntersection<
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
HKT.Apply<T, { value: any; meta: { registry: [] } }>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
callback: (value: unknown) => never,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export interface Mixin<T extends MixinHKT = MixinHKT> {
|
||||||
|
hkt: T;
|
||||||
|
fn: MixinFn<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Mixin<T extends MixinHKT>(fn: MixinFn<T>): Mixin<T> {
|
||||||
|
return {
|
||||||
|
hkt: never,
|
||||||
|
fn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Mixin {
|
||||||
|
export type HKT = MixinHKT;
|
||||||
|
export type Function<T extends MixinHKT> = MixinFn<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Fluent<T, P extends Props> = _Fluent<
|
||||||
|
// T,
|
||||||
|
// P["meta"]["registry"]
|
||||||
|
// > & {};
|
||||||
|
|
||||||
|
export type Input<P extends Props> = P["value"];
|
||||||
|
export type Return<T, P extends Props> = Fluent<
|
||||||
|
T,
|
||||||
|
P extends { meta: { registry: infer Reg } } ? Reg : never
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type Instansiate<
|
||||||
|
M extends Mixin,
|
||||||
|
T,
|
||||||
|
Reg extends Registry,
|
||||||
|
> = HKT.Apply<M["hkt"], { value: T; meta: { registry: Reg } }>;
|
||||||
+3
-9
@@ -1,10 +1,4 @@
|
|||||||
function add(a: number, b: number): number {
|
import { makeFluent } from "./base";
|
||||||
return a + b;
|
import { DEFAULT_REGISTRY } from "./registry";
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.vitest) {
|
export const fluent = makeFluent(DEFAULT_REGISTRY);
|
||||||
const { it, expect } = import.meta.vitest;
|
|
||||||
it("adds", () => {
|
|
||||||
expect(add(1, 2)).toBe(3);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { HKT } from "./base/hkt";
|
||||||
|
|
||||||
|
export interface Identity extends HKT {
|
||||||
|
new: (t: HKT.T<this>) => typeof t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
export const never = undefined as never;
|
||||||
|
|
||||||
|
export type Constraint<T, U extends T> = U;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
export type Empty = Constraint<Record<PropertyKey, unknown>, {}>;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { makeFluent } from "../base";
|
||||||
|
import type { HKT } from "../base/hkt";
|
||||||
|
import {
|
||||||
|
Mixin,
|
||||||
|
type Input,
|
||||||
|
type Props,
|
||||||
|
type Return,
|
||||||
|
} from "../base/mixin";
|
||||||
|
import type { Empty } from "../internal";
|
||||||
|
import type { Methods } from "../registry";
|
||||||
|
import { Base } from "./base";
|
||||||
|
|
||||||
|
class Awaited<T extends Promise<unknown>> {
|
||||||
|
public value: T;
|
||||||
|
public constructor(value: T) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AwaitedMixin 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>;
|
||||||
|
}
|
||||||
|
: 0
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AwaitedMixin = Mixin<AwaitedMixin>(
|
||||||
|
(value, $, fluent) => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { test, expect } = import.meta.vitest;
|
||||||
|
|
||||||
|
const registry = [Base, AwaitedMixin] as const;
|
||||||
|
const $ = makeFluent(registry);
|
||||||
|
|
||||||
|
test(".awaited", () => {
|
||||||
|
const promise = new Promise<number>((r) => {
|
||||||
|
r(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
const m = $(promise).V;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { makeFluent } from "../base";
|
||||||
|
import type { HKT } from "../base/hkt";
|
||||||
|
import { Mixin, type Input, type Return } from "../base/mixin";
|
||||||
|
import type { Empty } from "../internal";
|
||||||
|
|
||||||
|
export interface Base extends Mixin.HKT {
|
||||||
|
new: (t: HKT.T<this>) => Input<typeof t> extends infer T
|
||||||
|
? {
|
||||||
|
tap(
|
||||||
|
callback: (value: T) => void,
|
||||||
|
): Return<T, typeof t>;
|
||||||
|
transform<U>(
|
||||||
|
callback: (value: T) => U,
|
||||||
|
): Return<U, typeof t>;
|
||||||
|
readonly V: Return<number, typeof t>;
|
||||||
|
}
|
||||||
|
: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Base = Mixin<Base>((value, $, fluent) => {
|
||||||
|
$.tap = (callback) => {
|
||||||
|
callback(value);
|
||||||
|
return fluent(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.transform = (callback) => {
|
||||||
|
return fluent(callback(value));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { test, expect } = import.meta.vitest;
|
||||||
|
|
||||||
|
const registry = [Base] as const;
|
||||||
|
const $ = makeFluent(registry);
|
||||||
|
|
||||||
|
test("tap()", () => {
|
||||||
|
const value = 5;
|
||||||
|
let out = 0;
|
||||||
|
|
||||||
|
const result = $(value).tap((v) => (out = v)).value;
|
||||||
|
|
||||||
|
expect(result).toBe(value);
|
||||||
|
expect(out).toBe(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("transform()", () => {
|
||||||
|
const value = 5;
|
||||||
|
const increment = (v: number) => v + 1;
|
||||||
|
|
||||||
|
expect($(value).transform(increment).value).toBe(
|
||||||
|
increment(value),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
$(value).transform(increment).transform(increment).value,
|
||||||
|
).toBe(increment(increment(value)));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { Base } from "./base";
|
||||||
|
export { AwaitedMixin as Awaited } from "./awaited";
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Mixin, type Instansiate } from "./base/mixin";
|
||||||
|
import type { Empty } from "./internal";
|
||||||
|
import { Base, Awaited } from "./mixin";
|
||||||
|
|
||||||
|
export type Registry = readonly Mixin[];
|
||||||
|
|
||||||
|
export const DEFAULT_REGISTRY = [
|
||||||
|
Base,
|
||||||
|
Awaited,
|
||||||
|
] as const satisfies Registry;
|
||||||
|
export type DefaultRegistry = typeof DEFAULT_REGISTRY;
|
||||||
|
|
||||||
|
type Expand<T> = { [K in keyof T]: T[K] };
|
||||||
|
type Merge<T, U> = [T] extends [0] ? U : [U] extends [0] ? T : T & U;
|
||||||
|
|
||||||
|
export type Methods<
|
||||||
|
T,
|
||||||
|
Reg extends Registry,
|
||||||
|
TReg extends Registry = Reg,
|
||||||
|
TOut = {},
|
||||||
|
> = TReg extends readonly [
|
||||||
|
infer M extends Mixin,
|
||||||
|
...infer Rest extends Registry,
|
||||||
|
]
|
||||||
|
? Methods<T, Reg, Rest, Merge<TOut, Instansiate<M, T, Reg>>>
|
||||||
|
: TOut;
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"target": "es2023",
|
"target": "es2023",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"types": ["vitest/importMeta"],
|
"types": ["node", "vitest/importMeta"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
|
|||||||
Reference in New Issue
Block a user