Skip to content

Commit

Permalink
feat(input): allow request configuration (#1059)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Sep 3, 2024
1 parent 5d04f27 commit 9be0f08
Show file tree
Hide file tree
Showing 35 changed files with 534 additions and 380 deletions.
3 changes: 2 additions & 1 deletion examples/$generated-clients/SocialStudies/Global.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Index } from './Index.js'

declare global {
export namespace GraphQLRequestTypes {
export namespace GraffleGlobalTypes {
export interface Schemas {
SocialStudies: {
name: 'SocialStudies'
index: Index
customScalars: {}
featureOptions: {
Expand Down
11 changes: 11 additions & 0 deletions examples/transport-http_RequestInput.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
url: 'https://countries.trevorblades.com/graphql',
body: '{"query":"{ languages { code } }"}',
method: 'POST',
headers: Headers {
authorization: 'Bearer MY_TOKEN',
accept: 'application/graphql-response+json',
'content-type': 'application/json'
},
mode: 'cors'
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { publicGraphQLSchemaEndpoints } from './$helpers.js'
const graffle = Graffle
.create({
schema: publicGraphQLSchemaEndpoints.SocialStudies,
headers: { authorization: `Bearer MY_TOKEN` },
request: {
headers: {
authorization: `Bearer MY_TOKEN`,
},
mode: `cors`,
},
})
.use(async ({ exchange }) => {
show(exchange.input.request.headers)
show(exchange.input.request)
return exchange()
})

Expand Down
5 changes: 0 additions & 5 deletions examples/transport-http_headers.output.txt

This file was deleted.

2 changes: 1 addition & 1 deletion src/entrypoints/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { create as createSelect, select } from '../layers/5_select/select.js'
export { type Client, create } from '../layers/6_client/client.js'
export { createPrefilled, type InputPrefilled } from '../layers/6_client/prefilled.js'
export { type Input } from '../layers/6_client/Settings/Input.js'
export { type InputStatic } from '../layers/6_client/Settings/Input.js'
4 changes: 4 additions & 0 deletions src/layers/1_Schema/core/Index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { GlobalRegistry } from '../../2_generator/globalRegistry.js'
import type { Output } from '../Output/__.js'

/**
* A generic schema index type. Any particular schema index will be a subtype of this, with
* additional specificity such as on objects where here `Record` is used.
*/
export interface Index {
name: GlobalRegistry.SchemaNames
Root: {
Expand Down
3 changes: 2 additions & 1 deletion src/layers/2_generator/code/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ export const { moduleName: moduleNameGlobal, generate: generateGlobal } = create

code.push(`
declare global {
export namespace GraphQLRequestTypes {
export namespace GraffleGlobalTypes {
export interface Schemas {
${config.name}: {
name: '${config.name}'
index: Index
customScalars: {
${
Expand Down
38 changes: 25 additions & 13 deletions src/layers/2_generator/globalRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import type { TSError } from '../../lib/TSError.js'
import type { Schema } from '../1_Schema/__.js'

declare global {
export namespace GraphQLRequestTypes {
export namespace GraffleGlobalTypes {
interface Schemas {}
// Use this is for manual internal type testing.
// interface SchemasAlwaysEmpty {}
}
}

type SomeSchema = {
name: string
index: Schema.Index
customScalars: Record<string, Schema.Scalar.Scalar>
featureOptions: {
Expand All @@ -23,6 +24,7 @@ type SomeSchema = {
}

type ZeroSchema = {
name: GlobalRegistry.DefaultSchemaName
index: { name: never }
featureOptions: {
schemaErrors: false
Expand All @@ -33,35 +35,45 @@ type ZeroSchema = {
export type GlobalRegistry = Record<string, SomeSchema>

export namespace GlobalRegistry {
export type Schemas = GraphQLRequestTypes.Schemas
export type DefaultSchemaName = 'default'

export type Schemas = GraffleGlobalTypes.Schemas

export type IsEmpty = keyof Schemas extends never ? true : false

export type SchemaList = IsEmpty extends true ? ZeroSchema : Values<Schemas>
export type SchemaUnion = IsEmpty extends true ? ZeroSchema : Values<Schemas>

export type DefaultSchemaName = 'default'

export type SchemaNames = keyof GraphQLRequestTypes.Schemas extends never
export type SchemaNames = keyof GraffleGlobalTypes.Schemas extends never
? TSError<'SchemaNames', 'No schemas have been registered. Did you run graffle generate?'>
: keyof GraphQLRequestTypes.Schemas
: keyof GraffleGlobalTypes.Schemas

// dprint-ignore
export type HasDefaultUrlForSchema<$Schema extends SchemaUnion> =
$Schema['defaultSchemaUrl'] extends null
? false
: true

export type HasSchemaErrors<$Schema extends SchemaList> = $Schema['featureOptions']['schemaErrors']
// dprint-ignore
export type HasSchemaErrors<$Schema extends SchemaUnion> =
$Schema['featureOptions']['schemaErrors']

export type HasSchemaErrorsViaName<$Name extends SchemaNames> =
// todo use conditional types?
// eslint-disable-next-line
// @ts-ignore passes after generation
GraphQLRequestTypes.Schemas[$Name]['featureOptions']['schemaErrors']
GraffleGlobalTypes.Schemas[$Name]['featureOptions']['schemaErrors']

// eslint-disable-next-line
// @ts-ignore passes after generation
export type GetSchemaIndex<$Name extends SchemaNames> = GraphQLRequestTypes.Schemas[$Name]['index']
export type GetSchemaIndex<$Name extends SchemaNames> = GraffleGlobalTypes.Schemas[$Name]['index']

// eslint-disable-next-line
// @ts-ignore passes after generation
export type SchemaIndexDefault = GetSchemaIndex<DefaultSchemaName>

export type GetSchemaIndexOrDefault<$Name extends SchemaNames | undefined> = $Name extends SchemaNames
? GetSchemaIndex<$Name>
: SchemaIndexDefault
// dprint-ignore
export type GetSchemaIndexOrDefault<$Name extends SchemaNames | undefined> =
$Name extends SchemaNames
? GetSchemaIndex<$Name>
: SchemaIndexDefault
}
12 changes: 11 additions & 1 deletion src/layers/3_SelectionSet/encode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ const testEachArgs = [
]
) => {
const [description, ss] = args.length === 1 ? [undefined, args[0]] : args
const context: Context = { schemaIndex, config: { output: outputConfigDefault, transport: `memory` } }
const context: Context = {
schemaIndex,
config: {
output: outputConfigDefault,
transport: `memory`,
name: schemaIndex[`name`],
// eslint-disable-next-line
initialInput: {} as any,
requestInputOptions: {},
},
}
const graphqlDocumentString = rootTypeSelectionSet(context, schemaIndex[`Root`][`Query`], ss as any)
// Should parse, ensures is syntactically valid graphql document.
const document = parse(graphqlDocumentString)
Expand Down
71 changes: 21 additions & 50 deletions src/layers/5_core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { GraphQLObjectSelection } from '../3_SelectionSet/encode.js'
import * as Result from '../4_ResultSet/customScalars.js'
import type { GraffleExecutionResultVar } from '../6_client/client.js'
import type { Config } from '../6_client/Settings/Config.js'
import { mergeRequestInputOptions, type RequestInput } from '../6_client/Settings/inputIncrementable/request.js'
import type {
ContextInterfaceRaw,
ContextInterfaceTyped,
Expand Down Expand Up @@ -48,9 +49,7 @@ type TransportInput<$Config extends Config, $HttpProperties = {}, $MemoryPropert
TransportHttp extends $Config['transport']
? ({
transport: TransportHttp
transportConstructorConfig: {
headers?: HeadersInit
}

} & $HttpProperties)
: never
)
Expand Down Expand Up @@ -94,29 +93,6 @@ export type HookDefPack<$Config extends Config> = {
}>
}

export type RequestInput = {
url: string | URL
method:
| 'get'
| 'post'
| 'put'
| 'delete'
| 'patch'
| 'head'
| 'options'
| 'trace'
| 'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'PATCH'
| 'HEAD'
| 'OPTIONS'
| 'TRACE'
headers?: HeadersInit
body: BodyInit
}

export type HookDefExchange<$Config extends Config> = {
slots: {
fetch: typeof fetch
Expand Down Expand Up @@ -231,26 +207,26 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
}
case `http`: {
// TODO thrown error here is swallowed in examples.
const headers = mergeHeadersInit(
input.transportConstructorConfig.headers ?? {},
input.headers ?? {},
)
// @see https://graphql.github.io/graphql-over-http/draft/#sec-Accept
headers.set(`accept`, CONTENT_TYPE_GQL)
// @see https://graphql.github.io/graphql-over-http/draft/#sec-POST
// todo if body is something else, say upload extension turns it into a FormData, then fetch will automatically set the content-type header.
// ... however we should not rely on that behavior, and instead error here if there is no content type header and we cannot infer it here?
if (typeof input.body === `string`) {
headers.set(`content-type`, CONTENT_TYPE_JSON)
const request: RequestInput = {
url: input.url,
body: input.body,
// @see https://graphql.github.io/graphql-over-http/draft/#sec-POST
method: `POST`,
...mergeRequestInputOptions(input.context.config.requestInputOptions, {
headers: mergeHeadersInit(input.headers, {
// @see https://graphql.github.io/graphql-over-http/draft/#sec-Accept
accept: CONTENT_TYPE_GQL,
// todo if body is something else, say upload extension turns it into a FormData, then fetch will automatically set the content-type header.
// ... however we should not rely on that behavior, and instead error here if there is no content type header and we cannot infer it here?
...(typeof input.body === `string`
? { 'content-type': CONTENT_TYPE_JSON }
: {}),
}),
}),
}
return {
...input,
request: {
url: input.url,
body: input.body,
method: `POST`,
headers,
},
request,
}
}
default:
Expand All @@ -266,13 +242,8 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
run: async ({ input, slots }) => {
switch (input.transport) {
case `http`: {
const response = await slots.fetch(
new Request(input.request.url, {
method: input.request.method,
headers: input.request.headers,
body: input.request.body,
}),
)
const request = new Request(input.request.url, input.request)
const response = await slots.fetch(request)
return {
...input,
response,
Expand Down
8 changes: 8 additions & 0 deletions src/layers/6_client/Settings/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { Schema } from '../../1_Schema/__.js'
import type { GlobalRegistry } from '../../2_generator/globalRegistry.js'
import type { SelectionSet } from '../../3_SelectionSet/__.js'
import type { Transport } from '../../5_core/types.js'
import type { InputStatic } from './Input.js'
import type { RequestInputOptions } from './inputIncrementable/request.js'

export type OutputChannel = 'throw' | 'return'

Expand Down Expand Up @@ -102,8 +104,14 @@ export type OutputConfigDefault = {
}

export type Config = {
/**
* The initial input that was given to derive this config.
*/
initialInput: InputStatic<any> // InputStatic<GlobalRegistry.SchemaUnion>
name: GlobalRegistry.SchemaNames
output: OutputConfig
transport: Transport
requestInputOptions?: RequestInputOptions
}

// dprint-ignore
Expand Down
Loading

0 comments on commit 9be0f08

Please sign in to comment.