Skip to content

Commit

Permalink
feat(ts-client): mode to return all errors (#796)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Apr 25, 2024
1 parent d8bc6d5 commit 7b06232
Show file tree
Hide file tree
Showing 32 changed files with 476 additions and 101 deletions.
31 changes: 26 additions & 5 deletions src/client/Config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import type { ExecutionResult } from 'graphql'
import type { GraphQLExecutionResultError } from '../lib/graphql.js'
import type { SetProperty } from '../lib/prelude.js'

// todo: dataAndErrors | dataAndSchemaErrors
export type ReturnModeType = 'graphql' | 'data'
export type ReturnModeType =
| ReturnModeTypeGraphQL
| ReturnModeTypeData
| ReturnModeTypeDataAndSchemaErrors
| ReturnModeTypeDataAllErrors

export type ReturnModeTypeBase = ReturnModeTypeGraphQL | ReturnModeTypeData | ReturnModeTypeDataAllErrors

export type ReturnModeTypeGraphQL = 'graphql'

export type ReturnModeTypeData = 'data'

export type ReturnModeTypeDataAllErrors = 'dataAndAllErrors'

export type ReturnModeTypeDataAndSchemaErrors = 'dataAndSchemaErrors'

export type OptionsInput = {
returnMode: ReturnModeType | undefined
Expand All @@ -16,9 +31,15 @@ export type Config = {
}

export type ApplyInputDefaults<Input extends OptionsInput> = {
[Key in keyof OptionsInputDefaults]: undefined extends Input[Key] ? OptionsInputDefaults[Key] : Input[Key]
[Key in keyof OptionsInputDefaults]: undefined extends Input[Key] ? OptionsInputDefaults[Key]
: Exclude<Input[Key], undefined>
}

// dprint-ignore
export type ReturnMode<$Config extends Config, $Data> =
$Config['returnMode'] extends 'graphql' ? ExecutionResult<$Data> : $Data
export type ReturnMode<$Config extends Config, $Data, $DataRaw = undefined> =
$Config['returnMode'] extends 'graphql' ? ExecutionResult<$DataRaw extends undefined ? $Data : $DataRaw> :
$Config['returnMode'] extends 'data' ? $Data :
$Data | GraphQLExecutionResultError

export type OrThrowifyConfig<$Config extends Config> = $Config['returnMode'] extends 'graphql' ? $Config
: SetProperty<$Config, 'returnMode', 'data'>
39 changes: 23 additions & 16 deletions src/client/RootTypeMethods.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import type { ExecutionResult } from 'graphql'
import type { OperationName } from '../lib/graphql.js'
import type { Exact } from '../lib/prelude.js'
import type { TSError } from '../lib/TSError.js'
import type { InputFieldsAllNullable, Schema } from '../Schema/__.js'
import type { Config, OptionsInputDefaults, ReturnMode } from './Config.js'
import type { Config, OrThrowifyConfig, ReturnMode } from './Config.js'
import type { ResultSet } from './ResultSet/__.js'
import type { SelectionSet } from './SelectionSet/__.js'

type OperationName = 'query' | 'mutation'
type RootTypeFieldContext = {
Config: Config
Index: Schema.Index
RootTypeName: Schema.RootTypeName
RootTypeFieldName: string
Field: Schema.SomeField
}

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

// dprint-ignore
export type RootTypeMethods<$Config extends OptionsInputDefaults, $Index extends Schema.Index, $RootTypeName extends Schema.RootTypeName> =
export type RootTypeMethods<$Config extends Config, $Index extends Schema.Index, $RootTypeName extends Schema.RootTypeName> =
$Index['Root'][$RootTypeName] extends Schema.Object$2 ?
(
& {
$batch: RootMethod<$Config, $Index, $RootTypeName>
$batchOrThrow: RootMethod<OrThrowifyConfig<$Config>, $Index, $RootTypeName>
}
& {
[$RootTypeFieldName in keyof $Index['Root'][$RootTypeName]['fields'] & string]:
Expand All @@ -31,6 +38,16 @@ export type RootTypeMethods<$Config extends OptionsInputDefaults, $Index extends
Field: $Index['Root'][$RootTypeName]['fields'][$RootTypeFieldName]
}>
}
& {
[$RootTypeFieldName in keyof $Index['Root'][$RootTypeName]['fields'] & string as `${$RootTypeFieldName}OrThrow`]:
RootTypeFieldMethod<{
Config: OrThrowifyConfig<$Config>,
Index: $Index,
RootTypeName: $RootTypeName,
RootTypeFieldName: $RootTypeFieldName
Field: $Index['Root'][$RootTypeName]['fields'][$RootTypeFieldName]
}>
}
)
: TSError<'RootTypeMethods', `Your schema does not have the root type "${$RootTypeName}".`>

Expand Down Expand Up @@ -65,14 +82,4 @@ type ScalarFieldMethod<$Context extends RootTypeFieldContext> =
(() => Promise<ReturnModeForFieldMethod<$Context, ResultSet.Field<true, $Context['Field'], $Context['Index']>>>)
// dprint-ignore
type ReturnModeForFieldMethod<$Context extends RootTypeFieldContext, $Data> =
$Context['Config']['returnMode'] extends 'data'
? $Data
: ExecutionResult<{ [k in $Context['RootTypeFieldName']] : $Data }>

type RootTypeFieldContext = {
Config: Config
Index: Schema.Index
RootTypeName: Schema.RootTypeName
RootTypeFieldName: string
Field: Schema.SomeField
}
ReturnMode<$Context['Config'], $Data, { [k in $Context['RootTypeFieldName']] : $Data }>
5 changes: 5 additions & 0 deletions src/client/client.document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ test(`document with one mutation`, async () => {
await expect(run(undefined)).resolves.toEqual({ id: db.id1 })
})

test(`error`, async () => {
const { run } = client.document({ foo: { query: { error: true } } })
await expect(run()).rejects.toMatchObject({ errors: [{ message: `Something went wrong.` }] })
})

test(`document with one mutation and one query`, async () => {
const { run } = client.document({
foo: {
Expand Down
22 changes: 22 additions & 0 deletions src/client/client.input.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { test } from 'vitest'
import { $Index } from '../../tests/_/schema/generated/SchemaRuntime.js'
import { schema } from '../../tests/_/schema/schema.js'
import { create } from './client.js'

test(`works`, () => {
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `graphql` })
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `data` })
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `dataAndAllErrors` })
// @ts-expect-error bad returnMode
create({ schemaIndex: $Index, schema, name: `QueryOnly`, returnMode: `dataAndSchemaErrors` })

create({ schemaIndex: $Index, schema, name: `default`, returnMode: `graphql` })
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `data` })
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `dataAndAllErrors` })
create({ schemaIndex: $Index, schema, name: `default`, returnMode: `dataAndSchemaErrors` })

