From 629a1fe2d0bb75605d33bb300a36558cb6eefe5b Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Wed, 20 Nov 2024 12:46:05 -0500 Subject: [PATCH] improve(lib/builder): refine jsdoc and terms (#1270) --- src/client/builderExtensions/anyware.ts | 2 +- src/client/builderExtensions/scalar.ts | 8 +- src/client/builderExtensions/use.ts | 6 +- src/client/builderExtensions/with.ts | 4 +- src/client/client.ts | 22 +-- src/extensions/Throws/Throws.ts | 4 +- src/lib/builder/Definition.ts | 175 ++++++++++++++++-------- src/lib/builder/Extension.ts | 56 +++++++- src/lib/builder/__.test-d.ts | 69 +++++++--- 9 files changed, 241 insertions(+), 105 deletions(-) diff --git a/src/client/builderExtensions/anyware.ts b/src/client/builderExtensions/anyware.ts index b589511b8..2284f7230 100644 --- a/src/client/builderExtensions/anyware.ts +++ b/src/client/builderExtensions/anyware.ts @@ -18,7 +18,7 @@ export interface Anyware<$Arguments extends Builder.Extension.Parameters, - ) => Builder.Definition.MaterializeWithNewContext<$Arguments['chain'], $Arguments['context']> + ) => Builder.Definition.MaterializeWith<$Arguments['definition'], $Arguments['context']> } export const builderExtensionAnyware = Builder.Extension.create((builder, context) => { diff --git a/src/client/builderExtensions/scalar.ts b/src/client/builderExtensions/scalar.ts index 9d9806c9e..14ae1e0ef 100644 --- a/src/client/builderExtensions/scalar.ts +++ b/src/client/builderExtensions/scalar.ts @@ -34,8 +34,8 @@ type ScalarMethod<$Args extends Builder.Extension.Parameters $Decoded encode: (value: $Decoded) => string }, - ): Builder.Definition.MaterializeWithNewContext< - $Args['chain'], + ): Builder.Definition.MaterializeWith< + $Args['definition'], ConfigManager.SetAtPath< $Args['context'], ['scalars'], @@ -53,8 +53,8 @@ type ScalarMethod<$Args extends Builder.Extension.Parameters['schema']['scalarNamesUnion']>>( scalar: $Scalar, - ): Builder.Definition.MaterializeWithNewContext< - $Args['chain'], + ): Builder.Definition.MaterializeWith< + $Args['definition'], ConfigManager.SetAtPath< $Args['context'], ['scalars'], diff --git a/src/client/builderExtensions/use.ts b/src/client/builderExtensions/use.ts index e849ae5b7..72a5275d7 100644 --- a/src/client/builderExtensions/use.ts +++ b/src/client/builderExtensions/use.ts @@ -19,12 +19,12 @@ export interface Use<$Args extends Builder.Extension.Parameters, $Extension extends Extension, -> = Builder.Definition.MaterializeWithNewContext< +> = Builder.Definition.MaterializeWith< // Apply any builder extensions. ( ConfigManager.GetOptional<$Extension, ['builder', 'type']> extends Builder.Extension - ? Builder.Definition.AddExtension<$Args['chain'], ConfigManager.GetOptional<$Extension, ['builder', 'type']>> - : $Args['chain'] + ? Builder.Definition.AddExtension<$Args['definition'], ConfigManager.GetOptional<$Extension, ['builder', 'type']>> + : $Args['definition'] ), // Extend context. ConfigManager.SetMany< diff --git a/src/client/builderExtensions/with.ts b/src/client/builderExtensions/with.ts index 6a96e9b5c..640b2959a 100644 --- a/src/client/builderExtensions/with.ts +++ b/src/client/builderExtensions/with.ts @@ -20,8 +20,8 @@ export interface With<$Args extends Builder.Extension.Parameters Builder.Definition.MaterializeWithNewContext< - $Args['chain'], + ) => Builder.Definition.MaterializeWith< + $Args['definition'], ConfigManager.SetProperties< $Args['context'], { diff --git a/src/client/client.ts b/src/client/client.ts index e9c3d11fe..2d8bb68a5 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -33,19 +33,21 @@ import { type NormalizeInput } from './Settings/InputToConfig.js' // > // > -export type Client<$Context extends Context> = Builder.Definition.MaterializeWithNewContext< - Builder.Definition.Create<[ - BuilderExtensionInternal, - BuilderExtensionRequestMethods, - BuilderExtensionWith, - BuilderExtensionUse, - BuilderExtensionAnyware, - BuilderExtensionGql, - BuilderExtensionScalar, - ]>, +export type Client<$Context extends Context> = Builder.Definition.MaterializeWith< + ClientDefinition, $Context > +type ClientDefinition = Builder.Definition.Create<[ + BuilderExtensionInternal, + BuilderExtensionRequestMethods, + BuilderExtensionWith, + BuilderExtensionUse, + BuilderExtensionAnyware, + BuilderExtensionGql, + BuilderExtensionScalar, +]> + // dprint-ignore type Create = <$Input extends InputStatic>(input: Exact<$Input, InputStatic>) => // todo fixme diff --git a/src/extensions/Throws/Throws.ts b/src/extensions/Throws/Throws.ts index 6473ff0db..66af456b0 100644 --- a/src/extensions/Throws/Throws.ts +++ b/src/extensions/Throws/Throws.ts @@ -38,8 +38,8 @@ interface BuilderExtension_<$Args extends Builder.Extension.Parameters Builder.Definition.MaterializeWithNewContext< - $Args['chain'], + throws: () => Builder.Definition.MaterializeWith< + $Args['definition'], ConfigManager.UpdateAtKey< $Args['context'], 'config', diff --git a/src/lib/builder/Definition.ts b/src/lib/builder/Definition.ts index df74707e9..ff75fd290 100644 --- a/src/lib/builder/Definition.ts +++ b/src/lib/builder/Definition.ts @@ -5,21 +5,22 @@ import type { TypeFunction } from '../type-function/__.js' import type { Context, Extension } from './Extension.js' /** - * A chain. Type level function with extensions. - * When called, it returns itself with the given extensions. + * A builder definition. + * Technically it is a type level function with additional properties. + * When called, returns itself with the given extensions attached as a property. */ export interface Definition_<$Extensions extends [...Extension[]] = [...Extension[]]> extends TypeFunction.Fn { extensions: $Extensions return: Definition_> } -type InvokeDefinition<_ extends Definition_, $Arguments extends [...Extension[]]> = TypeFunction.Call< - _, +type InvokeDefinition<$Definition extends Definition_, $Arguments extends [...Extension[]]> = TypeFunction.Call< + $Definition, $Arguments > /** - * An empty chain definition. Useful for creation of new chains. + * A definition literal, empty. Useful for creation of new builders. */ export type Empty = Definition_<[]> @@ -29,107 +30,169 @@ export type Empty = Definition_<[]> export type Create<$Extensions extends [...Extension[]]> = AddExtensions /** - * Extend the definition with many extensions. Refer to `Extend` for more details. + * Extend the definition with many extensions. See {@link AddExtension} for details. */ // dprint-ignore -export type AddExtensions<$Chain_ extends Definition_, $Extensions extends [...Extension[]]> = +export type AddExtensions<$Definition extends Definition_, $Extensions extends [...Extension[]]> = $Extensions extends [infer $FirstExtension extends Extension, ...infer $RestExtensions extends Extension[]] - ? AddExtensions, $RestExtensions> - : $Chain_ + ? AddExtensions, $RestExtensions> + : $Definition /** * Extend the definition. During materialization the extension can contribute properties * that make up the final builder. */ // dprint-ignore -export type AddExtension<$Chain_ extends Definition_, $Extension_ extends Extension> = - InvokeDefinition<$Chain_, [...$Chain_['extensions'], $Extension_]> +export type AddExtension<$definition_ extends Definition_, $Extension_ extends Extension> = + InvokeDefinition<$definition_, [...$definition_['extensions'], $Extension_]> // // Materialize Utilities // +/** + * Materialize the definition using each extension's generic context type. + * + * Each extension will be invoked with its own generic context type. + * Then results will be merged to produce the builder object. + * + * All extensions' generic context types will be merged to produce + * the builder object context property. + */ // dprint-ignore -export type MaterializeGeneric<$Chain_ extends Definition_> = +export type MaterializeGeneric<$Definition extends Definition_> = Simplify< Private.Add< { - chain: $Chain_, - context: Tuple.IntersectItems< - MaterializeExtensionsGenericContext<$Chain_['extensions']> - > + definition: $Definition, + context: MaterializeGeneric_Context<$Definition> }, - Tuple.IntersectItems< - MaterializeExtensionsGeneric<$Chain_, $Chain_['extensions']> - > + MaterializeGeneric_Extensions<$Definition> > > // dprint-ignore -type MaterializeExtensionsGeneric<$Chain_ extends Definition_, $Extensions extends [...Extension[]]> = { - [$Index in keyof $Extensions]: Extension.Invoke<$Extensions[$Index], { - chain: $Chain_, - context: $Extensions[$Index]['context'] - }> -} +type MaterializeGeneric_Extensions<$Definition extends Definition_> = + Tuple.IntersectItems< + MaterializeGeneric_Extensions_< + $Definition, + $Definition['extensions'] + > + > // dprint-ignore -type MaterializeExtensionsGenericContext<$Extensions extends [...Extension[]]> = { +type MaterializeGeneric_Extensions_< + $Definition extends Definition_, + $Extensions extends [...Extension[]] +> = { + [$Index in keyof $Extensions]: + Extension.Invoke<$Extensions[$Index], { + definition: $Definition, + context: $Extensions[$Index]['context'], + }> +} + +type MaterializeGeneric_Context<$Definition extends Definition_> = Tuple.IntersectItems< + MaterializeGeneric_Context_<$Definition['extensions']> +> +type MaterializeGeneric_Context_<$Extensions extends [...Extension[]]> = { [$Index in keyof $Extensions]: $Extensions[$Index]['context'] } +// +// +// --------------------------------------------------------------------------------------------- +// +// + +/** + * Materialize the definition using each extension's empty context type. + * + * Each extension will be invoked with its own empty context. + * Then results will be merged to produce the builder object. + * + * All extensions' empty context types will be merged to produce + * the builder object context property. + */ // dprint-ignore -export type MaterializeSpecific<$Chain_ extends Definition_> = +export type MaterializeEmpty<$Definition extends Definition_> = Simplify< Private.Add< { - chain: $Chain_, - context: Tuple.IntersectItems< - MaterializeExtensionsInitialContext<$Chain_['extensions']> - > + definition: $Definition, + context: MaterializeEmpty_Context<$Definition>, }, Tuple.IntersectItems< - MaterializeExtensionsInitial<$Chain_, $Chain_['extensions']> + MaterializeEmpty_Extensions<$Definition, $Definition['extensions']> > > > // dprint-ignore -type MaterializeExtensionsInitial<$Chain_ extends Definition_, $Extensions extends [...Extension[]]> = { +type MaterializeEmpty_Extensions< + $Definition extends Definition_, + $Extensions extends [...Extension[]] +> = { [$Index in keyof $Extensions]: Extension.Invoke<$Extensions[$Index], { - chain: $Chain_, + definition: $Definition, context: $Extensions[$Index]['contextEmpty'] }> } // dprint-ignore -type MaterializeExtensionsInitialContext<$Extensions extends [...Extension[]]> = { +type MaterializeEmpty_Context<$Definition extends Definition_> = + Tuple.IntersectItems< + MaterializeEmpty_Context_<$Definition['extensions']> + > +// dprint-ignore +type MaterializeEmpty_Context_<$Extensions extends [...Extension[]]> = { [$Index in keyof $Extensions]: $Extensions[$Index]['contextEmpty'] } +// +// +// --------------------------------------------------------------------------------------------- +// +// + +/** + * Materialize the definition with a new context. + * + * Each extension will be invoked with the given context. + * Then results will be merged to produce the builder object. + * + * The given context will be used as-is for the builder object context property. + */ // dprint-ignore -export type MaterializeWithNewContext<$Chain_ extends Definition_, $Context extends Context> = - // Simplify< - Private.Add< - { - chain: $Chain_, - context: $Context - }, - Tuple.IntersectItems< - MaterializeExtensionsWithNewState< - $Chain_, - $Context, - $Chain_['extensions'] - > +export type MaterializeWith< + $Definition extends Definition_, + $Context extends Context +> = + +Private.Add< + { + definition: $Definition, + context: $Context + }, + Tuple.IntersectItems< + MaterializeWith_Extensions< + $Definition, + $Definition['extensions'], + $Context > > -// > + > -type MaterializeExtensionsWithNewState< - $Chain_ extends Definition_, - $Context extends Context, +// dprint-ignore +type MaterializeWith_Extensions< + $Definition extends Definition_, $Extensions extends [...Extension[]], + $Context extends Context, > = { - [$Index in keyof $Extensions]: Extension.Invoke< - $Extensions[$Index], - { chain: $Chain_; context: $Context } - > + [$Index in keyof $Extensions]: + Extension.Invoke< + $Extensions[$Index], + { + definition: $Definition + context: $Context + } + > } diff --git a/src/lib/builder/Extension.ts b/src/lib/builder/Extension.ts index 5a0b1aa3f..8975bac44 100644 --- a/src/lib/builder/Extension.ts +++ b/src/lib/builder/Extension.ts @@ -3,7 +3,7 @@ import type { Definition } from './_.js' import type { Definition_ } from './Definition.js' /** - * Statically known data that extensions can read from and write to. + * Statically known data that extensions can read from/write to. */ export type Context = object @@ -23,7 +23,9 @@ export interface Extension extends TypeFunction.Fn { * * The definition of "most empty" is owned by the extension. * - * For example if context is `{ count: number[] }` then an extension may decide that contextEmpty is `{}` or `{ count: [] }` or just the same (`{ count: number[] }`). + * For example if context is `{ count: number[] }` then an extension + * may decide that contextEmpty is `{}` or `{ count: [] }` or just + * the same (`{ count: number[] }`). */ contextEmpty: Context } @@ -32,14 +34,58 @@ export namespace Extension { export type Invoke<_ extends Extension, $Arguments extends Parameters> = TypeFunction.Call<_, $Arguments> /** - * The parameters for chain extension. Chain extensions are "type functions", callable at the type level. + * The parameters for an extension invocation (recall: builder extensions are "type functions". * - * If you pass your Extension definition then the context you have defined for it will be used to type + * If you pass your Extension definition then its context will be used to type * the `context` parameter. */ export type Parameters<$Extension_ extends Extension = Extension> = { + /** + * The context type as specified by this extension. + * + * Note that the type here could be more specific (subtype) upon later + * use in the materialized builder context. For example this or other extensions + * may contribute methods that return the builder with a new context. + */ context: $Extension_['context'] - chain: Definition_ + // todo rename to definition + /** + * The definition of the builder. + * + * If you need to reference the builder with a changed (or same) + * context then you'll need this definition. + * + * @example + * + * ```ts + * interface BuilderExtension extends Builder.Extension { + * context: { value: string } + * return: BuilderExtension_ + * } + * interface BuilderExtension_<$Arguments extends Builder.Extension.Parameters> { + * passthrough: () => Builder.Definition.MaterializeWith<$Arguments['definition'], $Arguments['context']> + * append: <$Value>(value: $Value) => + * Builder.Definition.MaterializeWith< + * $Arguments['definition'], + * { value: `${$Arguments['context']['value']}${$Value}` } + * > + * } + * ``` + */ + definition: Definition_ + /** + * The current builder. + * + * After materialization this type becomes the specifically produced builder type. + * + * If your extension needs to reference the builder without affecting its context then + * use this type. For example your extension has a method that returns the builder. + * + * If your extension needs to reference the builder **with a changed context** then you must + * materialize it from the definition. See the {@link Parameters.definition} parameter. + */ + // todo so far we couldn't achieve this without hitting infinite instantiation TS limitation. + // builder: object } /** diff --git a/src/lib/builder/__.test-d.ts b/src/lib/builder/__.test-d.ts index 6159d9527..df8547fcd 100644 --- a/src/lib/builder/__.test-d.ts +++ b/src/lib/builder/__.test-d.ts @@ -11,8 +11,12 @@ import type { Builder } from './__.js' } type B = Builder.Definition.AddExtension assertEqual() - type b = Builder.Definition.MaterializeSpecific - assertEqual>() + type b = Builder.Definition.MaterializeEmpty + type expectedPrivateState = { + definition: B + context: {} + } + assertEqual>() } // --------------------------------------------------------------------------------------------------------------------- { @@ -23,8 +27,12 @@ import type { Builder } from './__.js' } type B = Builder.Definition.AddExtensions assertEqual() - type b = Builder.Definition.MaterializeSpecific - assertEqual>() + type b = Builder.Definition.MaterializeEmpty + type expectedPrivateState = { + definition: B + context: {} + } + assertEqual>() } // --------------------------------------------------------------------------------------------------------------------- { @@ -33,11 +41,24 @@ import type { Builder } from './__.js' return: Reflect } type Reflect<$Arguments extends Builder.Extension.Parameters> = { - reflect: keyof $Arguments + arguments: $Arguments } type B = Builder.Definition.AddExtension - type b = Builder.Definition.MaterializeSpecific - assertEqual>() + type b = Builder.Definition.MaterializeEmpty + type expectedPrivateState = { + definition: B + context: {} + } + type expectedContext = { + arguments: { + context: {} + definition: B + } + } + assertEqual< + b, + Private.Add + >() } // --------------------------------------------------------------------------------------------------------------------- { @@ -47,33 +68,37 @@ import type { Builder } from './__.js' interface FooContextEmpty extends FooContext { calls: [] } - interface Foo_ extends Builder.Extension { + interface FooExtension extends Builder.Extension { context: FooContext contextEmpty: FooContextEmpty // @ts-expect-error untyped params - return: Foo + return: FooExtension_ } - interface Foo<$Args extends Builder.Extension.Parameters> { + interface FooExtension_<$Args extends Builder.Extension.Parameters> { _: $Args['context'] foo: ( number: $Number, - ) => Builder.Definition.MaterializeWithNewContext< - $Args['chain'], + ) => Builder.Definition.MaterializeWith< + $Args['definition'], { calls: [...$Args['context']['calls'], $Number] } > } - type BGeneric = Builder.Definition.MaterializeGeneric> - type b1 = Builder.Definition.MaterializeSpecific> + type bGeneric = Builder.Definition.MaterializeGeneric< + Builder.Definition.AddExtension + > + type bEmpty = Builder.Definition.MaterializeEmpty< + Builder.Definition.AddExtension + > - assertExtends() + assertExtends() - const b1: b1 = null as any - type b1_ = typeof b1._ - assertEqual() + const bEmpty: bEmpty = null as any + type bEmpty_ = typeof bEmpty._ + assertEqual() - const b2 = b1.foo(1) - type b2 = typeof b2 - type b2_ = typeof b2._ - assertEqual() + const b = bEmpty.foo(1) + type b = typeof b + type b_ = typeof b._ + assertEqual() }