Skip to content

Commit

Permalink
feat: preflight check all request methods (#1278)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Dec 15, 2024
1 parent 82fba60 commit fa687d6
Show file tree
Hide file tree
Showing 665 changed files with 22,743 additions and 16,624 deletions.
14 changes: 14 additions & 0 deletions examples/65_preset/preset_minimal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* This example shows use of the `minimal` preset which is the
* default preset used when importing Graffle. It bundles the
* Transport HTTP extension.
*/

import { Graffle } from '../../src/entrypoints/main.js'
import { create } from '../../src/entrypoints/presets/minimal.js'

console.log(`Is the default preset`, Graffle.create === create)

const graffle = create()

console.log(`The current transport is`, graffle._.transports.current)
25 changes: 25 additions & 0 deletions examples/65_preset/preset_none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* This example shows use of the `bare` preset which is Graffle at
* its most minimal. It uses no extensions, not even a transport.
*/

import { create } from '../../src/entrypoints/presets/bare.js'
import { Introspection } from '../../src/extensions/Introspection/Introspection.js'

const graffle = create()

/**
* Because we have no transports registered, the `transport` method
* is not available to us. Nor are the request methods.
*
* As well, request methods from extensions also check the status of the transport.
*/

const _t: never = graffle.transport
const _e1: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.gql
const _e2: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.document
const _e3: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.query.$batch
const _e4: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.query.id
const _e5: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.mutation.$batch
const _e6: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.mutation.id
const _e7: 'Error: You cannot send requests yet. You must setup a transport.' = graffle.use(Introspection()).introspect
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
headers: Headers {
accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
'content-type': 'application/json',
'x-sent-at-time': '1731961845707'
'x-sent-at-time': '1734293284713'
},
method: 'post',
url: 'http://localhost:3000/graphql',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Headers {
'content-type': 'application/json',
authorization: 'Bearer MY_TOKEN',
'x-from-raw': 'true'
}
}
2 changes: 1 addition & 1 deletion examples/__outputs__/20_output/output_envelope.output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
headers: Headers {
'content-type': 'application/graphql-response+json; charset=utf-8',
'content-length': '142',
date: 'Mon, 18 Nov 2024 20:30:46 GMT',
date: 'Sun, 15 Dec 2024 20:08:05 GMT',
connection: 'keep-alive',
'keep-alive': 'timeout=5'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
parentId: '832b1b8dea636439',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: '65825892207135a8',
traceState: undefined,
name: 'encode',
id: 'b4ac64f2faa2f565',
id: 'b4f6b90f9d4cb7f9',
kind: 0,
timestamp: 1731961846500000,
duration: 1634.667,
timestamp: 1734293285758000,
duration: 1165.458,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -29,18 +29,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
parentId: '832b1b8dea636439',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: '65825892207135a8',
traceState: undefined,
name: 'pack',
id: '53e4263bd960a2e0',
id: '28d86e022df6a130',
kind: 0,
timestamp: 1731961846508000,
duration: 16836.25,
timestamp: 1734293285761000,
duration: 123866.625,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -53,18 +53,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
parentId: '832b1b8dea636439',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: '65825892207135a8',
traceState: undefined,
name: 'exchange',
id: '49774b87c330f8a6',
id: '009c240ba45bff12',
kind: 0,
timestamp: 1731961846525000,
duration: 100583.459,
timestamp: 1734293285888000,
duration: 60630.292,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -77,18 +77,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
parentId: '832b1b8dea636439',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: '65825892207135a8',
traceState: undefined,
name: 'unpack',
id: 'b2ab76711cac0cda',
id: '1a49677d316b711e',
kind: 0,
timestamp: 1731961846626000,
duration: 949.5,
timestamp: 1734293285949000,
duration: 1003.209,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -101,18 +101,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
parentId: '832b1b8dea636439',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: '65825892207135a8',
traceState: undefined,
name: 'decode',
id: '02b77d59425b58b8',
id: '3b7f0a2030462e74',
kind: 0,
timestamp: 1731961846627000,
duration: 445,
timestamp: 1734293285950000,
duration: 444.291,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -125,18 +125,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.27.0'
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'b3ebc1b598698f83310bd235fcb07eab',
traceId: 'f223876fb94238d6e67c7f5ad5352b14',
parentId: undefined,
traceState: undefined,
name: 'request',
id: '832b1b8dea636439',
id: '65825892207135a8',
kind: 0,
timestamp: 1731961846499000,
duration: 128745.833,
timestamp: 1734293285757000,
duration: 193579.292,
attributes: {},
status: { code: 0 },
events: [],
Expand Down
2 changes: 2 additions & 0 deletions examples/__outputs__/65_preset/preset_minimal.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Is the default preset true
The current transport is http
Empty file.
15 changes: 9 additions & 6 deletions src/client/Configuration/ConfigInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ export type ConfigInitOutput =
}