create({ schemaIndex: $Index, schema, returnMode: `graphql` })
create({ schemaIndex: $Index, schema, returnMode: `data` })
create({ schemaIndex: $Index, schema, returnMode: `dataAndAllErrors` })
create({ schemaIndex: $Index, schema, returnMode: `dataAndSchemaErrors` })
})
54 changes: 46 additions & 8 deletions src/client/client.returnMode.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ import { describe } from 'node:test'
import { expectTypeOf, test } from 'vitest'
import { $Index as schemaIndex } from '../../tests/_/schema/generated/SchemaRuntime.js'
import { schema } from '../../tests/_/schema/schema.js'
import { GraphQLExecutionResultError } from '../lib/graphql.js'
import { create } from './client.js'

// dprint-ignore
describe('default', () => {
describe('default is data', () => {
const client = create({ schema, schemaIndex })
test(`document`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null }>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
})
test('query field method', async () => {
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
})
test('query $batch', async () => {
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
})
})

// dprint-ignore
Expand All @@ -30,14 +31,42 @@ describe('data', () => {
test(`document`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null }>()
})
test('query field method', async () => {
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
})
test('query $batch', async () => {
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
})
test('result',async () => {
const x = await client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})
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 }>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
})
})

// dprint-ignore
describe('dataAndAllErrors', () => {
const client = create({ schema, schemaIndex, returnMode: 'dataAndAllErrors' })
test(`document`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<{ id: string | null } | GraphQLExecutionResultError>()
})
test(`document runOrThrow`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqualTypeOf<{ id: string | null }>()
})
test('query field method', async () => {
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query'>()
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<'Query' | GraphQLExecutionResultError>()
})
test('query $batch', async () => {
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null }>()
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<{ __typename: 'Query', id: string|null } | GraphQLExecutionResultError>()
})
test('result',async () => {
const x = await client.query.result({$: { case: 'Object1' }, onObject1:{id:true},onErrorOne:{infoId:true},onErrorTwo:{infoInt:true}})
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>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
})
})

