Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve(request-pipeline): no conditional type on config #1260

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ const graffle = Graffle
.create({
schema: publicGraphQLSchemaEndpoints.Pokemon,
})
.anyware(async ({ pack }) => {
return await pack({
.anyware(({ pack }) => {
if (pack.input.transportType !== `http`) return pack()
return pack({
input: {
...pack.input,
headers: {
Expand All @@ -19,7 +20,7 @@ const graffle = Graffle
},
})
})
.anyware(async ({ exchange }) => {
.anyware(({ exchange }) => {
// todo wrong type / runtime value
show(exchange.input.request)
return exchange()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const graffle = Graffle
.with({
transport: { headers: { 'x-something-to-unset': `` } },
})
.anyware(async ({ exchange }) => {
.anyware(({ exchange }) => {
if (exchange.input.transportType !== `http`) return exchange()
show(exchange.input.request.headers)
return exchange()
})
Expand Down
2 changes: 2 additions & 0 deletions examples/50_anyware/anyware_jump-start__jump-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Graffle
// Notice how we **start** with the `exchange` hook, skipping the `encode` and `pack` hooks.
.anyware(async ({ exchange }) => {
// ^^^^^^^^
if (exchange.input.transportType !== `http`) return exchange()

const mergedHeaders = new Headers(exchange.input.request.headers)
mergedHeaders.set(`X-Custom-Header`, `123`)

Expand Down
3 changes: 3 additions & 0 deletions examples/50_anyware/anyware_short-circuit__short-circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Graffle
.anyware(async ({ encode }) => {
const { pack } = await encode()
const { exchange } = await pack()

if (exchange.input.transportType !== `http`) return exchange()

const mergedHeaders = new Headers(exchange.input.request.headers)
mergedHeaders.set(`X-Custom-Header`, `123`)
// Notice how we **end** with the `exchange` hook, skipping the `unpack` and `decode` hooks.
Expand Down
2 changes: 1 addition & 1 deletion src/client/builderExtensions/anyware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface Anyware<$Arguments extends Builder.Extension.Parameters<Builder
*/
anyware: (
interceptor: AnywareLib.Interceptor.InferConstructor<
requestPipeline.Spec<$Arguments['context']['config']>
requestPipeline.Spec
>,
) => Builder.Definition.MaterializeWithNewContext<$Arguments['chain'], $Arguments['context']>
}
Expand Down
6 changes: 4 additions & 2 deletions src/client/builderExtensions/use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ describe(`entrypoint pack`, () => {
return createResponse({ data: { id: db.id } })
})
const client2 = client.anyware(async ({ pack }) => {
return await pack({ input: { ...pack.input, headers } })
if (pack.input.transportType !== `http`) return pack()
return pack({ input: { ...pack.input, headers } })
})
expect(await client2.query.id()).toEqual(db.id)
})
Expand All @@ -51,8 +52,9 @@ describe(`entrypoint pack`, () => {
return createResponse({ data: { id: db.id } })
})
const client2 = client.anyware(async ({ pack }) => {
if (pack.input.transportType !== `http`) return pack()
const { exchange } = await pack({ input: { ...pack.input, headers } })
return await exchange({ input: exchange.input })
return exchange({ input: exchange.input })
})
expect(await client2.query.id()).toEqual(db.id)
})
Expand Down
52 changes: 26 additions & 26 deletions src/client/client.transport-http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,35 @@ import { Graffle as Pokemon } from '../../tests/_/schemas/pokemon/graffle/__.js'
import { schema as schemaPokemon } from '../../tests/_/schemas/pokemon/schema.js'
import { Graffle } from '../entrypoints/main.js'
import { ACCEPT_REC, CONTENT_TYPE_REC } from '../lib/grafaid/http/http.js'
import type { requestPipeline } from '../requestPipeline/__.js'
import { Transport, type TransportHttp } from '../types/Transport.js'
// import type { requestPipeline } from '../requestPipeline/__.js'
// import { Transport, type TransportHttp } from '../types/Transport.js'

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

test(`anyware hooks are typed to http transport`, () => {
Graffle.create({ schema }).anyware(async ({ encode }) => {
expectTypeOf(encode.input.transportType).toEqualTypeOf<TransportHttp>()
const { pack } = await encode()
expectTypeOf(pack.input.transportType).toEqualTypeOf(Transport.http)
const { exchange } = await pack()
expectTypeOf(exchange.input.transportType).toEqualTypeOf(Transport.http)
// todo we can statically track the method mode like we do the transport mode
expectTypeOf(exchange.input.request).toEqualTypeOf<
requestPipeline.Steps.CoreExchangePostRequest | requestPipeline.Steps.CoreExchangeGetRequest
>()
const { unpack } = await exchange()
expectTypeOf(unpack.input.transportType).toEqualTypeOf(Transport.http)
expectTypeOf(unpack.input.response).toEqualTypeOf<Response>()
const { decode } = await unpack()
expectTypeOf(decode.input.transportType).toEqualTypeOf(Transport.http)
expectTypeOf(decode.input.response).toEqualTypeOf<Response>()
const result = await decode()
if (!(result instanceof Error)) {
expectTypeOf(result.response).toEqualTypeOf<Response>()
}
return result
})
})
// test(`anyware hooks are typed to http transport`, () => {
// Graffle.create({ schema }).anyware(async ({ encode }) => {
// expectTypeOf(encode.input.transportType).toEqualTypeOf<TransportHttp>()
// const { pack } = await encode()
// expectTypeOf(pack.input.transportType).toEqualTypeOf(Transport.http)
// const { exchange } = await pack()
// expectTypeOf(exchange.input.transportType).toEqualTypeOf(Transport.http)
// // todo we can statically track the method mode like we do the transport mode
// expectTypeOf(exchange.input.request).toEqualTypeOf<
// requestPipeline.Steps.CoreExchangePostRequest | requestPipeline.Steps.CoreExchangeGetRequest
// >()
// const { unpack } = await exchange()
// expectTypeOf(unpack.input.transportType).toEqualTypeOf(Transport.http)
// expectTypeOf(unpack.input.response).toEqualTypeOf<Response>()
// const { decode } = await unpack()
// expectTypeOf(decode.input.transportType).toEqualTypeOf(Transport.http)
// expectTypeOf(decode.input.response).toEqualTypeOf<Response>()
// const result = await decode()
// if (!(result instanceof Error)) {
// expectTypeOf(result.response).toEqualTypeOf<Response>()
// }
// return result
// })
// })

test(`when envelope is used then response property is present even if relying on schema url default`, async () => {
const service = await serveSchema({ schema: schemaPokemon })
Expand Down
50 changes: 25 additions & 25 deletions src/client/client.transport-memory.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import { expectTypeOf } from 'vitest'
// 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 '../types/Transport.js'
// import { Transport } from '../types/Transport.js'

test(`anyware hooks are typed to memory transport`, () => {
Graffle.create({ schema }).anyware(async ({ encode }) => {
expectTypeOf(encode.input.transportType).toEqualTypeOf(Transport.memory)
const { pack } = await encode()
expectTypeOf(pack.input.transportType).toEqualTypeOf(Transport.memory)
const { exchange } = await pack()
expectTypeOf(exchange.input.transportType).toEqualTypeOf(Transport.memory)
const { unpack } = await exchange()
expectTypeOf(unpack.input.transportType).toEqualTypeOf(Transport.memory)
// @ts-expect-error any
unpack.input.response
const { decode } = await unpack()
expectTypeOf(decode.input.transportType).toEqualTypeOf(Transport.memory)
// @ts-expect-error any
decode.input.response
const result = await decode()
if (!(result instanceof Error)) {
// @ts-expect-error any
result.response
}
return result
})
})
// test(`anyware hooks are typed to memory transport`, () => {
// Graffle.create({ schema }).anyware(async ({ encode }) => {
// expectTypeOf(encode.input.transportType).toEqualTypeOf<Transport>()
// const { pack } = await encode()
// expectTypeOf(pack.input.transportType).toEqualTypeOf(Transport.memory)
// const { exchange } = await pack()
// expectTypeOf(exchange.input.transportType).toEqualTypeOf(Transport.memory)
// const { unpack } = await exchange()
// expectTypeOf(unpack.input.transportType).toEqualTypeOf(Transport.memory)
// // @ts-expect-error any
// unpack.input.response
// const { decode } = await unpack()
// expectTypeOf(decode.input.transportType).toEqualTypeOf(Transport.memory)
// // @ts-expect-error any
// decode.input.response
// const result = await decode()
// if (!(result instanceof Error)) {
// // @ts-expect-error any
// result.response
// }
// return result
// })
// })

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.
Expand Down
43 changes: 17 additions & 26 deletions src/requestPipeline/RequestPipeline.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { FormattedExecutionResult, GraphQLSchema } from 'graphql'
import type { Context } from '../client/context.js'
import type { GraffleExecutionResultEnvelope } from '../client/handleOutput.js'
import type { Config } from '../client/Settings/Config.js'
import { MethodMode, type MethodModeGetReads } from '../client/transportHttp/request.js'
import type { MethodModePost } from '../client/transportHttp/request.js'
import { Anyware } from '../lib/anyware/__.js'
Expand Down Expand Up @@ -207,14 +206,14 @@ export namespace requestPipeline {
// Possible from http transport fetch with abort controller.
// | DOMException

export type Result<$Config extends Config = Config> = Anyware.Pipeline.InferResultFromSpec<Spec<$Config>>
export type Result = Anyware.Pipeline.InferResultFromSpec<Spec>

export type Spec<$Config extends Config = Config> = Anyware.PipelineSpecFromSteps<[
Steps.HookDefEncode<$Config>,
Steps.HookDefPack<$Config>,
Steps.HookDefExchange<$Config>,
Steps.HookDefUnpack<$Config>,
Steps.HookDefDecode<$Config>,
export type Spec = Anyware.PipelineSpecFromSteps<[
Steps.HookDefEncode,
Steps.HookDefPack,
Steps.HookDefExchange,
Steps.HookDefUnpack,
Steps.HookDefDecode,
]>

export namespace Steps {
Expand All @@ -224,40 +223,35 @@ export namespace requestPipeline {

// dprint-ignore

type TransportInput<$Config extends Config, $HttpProperties = {}, $MemoryProperties = {}> =
type TransportInput<$HttpProperties = {}, $MemoryProperties = {}> =
| (
TransportHttp extends $Config['transport']['type']
? ({
({
transportType: TransportHttp
url: string | URL
} & $HttpProperties)
: never
)
| (
TransportMemory extends $Config['transport']['type']
? ({
({
transportType: TransportMemory
schema: GraphQLSchema
} & $MemoryProperties)
: never
)

// ---------------------------

export type HookDefEncode<$Config extends Config = Config> = {
export type HookDefEncode = {
name: `encode`
input:
& { request: Grafaid.RequestAnalyzedInput }
& HookInputBase
& TransportInput<$Config>
& TransportInput
}

export type HookDefPack<$Config extends Config = Config> = {
export type HookDefPack = {
name: `pack`
input:
& HookInputBase
& TransportInput<
$Config,
// todo why is headers here but not other http request properties?
{ headers?: HeadersInit }
>
Expand All @@ -274,41 +268,38 @@ export namespace requestPipeline {
}
}

export type HookDefExchange<$Config extends Config> = {
export type HookDefExchange = {
name: `exchange`
slots: {
fetch: (request: Request) => Response | Promise<Response>
}
input:
& HookInputBase
& TransportInput<
$Config,
{ request: CoreExchangePostRequest | CoreExchangeGetRequest; headers?: HeadersInit },
{ request: Grafaid.HTTP.RequestConfig }
>
}

export type HookDefUnpack<$Config extends Config> = {
export type HookDefUnpack = {
name: `unpack`
input:
& HookInputBase
& TransportInput<
$Config,
{ response: Response },
{ result: FormattedExecutionResult }
>
}

export type HookDefDecode<$Config extends Config> = {
export type HookDefDecode = {
name: `decode`
input:
& HookInputBase
& TransportInput<
$Config,
{ response: Response }
>
& { result: FormattedExecutionResult }
output: GraffleExecutionResultEnvelope<$Config>
output: GraffleExecutionResultEnvelope
}

/**
Expand Down
3 changes: 1 addition & 2 deletions tests/_/SpyExtension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { beforeEach } from 'vitest'
import { createExtension } from '../../src/entrypoints/main.js'
import type { Config } from '../../src/entrypoints/utilities-for-generated.js'
import type { requestPipeline } from '../../src/requestPipeline/__.js'

interface SpyData {
Expand All @@ -11,7 +10,7 @@ interface SpyData {
input: requestPipeline.Steps.HookDefPack['input'] | null
}
exchange: {
input: requestPipeline.Steps.HookDefExchange<Config>['input'] | null
input: requestPipeline.Steps.HookDefExchange['input'] | null
}
}

Expand Down