Skip to content

Commit

Permalink
refactor(lib/anyware): data type oriented builders (#1274)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Nov 25, 2024
1 parent 5abf7a8 commit a78ebbe
Show file tree
Hide file tree
Showing 67 changed files with 1,335 additions and 1,162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ContextualError: There was an error in the core implementation of hook "exchange
at new URL (node:internal/url:XX:XX)
at new Request (node:internal/deps/undici/undici:XX:XX)
at Object.run (/some/path/to/httpTransport.ts:XX:XX:27)
at Object.run (/some/path/to/builder.ts:XX:XX:53)
at Object.run (/some/path/to/Pipeline.ts:XX:XX:51)
at runStep (/some/path/to/runStep.ts:XX:XX:37)
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:8)
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:20)
Expand Down
6 changes: 2 additions & 4 deletions src/client/builderExtensions/anyware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ export interface Anyware<$Arguments extends Builder.Extension.Parameters<Builder
* TODO Anyware Docs.
*/
anyware: (
interceptor: AnywareLib.Interceptor.InferConstructor<
RequestPipeline['spec']
>,
interceptor: AnywareLib.Interceptor.InferFromPipeline<RequestPipeline>,
) => Builder.Definition.MaterializeWith<$Arguments['definition'], $Arguments['context']>
}

