From ed696dbf720d270afdb87aca6144e570b9d1f3c2 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Mon, 28 Oct 2024 20:20:16 -0400 Subject: [PATCH] refactor: inline custom scalars logic (#1238) --- ...output_preset__standard-graphql.output.txt | 14 ++-- src/extensions/CustomScalars/CustomScalars.ts | 43 ----------- .../injectTypenameOnRootResultFields.test.ts | 2 +- src/layers/5_request/__.ts | 1 - src/layers/6_client/Settings/Config.ts | 2 +- src/layers/6_client/Settings/InputToConfig.ts | 2 +- .../inputIncrementable/inputIncrementable.ts | 2 +- .../6_client/client.transport-http.test.ts | 4 +- .../6_client/client.transport-memory.test.ts | 2 +- src/layers/6_client/client.ts | 3 +- src/layers/6_client/clientPrefilled.ts | 3 +- src/layers/6_client/extension/extension.ts | 4 +- src/layers/6_client/fluent.ts | 4 +- src/layers/6_client/gql/gql.ts | 6 +- src/layers/6_client/handleOutput.ts | 2 +- src/layers/6_client/properties/anyware.ts | 6 +- src/layers/6_client/properties/retry.ts | 6 +- .../6_client/requestMethods/requestMethods.ts | 34 ++++++++- .../CustomScalars/decode.test.ts | 0 .../CustomScalars/decode.ts | 0 .../CustomScalars/encode.test.ts | 0 .../CustomScalars/encode.ts | 0 .../Transport.ts | 0 .../5_request => requestPipeline}/_.ts | 0 src/requestPipeline/__.ts | 1 + .../5_request => requestPipeline}/core.ts | 76 +++++++++---------- .../5_request => requestPipeline}/hooks.ts | 12 +-- tests/_/SpyExtension.ts | 2 +- 28 files changed, 108 insertions(+), 123 deletions(-) delete mode 100644 src/extensions/CustomScalars/CustomScalars.ts delete mode 100644 src/layers/5_request/__.ts rename src/{extensions => requestPipeline}/CustomScalars/decode.test.ts (100%) rename src/{extensions => requestPipeline}/CustomScalars/decode.ts (100%) rename src/{extensions => requestPipeline}/CustomScalars/encode.test.ts (100%) rename src/{extensions => requestPipeline}/CustomScalars/encode.ts (100%) rename src/{layers/5_request => requestPipeline}/Transport.ts (100%) rename src/{layers/5_request => requestPipeline}/_.ts (100%) create mode 100644 src/requestPipeline/__.ts rename src/{layers/5_request => requestPipeline}/core.ts (74%) rename src/{layers/5_request => requestPipeline}/hooks.ts (89%) diff --git a/examples/__outputs__/20_output/output_preset__standard-graphql.output.txt b/examples/__outputs__/20_output/output_preset__standard-graphql.output.txt index d4feb07a3..5a3306ff9 100644 --- a/examples/__outputs__/20_output/output_preset__standard-graphql.output.txt +++ b/examples/__outputs__/20_output/output_preset__standard-graphql.output.txt @@ -7,21 +7,25 @@ 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) - at async Object.run (/some/path/to/main.ts:XX:XX:22) - at async Object.send (/some/path/to/gql.ts:XX:XX:26) + ... 2 lines matching cause stack trace ... at async (/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/core.ts:XX:XX:29) - at runHook (/some/path/to/runHook.ts:XX:XX:37) - at runHook (/some/path/to/runHook.ts:XX:XX:16) { + ... 6 lines matching cause stack trace ... + at async (/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/core.ts:XX:XX:29) at runHook (/some/path/to/runHook.ts:XX:XX:37) - at runHook (/some/path/to/runHook.ts:XX:XX:16) { + 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.send (/some/path/to/gql.ts:XX:XX:26) + at async (/some/path/to/output_preset__standard-graphql.ts:XX:XX:16) { code: 'ERR_INVALID_URL', input: '...' } diff --git a/src/extensions/CustomScalars/CustomScalars.ts b/src/extensions/CustomScalars/CustomScalars.ts deleted file mode 100644 index 1a84eb2a4..000000000 --- a/src/extensions/CustomScalars/CustomScalars.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { createExtension } from '../../layers/6_client/extension/extension.js' -import { normalizeRequestToNode } from '../../lib/grafaid/request.js' -import { decodeResultData } from './decode.js' -import { encodeRequestVariables } from './encode.js' - -export const CustomScalars = () => - createExtension({ - name: `CustomScalars`, - // todo: can we make it easier to remove the string interface case? - // documentNode: true, - onRequest: (async ({ pack }) => { - const sddm = pack.input.state.config.schemaMap - const scalars = pack.input.state.scalars.map - if (!sddm) return pack() - - const request = normalizeRequestToNode(pack.input.request) - - // We will mutate query. Assign it back to input for it to be carried forward. - pack.input.request.query = request.query - - encodeRequestVariables({ sddm, scalars, request }) - - const { exchange } = await pack() - const { unpack } = await exchange() - const { decode } = await unpack() - - // If there has been an error and we definitely don't have any data, such as when - // giving an operation name that doesn't match any in the document, - // then don't attempt to decode. - const isError = !decode.input.result.data && (decode.input.result.errors?.length ?? 0) > 0 - - if (!isError) { - decodeResultData({ - sddm, - request, - data: decode.input.result.data, - scalars, - }) - } - - return await decode() - }), - }) diff --git a/src/extensions/SchemaErrors/injectTypenameOnRootResultFields.test.ts b/src/extensions/SchemaErrors/injectTypenameOnRootResultFields.test.ts index a082d5579..d1e989f02 100644 --- a/src/extensions/SchemaErrors/injectTypenameOnRootResultFields.test.ts +++ b/src/extensions/SchemaErrors/injectTypenameOnRootResultFields.test.ts @@ -1,7 +1,7 @@ import { expect, test } from 'vitest' import { normalizeOrThrow } from '../../documentBuilder/Select/document.js' import { SelectionSetGraphqlMapper } from '../../documentBuilder/SelectGraphQLMapper/__.js' -import { graffleMappedResultToRequest } from '../../layers/5_request/core.js' +import { graffleMappedResultToRequest } from '../../layers/6_client/requestMethods/requestMethods.js' import { injectTypenameOnRootResultFields } from './injectTypenameOnRootResultFields.js' import { GraffleSchemaErrors } from './tests/fixture/graffle/__.js' diff --git a/src/layers/5_request/__.ts b/src/layers/5_request/__.ts deleted file mode 100644 index e69c98a8b..000000000 --- a/src/layers/5_request/__.ts +++ /dev/null @@ -1 +0,0 @@ -export * as RequestCore from './_.js' diff --git a/src/layers/6_client/Settings/Config.ts b/src/layers/6_client/Settings/Config.ts index 3a06622c1..661080b34 100644 --- a/src/layers/6_client/Settings/Config.ts +++ b/src/layers/6_client/Settings/Config.ts @@ -1,8 +1,8 @@ import type { GraphQLSchema } from 'graphql' import type { RequireProperties } from '../../../lib/prelude.js' +import type { TransportHttp, TransportMemory } from '../../../requestPipeline/Transport.js' import type { GlobalRegistry } from '../../../types/GlobalRegistry/GlobalRegistry.js' import type { SchemaDrivenDataMap } from '../../../types/SchemaDrivenDataMap/__.js' -import type { TransportHttp, TransportMemory } from '../../5_request/Transport.js' import type { Extension } from '../extension/extension.js' import type { TransportHttpInput } from '../transportHttp/request.js' import type { InputStatic } from './Input.js' diff --git a/src/layers/6_client/Settings/InputToConfig.ts b/src/layers/6_client/Settings/InputToConfig.ts index 08feb45b0..1d970428f 100644 --- a/src/layers/6_client/Settings/InputToConfig.ts +++ b/src/layers/6_client/Settings/InputToConfig.ts @@ -1,7 +1,7 @@ import type { IsUnknown } from 'type-fest' import type { ConfigManager } from '../../../lib/config-manager/__.js' +import { Transport } from '../../../requestPipeline/Transport.js' import type { GlobalRegistry } from '../../../types/GlobalRegistry/GlobalRegistry.js' -import { Transport } from '../../5_request/Transport.js' import { defaultMethodMode } from '../transportHttp/request.js' import { outputConfigDefault, type TransportConfigHttp, type TransportConfigMemory } from './Config.js' import type { InputOutputEnvelopeLonghand, InputStatic, URLInput } from './Input.js' diff --git a/src/layers/6_client/Settings/inputIncrementable/inputIncrementable.ts b/src/layers/6_client/Settings/inputIncrementable/inputIncrementable.ts index 3cfcec9fc..10a430567 100644 --- a/src/layers/6_client/Settings/inputIncrementable/inputIncrementable.ts +++ b/src/layers/6_client/Settings/inputIncrementable/inputIncrementable.ts @@ -1,5 +1,5 @@ +import type { Transport, TransportMemory } from '../../../../requestPipeline/Transport.js' import type { GlobalRegistry } from '../../../../types/GlobalRegistry/GlobalRegistry.js' -import type { Transport, TransportMemory } from '../../../5_request/Transport.js' import type { TransportHttpInput } from '../../transportHttp/request.js' import type { Config } from '../Config.js' import type { NormalizeInput } from '../InputToConfig.js' diff --git a/src/layers/6_client/client.transport-http.test.ts b/src/layers/6_client/client.transport-http.test.ts index 9397ce8c8..c396304dd 100644 --- a/src/layers/6_client/client.transport-http.test.ts +++ b/src/layers/6_client/client.transport-http.test.ts @@ -4,7 +4,7 @@ import { serveSchema } from '../../../tests/_/lib/serveSchema.js' import { Graffle as Pokemon } from '../../../tests/_/schemas/pokemon/graffle/__.js' import { Graffle } from '../../entrypoints/main.js' import { ACCEPT_REC, CONTENT_TYPE_REC } from '../../lib/grafaid/http/http.js' -import { Transport } from '../5_request/Transport.js' +import { Transport } from '../../requestPipeline/Transport.js' const schema = new URL(`https://foo.io/api/graphql`) @@ -32,7 +32,7 @@ test(`anyware hooks are typed to http transport`, () => { }) import { schema as schemaPokemon } from '../../../tests/_/schemas/pokemon/schema.js' -import type { CoreExchangeGetRequest, CoreExchangePostRequest } from '../5_request/hooks.js' +import type { CoreExchangeGetRequest, CoreExchangePostRequest } from '../../requestPipeline/hooks.js' test(`when envelope is used then response property is present even if relying on schema url default`, async () => { const service = await serveSchema({ schema: schemaPokemon }) diff --git a/src/layers/6_client/client.transport-memory.test.ts b/src/layers/6_client/client.transport-memory.test.ts index a495f942b..db24b830e 100644 --- a/src/layers/6_client/client.transport-memory.test.ts +++ b/src/layers/6_client/client.transport-memory.test.ts @@ -2,7 +2,7 @@ import { expectTypeOf } from 'vitest' import { test } from '../../../tests/_/helpers.js' import { schema } from '../../../tests/_/schemas/kitchen-sink/schema.js' import { Graffle } from '../../entrypoints/main.js' -import { Transport } from '../5_request/Transport.js' +import { Transport } from '../../requestPipeline/Transport.js' test(`anyware hooks are typed to memory transport`, () => { Graffle.create({ schema }).anyware(async ({ encode }) => { diff --git a/src/layers/6_client/client.ts b/src/layers/6_client/client.ts index 60827988a..c5ff40f11 100644 --- a/src/layers/6_client/client.ts +++ b/src/layers/6_client/client.ts @@ -1,4 +1,3 @@ -import { CustomScalars } from '../../extensions/CustomScalars/CustomScalars.js' // todo import type { ConfigManager } from '../../lib/config-manager/__.js' import type { Fluent } from '../../lib/fluent/__.js' import { proxyGet } from '../../lib/prelude.js' @@ -56,7 +55,7 @@ type Create = <$Input extends InputStatic>(input: $Input) => export const create: Create = (input) => { const initialState = createState({ name: input.name ?? `default`, // todo import from shared constants - extensions: [CustomScalars()], + extensions: [], scalars: Schema.Scalar.Registry.empty, retry: null, input, diff --git a/src/layers/6_client/clientPrefilled.ts b/src/layers/6_client/clientPrefilled.ts index 2f2faded5..22c9934bb 100644 --- a/src/layers/6_client/clientPrefilled.ts +++ b/src/layers/6_client/clientPrefilled.ts @@ -1,5 +1,4 @@ import type { HasRequiredKeys } from 'type-fest' -import { CustomScalars } from '../../extensions/CustomScalars/CustomScalars.js' import { type Exact } from '../../lib/prelude.js' import type { GlobalRegistry } from '../../types/GlobalRegistry/GlobalRegistry.js' import { type Schema } from '../../types/Schema/__.js' @@ -16,7 +15,7 @@ export const createPrefilled: CreatePrefilled = (name, schemaMap, scalars, schem const constructor = (input: any) => { // todo generic input type const initialState = createState({ name, - extensions: [CustomScalars()], + extensions: [], scalars, retry: null, input: { diff --git a/src/layers/6_client/extension/extension.ts b/src/layers/6_client/extension/extension.ts index 62589d1ec..77312d11a 100644 --- a/src/layers/6_client/extension/extension.ts +++ b/src/layers/6_client/extension/extension.ts @@ -4,8 +4,8 @@ import type { FnProperty } from '../../../lib/fluent/Fluent.js' import type { AssertConstraint } from '../../../lib/prelude.js' import type { TypeFunction } from '../../../lib/type-function/__.js' import type { Fn } from '../../../lib/type-function/TypeFunction.js' +import type { RequestPipeline } from '../../../requestPipeline/__.js' import type { GlobalRegistry } from '../../../types/GlobalRegistry/GlobalRegistry.js' -import type { RequestCore } from '../../5_request/__.js' import type { Client } from '../client.js' import type { ClientContext } from '../fluent.js' import type { GraffleExecutionResultEnvelope } from '../handleOutput.js' @@ -77,7 +77,7 @@ interface Base { /** * Anyware executed on every request. */ - onRequest?: Anyware.Extension2 + onRequest?: Anyware.Extension2 /** * Hook into "get" events on the builder proxy. Useful for adding new methods or manipulating existing ones. * diff --git a/src/layers/6_client/fluent.ts b/src/layers/6_client/fluent.ts index b968ec51b..3305803d3 100644 --- a/src/layers/6_client/fluent.ts +++ b/src/layers/6_client/fluent.ts @@ -1,7 +1,7 @@ import type { Anyware } from '../../lib/anyware/__.js' import type { Fluent } from '../../lib/fluent/__.js' +import type { RequestPipeline } from '../../requestPipeline/__.js' import type { Schema } from '../../types/Schema/__.js' -import type { RequestCore } from '../5_request/__.js' import type { Extension } from './extension/extension.js' import type { Config } from './Settings/Config.js' import type { InputStatic } from './Settings/Input.js' @@ -50,7 +50,7 @@ export interface ClientContext { name: string input: InputStatic config: Config - retry: Anyware.Extension2 | null + retry: Anyware.Extension2 | null extensions: Extension[] scalars: Schema.Scalar.Registry } diff --git a/src/layers/6_client/gql/gql.ts b/src/layers/6_client/gql/gql.ts index 63d848585..14cbaf501 100644 --- a/src/layers/6_client/gql/gql.ts +++ b/src/layers/6_client/gql/gql.ts @@ -6,7 +6,7 @@ import { joinTemplateStringArrayAndArgs, type TemplateStringsArguments, } from '../../../lib/template-string.js' -import { RequestCore } from '../../5_request/__.js' // todo +import { RequestPipeline } from '../../../requestPipeline/__.js' // todo import { type ClientContext, defineTerminus } from '../fluent.js' import { handleOutput } from '../handleOutput.js' import type { Config } from '../Settings/Config.js' @@ -65,9 +65,9 @@ export const gqlProperties = defineTerminus((state) => { schema, // request, request: analyzedRequest, - } as RequestCore.Hooks.HookDefEncode['input'] + } as RequestPipeline.Hooks.HookDefEncode['input'] - const result = await RequestCore.anyware.run({ + const result = await RequestPipeline.anyware.run({ initialInput, retryingExtension: state.retry as any, extensions: state.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any, diff --git a/src/layers/6_client/handleOutput.ts b/src/layers/6_client/handleOutput.ts index 630a8173f..c200c484b 100644 --- a/src/layers/6_client/handleOutput.ts +++ b/src/layers/6_client/handleOutput.ts @@ -10,8 +10,8 @@ import { type GetOrNever, type Values, } from '../../lib/prelude.js' +import type { TransportHttp } from '../../requestPipeline/Transport.js' import type { GlobalRegistry } from '../../types/GlobalRegistry/GlobalRegistry.js' -import type { TransportHttp } from '../5_request/Transport.js' import type { RunTypeHookOnRequestResult } from './extension/extension.js' import type { ClientContext } from './fluent.js' import { diff --git a/src/layers/6_client/properties/anyware.ts b/src/layers/6_client/properties/anyware.ts index c482da22f..a9cf3befa 100644 --- a/src/layers/6_client/properties/anyware.ts +++ b/src/layers/6_client/properties/anyware.ts @@ -1,6 +1,6 @@ import type { Anyware, Anyware as AnywareLib } from '../../../lib/anyware/__.js' import type { Fluent } from '../../../lib/fluent/__.js' -import type { RequestCore } from '../../5_request/__.js' +import type { RequestPipeline } from '../../../requestPipeline/__.js' import { createExtension } from '../extension/extension.js' import { defineProperties, type FnParametersProperty } from '../fluent.js' @@ -14,13 +14,13 @@ export interface Anyware<$Args extends FnParametersProperty> { * TODO Anyware Docs. */ ( - anyware: AnywareLib.Extension2>, + anyware: AnywareLib.Extension2>, ): Fluent.IncrementNothing<$Args> } export const anywareProperties = defineProperties((builder, state) => { return { - anyware: (anyware: Anyware.Extension2) => { + anyware: (anyware: Anyware.Extension2) => { return builder({ ...state, extensions: [...state.extensions, createExtension({ name: `InlineAnyware`, onRequest: anyware })], diff --git a/src/layers/6_client/properties/retry.ts b/src/layers/6_client/properties/retry.ts index b8330eec4..c18f32842 100644 --- a/src/layers/6_client/properties/retry.ts +++ b/src/layers/6_client/properties/retry.ts @@ -1,6 +1,6 @@ import type { Anyware } from '../../../lib/anyware/__.js' import type { Fluent } from '../../../lib/fluent/__.js' -import type { RequestCore } from '../../5_request/__.js' +import type { RequestPipeline } from '../../../requestPipeline/__.js' import { defineProperties, type FnParametersProperty } from '../fluent.js' export interface FnRetry extends Fluent.FnProperty<`retry`> { @@ -12,12 +12,12 @@ export interface Retry<$Args extends FnParametersProperty> { /** * TODO Retry Docs. */ - (extension: Anyware.Extension2): Fluent.IncrementNothing<$Args> + (extension: Anyware.Extension2): Fluent.IncrementNothing<$Args> } export const retryProperties = defineProperties((builder, state) => { return { - retry: (anyware: Anyware.Extension2) => { + retry: (anyware: Anyware.Extension2) => { return builder({ ...state, retry: anyware }) }, } diff --git a/src/layers/6_client/requestMethods/requestMethods.ts b/src/layers/6_client/requestMethods/requestMethods.ts index 09aee3efa..bb8eb68e5 100644 --- a/src/layers/6_client/requestMethods/requestMethods.ts +++ b/src/layers/6_client/requestMethods/requestMethods.ts @@ -3,10 +3,11 @@ import { Select } from '../../../documentBuilder/Select/__.js' import { SelectionSetGraphqlMapper } from '../../../documentBuilder/SelectGraphQLMapper/__.js' import type { TypeFunction } from '../../../entrypoints/utilities-for-generated.js' import type { Fluent } from '../../../lib/fluent/__.js' +import type { Grafaid } from '../../../lib/grafaid/__.js' +import { getOperationDefinition } from '../../../lib/grafaid/document.js' import { isSymbol } from '../../../lib/prelude.js' +import { RequestPipeline } from '../../../requestPipeline/__.js' import type { GlobalRegistry } from '../../../types/GlobalRegistry/GlobalRegistry.js' -import { RequestCore } from '../../5_request/__.js' -import { graffleMappedResultToRequest } from '../../5_request/core.js' import { type ClientContext, defineTerminus } from '../fluent.js' import { handleOutput } from '../handleOutput.js' import type { Config } from '../Settings/Config.js' @@ -130,9 +131,9 @@ const executeDocument = async ( url, schema, request, - } as RequestCore.Hooks.HookDefEncode['input'] + } as RequestPipeline.Hooks.HookDefEncode['input'] - const result = await RequestCore.anyware.run({ + const result = await RequestPipeline.anyware.run({ initialInput, retryingExtension: state.retry as any, extensions: state.extensions.filter(_ => _.onRequest !== undefined).map(_ => _.onRequest!) as any, @@ -140,3 +141,28 @@ const executeDocument = async ( return handleOutput(state, result) } + +export const graffleMappedResultToRequest = ( + { document, operationsVariables }: SelectionSetGraphqlMapper.Encoded, + operationName?: string, +): Grafaid.RequestAnalyzedDocumentNodeInput => { + // We get back variables for every operation in the Graffle document. + // However, we only need the variables for the operation that was selected to be executed. + // If there was NO operation name provided then we assume that the first operation in the document is the one that should be executed. + // If there are MULTIPLE operations in the Graffle document AND the user has supplied an invalid operation name (either none or given matches none) + // then what happens here is the variables from one operation can be mixed into another operation. + // This shouldn't matter because such a state would be rejected by the server since it wouldn't know what operation to execute. + const variables_ = operationName + ? operationsVariables[operationName] + : Object.values(operationsVariables)[0] + + const operation_ = getOperationDefinition({ query: document, operationName }) + if (!operation_) throw new Error(`Unknown operation named "${String(operationName)}".`) + + return { + operationName, + operation: operation_, + query: document, + variables: variables_, + } +} diff --git a/src/extensions/CustomScalars/decode.test.ts b/src/requestPipeline/CustomScalars/decode.test.ts similarity index 100% rename from src/extensions/CustomScalars/decode.test.ts rename to src/requestPipeline/CustomScalars/decode.test.ts diff --git a/src/extensions/CustomScalars/decode.ts b/src/requestPipeline/CustomScalars/decode.ts similarity index 100% rename from src/extensions/CustomScalars/decode.ts rename to src/requestPipeline/CustomScalars/decode.ts diff --git a/src/extensions/CustomScalars/encode.test.ts b/src/requestPipeline/CustomScalars/encode.test.ts similarity index 100% rename from src/extensions/CustomScalars/encode.test.ts rename to src/requestPipeline/CustomScalars/encode.test.ts diff --git a/src/extensions/CustomScalars/encode.ts b/src/requestPipeline/CustomScalars/encode.ts similarity index 100% rename from src/extensions/CustomScalars/encode.ts rename to src/requestPipeline/CustomScalars/encode.ts diff --git a/src/layers/5_request/Transport.ts b/src/requestPipeline/Transport.ts similarity index 100% rename from src/layers/5_request/Transport.ts rename to src/requestPipeline/Transport.ts diff --git a/src/layers/5_request/_.ts b/src/requestPipeline/_.ts similarity index 100% rename from src/layers/5_request/_.ts rename to src/requestPipeline/_.ts diff --git a/src/requestPipeline/__.ts b/src/requestPipeline/__.ts new file mode 100644 index 000000000..35dd2529c --- /dev/null +++ b/src/requestPipeline/__.ts @@ -0,0 +1 @@ +export * as RequestPipeline from './_.js' diff --git a/src/layers/5_request/core.ts b/src/requestPipeline/core.ts similarity index 74% rename from src/layers/5_request/core.ts rename to src/requestPipeline/core.ts index c20c3d08f..147547417 100644 --- a/src/layers/5_request/core.ts +++ b/src/requestPipeline/core.ts @@ -1,20 +1,22 @@ -import type { SelectionSetGraphqlMapper } from '../../documentBuilder/SelectGraphQLMapper/__.js' -import { Anyware } from '../../lib/anyware/__.js' -import type { Grafaid } from '../../lib/grafaid/__.js' -import { getOperationDefinition, OperationTypeToAccessKind, print } from '../../lib/grafaid/document.js' -import { execute } from '../../lib/grafaid/execute.js' // todo +import type { GraffleExecutionResultVar } from '../layers/6_client/handleOutput.js' +import type { Config } from '../layers/6_client/Settings/Config.js' +import { MethodMode, type MethodModeGetReads } from '../layers/6_client/transportHttp/request.js' +import { Anyware } from '../lib/anyware/__.js' +import type { Grafaid } from '../lib/grafaid/__.js' +import { OperationTypeToAccessKind, print } from '../lib/grafaid/document.js' +import { execute } from '../lib/grafaid/execute.js' // todo import { getRequestEncodeSearchParameters, getRequestHeadersRec, parseExecutionResult, postRequestEncodeBody, postRequestHeadersRec, -} from '../../lib/grafaid/http/http.js' -import { mergeRequestInit, searchParamsAppendAll } from '../../lib/http.js' -import { casesExhausted, isString } from '../../lib/prelude.js' -import type { GraffleExecutionResultVar } from '../6_client/handleOutput.js' -import type { Config } from '../6_client/Settings/Config.js' -import { MethodMode, type MethodModeGetReads } from '../6_client/transportHttp/request.js' +} from '../lib/grafaid/http/http.js' +import { normalizeRequestToNode } from '../lib/grafaid/request.js' +import { mergeRequestInit, searchParamsAppendAll } from '../lib/http.js' +import { casesExhausted, isString } from '../lib/prelude.js' +import { decodeResultData } from './CustomScalars/decode.js' +import { encodeRequestVariables } from './CustomScalars/encode.js' import { type CoreExchangeGetRequest, type CoreExchangePostRequest, @@ -24,32 +26,6 @@ import { } from './hooks.js' import { Transport } from './Transport.js' -export const graffleMappedResultToRequest = ( - { document, operationsVariables }: SelectionSetGraphqlMapper.Encoded, - operationName?: string, -): Grafaid.RequestAnalyzedDocumentNodeInput => { - // We get back variables for every operation in the Graffle document. - // However, we only need the variables for the operation that was selected to be executed. - // If there was NO operation name provided then we assume that the first operation in the document is the one that should be executed. - // If there are MULTIPLE operations in the Graffle document AND the user has supplied an invalid operation name (either none or given matches none) - // then what happens here is the variables from one operation can be mixed into another operation. - // This shouldn't matter because such a state would be rejected by the server since it wouldn't know what operation to execute. - const variables_ = operationName - ? operationsVariables[operationName] - : Object.values(operationsVariables)[0] - - const operation_ = getOperationDefinition({ query: document, operationName }) - if (!operation_) throw new Error(`Unknown operation named "${String(operationName)}".`) - - return { - operationName, - operation: operation_, - query: document, - variables: variables_, - } -} - -// todo execution result only if transport is memory export const anyware = Anyware.create({ // If core errors caused by an abort error then raise it as a direct error. // This is an expected possible error. Possible when user cancels a request. @@ -62,6 +38,17 @@ export const anyware = Anyware.create { + const sddm = input.state.config.schemaMap + const scalars = input.state.scalars.map + if (sddm) { + const request = normalizeRequestToNode(input.request) + + // We will mutate query. Assign it back to input for it to be carried forward. + input.request.query = request.query + + encodeRequestVariables({ sddm, scalars, request }) + } + return input }, pack: { @@ -189,7 +176,20 @@ export const anyware = Anyware.create { + decode: ({ input, previous }) => { + // If there has been an error and we definitely don't have any data, such as when + // giving an operation name that doesn't match any in the document, + // then don't attempt to decode. + const isError = !input.result.data && (input.result.errors?.length ?? 0) > 0 + if (input.state.config.schemaMap && !isError) { + decodeResultData({ + sddm: input.state.config.schemaMap, + request: normalizeRequestToNode(previous.pack.input.request), + data: input.result.data, + scalars: input.state.scalars.map, + }) + } + const result = input.transportType === `http` ? { ...input.result, diff --git a/src/layers/5_request/hooks.ts b/src/requestPipeline/hooks.ts similarity index 89% rename from src/layers/5_request/hooks.ts rename to src/requestPipeline/hooks.ts index 8777b7140..eb9d8edb6 100644 --- a/src/layers/5_request/hooks.ts +++ b/src/requestPipeline/hooks.ts @@ -1,10 +1,10 @@ import type { FormattedExecutionResult, GraphQLSchema } from 'graphql' -import type { Grafaid } from '../../lib/grafaid/__.js' -import type { getRequestEncodeSearchParameters, postRequestEncodeBody } from '../../lib/grafaid/http/http.js' -import type { httpMethodGet, httpMethodPost } from '../../lib/http.js' -import type { ClientContext } from '../6_client/fluent.js' -import type { Config } from '../6_client/Settings/Config.js' -import type { MethodModeGetReads, MethodModePost } from '../6_client/transportHttp/request.js' +import type { ClientContext } from '../layers/6_client/fluent.js' +import type { Config } from '../layers/6_client/Settings/Config.js' +import type { MethodModeGetReads, MethodModePost } from '../layers/6_client/transportHttp/request.js' +import type { Grafaid } from '../lib/grafaid/__.js' +import type { getRequestEncodeSearchParameters, postRequestEncodeBody } from '../lib/grafaid/http/http.js' +import type { httpMethodGet, httpMethodPost } from '../lib/http.js' import type { TransportHttp, TransportMemory } from './Transport.js' interface HookInputBase { diff --git a/tests/_/SpyExtension.ts b/tests/_/SpyExtension.ts index 8a37f2020..8586dca11 100644 --- a/tests/_/SpyExtension.ts +++ b/tests/_/SpyExtension.ts @@ -1,7 +1,7 @@ import { beforeEach } from 'vitest' import { createExtension } from '../../src/entrypoints/main.js' import type { Config } from '../../src/entrypoints/utilities-for-generated.js' -import type { HookDefEncode, HookDefExchange, HookDefPack } from '../../src/layers/5_request/hooks.js' +import type { HookDefEncode, HookDefExchange, HookDefPack } from '../../src/requestPipeline/hooks.js' export const Spy = () => createExtension({