Skip to content

Instantly share code, notes, and snippets.

@rhom6us
Last active May 22, 2024 04:55
Show Gist options
  • Save rhom6us/bcba93e46994428c2420bd868b406a28 to your computer and use it in GitHub Desktop.
Save rhom6us/bcba93e46994428c2420bd868b406a28 to your computer and use it in GitHub Desktop.
Fluent Object Builder
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