export const builderExtensionAnyware = Builder.Extension.create<BuilderExtensionAnyware>((builder, context) => {
const properties = {
anyware: (interceptor: AnywareLib.Interceptor.InferConstructor<RequestPipeline['spec']>) => {
anyware: (interceptor: AnywareLib.Interceptor.InferFromPipeline<RequestPipeline>) => {
return builder({
...context,
extensions: [
Expand Down
4 changes: 2 additions & 2 deletions src/client/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export const builderExtensionGql = Builder.Extension.create<BuilderExtensionGql>
url,
schema,
request: analyzedRequest,
} as RequestPipeline['spec']['input']
} as RequestPipeline['input']

const result = await Anyware.Pipeline.run(requestPipeline, {
const result = await Anyware.PipelineDef.run(requestPipeline, {
initialInput,
// retryingExtension: context.retry as any,
interceptors: context.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
Expand Down
5 changes: 3 additions & 2 deletions src/client/handleOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { GraphQLError } from 'graphql'
import type { Simplify } from 'type-fest'
import type { SimplifyDeepExcept } from '../documentBuilder/Simplify.js'
import type { RunTypeHookOnRequestResult } from '../extension/extension.js'
import type { Anyware } from '../lib/anyware/__.js'
import { Errors } from '../lib/errors/__.js'
import type { Grafaid } from '../lib/grafaid/__.js'
import type { SomeObjectData } from '../lib/grafaid/graphql.js'
Expand All @@ -13,7 +14,7 @@ import {
type GetOrNever,
type Values,
} from '../lib/prelude.js'
import type { RequestPipeline, requestPipeline } from '../requestPipeline/RequestPipeline.js'
import type { requestPipeline, RequestPipelineSpec } from '../requestPipeline/RequestPipeline.js'
import type { GlobalRegistry } from '../types/GlobalRegistry/GlobalRegistry.js'
import type { TransportHttp } from '../types/Transport.js'
import type { Context } from './context.js'
Expand Down Expand Up @@ -52,7 +53,7 @@ export type GraffleExecutionResultEnvelope<$Config extends Config = Config> =

export const handleOutput = (
state: Context,
result: RequestPipeline['output'],
result: Anyware.PipelineDef.Utilities.InferResult<RequestPipelineSpec>,
) => {
if (isContextConfigTraditionalGraphQLOutput(state.config)) {
if (result instanceof Error) throw result
Expand Down
2 changes: 1 addition & 1 deletion src/documentBuilder/requestMethods/requestMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const executeDocument = async (
request,
} as RequestPipeline['input']

const result = await Anyware.Pipeline.run(requestPipeline, {
const result = await Anyware.PipelineDef.run(requestPipeline, {
initialInput,
// retryingExtension: state.retry as any,
interceptors: state.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
Expand Down
4 changes: 2 additions & 2 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface Extension<
/**
* Anyware executed on every request.
*/
onRequest?: Anyware.Interceptor.InferConstructor<RequestPipeline['spec']>
onRequest?: Anyware.Interceptor.InferFromPipeline<RequestPipeline>
/**
* Manipulate the builder.
* You can extend the builder with new properties at both runtime AND buildtime (types, TypeScript).
Expand Down Expand Up @@ -168,7 +168,7 @@ export const createExtension = <
custom?: $Custom
create: (params: { config: $Config }) => {
builder?: $BuilderExtension
onRequest?: Anyware.Interceptor.InferConstructor<RequestPipeline['spec']>
onRequest?: Anyware.Interceptor.InferFromPipeline<RequestPipeline>
typeHooks?: () => $TypeHooks
}
},
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/Throws/Throws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface BuilderExtension_<$Args extends Builder.Extension.Parameters<BuilderEx
*/
throws: () => Builder.Definition.MaterializeWith<
$Args['definition'],
ConfigManager.UpdateAtKey<
ConfigManager.SetKey<
$Args['context'],
'config',
// @ts-expect-error fixme
Expand All @@ -49,7 +49,7 @@ interface BuilderExtension_<$Args extends Builder.Extension.Parameters<BuilderEx
>
}

type ThrowsifyConfig<$BuilderConfig extends BuilderConfig> = ConfigManager.SetAtKeyPath<
type ThrowsifyConfig<$BuilderConfig extends BuilderConfig> = ConfigManager.SetKeyAtPath<
$BuilderConfig,
['output', 'errors'],
{ other: 'throw'; execution: 'throw' }
Expand Down
6 changes: 0 additions & 6 deletions src/lib/anyware/ExecutableStep.ts

This file was deleted.

47 changes: 28 additions & 19 deletions src/lib/anyware/Extension/Builder.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import type { ConfigManager } from '../../config-manager/__.js'
import type { Overload } from '../Overload/__.js'
import type { Pipeline } from '../Pipeline/__.js'
import { Overload } from '../Overload/__.js'
import type { PipelineDef } from '../PipelineDef/__.js'
import type { Extension } from './__.js'

type Create = <$Pipeline extends PipelineDef>() => Builder<$Pipeline, Extension.States.Empty>

export interface Builder<
$PipelineContext extends Pipeline.Context = Pipeline.Context,
$Context extends Context = Context,
$Pipeline extends PipelineDef = PipelineDef,
$Extension extends Extension = Extension,
> {
context: $Context
type: $Extension
/**
* TODO
*/
overload: <$OverloadBuilder extends Overload.Builder<$PipelineContext>>(
overloadBuilder: Overload.BuilderCallback<$PipelineContext, $OverloadBuilder>,
overload: <$OverloadBuilder extends Overload.Builder<$Pipeline>>(
overloadBuilderCallback: Overload.BuilderCallback<$Pipeline, $OverloadBuilder>,
) => Builder<
$PipelineContext,
ConfigManager.AppendAtKey<
$Context,
'overloads',
$OverloadBuilder['context']
>
$Pipeline,
Extension.Updaters.AddOverload<$Extension, $OverloadBuilder['type']>
>
}

export interface Context {
overloads: Overload.BuilderContext[]
}
export namespace Builder {
export const create: Create = () => {
const extension: Extension = {
overloads: [],
}

const builder: Builder = {
type: extension,
overload: (builderCallback) => {
const overload = builderCallback({ create: Overload.create })
extension.overloads.push(overload.type)
return builder as any
},
}

export interface ContextEmpty extends Context {
overloads: []
return builder as any
}
}
25 changes: 0 additions & 25 deletions src/lib/anyware/Extension/Extension.ts
Original file line number Diff line number Diff line change
@@ -1,25 +0,0 @@
import { __ } from '../../prelude.js'
import { Overload } from '../Overload/__.js'
import type { Pipeline } from '../Pipeline/__.js'
import type { Builder, Context, ContextEmpty } from './Builder.js'

export * from './Builder.js'

export const create: Create = () => {
const context: Context = {
overloads: [],
}

const builder: Builder = {
context,
overload: (builderCallback) => {
const overload = builderCallback({ create: Overload.create })
context.overloads.push(overload.context)
return builder as any
},
}

return builder as any
}

type Create = <$PipelineContext extends Pipeline.Context>() => Builder<$PipelineContext, ContextEmpty>
7 changes: 7 additions & 0 deletions src/lib/anyware/Extension/States.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Extension } from './__.js'

export namespace States {
export interface Empty extends Extension {
overloads: []
}
}
14 changes: 14 additions & 0 deletions src/lib/anyware/Extension/Updaters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ConfigManager } from '../../config-manager/__.js'
import type { Overload } from '../Overload/__.js'
import type { Extension } from './__.js'

export namespace Updaters {
export type AddOverload<
$Extension extends Extension,
$Overload extends Overload,
> = ConfigManager.UpdateKeyWithAppend<
$Extension,
'overloads',
$Overload
>
}
3 changes: 3 additions & 0 deletions src/lib/anyware/Extension/_.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Builder.js'
export * from './States.js'
export * from './Updaters.js'
8 changes: 7 additions & 1 deletion src/lib/anyware/Extension/__.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export * as Extension from './Extension.js'
import type { Overload } from '../Overload/__.js'

export * as Extension from './_.js'

export interface Extension {
overloads: Overload[]
}
36 changes: 19 additions & 17 deletions src/lib/anyware/Interceptor/Interceptor.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { describe, expectTypeOf, test } from 'vitest'
import { _, type ExcludeUndefined } from '../../prelude.js'
import type { Interceptor } from '../_.js'
import { Pipeline } from '../_.js'
import { Pipeline, PipelineDef } from '../_.js'
import type { initialInput } from '../__.test-helpers.js'
import { results, slots } from '../__.test-helpers.js'
import type { StepTriggerEnvelope } from '../StepTriggerEnvelope.js'

const b0 = Pipeline.create<initialInput>()
const b0 = PipelineDef.create().input<initialInput>()

describe(`interceptor constructor`, () => {
test(`receives keyword arguments, a step trigger for each step`, () => {
const p1 = b0
const b1 = b0
.step({ name: `a`, run: () => results.a })
.step({ name: `b`, run: () => results.b })
.step({ name: `c`, run: () => results.c })
.done()
type i1 = Interceptor.InferConstructor<typeof p1['spec']>
const p1 = Pipeline.create(b1.type)
type i1 = Interceptor.InferFromPipeline<typeof p1>
expectTypeOf<Parameters<i1>>().toMatchTypeOf<[steps: { a: any; b: any; c: any }]>()
// const x: Parameters<i1>['0']['a'] = _
// x({input:{x:1}}).then(r => r.b({input:{}}))
expectTypeOf<Parameters<i1>>().toMatchTypeOf<[steps: {
a: (params: { input?: initialInput }) => Promise<{ b: (params: { input?: results['a'] }) => any }>
b: (params: { input?: results['a'] }) => Promise<{ c: (params: { input?: results['b'] }) => any }>
Expand All @@ -28,22 +30,22 @@ describe(`interceptor constructor`, () => {

test(`original input on self`, () => {
const p = b0.step({ name: `a`, run: () => results.a }).done()
type triggerA = GetTriggerFromPipeline<typeof p, 'a'>
type triggerA = PipelineGetTrigger<typeof p, 'a'>
expectTypeOf<triggerA['input']>().toMatchTypeOf<initialInput>()
})

test(`trigger arguments are optional`, () => {
const p = b0.step({ name: `a`, run: () => results.a }).done()
type triggerA = GetTriggerFromPipeline<typeof p, 'a'>
type triggerA = PipelineGetTrigger<typeof p, 'a'>
expectTypeOf<[]>().toMatchTypeOf<Parameters<triggerA>>()
})

// --- slots ---

test(`trigger accepts slots if definition has them, otherwise does NOT so much as accept the slots key`, () => {
const p = b0.step({ name: `a`, slots, run: () => results.a }).step({ name: `b`, run: () => results.b }).done()
type triggerA = GetTriggerFromPipeline<typeof p, 'a'>
type triggerB = GetTriggerFromPipeline<typeof p, 'b'>
type triggerA = PipelineGetTrigger<typeof p, 'a'>
type triggerB = PipelineGetTrigger<typeof p, 'b'>
expectTypeOf<Parameters<triggerA>>().toEqualTypeOf<[params?: {
input?: initialInput
using?: {
Expand All @@ -56,14 +58,14 @@ describe(`interceptor constructor`, () => {

test(`slots are optional`, () => {
const p = b0.step({ name: `a`, slots, run: () => results.a }).done()
type triggerA = GetTriggerFromPipeline<typeof p, 'a'>
type triggerA = PipelineGetTrigger<typeof p, 'a'>
type triggerASlotInputs = ExcludeUndefined<ExcludeUndefined<Parameters<triggerA>[0]>['using']>
expectTypeOf<{ m?: any; n?: any }>().toMatchTypeOf<triggerASlotInputs>()
})

test(`slot function can return undefined (falls back to default slot)`, () => {
const p = b0.step({ name: `a`, slots, run: () => results.a }).done()
type triggerA = GetTriggerFromPipeline<typeof p, 'a'>
type triggerA = PipelineGetTrigger<typeof p, 'a'>
type triggerASlotMOutput = ReturnType<
ExcludeUndefined<ExcludeUndefined<ExcludeUndefined<Parameters<triggerA>[0]>['using']>['m']>
>
Expand All @@ -78,23 +80,23 @@ describe(`interceptor constructor`, () => {
//
test(`can return pipeline output or a step envelope`, () => {
const p = b0.step({ name: `a`, run: () => results.a }).done()
type i = GetReturnTypeFromPipeline<typeof p>
type i = PipelineGetReturnType<typeof p>
expectTypeOf<i>().toEqualTypeOf<Promise<results['a'] | StepTriggerEnvelope>>()
})

test(`return type awaits pipeline output`, () => {
const p = b0.step({ name: `a`, run: () => Promise.resolve(results.a) }).done()
expectTypeOf<GetReturnTypeFromPipeline<typeof p>>().toEqualTypeOf<Promise<results['a'] | StepTriggerEnvelope>>()
expectTypeOf<PipelineGetReturnType<typeof p>>().toEqualTypeOf<Promise<results['a'] | StepTriggerEnvelope>>()
})
})

// --- Helpers ---

// dprint-ignore
type GetTriggerFromPipeline<$Pipeline extends Pipeline.ExecutablePipeline, $TriggerName extends string> =
type PipelineGetTrigger<$Pipeline extends Pipeline, $TriggerName extends string> =
// @ts-expect-error
Parameters<Interceptor.InferConstructor<$Pipeline['spec']>>[0][$TriggerName]
Parameters<Interceptor.InferFromPipeline<$Pipeline>>[0][$TriggerName]

// dprint-ignore
type GetReturnTypeFromPipeline<$Pipeline extends Pipeline.ExecutablePipeline> =
ReturnType<Interceptor.InferConstructor<$Pipeline['spec']>>
type PipelineGetReturnType<$Pipeline extends Pipeline> =
ReturnType<Interceptor.InferFromPipeline<$Pipeline>>
Loading

0 comments on commit a78ebbe

Please sign in to comment.