namespace Poly { export type Omit = { [P in Exclude]: T[P]; } } namespace Use { type ThenArg = T extends Promise ? U : T type RelatedThen = A extends Promise ? Promise : B export type Func = (...args: any[]) => any export type Return = RelatedThen, B> export type CallbackValue = ThenArg> } namespace Profound { export type Func = (...arg: any) => any export type SingleArgFunc = (arg: any) => any export type SingleArgFuncs = { [k: string]: SingleArgFunc } export namespace Params { type Func = (arg: any) => any type Funcs = { [k: string]: Func } type FirstArg = T extends [infer R, ...any[]] ? R : T extends [] ? undefined : T; type ThenArg = T extends Promise ? U : T type UnionToIntersectionValues = ([K] extends [never] ? unknown : K extends unknown ? (k: U[K]) => void : never ) extends (k: infer I) => void ? I : never export type FnsBoth = UnionToIntersectionValues<{ [K in keyof T]: Parameters[0] extends undefined ? { [KK in K]?: ThenArg> } : { [KK in K]: ThenArg> } | FirstArg> }> export type FnsNoArg = UnionToIntersectionValues[0] extends undefined ? { [KK in K]?: ThenArg> } : never }>>> export type FnsHasArg = UnionToIntersectionValues[0] extends undefined ? never : { [KK in K]: ThenArg> } | FirstArg> }>>> export type Keys = { [K in keyof T]: T[K] }[keyof T] export type ObjEmptyNever = Keys extends never ? never : T export type ObjKeysWithoutNever = { [K in keyof T]: T[K] extends never ? never : K }[keyof T] export type ObjWithoutNever = Pick>; export type ObjClean = ObjEmptyNever> export type FnsOverride = { [K in keyof T]: T[K] extends Func ? ThenArg> : never } export type FnArguments = ObjClean<{ [K in keyof T]: T[K] extends (arg: any) => any ? Parameters[0] extends undefined ? never : Parameters[0] : never }> export type Value = FnArguments extends never ? Partial> | void : FnsHasArg | FnsNoArg // export type Value = FnArguments extends never ? FnsBoth | void : FnsBoth // export type Value = FnsHasArg & FnsNoArg // export type Value = FnsBoth } export namespace Callback { type ThenArg = T extends Promise ? U : T export type ObjReturn = { [K in keyof T]: T[K] extends SingleArgFunc ? ThenArg> : never } } export namespace Return { export type IsPromise = T extends Promise ? T : never export type FnIsPromise = T extends (...args: any) => any ? IsPromise> : never export type FnIsPromiseFn = T extends (...args: any) => any ? IsPromise> extends never ? never : T : never // gets object keys in object, never if no keys export type Keys = { [K in keyof T]: T[K] }[keyof T] // get object if has keys, never if no keys export type ObjEmptyNever = Keys extends never ? never : T // gets object keys without value set to never export type ObjKeysWithoutNever = { [K in keyof T]: T[K] extends never ? never : K }[keyof T] // gets object without never keys export type ObjWithoutNever = Pick>; // gets functions that return promises in object never if empty, without never properties export type ObjFnIsPromise = ObjEmptyNever }>> // removes keys from B in A export type ObjDiff = Poly.Omit[keyof Poly.Omit] // gets promises from A and removes them if theyr'e in C export type ObjPromiseDiff = ObjFnIsPromise extends never ? never : ObjDiff, C> // if B returns promise, or if there are promises in A and don't match keys in C export type RetunsPromise = FnIsPromiseFn | ObjPromiseDiff // if promise, provides inner promise type type ThenArg = T extends Promise ? U : T // gets return type from function, withou inner promise if any type ReturnValue = T extends (args: any) => any ? ThenArg> : never // gets all return types in an object of functions type ObjReturn = { [K in keyof T]: ReturnValue } // checks A diffed with C for promsies, if so wraps all ObjectReturns in promise type ReturnNoCallback = ObjPromiseDiff extends never ? ObjReturn : Promise> // checks for promise, returns value of inner callback wrapped in promise type ReturnYesCallback = RetunsPromise extends never ? ReturnValue : Promise> // checks if callback is available, before deligation of return // export type Value = unknown extends B ? ReturnNoCallback : ReturnYesCallback // for some reason have to check if B actually returns something because `B` is known export type Value = unknown extends ReturnValue ? ReturnNoCallback : ReturnYesCallback } } export function isPromise(obj: any) { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' } function use(fn: A, callback: (e: Error | null, a: Use.CallbackValue) => G) { return (...args: Parameters): Use.Return => { try { const v = fn(...args as any[]) if (isPromise(v)) { return v.then((v: any) => callback(null, v)).catch((e: any) => callback(e, undefined as Use.CallbackValue)) } return callback(null, v) as Use.Return } catch (e) { return callback(e, undefined as Use.CallbackValue) as unknown as Use.Return } } } export function placeholder() { return (param: G) => { return (a: Record): T => { throw new Error(`Need ${param}`) } } } const profoundRef = Symbol('Profound') function isPlainObject(obj: any): obj is ({ [name: string]: any }) { return obj && obj.constructor === Object || false; } function pick(o: any, ...props: string[]) { return Object.assign({}, ...props.map(prop => ({ [prop]: o[prop] }))); } function nest(value: T): () => T { return () => { return value } } export function profound) => G, C extends Profound.Params.Value>(funcs: A, callback?: B) { function reduceObjectOfFuncs(funcs: any, input: any = {}) { if (!isPlainObject(input)) throw new Error('Needs to be plain object') // console.log(input) const keys = Object.keys(funcs) return keys.reduce((acq: Profound.Func, key: string) => { return use(acq, (acqError, acqValue) => { if (acqError) throw acqError if (acqValue && acqValue.hasOwnProperty(key)) return acqValue const isProfound = Boolean(funcs[key] && funcs[key].isProfound && funcs[key].isProfound === profoundRef && funcs[key].pass) const fn = (isProfound) ? funcs[key].pass : funcs[key] return use(fn, (fnError, fnValue) => { if (fnError) throw fnError const [value, data] = (isProfound) ? fnValue : [fnValue, {}] return { ...acqValue, ...data, [key]: value } })(acqValue) }) }, nest(input))() } // NOTE returns (callbackValue) // NOTE MARKER function profound(preInput: C): Profound.Return.Value { return use(reduceObjectOfFuncs, (err, input) => { if (err) throw err const keys = Object.keys(funcs) if (!callback) return pick(input, ...keys) return use(callback as Profound.Func, (err, callbackValue) => { if (err) throw err return callbackValue })(input) })(funcs, preInput) } // NOTE returns ([callbackValue, reduceObjectOfFuncsValues]) function pass(preInput: any): any { return use(reduceObjectOfFuncs, (err, input) => { if (err) throw err const keys = Object.keys(funcs) if (!callback) return [pick(input, ...keys), input] return use(callback as Profound.Func, (err, callbackValue) => { if (err) throw err return [callbackValue, input] })(input) })(funcs, preInput) } profound.isProfound = profoundRef profound.pass = pass return profound }