Expand All @@ -47,13 +76,22 @@ describe('graphql', () => {
test(`document`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).run()).resolves.toEqualTypeOf<ExecutionResult<{ id: string | null }, ObjMap<unknown>>>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
test(`document runOrThrow`, async () => {
expectTypeOf(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqualTypeOf<ExecutionResult<{ id: string | null }, ObjMap<unknown>>>()
})
test('query field method', async () => {
await expectTypeOf(client.query.__typename()).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query' }>>()
})
test('query field methodOrThrow', async () => {
await expectTypeOf(client.query.__typenameOrThrow()).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query' }>>()
})
test('query $batch', async () => {
await expectTypeOf(client.query.$batch({ __typename: true, id: true })).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query', id: string|null }>>()
})
test('query $batchOrThrow', async () => {
await expectTypeOf(client.query.$batchOrThrow({ __typename: true, id: true })).resolves.toEqualTypeOf<ExecutionResult<{ __typename: 'Query', id: string|null }>>()
})
test(`raw`, async () => {
expectTypeOf(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqualTypeOf<ExecutionResult>()
})
})
53 changes: 50 additions & 3 deletions src/client/client.returnMode.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
/* eslint-disable */
import { GraphQLError } from 'graphql'
import { describe, expect, test } from 'vitest'
import { db } from '../../tests/_/db.js'
import { $Index as schemaIndex } from '../../tests/_/schema/generated/SchemaRuntime.js'
import { schema } from '../../tests/_/schema/schema.js'
import { Errors } from '../lib/errors/__.js'
import { __typename } from '../Schema/_.js'
import { create } from './client.js'

// dprint-ignore
describe('default', () => {
describe('default (data)', () => {
const client = create({ schema, schemaIndex })
test(`document`, async () => {
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ id: db.id })
})
test(`document runOrThrow`, async () => {
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({ id: db.id })
})
test(`document runOrThrow error`, async () => {
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
})
test('raw', async () => {
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
})
test('query field method', async () => {
await expect(client.query.__typename()).resolves.toEqual('Query')
})
test('query field method error', async () => {
await expect(client.query.error()).rejects.toMatchObject(db.error)
})
test('query field method error orThrow', async () => {
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
})
test('query $batch', async () => {
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ __typename: 'Query', id: db.id })
})
test('query $batchOrThrow error', async () => {
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
})
test('mutation field method', async () => {
await expect(client.mutation.__typename()).resolves.toEqual('Mutation')
})
Expand All @@ -30,20 +47,35 @@ describe('default', () => {
})

// dprint-ignore
describe('data', () => {
const client = create({ schema, schemaIndex, returnMode: 'data' })
describe('dataAndAllErrors', () => {
const client = create({ schema, schemaIndex, returnMode: 'dataAndAllErrors' })
test(`document`, async () => {
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ id: db.id })
})
test(`document runOrThrow`, async () => {
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({ id: db.id })
})
test(`document runOrThrow error`, async () => {
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
})
test('raw', async () => {
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
})
test('query field method', async () => {
await expect(client.query.__typename()).resolves.toEqual('Query')
})
test('query field method error', async () => {
await expect(client.query.error()).resolves.toMatchObject(db.error)
})
test('query field method error orThrow', async () => {
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
})
test('query $batch', async () => {
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ __typename: 'Query', id: db.id })
})
test('query $batchOrThrow error', async () => {
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
})
test('mutation field method', async () => {
await expect(client.mutation.__typename()).resolves.toEqual('Mutation')
})
Expand All @@ -58,15 +90,30 @@ describe('graphql', () => {
test(`document`, async () => {
await expect(client.document({ main: { query: { id: true } } }).run()).resolves.toEqual({ data: { id: db.id } }) // dprint-ignore
})
test(`document runOrThrow`, async () => {
await expect(client.document({ main: { query: { id: true } } }).runOrThrow()).resolves.toEqual({data:{ id: db.id }})
})
test(`document runOrThrow error`, async () => {
await expect(client.document({ main: { query: { error: true } } }).runOrThrow()).rejects.toEqual(db.error)
})
test('raw', async () => {
await expect(client.raw('query main {\nid\n}', {}, 'main')).resolves.toEqual({ data: { id: db.id } })
})
test('query field method', async () => {
await expect(client.query.__typename()).resolves.toEqual({ data: { __typename: 'Query' } })
})
test('query field method error', async () => {
await expect(client.query.error()).resolves.toMatchObject({ errors:db.error['errors'] })
})
test('query field method error orThrow', async () => {
await expect(client.query.errorOrThrow()).rejects.toMatchObject(db.error)
})
test('query $batch', async () => {
await expect(client.query.$batch({ __typename: true, id: true })).resolves.toEqual({ data: { __typename: 'Query', id: db.id } })
})
test('query $batchOrThrow error', async () => {
await expect(client.query.$batchOrThrow({ error: true })).rejects.toMatchObject(db.error)
})
test('mutation field method', async () => {
await expect(client.mutation.__typename()).resolves.toEqual({ data: { __typename: 'Mutation' } })
})
Expand Down
1 change: 1 addition & 0 deletions src/client/client.rootTypeMethods.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable */
import { expectTypeOf, test } from 'vitest'
import * as Schema from '../../tests/_/schema/schema.js'
import { GraphQLExecutionResultError } from '../lib/graphql.js'
import { create } from './client.js'

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

0 comments on commit 7b06232

Please sign in to comment.