UNPKG

type-plus

Version:
67 lines (61 loc) 2.25 kB
import type { LeftJoin } from '../object/index.js' export type ContextBaseShape = Record<string | symbol, any> /** * Extends the context with new props. * @param context the current context. * @return an additional context with new properties. */ export type ContextExtender<Current, Additional> = (context: Current) => Additional export type ContextBuilder<Init extends ContextBaseShape, Ctx extends ContextBaseShape> = { /** * Extends the context using an extender. * * @type Additional The additional context to be added by the `extender`. * By default this is inferred by the `extender`. * But you can also explicitly specify it, * if the type is a superset of the actual return type of the `extender`. * @param extender function that add new props to the context. * * The extender only need to return a new object with additional properties. * The builder will merge that with the current context. * * If the extender specify an existing property, * it overrides the existing value. */ extend<Additional extends ContextBaseShape = ContextBaseShape>( extender: ContextExtender<Ctx, Additional> ): ContextBuilder<Init, LeftJoin<Ctx, Additional>>, /** * Build and return the context. */ build(): Ctx } /** * Creates a context builder. * * @param init The initial context or an context initializer. * @return the context builder where you can * use `extend()` to add context, and * use `build()` to build the context. */ export function context<Init extends ContextBaseShape, Ctx extends ContextBaseShape = Init>( init?: Init | (() => Init) ): ContextBuilder<Init, Ctx> { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return typeof init === 'function' ? contextBuilder({}, [[init]]) : (contextBuilder(init ?? {}, []) as any) } /* eslint-disable */ function contextBuilder(init: ContextBaseShape, extenders: Array<[ContextExtender<any, any> | any]>) { return { extend(extender: ContextExtender<any, any>) { return contextBuilder(init, extenders.concat([[extender]])) }, build() { return extenders.reduce((p, [t], i) => { const v = typeof t === 'function' ? (extenders[i]![0] = t(p)) : t return { ...p, ...v } }, init) } } } /* eslint-enable */