Skip to content

Commit

Permalink
refactor: anyware cleanup (#1253)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Nov 4, 2024
1 parent 8bfe287 commit 24c38cf
Show file tree
Hide file tree
Showing 47 changed files with 566 additions and 518 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---------------------------------------- SHOW ----------------------------------------
{
errors: [
ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode".
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/main.ts:XX:XX:22)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
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) {
context: {
hookName: 'encode',
source: 'extension',
extensionName: 'anonymous'
interceptorName: 'anonymous'
},
cause: Error: Something went wrong.
at <anonymous> (/some/path/to/output_envelope_envelope-error__envelope-error.ts:XX:XX:11)
at applyBody (/some/path/to/main.ts:XX:XX:28)
at applyBody (/some/path/to/runner.ts:XX:XX:28)
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
^


ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode".
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/main.ts:XX:XX:22)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
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) {
context: {
hookName: 'encode',
source: 'extension',
extensionName: 'anonymous'
interceptorName: 'anonymous'
},
cause: Error: Something went wrong.
at <anonymous> (/some/path/to/output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX:11)
at applyBody (/some/path/to/main.ts:XX:XX:28)
at applyBody (/some/path/to/runner.ts:XX:XX:28)
}

Node.js vXX.XX.XX
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ContextualError: There was an error in the core implementation of hook "exchange
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/main.ts:XX:XX:22)
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) {
code: 'ERR_INVALID_URL',
Expand Down
8 changes: 4 additions & 4 deletions examples/__outputs__/20_output/output_return-error.output.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---------------------------------------- SHOW ----------------------------------------
ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode".
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/main.ts:XX:XX:22)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
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) {
context: {
hookName: 'encode',
source: 'extension',
extensionName: 'anonymous'
interceptorName: 'anonymous'
},
cause: Error: Something went wrong.
at <anonymous> (/some/path/to/output_return-error.ts:XX:XX:11)
at applyBody (/some/path/to/main.ts:XX:XX:28)
at applyBody (/some/path/to/runner.ts:XX:XX:28)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ ContextualAggregateError: One or more errors in the execution result.
]
}
---------------------------------------- SHOW ----------------------------------------
ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode".
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/main.ts:XX:XX:22)
at async Object.run (/some/path/to/runner.ts:XX:XX:20)
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) {
context: {
hookName: 'encode',
source: 'extension',
extensionName: 'anonymous'
interceptorName: 'anonymous'
},
cause: Error: Something went wrong.
at <anonymous> (/some/path/to/output_return-error_return-error-execution__return-error-execution.ts:XX:XX:13)
at applyBody (/some/path/to/main.ts:XX:XX:28)
at applyBody (/some/path/to/runner.ts:XX:XX:28)
at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX)
}
6 changes: 3 additions & 3 deletions src/client/builderExtensions/anyware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ export interface Anyware<$Arguments extends Builder.Extension.Parameters<Builder
* TODO Anyware Docs.
*/
anyware: (
anyware: AnywareLib.Extension2<RequestPipeline.RequestPipeline<$Arguments['context']['config']>>,
interceptor: AnywareLib.Interceptor<RequestPipeline.RequestPipeline<$Arguments['context']['config']>>,
) => Builder.Definition.MaterializeWithNewContext<$Arguments['chain'], $Arguments['context']>
}