// dprint-ignore
export type NormalizeConfigInit<$ConfigInit extends ConfigInit> = {
[_ in keyof $ConfigInit]:
_ extends 'output'
? NormalizeConfigInitOutput<$ConfigInit['output']>
: $ConfigInit[_]
}
export type NormalizeConfigInit<$ConfigInit extends ConfigInit> =
ConfigInit extends $ConfigInit
? {}
: {
[_ in keyof $ConfigInit]:
_ extends 'output'
? NormalizeConfigInitOutput<$ConfigInit['output']>
: $ConfigInit[_]
}

// dprint-ignore
type NormalizeConfigInitOutput<$Output extends ConfigInitOutput | undefined> = {
Expand Down
23 changes: 15 additions & 8 deletions src/client/Configuration/client.create.config.output.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { type GraphQLExecutionResultError } from '../../lib/grafaid/graphql.js'

const G = Graffle.create

const defaultGraffle = Graffle.create()
const defaultGraffle = Graffle.create({ checkPreflight: false })

describe('default is errors thrown, no envelope, no schema errors', async () => {
const graffle = G({
checkPreflight: false,
output: {
defaults: {
errorChannel: 'throw',
Expand Down Expand Up @@ -55,7 +56,7 @@ describe('.envelope', () => {
// const fieldMethod = <$Graffle extends {query:{__typename:()=>Promise<any>}}>(g: $Graffle) => g.query.__typename()

describe('false disables it ', () => {
const g = G({ output: { envelope: false } })
const g = G({ output: { envelope: false }, checkPreflight: false })

test('query.<fieldMethod>', () => {
expectTypeOf(g.query.__typename()).resolves.toEqualTypeOf<FieldMethodResultDisabled>()
Expand All @@ -68,7 +69,7 @@ describe('.envelope', () => {
})
})
describe('true enables it', () => {
const g = Graffle.create({ output: { envelope: true } })
const g = Graffle.create({ output: { envelope: true }, checkPreflight: false })
test('query.<fieldMethod>', () => {
expectTypeOf(g.query.__typename()).resolves.toMatchTypeOf<FieldMethodResultEnabled>()
})
Expand All @@ -81,28 +82,29 @@ describe('.envelope', () => {
})
})
test('object enables it', async () => {
const graffle = Graffle.create({ output: { envelope: {} } })
const graffle = Graffle.create({ output: { envelope: {} }, checkPreflight: false })
expectTypeOf(await graffle.query.__typename()).toMatchTypeOf<FieldMethodResultEnabled>()
})
describe('.enabled', () => {
test('false disables it', async () => {
const graffle = Graffle.create({ output: { envelope: { enabled: false } } })
const graffle = Graffle.create({ output: { envelope: { enabled: false } }, checkPreflight: false })
expectTypeOf(await graffle.query.__typename()).toEqualTypeOf<FieldMethodResultDisabled>()
})
test('true enables it', async () => {
const graffle = Graffle.create({ output: { envelope: { enabled: true } } })
const graffle = Graffle.create({ output: { envelope: { enabled: true } }, checkPreflight: false })
expectTypeOf(await graffle.query.__typename()).toMatchTypeOf<FieldMethodResultEnabled>()
})
})
describe('with defaults.errorChannel: "return"', () => {
describe('.errors', () => {
test('defaults to execution errors in envelope', () => {
const g = G({ output: { defaults: { errorChannel: 'return' }, envelope: true } })
const g = G({ output: { defaults: { errorChannel: 'return' }, envelope: true }, checkPreflight: false })
expectTypeOf(g.query.__typename()).resolves.toMatchTypeOf<ExecutionResult<{ __typename: 'Query' }> | Anyware.ResultFailure>()
})
test('.execution:false restores errors to return', async () => {
const g = G({
output: { defaults: { errorChannel: 'return' }, envelope: { errors: { execution: false } } },
checkPreflight: false,
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<
Omit<ExecutionResult<{ __typename: 'Query' }>, 'errors'> | Anyware.ResultFailure | GraphQLExecutionResultError
Expand All @@ -111,6 +113,7 @@ describe('.envelope', () => {
test('.other:true raises them to envelope', () => {
const g = G({
output: { defaults: { errorChannel: 'return' }, envelope: { errors: { other: true } } },
checkPreflight: false,
})
expectTypeOf(g.query.__typename()).resolves.toMatchTypeOf<ExecutionResult<{ __typename: 'Query' }>>()
})
Expand All @@ -121,6 +124,7 @@ describe('.envelope', () => {
// todo allow this shorthand
// output: { envelope: false },
output: { envelope: { errors: { execution:false, other:false } } },
checkPreflight: false,
})
const result = await g.query.__typename()
expectTypeOf<keyof typeof result>().toEqualTypeOf<'data'|'extensions'> // no errors
Expand All @@ -129,7 +133,7 @@ describe('.envelope', () => {

describe('defaults.errorChannel: "return"', () => {
describe('puts errors into return type', () => {
const g = G({ output: { defaults: { errorChannel: 'return' } } })
const g = G({ output: { defaults: { errorChannel: 'return' } }, checkPreflight: false })
test('query.<fieldMethod>', async () => {
expectTypeOf(await g.query.__typename()).toEqualTypeOf<
'Query' | Anyware.ResultFailure | GraphQLExecutionResultError
Expand All @@ -140,18 +144,21 @@ describe('defaults.errorChannel: "return"', () => {
test('.execution: throw', async () => {
const g = G({
output: { defaults: { errorChannel: 'return' }, errors: { execution: 'throw' } },
checkPreflight: false,
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | Anyware.ResultFailure>()
})
test('.other: throw', async () => {
const g = G({
output: { defaults: { errorChannel: 'return' }, errors: { other: 'throw' } },
checkPreflight: false,
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | GraphQLExecutionResultError>()
})
test('.*: throw', async () => {
const g = G({
output: { defaults: { errorChannel: 'return' }, errors: { other: 'throw', execution: 'throw' } },
checkPreflight: false,
})
expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query'>()
})
Expand Down
16 changes: 12 additions & 4 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@ export type Client<
& {
// eslint-disable-next-line
// @ts-ignore Passes after generation
document: TypeFunction.Call<GlobalRegistry.GetOrDefault<$Context['name']>['interfaces']['Document'], $Context>
document: ClientTransports.PreflightCheck<
$Context,
TypeFunction.Call<
GlobalRegistry.GetOrDefault<
// @ts-expect-error
$Context['name']
>['interfaces']['Document'],
$Context
>
>
}
)
)
Expand Down Expand Up @@ -128,7 +137,7 @@ export const createConstructorWithContext = <$Context extends Context>(
}

export type ClientConstructor<$Context extends Context = Context.States.Empty> = <
const $ConfigInit extends ConfigInit = {},
const $ConfigInit extends ConfigInit,
>(
configInit?: $ConfigInit,
) => Client<
Expand All @@ -143,8 +152,7 @@ export type ClientConstructor<$Context extends Context = Context.States.Empty> =

export const create: ClientConstructor = (configInit) => {
const initialContext = Context.Updaters.addConfigInit(
// todo remove this, client builder should never mutate context, just putting this here for now while working on other stuff.
structuredClone(Context.States.contextEmpty),
Context.States.contextEmpty,
configInit ?? {},
)
return createWithContext(initialContext)
Expand Down
Loading

0 comments on commit fa687d6

Please sign in to comment.