wip: wtf
This commit is contained in:
@@ -45,6 +45,7 @@ export default defineConfig([
|
||||
reportUsedIgnorePattern: true,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^10.3.0",
|
||||
"jiti": "^2.7.0",
|
||||
"madge": "^8.0.0",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "~6.0.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 {
|
||||
return a + b;
|
||||
}
|
||||
import { makeFluent } from "./base";
|
||||
import { DEFAULT_REGISTRY } from "./registry";
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { it, expect } = import.meta.vitest;
|
||||
it("adds", () => {
|
||||
expect(add(1, 2)).toBe(3);
|
||||
});
|
||||
}
|
||||
export const fluent = makeFluent(DEFAULT_REGISTRY);
|
||||
|
||||
@@ -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",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["vitest/importMeta"],
|
||||
"types": ["node", "vitest/importMeta"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
|
||||
Reference in New Issue
Block a user