Skip to content

Commit 5658370

Browse files
authored
feat(ts-client): custom scalar runtime codecs (#746)
1 parent be14f81 commit 5658370

39 files changed

+2078
-578
lines changed

src/ResultSet/ResultSet.test-d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22

33
import { expectTypeOf, test } from 'vitest'
4-
import type * as Schema from '../../tests/ts/_/schema/Schema.js'
4+
import type { $ } from '../../tests/ts/_/schema/generated/Index.js'
5+
import type * as Schema from '../../tests/ts/_/schema/generated/SchemaBuildtime.js'
56
import type { SelectionSet } from '../SelectionSet/__.js'
67
import type { ResultSet } from './__.js'
78

8-
type I = Schema.$.Index
9+
type I = $.Index
910
type RS<$selectionSet extends SelectionSet.Query<I>> = ResultSet.Query<$selectionSet, I>
1011

1112
// dprint-ignore
@@ -24,7 +25,7 @@ test(`general`, () => {
2425
expectTypeOf<RS<{ id: true; string: undefined }>>().toEqualTypeOf<{ id: null | string }>()
2526

2627
// Custom Scalar
27-
expectTypeOf<RS<{ date: true }>>().toEqualTypeOf<{ date: null | string }>()
28+
expectTypeOf<RS<{ date: true }>>().toEqualTypeOf<{ date: null | Date }>()
2829

2930
// List
3031
expectTypeOf<RS<{ listIntNonNull: true }>>().toEqualTypeOf<{ listIntNonNull: number[] }>()
@@ -41,11 +42,11 @@ test(`general`, () => {
4142
expectTypeOf<RS<{ objectNonNull: { id: true } }>>().toEqualTypeOf<{ objectNonNull: { id: string | null } }>()
4243

4344
// scalars-wildcard
44-
expectTypeOf<RS<{ objectNonNull: { $scalars: true } }>>().toEqualTypeOf<{ objectNonNull: { __typename: "Object"; string: null|string; int: null|number; float: null|number; boolean: null|boolean; id: null|string; } }>()
45+
expectTypeOf<RS<{ objectNonNull: { $scalars: true } }>>().toEqualTypeOf<{ objectNonNull: { __typename: "Object1"; string: null|string; int: null|number; float: null|number; boolean: null|boolean; id: null|string } }>()
4546
// scalars-wildcard with nested object
4647
expectTypeOf<RS<{ objectNested: { $scalars: true } }>>().toEqualTypeOf<{ objectNested: null | { __typename: "ObjectNested"; id: null|string } }>()
4748
// __typename
48-
expectTypeOf<RS<{ objectNonNull: { __typename: true } }>>().toEqualTypeOf<{ objectNonNull: { __typename: "Object" } }>()
49+
expectTypeOf<RS<{ objectNonNull: { __typename: true } }>>().toEqualTypeOf<{ objectNonNull: { __typename: "Object1" } }>()
4950

5051
// Union
5152
expectTypeOf<RS<{ fooBarUnion: { __typename: true } }>>().toEqualTypeOf<{ fooBarUnion: null | { __typename: "Foo" } | { __typename: "Bar" } }>()

src/ResultSet/ResultSet.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ import type { SelectionSet } from '../SelectionSet/__.js'
88

99
// dprint-ignore
1010
export type Query<$SelectionSetQuery extends object, $Index extends Schema.Index> =
11-
SimplifyDeep<Object<$SelectionSetQuery, Exclude<$Index['Root']['Query'], null>, $Index>>
11+
SimplifyDeep<Object$<$SelectionSetQuery, Exclude<$Index['Root']['Query'], null>, $Index>>
1212

1313
// dprint-ignore
1414
export type Mutation<$SelectionSetMutation extends object, $Index extends Schema.Index> =
15-
SimplifyDeep<Object<$SelectionSetMutation, Exclude<$Index['Root']['Mutation'], null>, $Index>>
15+
SimplifyDeep<Object$<$SelectionSetMutation, Exclude<$Index['Root']['Mutation'], null>, $Index>>
1616

1717
// dprint-ignore
1818
export type Subscription<$SelectionSetSubscription extends object, $Index extends Schema.Index> =
19-
SimplifyDeep<Object<$SelectionSetSubscription, Exclude<$Index['Root']['Subscription'], null>, $Index>>
19+
SimplifyDeep<Object$<$SelectionSetSubscription, Exclude<$Index['Root']['Subscription'], null>, $Index>>
2020

2121
// dprint-ignore
22-
export type Object<$SelectionSet, $Node extends Schema.Named.Object, $Index extends Schema.Index> =
22+
export type Object$<$SelectionSet, $Node extends Schema.Named.Object$2, $Index extends Schema.Index> =
2323
SelectionSet.IsSelectScalarsWildcard<$SelectionSet> extends true
2424

2525
/**
@@ -52,9 +52,9 @@ type Interface<$SelectionSet, $Node extends Schema.Named.Interface, $Index exten
5252
OnTypeFragment<$SelectionSet, $Node['implementors'][number], $Index>
5353

5454
// dprint-ignore
55-
type OnTypeFragment<$SelectionSet, $Node extends Schema.Named.Object, $Index extends Schema.Index> =
55+
type OnTypeFragment<$SelectionSet, $Node extends Schema.Named.Object$2, $Index extends Schema.Index> =
5656
$Node extends any // force distribution
57-
? Object<
57+
? Object$<
5858
GetKeyOr<$SelectionSet, `on${Capitalize<$Node['fields']['__typename']['type']['type']>}`, {}> & SelectionSet.OmitOnTypeFragments<$SelectionSet>,
5959
$Node,
6060
$Index
@@ -81,8 +81,8 @@ type FieldType<
8181
$Type extends Schema.Field.Type.Output.Nullable<infer $InnerType> ? null | FieldType<$SelectionSet, $InnerType, $Index> :
8282
$Type extends Schema.Field.Type.Output.List<infer $InnerType> ? Array<FieldType<$SelectionSet, $InnerType, $Index>> :
8383
$Type extends Schema.Named.Enum<infer _, infer $Members> ? $Members[number] :
84-
$Type extends Schema.Named.Scalar.Any ? ReturnType<$Type['constructor']> :
85-
$Type extends Schema.Named.Object ? Object<$SelectionSet,$Type,$Index> :
84+
$Type extends Schema.Named.Scalar.Any ? ReturnType<$Type['codec']['decode']> :
85+
$Type extends Schema.Named.Object$2 ? Object$<$SelectionSet,$Type,$Index> :
8686
$Type extends Schema.Named.Interface ? Interface<$SelectionSet,$Type,$Index> :
8787
$Type extends Schema.Named.Union ? Union<$SelectionSet,$Type,$Index> :
8888
TSError<'FieldType', `Unknown type`, { $Type: $Type }>
@@ -104,5 +104,5 @@ type FieldDirectiveSkip<$SelectionSet> =
104104

105105
// dprint-ignore
106106
export namespace Errors {
107-
export type UnknownFieldName<$FieldName extends string, $Object extends Schema.Named.Object> = TSError<'Object', `field "${$FieldName}" does not exist on object "${$Object['fields']['__typename']['type']['type']}"`>
107+
export type UnknownFieldName<$FieldName extends string, $Object extends Schema.Named.Object$2> = TSError<'Object', `field "${$FieldName}" does not exist on object "${$Object['fields']['__typename']['type']['type']}"`>
108108
}

src/Schema/Field/Field.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { NamedType } from '../NamedType/__.js'
22
import type { Scalar } from '../NamedType/Scalar/_.js'
3-
import * as Type from './Type.js'
3+
4+
import type * as Type from './Type.js'
45

56
export type * as Type from './Type.js'
67

@@ -22,16 +23,11 @@ export interface Args<$Fields extends any = any> {
2223
fields: $Fields
2324
}
2425

25-
export const field = <$Type extends Type.Output.Any, $Args extends null | Args = null>(
26-
type: $Type,
27-
args: $Args = null as $Args,
28-
): Field<$Type, $Args> => {
26+
export const Args = <F>(fields: F): Args<F> => {
2927
return {
30-
// eslint-disable-next-line
31-
// @ts-ignore infinite depth issue, can this be fixed?
32-
typeUnwrapped: Type.Output.unwrap(type),
33-
type,
34-
args,
28+
// @ts-expect-error todo
29+
allOptional: false,
30+
fields,
3531
}
3632
}
3733

src/Schema/Field/Type.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { TSError } from '../../lib/TSError.js'
22
import type { NamedType } from '../NamedType/__.js'
3+
import type { Args } from './Field.js'
34

5+
const buildTimeOnly: any = undefined
46
export namespace Base {
57
export interface Nullable<$Type> {
68
kind: 'nullable'
@@ -17,17 +19,29 @@ export namespace Output {
1719
kind: 'typename'
1820
type: $Type
1921
}
20-
export type Nullable<$Type extends Any> = Base.Nullable<$Type>
22+
23+
export type Named = NamedType.AnyOutput
24+
25+
export type Nullable<$Type extends Output.List<any> | __typename<any> | NamedType.AnyOutput> = Base.Nullable<$Type>
26+
2127
export type List<$Type extends Any> = Base.List<$Type>
2228

2329
export type Any = Output.List<any> | __typename<any> | Base.Nullable<any> | NamedType.AnyOutput
2430

2531
export const __typename = <$Type extends string>(type: $Type): __typename<$Type> => ({ kind: `typename`, type })
26-
export const nullable = <$Type extends __typename<any> | List<any>>(type: $Type): Nullable<$Type> => ({
32+
33+
export const Nullable = <$Type extends __typename<any> | List<any> | NamedType.AnyOutput>(
34+
type: MaybeThunk<$Type>,
35+
): Nullable<$Type> => ({
2736
kind: `nullable`,
37+
// at type level "type" is not a thunk
38+
type: type as any, // eslint-disable-line
39+
})
40+
41+
export const List = <$Type extends Any>(type: $Type): List<$Type> => ({
42+
kind: `list`,
2843
type,
2944
})
30-
export const list = <$Type extends Any>(type: $Type): List<$Type> => ({ kind: `list`, type })
3145

3246
// todo extends any because of infinite depth issue in generated schema types
3347
// dprint-ignore
@@ -37,15 +51,84 @@ export namespace Output {
3751
$Type extends __typename ? $Type['type'] :
3852
$Type extends NamedType.AnyOutput ? $Type :
3953
TSError<'Unwrap', 'Unknown $Type', { $Type: $Type }>
54+
// dprint-ignore
55+
export type UnwrapNonNull<$Type> =
56+
$Type extends Nullable<infer $innerType> ? UnwrapNonNull<$innerType>
57+
: $Type
58+
59+
export const unwrapNonNull = <$Type extends Any>(type: $Type): UnwrapNonNull<$Type> => {
60+
if (type.kind === `nullable`) return type.type
61+
return type as UnwrapNonNull<$Type>
62+
}
4063

4164
export const unwrap = <$Type extends Any>(type: $Type): Unwrap<$Type> => {
65+
console.log({ type })
4266
// @ts-expect-error fixme
4367
return type.kind === `named` ? type.type : unwrap(type.type)
4468
}
69+
70+
export const field = <$Type extends Any, $Args extends null | Args = null>(
71+
type: MaybeThunk<$Type>,
72+
args: $Args = null as $Args,
73+
): Field<$Type, $Args> => {
74+
return {
75+
typeUnwrapped: buildTimeOnly, // eslint-disable-line
76+
// At type level "type" is not a thunk
77+
type: type as any, // eslint-disable-line
78+
args,
79+
}
80+
}
81+
82+
export type Field<$Type extends any = any, $Args extends Args | null = Args | null> = {
83+
typeUnwrapped: Unwrap<$Type>
84+
type: $Type
85+
args: $Args
86+
}
4587
}
4688

4789
export namespace Input {
4890
export type Nullable<$InnerType extends Any = Any> = Base.Nullable<$InnerType>
4991
export type List<$InnerType extends Any = Any> = Base.List<$InnerType>
5092
export type Any = List<any> | Nullable<any> | NamedType.AnyInput
93+
94+
export const Nullable = <$InnerType extends Any>(type: MaybeThunk<$InnerType>): Nullable<$InnerType> => ({
95+
kind: `nullable`,
96+
// at type level "type" is not a thunk
97+
type: type as any, // eslint-disable-line
98+
})
99+
100+
export const List = <$InnerType extends Any>(type: $InnerType): List<$InnerType> => ({
101+
kind: `list`,
102+
type,
103+
})
104+
105+
export const field = <$Type extends Any>(type: $Type): Field<$Type> => {
106+
return {
107+
type: type,
108+
}
109+
}
110+
111+
// dprint-ignore
112+
type UnwrapNonNull<$Type> =
113+
$Type extends Nullable<infer $innerType> ? UnwrapNonNull<$innerType>
114+
: $Type
115+
116+
export const unwrapNullable = <$Type extends Any>(type: $Type): UnwrapNonNull<$Type> => {
117+
if (type.kind === `nullable`) return type.type
118+
// @ts-expect-error fixme
119+
return type
120+
}
121+
122+
export type Field<$Type extends any = any> = {
123+
// typeUnwrapped: Type.Output.Unwrap<$Type>
124+
type: $Type
125+
}
51126
}
127+
128+
type MaybeThunk<$Type> = $Type | Thunk<$Type>
129+
130+
type Thunk<$Type> = () => $Type
131+
132+
export const readMaybeThunk = <T>(maybeThunk: MaybeThunk<T>): T =>
133+
// @ts-expect-error fixme
134+
typeof maybeThunk === `function` ? maybeThunk() : maybeThunk

src/Schema/Index.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22

3-
import type { Object, Union } from './__.js'
3+
import type { Object$2, Union } from './__.js'
44

55
export interface Index {
66
Root: {
7-
Query: null | Object
8-
Mutation: null | Object
9-
Subscription: null | Object
10-
}
11-
objects: Record<string, Object>
12-
unions: {
13-
Union: null | Union
7+
Query: null | Object$2
8+
Mutation: null | Object$2
9+
Subscription: null | Object$2
1410
}
11+
objects: Record<string, Object$2>
12+
unions: Record<string, Union>
1513
}

src/Schema/NamedType/Interface.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22

3-
import type { Field } from '../Field/Field.js'
4-
import type { Object } from './Object.js'
3+
import type { Output } from '../__.js'
4+
import type { Object$2 } from './Object.js'
55

66
export type Interface<
77
$Name extends string = string,
8-
$Fields extends Record<string, Field<any>> = Record<string, Field<any>>,
9-
$Implementors extends [Object, ...Object[]] = [Object, ...Object[]],
8+
$Fields extends Record<string, Output.Field<any>> = Record<string, Output.Field<any>>,
9+
$Implementors extends [Object$2, ...Object$2[]] = [Object$2, ...Object$2[]],
1010
> = {
1111
kind: 'Interface'
1212
name: $Name
@@ -16,8 +16,8 @@ export type Interface<
1616

1717
export const Interface = <
1818
$Name extends string,
19-
$Fields extends Record<keyof $Fields, Field>,
20-
$Implementors extends [Object, ...Object[]],
19+
$Fields extends Record<keyof $Fields, Output.Field>,
20+
$Implementors extends [Object$2, ...Object$2[]],
2121
>(
2222
name: $Name,
2323
fields: $Fields,

src/Schema/NamedType/NamedType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { Digit, Letter } from '../../lib/prelude.js'
44
import type { Enum } from './Enum.js'
55
import type { InputObject } from './InputObjet.js'
66
import type { Interface } from './Interface.js'
7-
import type { Object } from './Object.js'
7+
import type { Object$2 } from './Object.js'
88
import type { Scalar } from './Scalar/_.js'
99
import type { Union } from './Union.js'
1010

11-
export type AnyOutput = Interface | Enum | Object | Scalar.Any | Union
11+
export type AnyOutput = Interface | Enum | Object$2 | Scalar.Any | Union
1212
export type AnyInput = Enum | Scalar.Any | InputObject
1313
export type Any = AnyOutput | AnyInput
1414

src/Schema/NamedType/Object.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
/* eslint-disable @typescript-eslint/ban-types */
22

3-
import type { Field } from '../Field/Field.js'
4-
import { field } from '../Field/Field.js'
53
import { Output } from '../Field/Type.js'
4+
import type { Scalar } from './_.js'
5+
import type { Enum } from './Enum.js'
66

7-
export type Fields = Record<string, Field<any>>
7+
export type Fields = Record<
8+
string,
9+
Output.Field<Output.List<any> | Output.Nullable<any> | Object$2 | Enum | Scalar.Any>
10+
>
811

912
export type ObjectFields = {
10-
__typename: Field<Output.__typename>
13+
__typename: Output.Field<Output.__typename>
1114
} & Fields
1215

13-
export interface Object<
16+
export interface Object$2<
1417
$Name extends string = string,
1518
$Fields extends Fields = Fields,
1619
> {
1720
kind: 'Object'
1821
fields: {
19-
__typename: Field<Output.__typename<$Name>>
22+
__typename: Output.Field<Output.__typename<$Name>>
2023
} & $Fields
2124
}
2225

23-
export const Object = <$Name extends string, $Fields extends Record<keyof $Fields, Field>>(
26+
// Naming this "Object" breaks Vitest: https://github.com/vitest-dev/vitest/issues/5463
27+
export const Object$ = <$Name extends string, $Fields extends Record<keyof $Fields, Output.Field>>(
2428
name: $Name,
2529
fields: $Fields,
26-
): Object<$Name, $Fields> => ({
30+
// eslint-disable-next-line
31+
// @ts-ignore infinite depth issue
32+
): Object$2<$Name, $Fields> => ({
2733
kind: `Object`,
2834
fields: {
29-
__typename: field(Output.__typename(name)),
35+
__typename: Output.field(Output.__typename(name)),
3036
...fields,
3137
},
3238
})
39+
40+
export { Object$ as Object }

0 commit comments

Comments
 (0)