Skip to content

Commit

Permalink
fix: constructor headers sent through to exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Aug 4, 2024
1 parent e268c00 commit df6954a
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 6 deletions.
11 changes: 9 additions & 2 deletions src/layers/5_core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { print } from 'graphql'
import { Anyware } from '../../lib/anyware/__.js'
import { type StandardScalarVariables } from '../../lib/graphql.js'
import { parseExecutionResult } from '../../lib/graphqlHTTP.js'
import { CONTENT_TYPE_GQL, CONTENT_TYPE_JSON } from '../../lib/http.js'
import { CONTENT_TYPE_GQL, CONTENT_TYPE_JSON, mergeHeadersInit } from '../../lib/http.js'
import { casesExhausted } from '../../lib/prelude.js'
import { execute } from '../0_functions/execute.js'
import type { Schema } from '../1_Schema/__.js'
Expand Down Expand Up @@ -43,6 +43,9 @@ type InterfaceInput<TypedProperties = {}, RawProperties = {}> =
type TransportInput<HttpProperties = {}, MemoryProperties = {}> =
| ({
transport: TransportHttp
transportConstructorConfig: {
headers?: HeadersInit
}
} & HttpProperties)
| ({
transport: TransportMemory
Expand Down Expand Up @@ -259,10 +262,14 @@ export const anyware = Anyware.create<HookSequence, HookMap, ExecutionResult>({
run: async ({ input, slots }) => {
switch (input.transport) {
case `http`: {
const headers = mergeHeadersInit(
input.transportConstructorConfig.headers ?? {},
input.request.headers ?? {},
)
const response = await slots.fetch(
new Request(input.request.url, {
method: input.request.method,
headers: input.request.headers,
headers,
body: input.request.body,
}),
)
Expand Down
2 changes: 1 addition & 1 deletion src/layers/6_client/Settings/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type Input<$Schema extends GlobalRegistry.SchemaList> = {
export type InputRaw<$Schema extends GlobalRegistry.SchemaList> = {
schema: URLInput
/**
* Headers to send with the request.
* Headers to send with each sent request.
*/
headers?: HeadersInit
/**
Expand Down
21 changes: 18 additions & 3 deletions src/layers/6_client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { schema } from '../../../tests/_/schema/schema.js'
import { Graffle } from '../../entrypoints/alpha/main.js'
import { CONTENT_TYPE_GQL, CONTENT_TYPE_JSON } from '../../lib/http.js'

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

describe(`without schemaIndex only raw is available`, () => {
const graffle = Graffle.create({ schema: schemaUrl })
const graffle = Graffle.create({ schema: endpoint })

test(`unavailable methods`, () => {
// @ts-expect-error
Expand All @@ -29,6 +29,14 @@ describe(`without schemaIndex only raw is available`, () => {

describe(`interface`, () => {
describe(`http`, () => {
test(`can set headers in constructor`, async ({ fetch }) => {
fetch.mockImplementationOnce(() => Promise.resolve(createResponse({ data: { id: `abc` } })))
const graffle = Graffle.create({ schema: endpoint, headers: { 'x-foo': `bar` } })
await graffle.raw(`query { id }`)
const request = fetch.mock.calls[0]?.[0]
expect(request?.headers.get(`x-foo`)).toEqual(`bar`)
})

test(`sends well formed request`, async ({ fetch, graffle }) => {
fetch.mockImplementationOnce(() => Promise.resolve(createResponse({ data: { greetings: `Hello World` } })))
await graffle.raw({ document: `query { greetings }` })
Expand All @@ -37,12 +45,19 @@ describe(`interface`, () => {
expect(request?.headers.get(`accept`)).toEqual(CONTENT_TYPE_GQL)
})
})
describe(`memory`, () => {
test(`cannot set headers in constructor`, () => {
// todo: This error is poor for the user. It refers to schema not being a URL. The better message would be that headers is not allowed with memory transport.
// @ts-expect-error headers not allowed with GraphQL schema
Graffle.create({ schema, headers: { 'x-foo': `bar` } })
})
})
})

describe(`output`, () => {
test(`when using envelope and transport is http, response property is available`, async ({ fetch }) => {
fetch.mockImplementationOnce(() => Promise.resolve(createResponse({ data: { id: `abc` } })))
const graffle = Graffle2.create({ schema: schemaUrl, output: { envelope: true } })
const graffle = Graffle2.create({ schema: endpoint, output: { envelope: true } })
const result = await graffle.query.id()
expectTypeOf(result.response).toEqualTypeOf<Response>()
expect(result.response.status).toEqual(200)
Expand Down
6 changes: 6 additions & 0 deletions src/layers/6_client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ export const createInternal = (
selection: rootTypeSelectionSet,
rootTypeName,
schema: input.schema,
transportConstructorConfig: {
headers: input.headers,
},
context: {
config: context.config,
transport,
Expand Down Expand Up @@ -274,6 +277,9 @@ export const createInternal = (
const initialInput = {
interface: interface_,
transport,
transportConstructorConfig: {
headers: input.headers,
},
document: rawInput.document,
schema: input.schema,
context: {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ export const CONTENT_TYPE_GQL = `application/graphql-response+json`
export const statusCodes = {
success: 200,
}

export const mergeHeadersInit = (headers: HeadersInit, additionalHeaders: HeadersInit) => {
const base = new Headers(headers)
const additional = new Headers(additionalHeaders)
for (const [key, value] of additional.entries()) {
base.set(key, value)
}
return base
}

0 comments on commit df6954a

Please sign in to comment.