Skip to content

Commit 7b06232

Browse files
authored
feat(ts-client): mode to return all errors (#796)
1 parent d8bc6d5 commit 7b06232

32 files changed

Lines changed: 476 additions & 101 deletions

src/client/Config.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import type { ExecutionResult } from 'graphql'
2+
import type { GraphQLExecutionResultError } from '../lib/graphql.js'
3+
import type { SetProperty } from '../lib/prelude.js'
24

3-
// todo: dataAndErrors | dataAndSchemaErrors
4-
export type ReturnModeType = 'graphql' | 'data'
5+
export type ReturnModeType =
6+
| ReturnModeTypeGraphQL
7+
| ReturnModeTypeData
8+
| ReturnModeTypeDataAndSchemaErrors
9+
| ReturnModeTypeDataAllErrors
10+
11+
export type ReturnModeTypeBase = ReturnModeTypeGraphQL | ReturnModeTypeData | ReturnModeTypeDataAllErrors
12+
13+
export type ReturnModeTypeGraphQL = 'graphql'
14+
15+
export type ReturnModeTypeData = 'data'
16+
17+
export type ReturnModeTypeDataAllErrors = 'dataAndAllErrors'
18+
19+
export type ReturnModeTypeDataAndSchemaErrors = 'dataAndSchemaErrors'
520

621
export type OptionsInput = {
722
returnMode: ReturnModeType | undefined
@@ -16,9 +31,15 @@ export type Config = {
1631
}
1732

1833
export type ApplyInputDefaults<Input extends OptionsInput> = {
19-
[Key in keyof OptionsInputDefaults]: undefined extends Input[Key] ? OptionsInputDefaults[Key] : Input[Key]
34+
[Key in keyof OptionsInputDefaults]: undefined extends Input[Key] ? OptionsInputDefaults[Key]
35+
: Exclude<Input[Key], undefined>
2036
}
2137

2238
// dprint-ignore
23-
export type ReturnMode<$Config extends Config, $Data> =
24-
$Config['returnMode'] extends 'graphql' ? ExecutionResult<$Data> : $Data
39+
export type ReturnMode<$Config extends Config, $Data, $DataRaw = undefined> =
40+
$Config['returnMode'] extends 'graphql' ? ExecutionResult<$DataRaw extends undefined ? $Data : $DataRaw> :
41+
$Config['returnMode'] extends 'data' ? $Data :
42+
$Data | GraphQLExecutionResultError
43+
44+
export type OrThrowifyConfig<$Config extends Config> = $Config['returnMode'] extends 'graphql' ? $Config
45+
: SetProperty<$Config, 'returnMode', 'data'>

src/client/RootTypeMethods.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
1-
import type { ExecutionResult } from 'graphql'
1+
import type { OperationName } from '../lib/graphql.js'
22
import type { Exact } from '../lib/prelude.js'
33
import type { TSError } from '../lib/TSError.js'
44
import type { InputFieldsAllNullable, Schema } from '../Schema/__.js'
5-
import type { Config, OptionsInputDefaults, ReturnMode } from './Config.js'
5+
import type { Config, OrThrowifyConfig, ReturnMode } from './Config.js'
66
import type { ResultSet } from './ResultSet/__.js'
77
import type { SelectionSet } from './SelectionSet/__.js'
88

9-
type OperationName = 'query' | 'mutation'
9+
type RootTypeFieldContext = {
10+
Config: Config
11+
Index: Schema.Index
12+
RootTypeName: Schema.RootTypeName
13+
RootTypeFieldName: string
14+
Field: Schema.SomeField
15+
}
1016

1117
// dprint-ignore
12-
export type GetRootTypeMethods<$Config extends OptionsInputDefaults, $Index extends Schema.Index> = {
18+
export type GetRootTypeMethods<$Config extends Config, $Index extends Schema.Index> = {
1319
[$OperationName in OperationName as $Index['Root'][Capitalize<$OperationName>] extends null ? never : $OperationName]:
1420
RootTypeMethods<$Config, $Index, Capitalize<$OperationName>>
1521
}
1622

1723
// dprint-ignore
18-
export type RootTypeMethods<$Config extends OptionsInputDefaults, $Index extends Schema.Index, $RootTypeName extends Schema.RootTypeName> =
24+
export type RootTypeMethods<$Config extends Config, $Index extends Schema.Index, $RootTypeName extends Schema.RootTypeName> =
1925
$Index['Root'][$RootTypeName] extends Schema.Object$2 ?
2026
(
2127
& {
2228
$batch: RootMethod<$Config, $Index, $RootTypeName>
29+
$batchOrThrow: RootMethod<OrThrowifyConfig<$Config>, $Index, $RootTypeName>
2330
}
2431
& {
2532
[$RootTypeFieldName in keyof $Index['Root'][$RootTypeName]['fields'] & string]:
@@ -31,6 +38,16 @@ export type RootTypeMethods<$Config extends OptionsInputDefaults, $Index extends
3138
Field: $Index['Root'][$RootTypeName]['fields'][$RootTypeFieldName]
3239
}>
3340
}
41+
& {
42+
[$RootTypeFieldName in keyof $Index['Root'][$RootTypeName]['fields'] & string as `${$RootTypeFieldName}OrThrow`]:
43+
RootTypeFieldMethod<{
44+
Config: OrThrowifyConfig<$Config>,
45+
Index: $Index,
46+
RootTypeName: $RootTypeName,
47+
RootTypeFieldName: $RootTypeFieldName
48+
Field: $Index['Root'][$RootTypeName]['fields'][$RootTypeFieldName]
49+
}>
50+
}
3451
)
3552
: TSError<'RootTypeMethods', `Your schema does not have the root type "${$RootTypeName}".`>
3653

@@ -65,14 +82,4 @@ type ScalarFieldMethod<$Context extends RootTypeFieldContext> =
6582
(() => Promise<ReturnModeForFieldMethod<$Context, ResultSet.Field<true, $Context['Field'], $Context['Index']>>>)
6683
// dprint-ignore
6784
type ReturnModeForFieldMethod<$Context extends RootTypeFieldContext, $Data> =
68-
$Context['Config']['returnMode'] extends 'data'
69-
? $Data
70-
: ExecutionResult<{ [k in $Context['RootTypeFieldName']] : $Data }>
71-
72-
type RootTypeFieldContext = {
73-
Config: Config
74-
Index: Schema.Index
75-
RootTypeName: Schema.RootTypeName
76-
RootTypeFieldName: string
77-
Field: Schema.SomeField
78-
}
85+
ReturnMode<$Context['Config'], $Data, { [k in $Context['RootTypeFieldName']] : $Data }>

src/client/client.document.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ test(`document with one mutation`, async () => {
5252
await expect(run(undefined)).resolves.toEqual({ id: db.id1 })
5353
})
5454

55+
test(`error`, async () => {
56+
const { run } = client.document({ foo: { query: { error: true } } })
57+
await expect(run()).rejects.toMatchObject({ errors: [{ message: `Something went wrong.` }] })
58+
})
59+
5560
test(`document with one mutation and one query`, async () => {
5661
const { run } = client.document({
5762
foo: {

src/client/client.input.test-d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { test } from 'vitest'
2+
import { $Index } from '../../tests/_/schema/generated/SchemaRuntime.js'
3+
import { schema } from '../../tests/_/schema/schema.js'
4+
import { create } from './client.js'
5+
6+
test(`works`, () => {
7+
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `graphql` })
8+
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `data` })
9+
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `dataAndAllErrors` })
10+
// @ts-expect-error bad returnMode
11+
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `dataAndSchemaErrors` })
12+
13+
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `graphql` })
14+
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `data` })
15+
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `dataAndAllErrors` })
16+
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `dataAndSchemaErrors` })
17+
18+
create({ schemaIndex: $Index, schema, returnMode: `graphql` })
19+
create({ schemaIndex: $Index, schema, returnMode: `data` })
20+
create({ schemaIndex: $Index, schema, returnMode: `dataAndAllErrors` })
21+
create({ schemaIndex: $Index, schema, returnMode: `dataAndSchemaErrors` })
22+
})