export const builderExtensionAnyware = Builder.Extension.create<BuilderExtensionAnyware>((builder, context) => {
const properties = {
anyware: (anyware: Anyware.Extension2<RequestPipeline.RequestPipeline>) => {
anyware: (interceptor: Anyware.Interceptor<RequestPipeline.RequestPipeline>) => {
return builder({
...context,
extensions: [
...context.extensions,
createExtension({
name: `InlineAnyware`,
create: () => ({ onRequest: anyware }),
create: () => ({ onRequest: interceptor }),
})(),
],
})
Expand Down
2 changes: 1 addition & 1 deletion src/client/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const builderExtensionGql = Builder.Extension.create<BuilderExtensionGql>
const result = await RequestPipeline.RequestPipeline.run({
initialInput,
// retryingExtension: context.retry as any,
extensions: context.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
interceptors: context.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
})

return handleOutput(context, 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 @@ -140,7 +140,7 @@ const executeDocument = async (
const result = await RequestPipeline.RequestPipeline.run({
initialInput,
// retryingExtension: state.retry as any,
extensions: state.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
interceptors: state.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any,
})

return handleOutput(state, result)
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.Extension2<RequestPipeline.RequestPipeline>
onRequest?: Anyware.Interceptor<RequestPipeline.RequestPipeline>
/**
* Manipulate the builder.
* You can extend the builder with new properties at both runtime AND buildtime (types, TypeScript).
Expand Down Expand Up @@ -169,7 +169,7 @@ export const createExtension = <
custom?: $Custom
create: (params: { config: $Config }) => {
builder?: $BuilderExtension
onRequest?: Anyware.Extension2<RequestPipeline.RequestPipeline>
onRequest?: Anyware.Interceptor<RequestPipeline.RequestPipeline>
typeHooks?: () => $TypeHooks
}
},
Expand Down
62 changes: 62 additions & 0 deletions src/lib/anyware/Interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Deferred, MaybePromise } from '../prelude.js'
import type { Private } from '../private.js'
import type { InferPublicHooks, SomePublicHookEnvelope } from './hook/public.js'
import type { Pipeline } from './Pipeline.js'

export type InterceptorOptions = {
retrying: boolean
}

export type Interceptor<
$Core extends Pipeline = Pipeline,
$Options extends InterceptorOptions = InterceptorOptions,
> = (
hooks: InferPublicHooks<
Private.Get<$Core>['hookSequence'],
Private.Get<$Core>['hookMap'],
Private.Get<$Core>['result'],
$Options
>,
) => Promise<
| Private.Get<$Core>['result']
| SomePublicHookEnvelope
>

export type InterceptorGeneric = NonRetryingInterceptor | RetryingInterceptor

export type NonRetryingInterceptor = {
retrying: false
name: string
entrypoint: string
body: Deferred<unknown>
currentChunk: Deferred<SomePublicHookEnvelope /* | unknown (result) */>
}

export type RetryingInterceptor = {
retrying: true
name: string
entrypoint: string
body: Deferred<unknown>
currentChunk: Deferred<SomePublicHookEnvelope | Error /* | unknown (result) */>
}

export const createRetryingInterceptor = (extension: NonRetryingInterceptorInput): RetryingInterceptorInput => {
return {
retrying: true,
run: extension,
}
}

// export type ExtensionInput<$Input extends object = object> = (input: $Input) => MaybePromise<unknown>
export type InterceptorInput<$Input extends object = any> =
| NonRetryingInterceptorInput<$Input>
| RetryingInterceptorInput<$Input>

export type NonRetryingInterceptorInput<$Input extends object = any> = (
input: $Input,
) => MaybePromise<unknown>

export type RetryingInterceptorInput<$Input extends object = any> = {
retrying: boolean
run: (input: $Input) => MaybePromise<unknown>
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe(`invalid destructuring cases`, () => {
"issue": "noParameters",
},
"errors": [
[ContextualError: Extension must destructure the first parameter passed to it and select exactly one entrypoint.],
[ContextualError: Interceptor must destructure the first parameter passed to it and select exactly one entrypoint.],
],
"result": [ContextualAggregateError: One or more extensions are invalid.],
}
Expand All @@ -36,7 +36,7 @@ describe(`invalid destructuring cases`, () => {
"issue": "destructuredWithoutEntryHook",
},
"errors": [
[ContextualError: Extension must destructure the first parameter passed to it and select exactly one entrypoint.],
[ContextualError: Interceptor must destructure the first parameter passed to it and select exactly one entrypoint.],
],
"result": [ContextualAggregateError: One or more extensions are invalid.],
}
Expand All @@ -57,7 +57,7 @@ describe(`invalid destructuring cases`, () => {
"issue": "multipleParameters",
},
"errors": [
[ContextualError: Extension must destructure the first parameter passed to it and select exactly one entrypoint.],
[ContextualError: Interceptor must destructure the first parameter passed to it and select exactly one entrypoint.],
],
"result": [ContextualAggregateError: One or more extensions are invalid.],
}
Expand All @@ -76,7 +76,7 @@ describe(`invalid destructuring cases`, () => {
"issue": "notDestructured",
},
"errors": [
[ContextualError: Extension must destructure the first parameter passed to it and select exactly one entrypoint.],
[ContextualError: Interceptor must destructure the first parameter passed to it and select exactly one entrypoint.],
],
"result": [ContextualAggregateError: One or more extensions are invalid.],
}
Expand All @@ -94,7 +94,7 @@ describe(`invalid destructuring cases`, () => {
"issue": "multipleDestructuredHookNames",
},
"errors": [
[ContextualError: Extension must destructure the first parameter passed to it and select exactly one entrypoint.],
[ContextualError: Interceptor must destructure the first parameter passed to it and select exactly one entrypoint.],
],
"result": [ContextualAggregateError: One or more extensions are invalid.],
}
Expand Down
32 changes: 16 additions & 16 deletions src/lib/anyware/main.test.ts → src/lib/anyware/Pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Errors } from '../errors/__.js'
import type { ContextualError } from '../errors/ContextualError.js'
import { Anyware } from './__.js'
import { core, createHook, initialInput, oops, run, runWithOptions } from './__.test-helpers.js'
import { createRetryingExtension } from './main.js'
import { createRetryingInterceptor } from './Interceptor.js'

describe(`no extensions`, () => {
test(`passthrough to implementation`, async () => {
Expand Down Expand Up @@ -178,11 +178,11 @@ describe(`errors`, () => {
{
"cause": [Error: oops],
"context": {
"extensionName": "anonymous",
"hookName": "a",
"interceptorName": "anonymous",
"source": "extension",
},
"result": [ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "a".],
"result": [ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "a".],
}
`)
})
Expand All @@ -198,11 +198,11 @@ describe(`errors`, () => {
{
"cause": [Error: oops],
"context": {
"extensionName": "anonymous",
"hookName": "a",
"interceptorName": "anonymous",
"source": "extension",
},
"result": [ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "a".],
"result": [ContextualError: There was an error in the interceptor "anonymous" (use named functions to improve this error message) while running hook "a".],
}
`)
})
Expand Down Expand Up @@ -261,9 +261,9 @@ describe(`errors`, () => {
passthroughErrorInstanceOf: [SpecialError1],
})
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new Error('oops') }, extensions: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
expect(anyware.run({ initialInput: { throws: new Error('oops') }, interceptors: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new SpecialError1('oops') }, extensions: [] })).resolves.toBeInstanceOf(SpecialError1)
expect(anyware.run({ initialInput: { throws: new SpecialError1('oops') }, interceptors: [] })).resolves.toBeInstanceOf(SpecialError1)
})
test('via passthroughErrorInstanceOf (multiple)', async () => {
const anyware = Anyware.create<['a'], Anyware.HookDefinitionMap<['a']>>({
Expand All @@ -272,9 +272,9 @@ describe(`errors`, () => {
passthroughErrorInstanceOf: [SpecialError1, SpecialError2],
})
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new Error('oops') }, extensions: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
expect(anyware.run({ initialInput: { throws: new Error('oops') }, interceptors: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new SpecialError2('oops') }, extensions: [] })).resolves.toBeInstanceOf(SpecialError2)
expect(anyware.run({ initialInput: { throws: new SpecialError2('oops') }, interceptors: [] })).resolves.toBeInstanceOf(SpecialError2)
})
test('via passthroughWith', async () => {
const anyware = Anyware.create<['a'], Anyware.HookDefinitionMap<['a']>>({
Expand All @@ -287,17 +287,17 @@ describe(`errors`, () => {
},
})
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new Error('oops') }, extensions: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
expect(anyware.run({ initialInput: { throws: new Error('oops') }, interceptors: [] })).resolves.toBeInstanceOf(Errors.ContextualError)
// dprint-ignore
expect(anyware.run({ initialInput: { throws: new SpecialError1('oops') }, extensions: [] })).resolves.toBeInstanceOf(SpecialError1)
expect(anyware.run({ initialInput: { throws: new SpecialError1('oops') }, interceptors: [] })).resolves.toBeInstanceOf(SpecialError1)
})
})
})

describe('retrying extension', () => {
test('if hook fails, extension can retry, then short-circuit', async () => {
core.hooks.a.run.mockReset().mockRejectedValueOnce(oops).mockResolvedValueOnce(1)
const result = await run(createRetryingExtension(async function foo({ a }) {
const result = await run(createRetryingInterceptor(async function foo({ a }) {
const result1 = await a()
expect(result1).toEqual(oops)
const result2 = await a()
Expand All @@ -311,7 +311,7 @@ describe('retrying extension', () => {
describe('errors', () => {
test('not last extension', async () => {
const result = await run(
createRetryingExtension(async function foo({ a }) {
createRetryingInterceptor(async function foo({ a }) {
return a()
}),
async function bar({ a }) {
Expand All @@ -332,7 +332,7 @@ describe('retrying extension', () => {
test('call hook twice even though it succeeded the first time', async () => {
let neverRan = true
const result = await run(
createRetryingExtension(async function foo({ a }) {
createRetryingInterceptor(async function foo({ a }) {
const result1 = await a()
expect('b' in result1).toBe(true)
await a() // <-- Extension bug here under test.
Expand All @@ -341,13 +341,13 @@ describe('retrying extension', () => {
)
expect(neverRan).toBe(true)
expect(result).toMatchInlineSnapshot(
`[ContextualError: There was an error in the extension "foo".]`,
`[ContextualError: There was an error in the interceptor "foo".]`,
)
expect((result as Errors.ContextualError).context).toMatchInlineSnapshot(
`
{
"extensionName": "foo",
"hookName": "a",
"interceptorName": "foo",
"source": "extension",
}
`,
Expand Down
Loading

0 comments on commit 24c38cf

Please sign in to comment.