Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(lib/anyware): data type oriented builders #1274

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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