src/client/client.returnMode.test-d.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,24 @@ import { describe } from 'node:test'
55
import { expectTypeOf, test } from 'vitest'
66
import { $Index as schemaIndex } from '../../tests/_/schema/generated/SchemaRuntime.js'
77
import { schema } from '../../tests/_/schema/schema.js'
8+
import { GraphQLExecutionResultError } from '../lib/graphql.js'
89
import { create } from './client.js'
910

1011
// dprint-ignore
11-
describe('default', () => {
12+
describe('default is data', () => {
1213
const client = create({ schema, schemaIndex })
1314
test(`document`, async () => {
1415
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null }>()
1516
})
16-
test(`raw`, async () => {
17-
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
18-
})
1917
test('query field method', async () => {
2018
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
2119
})
2220
test('query $batch', async () => {
2321
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
2422
})
23+
test(`raw`, async () => {
24+
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
25+
})
2526
})
2627

2728
// dprint-ignore
@@ -30,14 +31,42 @@ describe('data', () => {
3031
test(`document`, async () => {
3132
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null }>()
3233
})
34+
test('query field method', async () => {
35+
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
36+
})
37+
test('query $batch', async () => {
38+
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
39+
})
40+
test('result',async () => {
41+
const x = await client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})
42+
await expectTypeOf(client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})).resolves.toEqualTypeOf<null | { infoId: string | null } | { infoInt: number | null } | { id: null | string }>()
43+
})
3344
test(`raw`, async () => {
3445
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
3546
})
47+
})
48+
49+
// dprint-ignore
50+
describe('dataAndAllErrors', () => {
51+
const client = create({ schema, schemaIndex, returnMode: 'dataAndAllErrors' })
52+
test(`document`, async () => {
53+
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null } | GraphQLExecutionResultError>()
54+
})
55+
test(`document runOrThrow`, async () => {
56+
expectTypeOf(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqualTypeOf<{ id: string | null }>()
57+
})
3658
test('query field method', async () => {
37-
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
59+
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query' | GraphQLExecutionResultError>()
3860
})
3961
test('query $batch', async () => {
40-
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
62+
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null } | GraphQLExecutionResultError>()
63+
})
64+
test('result',async () => {
65+
const x = await client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})
66+
await expectTypeOf(client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})).resolves.toEqualTypeOf<null | { infoId: string | null } | { infoInt: number | null } | { id: null | string } | GraphQLExecutionResultError>()
67+
})
68+
test(`raw`, async () => {
69+
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
4170
})
4271
})
4372

