r19
Version:
Simple remote procedure calls in TypeScript
137 lines (119 loc) • 3.49 kB
text/typescript
type AppendDotPathSegment<
TDotPath extends string,
TSegment extends string,
> = TDotPath extends "" ? TSegment : `${TDotPath}.${TSegment}`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyFunction = (...args: any[]) => any;
type Primitives =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint
| AnyFunction;
type AllObjDotPaths<TObj, TDotPath extends string = ""> = TObj extends
| Primitives
| unknown[]
? TDotPath
: {
[P in keyof TObj]: P extends string
?
| AppendDotPathSegment<TDotPath, P>
| AllObjDotPaths<TObj[P], AppendDotPathSegment<TDotPath, P>>
: TDotPath;
}[keyof TObj];
type RecursiveOmitNested<
TObj,
TOmitPath extends string,
TDotPath extends string = "",
> = TObj extends Primitives | unknown[]
? TObj
: {
[P in keyof TObj as P extends string
? AppendDotPathSegment<TDotPath, P> extends TOmitPath
? never
: P
: never]: P extends string
? RecursiveOmitNested<
TObj[P],
TOmitPath,
AppendDotPathSegment<TDotPath, P>
>
: TObj[P];
};
type OnlyProcedures<TProceduresInstance> =
TProceduresInstance extends AnyFunction
? TProceduresInstance
: {
[P in keyof TProceduresInstance as OnlyProcedures<
TProceduresInstance[P]
> extends Exclude<Primitives, AnyFunction> | Record<PropertyKey, never>
? never
: P]: OnlyProcedures<TProceduresInstance[P]>;
};
type _ProceduresFromInstanceConfig = {
omit?: readonly string[];
};
const _proceduresFromInstance = <TProceduresInstance>(
proceduresInstance: TProceduresInstance,
config: _ProceduresFromInstanceConfig = {},
path: string[] = [],
): OnlyProcedures<TProceduresInstance> => {
const omit: readonly string[] = config.omit ?? [];
const res = {} as OnlyProcedures<TProceduresInstance>;
if (proceduresInstance) {
const proto = Object.getPrototypeOf(proceduresInstance);
const properties = [
...Object.getOwnPropertyNames(proceduresInstance),
...(proto ? Object.getOwnPropertyNames(proto) : []),
];
for (const key of properties) {
const value = proceduresInstance[key as keyof typeof proceduresInstance];
if (key === "constructor" || omit.includes([...path, key].join("."))) {
continue;
}
if (typeof value === "function") {
res[key as keyof typeof res] = value.bind(proceduresInstance);
} else if (typeof value === "object") {
res[key as keyof typeof res] = _proceduresFromInstance(value, config, [
...path,
key,
]) as (typeof res)[keyof typeof res];
}
}
}
return res;
};
export type OmittableProcedures<TProceduresInstance> = AllObjDotPaths<
OnlyProcedures<TProceduresInstance>
>;
export type ProceduresFromInstance<
TProceduresInstance,
TOmitPaths extends string = never,
> = RecursiveOmitNested<OnlyProcedures<TProceduresInstance>, TOmitPaths>;
type ProceduresFromInstanceConfig<
TProceduresInstance,
TUnknownOmitPaths extends string,
> = {
omit?: readonly (
| TUnknownOmitPaths
| OmittableProcedures<TProceduresInstance>
)[];
};
export const proceduresFromInstance = <
TProceduresInstance,
TUnknownOmitPaths extends string = never,
>(
proceduresInstance: TProceduresInstance,
config: ProceduresFromInstanceConfig<
TProceduresInstance,
TUnknownOmitPaths
> = {},
): ProceduresFromInstance<TProceduresInstance, TUnknownOmitPaths> => {
return _proceduresFromInstance(
proceduresInstance,
config,
) as ProceduresFromInstance<TProceduresInstance, TUnknownOmitPaths>;
};