Skip to content

Commit

Permalink
feat(document-builder): support interface chains (#1276)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Dec 3, 2024
1 parent c974cbd commit d95f38a
Show file tree
Hide file tree
Showing 49 changed files with 7,438 additions and 1,107 deletions.
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export default tsEslint.config({
['@typescript-eslint/no-unsafe-call']: 'off',
['@typescript-eslint/no-unsafe-member-access']: 'off',
['@typescript-eslint/ban-types']: 'off',
['@typescript-eslint/no-unnecessary-condition']: 'off',
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const graffle = Graffle
url: publicGraphQLSchemaEndpoints.Pokemon,
})
.anyware(({ pack }) => {
// eslint-disable-next-line
if (pack.input.transportType !== `http`) return pack()
return pack({
input: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const graffle = Graffle
headers: { 'x-something-to-unset': `` },
})
.anyware(({ exchange }) => {
// eslint-disable-next-line
if (exchange.input.transportType !== `http`) return exchange()
show(exchange.input.request.headers)
return exchange()
Expand Down
1 change: 0 additions & 1 deletion examples/50_anyware/anyware_jump-start__jump-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Graffle
// Notice how we **start** with the `exchange` hook, skipping the `encode` and `pack` hooks.
.anyware(async ({ exchange }) => {
// ^^^^^^^^
// eslint-disable-next-line
if (exchange.input.transportType !== `http`) return exchange()

const mergedHeaders = new Headers(exchange.input.request.headers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Graffle
const { pack } = await encode()
const { exchange } = await pack()

// eslint-disable-next-line
if (exchange.input.transportType !== `http`) return exchange()

const mergedHeaders = new Headers(exchange.input.request.headers)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"clean": "tsc --build --clean && rm -rf build",
"test:unit": "vitest --exclude tests/examples --exclude tests/e2e",
"test:examples": "vitest --config vitest.examples.config.ts --dir tests/examples",
"test:e2e": "vitest --dir tests/e2e --testTimeout 20000",
"test:e2e": "vitest --dir tests/e2e --testTimeout 30000 --run",
"test": "vitest",
"test:web": "vitest --environment jsdom",
"test:types": "vitest --typecheck --dir src --testNamePattern .*.test-d.ts",
Expand Down
91 changes: 47 additions & 44 deletions src/client/properties/transport.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PartialOrUndefined } from '../../lib/prelude.js'
import type { ClientTransports } from '../../types/context.js'
import type { ClientTransports, ClientTransportsConfiguration } from '../../types/context.js'
import { type Context } from '../../types/context.js'
import { type Client, type ExtensionChainableRegistry } from '../client.js'
import { createProperties } from '../helpers.js'
Expand Down Expand Up @@ -53,7 +53,7 @@ export type TransportMethod<
*/
<
name extends ClientTransports.GetNames<$Context['transports']>,
config extends $Context['transports']['registry'][name]['config'] = {}
config extends undefined | $Context['transports']['registry'][name]['config'] = undefined
>
(name: name, config?: config):
Client<
Expand All @@ -67,14 +67,15 @@ export type TransportMethod<
? $Context['transports']['configurations']
: {
[configKey in keyof $Context['transports']['configurations']]:
configKey extends $Context['transports']['current']
configKey extends name
?
{
[configValueKey in keyof $Context['transports']['configurations'][configKey]]:
configValueKey extends keyof config
? config[configValueKey]
? unknown
: $Context['transports']['configurations'][configKey][configValueKey]
}
} & config
& {debug:config}
: $Context['transports']['configurations'][configKey]
}
}
Expand All @@ -88,49 +89,51 @@ export type TransportMethod<

export const transportProperties = createProperties((builder, state) => {
return {
transport: (transport: string | object) => {
if (typeof transport === `string`) {
return builder({
...state,
transports: {
...state.transports,
current: transport,
},
})
}

if (!state.transports.current) {
transport: (...args: [config: object] | [transportName: string, config?: object]) => {
const transportName = typeof args[0] === `string` ? args[0] : state.transports.current
const transportConfig = (typeof args[0] === `string` ? args[1] : args[0]) ?? {}
if (!transportName) {
throw new Error(`No transport is currently set.`)
}
const newContext = reducerTransportConfig(state, transportName, transportConfig)
return builder(newContext)
},
} as any
})

const newConfiguration = {
...state.transports.configurations[state.transports.current] ?? {},
...transport,
}
// hack: transport need to provide this function
if (state.transports.current === `http`) {
const reducerTransportConfig = (
state: Context,
transportName: string,
config: ClientTransportsConfiguration,
): Context => {
const newConfiguration = {
...state.transports.configurations[transportName] ?? {},
...config,
}

// hack: transport need to provide this function
if (transportName === `http`) {
// @ts-expect-error
if (config.headers) {
// @ts-expect-error
newConfiguration.headers = {
// @ts-expect-error
...state.transports.configurations[transportName]?.headers,
// @ts-expect-error
if (transport.headers) {
// @ts-expect-error
newConfiguration.headers = {
// @ts-expect-error
...state.transports.configurations[state.transports.current]?.headers,
// @ts-expect-error
...transport.headers,
}
}
...config.headers,
}
}
}

return builder({
...state,
transports: {
...state.transports,
configurations: {
...state.transports.configurations,
[state.transports.current]: newConfiguration,
},
},
})
return {
...state,
transports: {
...state.transports,
current: transportName,
configurations: {
...state.transports.configurations,
[transportName]: newConfiguration,
},
},
} as any
})
}
}
8 changes: 4 additions & 4 deletions src/documentBuilder/InferResult/Alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { OutputField } from './OutputField.js'
// dprint-ignore
export type Alias<
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike,
$SelectionSet,
> =
UnionMerge<
Expand All @@ -33,7 +33,7 @@ type InferSelectAlias<
$SelectAlias extends Select.SelectAlias.SelectAlias,
$FieldName extends string,
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike,
> =
$SelectAlias extends Select.SelectAlias.SelectAliasOne ? InferSelectAliasOne<$SelectAlias, $FieldName, $Schema, $Node> :
$SelectAlias extends Select.SelectAlias.SelectAliasMultiple ? InferSelectAliasMultiple<$SelectAlias, $FieldName, $Schema, $Node> :
Expand All @@ -43,7 +43,7 @@ type InferSelectAliasMultiple<
$SelectAliasMultiple extends Select.SelectAlias.SelectAliasMultiple,
$FieldName extends string,
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike,
> = Tuple.IntersectItems<
{
[_ in keyof $SelectAliasMultiple]: InferSelectAliasOne<$SelectAliasMultiple[_], $FieldName, $Schema, $Node>
Expand All @@ -54,7 +54,7 @@ type InferSelectAliasOne<
$SelectAliasOne extends Select.SelectAlias.SelectAliasOne,
$FieldName extends string,
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike,
> = {
[_ in $SelectAliasOne[0]]: OutputField<$SelectAliasOne[1], $Node['fields'][$FieldName], $Schema>
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { type GetKeyOr } from '../../lib/prelude.js'
import type { Schema } from '../../types/Schema/__.js'
import type { Select } from '../Select/__.js'
import type { OutputObject } from './OutputObject.js'
import type { OutputObjectLike } from './OutputObjectLike.js'

// dprint-ignore
export type InlineFragmentTypeConditional<$SelectionSet, $Node extends Schema.OutputObject, $Schema extends Schema> =
export type InlineFragmentTypeConditional<$SelectionSet, $Node extends Schema.InlineFragmentTypeConditionTypes, $Schema extends Schema> =
$Node extends any // force distribution
? OutputObject<
// ? $Node extends Schema.Interface
// ? {
// debug: GetKeyOr<
// $SelectionSet,
// `${Select.InlineFragment.TypeConditionalKeyPrefix}${$Node['name']}`,
// {}
// >
// & Select.InlineFragment.OmitInlineFragmentsWithTypeConditions<$SelectionSet>,
// debug2: $Node['fields']
// }
? OutputObjectLike<
& GetKeyOr<
$SelectionSet,
`${Select.InlineFragment.TypeConditionalKeyPrefix}${$Node['name']}`,
Expand Down
2 changes: 1 addition & 1 deletion src/documentBuilder/InferResult/Interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Schema } from '../../types/Schema/__.js'
import type { InlineFragmentTypeConditional } from './InlineFragment.js'
import type { InlineFragmentTypeConditional } from './InlineFragmentTypeConditional.js'

// dprint-ignore
export type Interface<$SelectionSet, $Schema extends Schema, $Node extends Schema.Interface> =
Expand Down
4 changes: 2 additions & 2 deletions src/documentBuilder/InferResult/OutputField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TSErrorDescriptive } from '../../lib/ts-error.js'
import type { Schema } from '../../types/Schema/__.js'
import type { InlineType } from '../../types/SchemaDrivenDataMap/InlineType.js'
import type { Interface } from './Interface.js'
import type { OutputObject } from './OutputObject.js'
import type { OutputObjectLike } from './OutputObjectLike.js'
import type { Union } from './Union.js'

// dprint-ignore
Expand All @@ -19,7 +19,7 @@ type FieldType<
$Node extends Schema.NamedOutputTypes,
> =
$Node extends Schema.OutputObject ? $SelectionSet extends object
? OutputObject<$SelectionSet, $Schema, $Node>
? OutputObjectLike<$SelectionSet, $Schema, $Node>
: TSErrorDescriptive<'FieldType', 'When $Node extends Schema.OutputObject then $SelectionSet must extend object', { $Type: $Node; $SelectionSet: $SelectionSet; $Schema:$Schema } > :
$Node extends Schema.Scalar ? Schema.Scalar.GetDecoded<$Node> : // TODO use TS compiler API to extract this type at build time.
$Node extends Schema.Scalar.ScalarCodecless ? Schema.Scalar.GetDecoded<GetCodecForCodecless<$Schema, $Node>> :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import type { OutputField } from './OutputField.js'
import type { ScalarsWildcard } from './ScalarsWildcard.js'

// dprint-ignore
export type OutputObject<
export type OutputObjectLike<
$SelectionSet extends object,
$Schema extends Schema,
$Node extends Schema.OutputObject
$Node extends Schema.OutputObjectLike
> =
& OutputObject_<$SelectionSet, $Schema, $Node>
& OutputObjectLike_<$SelectionSet, $Schema, $Node>
& InlineFragmentKeys<$SelectionSet, $Schema, $Node>

// dprint-ignore
type OutputObject_<
type OutputObjectLike_<
$SelectionSet extends object,
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike
> =
Select.SelectScalarsWildcard.IsSelectScalarsWildcard<$SelectionSet> extends true
// todo this needs to be an extension and/or only available when sddm is present
Expand All @@ -38,7 +38,7 @@ type OutputObject_<
& Alias<$Schema, $Node, $SelectionSet>

// dprint-ignore
type OtherKeys<$SelectionSet, $Schema extends Schema, $Node extends Schema.OutputObject> =
type OtherKeys<$SelectionSet, $Schema extends Schema, $Node extends Schema.OutputObjectLike> =
{
[
$Field in keyof $SelectionSet as
Expand Down Expand Up @@ -93,7 +93,7 @@ type PickApplicableFieldKeys<$SelectionSet> = StringKeyof<
}
>
// dprint-ignore
type InlineFragmentKeys<$SelectionSet extends object, $Schema extends Schema, $Node extends Schema.OutputObject> =
type InlineFragmentKeys<$SelectionSet extends object, $Schema extends Schema, $Node extends Schema.OutputObjectLike> =
InlineFragmentKey_<
AssertExtendsObject<
GetOrNever<$SelectionSet, Select.InlineFragment.Key>
Expand All @@ -103,21 +103,21 @@ type InlineFragmentKeys<$SelectionSet extends object, $Schema extends Schema, $N
>

// dprint-ignore
type InlineFragmentKey_<$SelectionSet extends object, $Schema extends Schema, $Node extends Schema.OutputObject> =
type InlineFragmentKey_<$SelectionSet extends object, $Schema extends Schema, $Node extends Schema.OutputObjectLike> =
IsNever<$SelectionSet> extends true
? {}
: IsNeverViaDirective<$SelectionSet> extends true
? {}
: IsOptionalViaDirective<$SelectionSet> extends true
? Partial<
OutputObject_<OmitDirectiveAndArgumentKeys<$SelectionSet>, $Schema, $Node>
OutputObjectLike_<OmitDirectiveAndArgumentKeys<$SelectionSet>, $Schema, $Node>
>
: OutputObject_<OmitDirectiveAndArgumentKeys<$SelectionSet>, $Schema, $Node>
: OutputObjectLike_<OmitDirectiveAndArgumentKeys<$SelectionSet>, $Schema, $Node>

export namespace Errors {
export type UnknownKey<
$Key extends PropertyKey,
$Object extends Schema.OutputObject,
$Object extends Schema.OutputObjectLike,
> = TSErrorDescriptive<'Object', `field "${PropertyKeyToString<$Key>}" does not exist on object "${$Object['name']}"`>
}

Expand Down
4 changes: 2 additions & 2 deletions src/documentBuilder/InferResult/ScalarsWildcard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import type { OutputField } from './OutputField.js'
export type ScalarsWildcard<
$SelectionSet,
$Schema extends Schema,
$Node extends Schema.OutputObject,
$Node extends Schema.OutputObjectLike,
> = {
[$Key in keyof PickScalarFields<$Node>]: OutputField<$SelectionSet, $Node['fields'][$Key], $Schema>
}

// dprint-ignore
type PickScalarFields<$Object extends Schema.OutputObject> = {
type PickScalarFields<$Object extends Schema.OutputObjectLike> = {
[
$Key in keyof $Object['fields']
as Schema.GetNamedType<$Object['fields'][$Key]['namedType']> extends Schema.ScalarLikeTypes
Expand Down
2 changes: 1 addition & 1 deletion src/documentBuilder/InferResult/Union.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Schema } from '../../types/Schema/__.js'
import type { InlineFragmentTypeConditional } from './InlineFragment.js'
import type { InlineFragmentTypeConditional } from './InlineFragmentTypeConditional.js'

// dprint-ignore
export type Union<$SelectionSet, $Schema extends Schema, $Node extends Schema.Union> =
Expand Down
2 changes: 1 addition & 1 deletion src/documentBuilder/InferResult/_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ export * from './Alias.js'
export * from './Interface.js'
export * from './operation.js'
export * from './OutputField.js'
export * from './OutputObject.js'
export * from './OutputObjectLike.js'
export * from './ScalarsWildcard.js'
export * from './Union.js'
15 changes: 14 additions & 1 deletion src/documentBuilder/InferResult/__.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ assertEqual<$<{ unionFooBarWithArgs: { $: { id: `abc` }, ___on_Foo: { id: true }
// Union fragments Case
assertEqual<$<{ lowerCaseUnion: { __typename:true, ___on_lowerCaseObject: { id: true }, ___on_lowerCaseObject2: { int: true } } }>, { lowerCaseUnion: null | { __typename: 'lowerCaseObject'; id: null|string } | { __typename: 'lowerCaseObject2'; int: null|number } }>()


// Interface
assertEqual<$<{ interface: { ___on_Object1ImplementingInterface: { id: true }}}>, { interface: null | { id: null | string} | {} }>()
assertEqual<$<{ interface: { ___on_Object1ImplementingInterface: { int: true }}}>, { interface: null | { int: null | number} | {} }>()
Expand Down Expand Up @@ -177,4 +176,18 @@ type Result = $<{ id2: true }>
// unknown field
assertEqual<Result, { id2: InferResult.Errors.UnknownKey<'id2', Schema.Query> }>()

// Interface Hierarchy


// Can select own fields directly
assertEqual<$<{ interfaceHierarchyGrandparents: { a: true } }>, { interfaceHierarchyGrandparents: { a: string }[] }>()

// Can use inline fragment of an implementor interface
assertEqual<$<{ interfaceHierarchyGrandparents: { ___on_InterfaceParent: { a: true } } }>, { interfaceHierarchyGrandparents: ({}|{ a: string })[] }>()
assertEqual<$<{ interfaceHierarchyGrandparents: { ___on_InterfaceChildA: { a: true } } }>, { interfaceHierarchyGrandparents: ({}|{ a: string })[] }>()
assertEqual<$<{ interfaceHierarchyGrandparents: { ___on_InterfaceChildB: { a: true } } }>, { interfaceHierarchyGrandparents: ({}|{ a: string })[] }>()

// @ts-expect-error cannot select child interface field
assertEqual<$<{ interfaceHierarchyGrandparents: { ___on_InterfaceParent: { c1: true } } }>, { interfaceHierarchyGrandparents: { a: string }[] }>()

}
Loading

0 comments on commit d95f38a

Please sign in to comment.