Last active
May 22, 2024 04:55
-
-
Save rhom6us/bcba93e46994428c2420bd868b406a28 to your computer and use it in GitHub Desktop.
Fluent Object Builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Func } from "@rhombus-toolkit/func"; | |
type Func<Args extends any[], Result = void> = (...args: Args) => Result; | |
type AnyRecord = Record<string|symbol, any>; | |
type AnyFunction = (...args: any[]) => any; | |
type MakeBuilder<T extends AnyRecord> = | |
T extends infer a extends AnyRecord | infer b extends AnyRecord ? ObjectBuilder<a> | ObjectBuilder<b> : ObjectBuilder<T>; | |
type ExtractObject<T> = | |
T extends any[] ? never : | |
T extends AnyFunction ? never : | |
Extract<T, AnyRecord>; | |
type ExtractArray<T> = Extract<T, any[]>; | |
type ExtractFunction<T> = Extract<T, AnyFunction>; | |
type BuilderValueArg<T> = | |
// T extends any[] ? T : | |
T extends ExtractObject<T> ? [builder: Func<[NonNullable<ObjectBuilder<ExtractObject<T>>>], void>] : | |
// T extends ExtractObject<T> ? [builder:Func<[number], void>] : | |
never; | |
export type ObjectBuilder<T extends AnyRecord> = { | |
[K in keyof T]-?: | |
(...value: [value:T[K]] | BuilderValueArg<T[K]>) => ObjectBuilder<Omit<T, K>>; | |
} & { | |
(): | |
} | |
function createProxy(target: AnyRecord) { | |
if (typeof target !== 'object') { | |
throw new Error('ObjectBuilder can only be created on types of "Record<any, any>".'); | |
} | |
const proxy = new Proxy(function(){}, { | |
apply(t, thisArg, argArray) { | |
return target; | |
}, | |
get(t, name, receiver) { | |
return function (...args: any) { | |
if (!args.length) { | |
throw new Error(`No arguments provided in call to "ObjectBuilder${typeof name === 'symbol' ? `[${name.description}]` : `.${name}`}()".`); | |
} | |
const [arg, ...{length: multipleArgs}] = args; | |
if (multipleArgs) { | |
target[name] = args; | |
return proxy; | |
} | |
if (typeof arg === 'function' && !arg[_raw]) { | |
// arg is a child ObjectBuilder function | |
target[name] ??= {}; | |
arg(createProxy(target[name])); | |
return proxy; | |
} | |
// arg is a simple value | |
target[name] = arg; | |
return proxy; | |
}; | |
}, | |
}); | |
return proxy; | |
} | |
export function createBuilder<T extends AnyRecord>(): ObjectBuilder<T> { | |
return createProxy({}) as any as ObjectBuilder<T>; | |
} | |
const _raw: unique symbol = Symbol(); | |
export function raw(value:Func<any,any>){ | |
value[_raw] = true; | |
} | |
interface TestType { | |
imaSimple: boolean; | |
imaObj: { | |
imaSimple: string; | |
ohmy?: { | |
deep: number; | |
type: 'filesystem' | 'fubar'; | |
}; | |
}; | |
imaFunc(): string; | |
imaList: []; | |
} | |
type ex = ExtractRecordValues<TestType>; | |
type ob = ObjectKeys<TestType>; | |
type aob = ArrayKeys<TestType>; | |
type sob = SimpleKeys<TestType>; | |
type fsob = FunctionKeys<TestType>; | |
let foo!: ObjectBuilder<TestType>; | |
foo.imaObj(p => p.imaSimple("").ohmy(om => om.deep(33).type('fubar'))); | |
foo.imaObj({ imaSimple: "true" }); | |
foo.imaSimple(true); | |
foo.imaObj(p => p.ohmy(q => q.deep(3))) | |
const mb = createBuilder<TestType>(); | |
const result = mb | |
// .imaSimple(true)(); | |
// .imaObj(p => p.imaSimple("")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment