Skip to content

Commit

Permalink
improve: anyware builder (#1254)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Nov 11, 2024
1 parent 4f17a2e commit c92a67b
Show file tree
Hide file tree
Showing 61 changed files with 1,811 additions and 1,262 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
errors: [
ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode".
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:18)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
at async <anonymous> (/some/path/to/runner.ts:XX:XX:20)
at async Module.run (/some/path/to/run.ts:XX:XX:10)
at async executeDocument (/some/path/to/requestMethods.ts:XX:XX:18)
at async executeRootField (/some/path/to/requestMethods.ts:XX:XX:18)
at async <anonymous> (/some/path/to/output_envelope_envelope-error__envelope-error.ts:XX:XX:16) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode".
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:18)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
at async <anonymous> (/some/path/to/runner.ts:XX:XX:20)
at async Module.run (/some/path/to/run.ts:XX:XX:10)
at async executeDocument (/some/path/to/requestMethods.ts:XX:XX:18)
at async executeRootField (/some/path/to/requestMethods.ts:XX:XX:18)
at async <anonymous> (/some/path/to/output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX:1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ ContextualError: There was an error in the core implementation of hook "exchange
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:18)
at async runPipeline (/some/path/to/runPipeline.ts:XX:XX:14)
at async runPipeline (/some/path/to/runPipeline.ts:XX:XX:14)
... 2 lines matching cause stack trace ...
... 3 lines matching cause stack trace ...
at async <anonymous> (/some/path/to/output_preset__standard-graphql.ts:XX:XX:16) {
context: { hookName: 'exchange', source: 'implementation' },
[cause]: TypeError: Failed to parse URL from ...
at new Request (node:internal/deps/undici/undici:XX:XX)
at Object.run (/some/path/to/RequestPipeline.ts:XX:XX:29)
... 6 lines matching cause stack trace ...
at async Object.send (/some/path/to/gql.ts:XX:XX:26)
at async <anonymous> (/some/path/to/output_preset__standard-graphql.ts:XX:XX:16) {
[cause]: TypeError: Invalid URL
at new URL (node:internal/url:XX:XX)
at new Request (node:internal/deps/undici/undici:XX:XX)
at Object.run (/some/path/to/RequestPipeline.ts:XX:XX:29)
at runHook (/some/path/to/runHook.ts:XX:XX:37)
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)
at async runPipeline (/some/path/to/runPipeline.ts:XX:XX:14)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
at async Object.send (/some/path/to/gql.ts:XX:XX:26)
at async <anonymous> (/some/path/to/output_preset__standard-graphql.ts:XX:XX:16) {
at async <anonymous> (/some/path/to/runner.ts:XX:XX:20)
at async Module.run (/some/path/to/run.ts:XX:XX:10)
at async Object.send (/some/path/to/gql.ts:XX:XX:26) {
code: 'ERR_INVALID_URL',
input: '...'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---------------------------------------- SHOW ----------------------------------------
ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode".
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:18)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
at async <anonymous> (/some/path/to/runner.ts:XX:XX:20)
at async Module.run (/some/path/to/run.ts:XX:XX:10)
at async executeDocument (/some/path/to/requestMethods.ts:XX:XX:18)
at async executeRootField (/some/path/to/requestMethods.ts:XX:XX:18)
at async <anonymous> (/some/path/to/output_return-error.ts:XX:XX:18) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ContextualAggregateError: One or more errors in the execution result.
]
at <anonymous> (/some/path/to/handleOutput.ts:XX:XX:16)
at Array.map (<anonymous>)
at handleOutput (/some/path/to/handleOutput.ts:XX:XX:21)
at handleOutput (/some/path/to/handleOutput.ts:XX:XX:27)
at executeDocument (/some/path/to/requestMethods.ts:XX:XX:10)
at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX)
at async executeRootField (/some/path/to/requestMethods.ts:XX:XX:18)
Expand All @@ -37,7 +37,8 @@ ContextualAggregateError: One or more errors in the execution result.
ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "encode".
at runPipeline (/some/path/to/runPipeline.ts:XX:XX:18)
at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
at async <anonymous> (/some/path/to/runner.ts:XX:XX:20)
at async Module.run (/some/path/to/run.ts:XX:XX:10)
at async executeDocument (/some/path/to/requestMethods.ts:XX:XX:18)
at async executeRootField (/some/path/to/requestMethods.ts:XX:XX:18)
at async <anonymous> (/some/path/to/output_return-error_return-error-execution__return-error-execution.ts:XX:XX:3) {
Expand Down
4 changes: 2 additions & 2 deletions src/ClientPreset/ClientPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
} from '../extension/extension.js'
import type { Builder } from '../lib/builder/__.js'
import type { ConfigManager } from '../lib/config-manager/__.js'
import { type mergeArrayOfObjects, type ToParametersExact } from '../lib/prelude.js'
import { type ToParametersExact, type Tuple } from '../lib/prelude.js'
import type { GlobalRegistry } from '../types/GlobalRegistry/GlobalRegistry.js'
import { Schema } from '../types/Schema/__.js'
import type { SchemaDrivenDataMap } from '../types/SchemaDrivenDataMap/__.js'
Expand Down Expand Up @@ -101,7 +101,7 @@ type ConstructorParameters<
$Extensions extends [...ExtensionConstructor[]],
> =
& InputBase<GlobalRegistry.GetOrGeneric<$Name>>
& mergeArrayOfObjects<GetParametersContributedByExtensions<$Extensions>>
& Tuple.IntersectItems<GetParametersContributedByExtensions<$Extensions>>

// dprint-ignore
type GetParametersContributedByExtensions<Extensions extends [...ExtensionConstructor[]]> = {
Expand Down
12 changes: 7 additions & 5 deletions src/client/Settings/client.create.config.output.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Graffle } from '../../../tests/_/schemas/kitchen-sink/graffle/__.js'
import { schema } from '../../../tests/_/schemas/kitchen-sink/schema.js'
import { assertEqual } from '../../lib/assert-equal.js'
import { type GraphQLExecutionResultError } from '../../lib/grafaid/graphql.js'
import type { ErrorsOther } from '../handleOutput.js'
import type { requestPipeline } from '../../requestPipeline/RequestPipeline.js'

const G = Graffle.create

Expand Down Expand Up @@ -99,15 +99,15 @@ describe('.envelope', () => {
describe('.errors', () => {
test('defaults to execution errors in envelope', () => {
const g = G({ schema, output: { defaults: { errorChannel: 'return' }, envelope: true } })
expectTypeOf(g.query.__typename()).resolves.toMatchTypeOf<ExecutionResult<{ __typename: 'Query' }> | ErrorsOther>()
expectTypeOf(g.query.__typename()).resolves.toMatchTypeOf<ExecutionResult<{ __typename: 'Query' }> | requestPipeline.ResultFailure>()
})
test('.execution:false restores errors to return', async () => {
const g = G({
schema,
output: { defaults: { errorChannel: 'return' }, envelope: { errors: { execution: false } } },
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<
Omit<ExecutionResult<{ __typename: 'Query' }>, 'errors'> | ErrorsOther | GraphQLExecutionResultError
Omit<ExecutionResult<{ __typename: 'Query' }>, 'errors'> | requestPipeline.ResultFailure | GraphQLExecutionResultError
>()
})
test('.other:true raises them to envelope', () => {
Expand Down Expand Up @@ -135,7 +135,9 @@ describe('defaults.errorChannel: "return"', () => {
describe('puts errors into return type', () => {
const g = G({ schema, output: { defaults: { errorChannel: 'return' } } })
test('query.<fieldMethod>', async () => {
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | ErrorsOther | GraphQLExecutionResultError>()
expectTypeOf(await g.query.__typename()).toEqualTypeOf<
'Query' | requestPipeline.ResultFailure | GraphQLExecutionResultError
>()
})
})
describe('with .errors', () => {
Expand All @@ -144,7 +146,7 @@ describe('defaults.errorChannel: "return"', () => {
schema,
output: { defaults: { errorChannel: 'return' }, errors: { execution: 'throw' } },
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | ErrorsOther>()
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | requestPipeline.ResultFailure>()
})
test('.other: throw', async () => {
const g = G({
Expand Down
10 changes: 6 additions & 4 deletions src/client/builderExtensions/anyware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createExtension } from '../../extension/extension.js'
import type { Anyware, Anyware as AnywareLib } from '../../lib/anyware/__.js'
import type { Anyware as AnywareLib } from '../../lib/anyware/__.js'
import { Builder } from '../../lib/builder/__.js'
import type { RequestPipeline } from '../../requestPipeline/__.js'
import type { requestPipeline } from '../../requestPipeline/__.js'
import { type Context } from '../context.js'

export interface BuilderExtensionAnyware extends Builder.Extension {
Expand All @@ -15,13 +15,15 @@ export interface Anyware<$Arguments extends Builder.Extension.Parameters<Builder
* TODO Anyware Docs.
*/
anyware: (
interceptor: AnywareLib.Interceptor<RequestPipeline.RequestPipeline<$Arguments['context']['config']>>,
interceptor: AnywareLib.Interceptor.InferConstructor<
requestPipeline.Spec<$Arguments['context']['config']>
>,
) => Builder.Definition.MaterializeWithNewContext<$Arguments['chain'], $Arguments['context']>
}

export const builderExtensionAnyware = Builder.Extension.create<BuilderExtensionAnyware>((builder, context) => {
const properties = {
anyware: (interceptor: Anyware.Interceptor<RequestPipeline.RequestPipeline>) => {
anyware: (interceptor: AnywareLib.Interceptor.InferConstructor<requestPipeline.Spec>) => {
return builder({
...context,
extensions: [
Expand Down
8 changes: 4 additions & 4 deletions src/client/builderExtensions/scalar.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DateScalar, FooScalar } from '../../../tests/_/fixtures/scalars.js'
import { schemaMap } from '../../../tests/_/schemas/kitchen-sink/graffle/__.js'
import { Graffle } from '../../entrypoints/__Graffle.js'
import { assertEqual } from '../../lib/assert-equal.js'
import { any, type SomeFunction } from '../../lib/prelude.js'
import { _, type SomeFunction } from '../../lib/prelude.js'
import type { TypeErrorMissingSchemaMap } from './scalar.js'

const g1 = Graffle.create({ schema: `foo` })
Expand All @@ -12,9 +12,9 @@ const g2 = Graffle.create({ schema: `foo`, schemaMap })
assertEqual<typeof g2.scalar, SomeFunction>()

// @ts-expect-error "Foo" is not a scalar name in the schema.
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Foo`, any)
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Foo`, _)
// @ts-expect-error "Foo" is not a scalar name in the schema.
Graffle.create({ schema: `foo`, schemaMap }).scalar(FooScalar)
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Date`, any)
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Date`, _)
Graffle.create({ schema: `foo`, schemaMap }).scalar(DateScalar)
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Int`, any)
Graffle.create({ schema: `foo`, schemaMap }).scalar(`Int`, _)
10 changes: 6 additions & 4 deletions src/client/client.transport-http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import { Graffle as Pokemon } from '../../tests/_/schemas/pokemon/graffle/__.js'
import { schema as schemaPokemon } from '../../tests/_/schemas/pokemon/schema.js'
import { Graffle } from '../entrypoints/main.js'
import { ACCEPT_REC, CONTENT_TYPE_REC } from '../lib/grafaid/http/http.js'
import type { CoreExchangeGetRequest, CoreExchangePostRequest } from '../requestPipeline/types.js'
import { Transport } from '../types/Transport.js'
import type { requestPipeline } from '../requestPipeline/__.js'
import { Transport, type TransportHttp } from '../types/Transport.js'

const schema = new URL(`https://foo.io/api/graphql`)

test(`anyware hooks are typed to http transport`, () => {
Graffle.create({ schema }).anyware(async ({ encode }) => {
expectTypeOf(encode.input.transportType).toEqualTypeOf(Transport.http)
expectTypeOf(encode.input.transportType).toEqualTypeOf<TransportHttp>()
const { pack } = await encode()
expectTypeOf(pack.input.transportType).toEqualTypeOf(Transport.http)
const { exchange } = await pack()
expectTypeOf(exchange.input.transportType).toEqualTypeOf(Transport.http)
// todo we can statically track the method mode like we do the transport mode
expectTypeOf(exchange.input.request).toEqualTypeOf<CoreExchangePostRequest | CoreExchangeGetRequest>()
expectTypeOf(exchange.input.request).toEqualTypeOf<
requestPipeline.Steps.CoreExchangePostRequest | requestPipeline.Steps.CoreExchangeGetRequest
>()
const { unpack } = await exchange()
expectTypeOf(unpack.input.transportType).toEqualTypeOf(Transport.http)
expectTypeOf(unpack.input.response).toEqualTypeOf<Response>()
Expand Down
8 changes: 4 additions & 4 deletions src/client/gql/gql.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Anyware } from '../../lib/anyware/__.js'
import { Builder } from '../../lib/builder/__.js'
import type { Grafaid } from '../../lib/grafaid/__.js'
import { getOperationType } from '../../lib/grafaid/document.js'
Expand All @@ -6,10 +7,9 @@ import {
joinTemplateStringArrayAndArgs,
type TemplateStringsArguments,
} from '../../lib/template-string.js'
import { RequestPipeline } from '../../requestPipeline/__.js' // todo
import { requestPipeline } from '../../requestPipeline/__.js' // todo
import { type Context } from '../context.js'
import { handleOutput } from '../handleOutput.js'
import type { Config } from '../Settings/Config.js'
import { type DocumentController, resolveSendArguments, type sendArgumentsImplementation } from './send.js'

export interface BuilderExtensionGql extends Builder.Extension {
Expand Down Expand Up @@ -71,9 +71,9 @@ export const builderExtensionGql = Builder.Extension.create<BuilderExtensionGql>
schema,
// request,
request: analyzedRequest,
} as RequestPipeline.Hooks.HookDefEncode<Config>['input']
} as requestPipeline.Steps.HookDefEncode['input']

const result = await RequestPipeline.RequestPipeline.run({
const result = await Anyware.Pipeline.run(requestPipeline, {
initialInput,
// retryingExtension: context.retry as any,
interceptors: context.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
Expand Down
71 changes: 10 additions & 61 deletions src/client/handleOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type GetOrNever,
type Values,
} from '../lib/prelude.js'
import type { requestPipeline } 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 All @@ -22,16 +23,7 @@ import {
readConfigErrorCategoryOutputChannel,
} from './Settings/Config.js'

/**
* Types of "other" Graffle Error.
*/
export type ErrorsOther =
| Errors.ContextualError
// Possible from http transport fetch with abort controller.
| DOMException

export type GraffleExecutionResultEnvelope<$Config extends Config = Config> =
// & ExecutionResult
& {
errors?: ReadonlyArray<
// formatted comes from http transport
Expand All @@ -56,17 +48,13 @@ export type GraffleExecutionResultEnvelope<$Config extends Config = Config> =
}
: {})

export type GraffleExecutionResultVar<$Config extends Config = Config> =
| GraffleExecutionResultEnvelope<$Config>
| ErrorsOther

export const handleOutput = (
state: Context,
result: GraffleExecutionResultVar,
result: requestPipeline.Result,
) => {
if (isContextConfigTraditionalGraphQLOutput(state.config)) {
if (result instanceof Error) throw result
return result
return result.value
}

const config = state.config
Expand All @@ -93,26 +81,26 @@ export const handleOutput = (
return isEnvelope ? { errors: [result] } : result
}

if (result.errors && result.errors.length > 0) {
if (result.value.errors && result.value.errors.length > 0) {
const error = new Errors.ContextualAggregateError(
`One or more errors in the execution result.`,
{},
result.errors.map(e => {
result.value.errors.map(e => {
if (e instanceof Error) return e
const { message, ...context } = e
return new Errors.ContextualError(message, context)
}),
)
if (isThrowExecution) throw error
if (isReturnExecution) return error
return isEnvelope ? { ...result, errors: [...result.errors ?? [], error] } : error
return isEnvelope ? { ...result.value, errors: [...result.value.errors ?? [], error] } : error
}

if (isEnvelope) {
return result
return result.value
}

return result.data
return result.value.data
}

/**
Expand Down Expand Up @@ -156,49 +144,10 @@ type HandleOutput_Envelope<$Context extends Context, $Envelope extends GraffleEx
? $Envelope
: ExcludeUndefined<$Envelope['data']> // todo make data field not undefinable

// type HandleOutputGql_Envelope

// | IfConfiguredGetOutputErrorReturns<$Config>
// | (
// $Config['output']['envelope']['enabled'] extends true
// // todo even when envelope is enabled, its possible errors can not be included in its output.
// // When not, undefined should be removed from the data property.
// ? Envelope<$Config, $Data>
// // Note 1
// // `undefined` is not a possible type because that would only happen if an error occurred.
// // If an error occurs when the envelope is disabled then either it throws or is returned.
// // No case swallows the error and returns undefined data.
// //
// // Note 2
// // null is possible because of GraphQL null propagation.
// // todo We need to integrate this reality into the the other typed non-envelope output types too.
// : $Data | null
// )

// // dprint-ignore
// export type HandleOutputGraffleRootType<$Config extends Config, $Data> =
// | IfConfiguredGetOutputErrorReturns<$Config>
// | (
// $Config['output']['envelope']['enabled'] extends true
// ? Envelope<$Config, $Data>
// : $Data
// )

// // dprint-ignore
// export type HandleOutputGraffleRootField<$Config extends Config, $RootFieldName extends string, $Data> =
// | IfConfiguredGetOutputErrorReturns<$Config>
// | (
// $Config['output']['envelope']['enabled'] extends true
// // todo: a typed execution result that allows for additional error types.
// // currently it is always graphql execution error however envelope configuration can put more errors into that.
// ? Envelope<$Config, { [_ in $RootFieldName]: $Data }>
// : $Data
// )

// dprint-ignore
type IfConfiguredGetOutputErrorReturns<$Context extends Context> =
| (ConfigGetOutputError<$Context, 'execution'> extends 'return' ? GraphQLExecutionResultError : never)
| (ConfigGetOutputError<$Context, 'other'> extends 'return' ? ErrorsOther : never)
| (ConfigGetOutputError<$Context, 'execution'> extends 'return' ? GraphQLExecutionResultError : never)
| (ConfigGetOutputError<$Context, 'other'> extends 'return' ? requestPipeline.ResultFailure : never)

// dprint-ignore
export type ConfigGetOutputError<$Context extends Context, $ErrorCategory extends ErrorCategory> =
Expand Down
Loading

0 comments on commit c92a67b

Please sign in to comment.