@@ -47,13 +76,22 @@ describe('graphql', () => {
4776
test(`document`, async () => {
4877
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<ExecutionResult<{ id: string | null }, ObjMap<unknown>>>()
4978
})
50-
test(`raw`, async () => {
51-
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
79+
test(`document runOrThrow`, async () => {
80+
expectTypeOf(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqualTypeOf<ExecutionResult<{ id: string | null }, ObjMap<unknown>>>()
5281
})
5382
test('query field method', async () => {
5483
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query' }>>()
5584
})
85+
test('query field methodOrThrow', async () => {
86+
await expectTypeOf(client.query.__typenameOrThrow()).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query' }>>()
87+
})
5688
test('query $batch', async () => {
5789
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query', id: string|null }>>()
5890
})
91+
test('query $batchOrThrow', async () => {
92+
await expectTypeOf(client.query.$batchOrThrow({ __typename: true, id: true })).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query', id: string|null }>>()
93+
})
94+
test(`raw`, async () => {
95+
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
96+
})
5997
})

src/client/client.returnMode.test.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
11
/* eslint-disable */
2+
import { GraphQLError } from 'graphql'
23
import { describe, expect, test } from 'vitest'
34
import { db } from '../../tests/_/db.js'
45
import { $Index as schemaIndex } from '../../tests/_/schema/generated/SchemaRuntime.js'
56
import { schema } from '../../tests/_/schema/schema.js'
7+
import { Errors } from '../lib/errors/__.js'
68
import { __typename } from '../Schema/_.js'
79
import { create } from './client.js'
810

911
// dprint-ignore
10-
describe('default', () => {
12+
describe('default (data)', () => {
1113
const client = create({ schema, schemaIndex })
1214
test(`document`, async () => {
1315
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ id: db.id })
1416
})
17+
test(`document runOrThrow`, async () => {
18+
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({ id: db.id })
19+
})
20+
test(`document runOrThrow error`, async () => {
21+
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
22+
})
1523
test('raw', async () => {
1624
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
1725
})
1826
test('query field method', async () => {
1927
await expect(client.query.__typename()).resolves.toEqual('Query')
2028
})
29+
test('query field method error', async () => {
30+
await expect(client.query.error()).rejects.toMatchObject(db.error)
31+
})
32+
test('query field method error orThrow', async () => {
33+
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
34+
})
2135
test('query $batch', async () => {
2236
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ __typename: 'Query', id: db.id })
2337
})
38+
test('query $batchOrThrow error', async () => {
39+
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
40+
})
2441
test('mutation field method', async () => {
2542
await expect(client.mutation.__typename()).resolves.toEqual('Mutation')
2643
})
@@ -30,20 +47,35 @@ describe('default', () => {
3047
})
3148

3249
// dprint-ignore
33-
describe('data', () => {
34-
const client = create({ schema, schemaIndex, returnMode: 'data' })
50+
describe('dataAndAllErrors', () => {
51+
const client = create({ schema, schemaIndex, returnMode: 'dataAndAllErrors' })
3552
test(`document`, async () => {
3653
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ id: db.id })
3754
})
55+
test(`document runOrThrow`, async () => {
56+
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({ id: db.id })
57+
})
58+
test(`document runOrThrow error`, async () => {
59+
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
60+
})
3861
test('raw', async () => {
3962
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
4063
})
4164
test('query field method', async () => {
4265
await expect(client.query.__typename()).resolves.toEqual('Query')
4366
})
67+
test('query field method error', async () => {
68+
await expect(client.query.error()).resolves.toMatchObject(db.error)
69+
})
70+
test('query field method error orThrow', async () => {
71+
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
72+
})
4473
test('query $batch', async () => {
4574
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ __typename: 'Query', id: db.id })
4675
})
76+
test('query $batchOrThrow error', async () => {
77+
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
78+
})
4779
test('mutation field method', async () => {
4880
await expect(client.mutation.__typename()).resolves.toEqual('Mutation')
4981
})
@@ -58,15 +90,30 @@ describe('graphql', () => {
5890
test(`document`, async () => {
5991
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ data: { id: db.id } }) // dprint-ignore
6092
})
93+
test(`document runOrThrow`, async () => {
94+
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({data:{ id: db.id }})
95+
})
96+
test(`document runOrThrow error`, async () => {
97+
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
98+
})
6199
test('raw', async () => {
62100
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
63101
})
64102
test('query field method', async () => {
65103
await expect(client.query.__typename()).resolves.toEqual({ data: { __typename: 'Query' } })
66104
})
105+
test('query field method error', async () => {
106+
await expect(client.query.error()).resolves.toMatchObject({ errors:db.error['errors'] })
107+
})
108+
test('query field method error orThrow', async () => {
109+
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
110+
})
67111
test('query $batch', async () => {
68112
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ data: { __typename: 'Query', id: db.id } })
69113
})
114+
test('query $batchOrThrow error', async () => {
115+
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
116+
})
70117
test('mutation field method', async () => {
71118
await expect(client.mutation.__typename()).resolves.toEqual({ data: { __typename: 'Mutation' } })
72119
})

src/client/client.rootTypeMethods.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable */
22
import { expectTypeOf, test } from 'vitest'
33
import * as Schema from '../../tests/_/schema/schema.js'
4+
import { GraphQLExecutionResultError } from '../lib/graphql.js'
45
import { create } from './client.js'
56

67
const client = create({ schema: Schema.schema, schemaIndex: Schema.$Index })

0 commit comments

Comments
 (0)