Skip to content

Commit

Permalink
feat(ts-client): document method (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Apr 16, 2024
1 parent 98cb065 commit 73adae5
Show file tree
Hide file tree
Showing 40 changed files with 918 additions and 95 deletions.
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import configPrisma from 'eslint-config-prisma'
import tsEslint from 'typescript-eslint'

export default tsEslint.config({
ignores: ['**/build/**/*', 'eslint.config.js'],
ignores: ['**/build/**/*', 'eslint.config.js', 'vite.config.ts', '**/generated/**/*'],
extends: configPrisma,
languageOptions: {
parserOptions: {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
},
"homepage": "https://github.com/jasonkuhrt/graphql-request",
"scripts": {
"demo": "tsx src/cli/generate.ts && dprint fmt src/demo.ts",
"gen:test:schema": "tsx tests/_/schemaGenerate.ts",
"demo": "tsx src/cli/generateSchema.ts && dprint fmt src/demo.ts",
"dev": "rm -rf dist && tsc --watch",
"format": "pnpm build:docs && dprint fmt",
"lint": "eslint . --fix",
Expand Down Expand Up @@ -89,6 +90,7 @@
"graphql": "14 - 16"
},
"devDependencies": {
"@pothos/core": "^3.41.0",
"@tsconfig/node16": "^16.1.3",
"@types/body-parser": "^1.19.5",
"@types/express": "^4.17.21",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Schema/_.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './Args.js'
export { RootTypeName } from './core/helpers.js'
export { type RootTypeName } from './core/helpers.js'
export * from './core/Index.js'
export * from './core/Named/__.js'
export * from './Field.js'
Expand All @@ -10,5 +10,5 @@ export * from './Input/types/InputObject.js'
export * from './Output/__.js'
export * from './Output/types/__typename.js'
export * from './Output/types/Interface.js'
export { Object$, Object$2 } from './Output/types/Object.js'
export { Object$, type Object$2 } from './Output/types/Object.js'
export * from './Output/types/Union.js'
1 change: 1 addition & 0 deletions src/Schema/core/Named/NamedType.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ test(`NameParse`, () => {
expectTypeOf<NamedType.NameParse<'1_a'>>().toEqualTypeOf<never>()
expectTypeOf<NamedType.NameParse<'$'>>().toEqualTypeOf<never>()
expectTypeOf<NamedType.NameParse<'$a'>>().toEqualTypeOf<never>()
expectTypeOf<NamedType.NameParse<'foo$'>>().toEqualTypeOf<never>()
})
18 changes: 9 additions & 9 deletions src/Schema/core/Named/NamedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import type { Digit, Letter } from '../../../lib/prelude.js'
* @see http://spec.graphql.org/draft/#sec-Names
*/
// dprint-ignore
export type NameParse<T extends string> =
T extends NameHead ? T :
T extends `${NameHead}${infer Rest}` ? Rest extends NameBodyParse<Rest> ? T
: never
: never
export type NameParse<S extends string> =
S extends NameHead ? S :
S extends `${NameHead}${infer Rest}` ? NameBodyParse<Rest> extends never ? never :
S :
never

// dprint-ignore
export type NameBodyParse<S extends string> =
type NameBodyParse<S extends string> =
S extends NameBody ? S :
S extends `${NameBody}${infer Rest}` ? NameBodyParse<Rest> extends string ? S
: never
: never
S extends `${NameBody}${infer Rest}` ? NameBodyParse<Rest> extends never ? never :
S :
never

export type NameHead = Letter | '_'
export type NameBody = Letter | '_' | Digit
8 changes: 5 additions & 3 deletions src/client/SelectionSet/toGraphQLDocumentString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ type SS = {
} & SpecialFields

export const toGraphQLDocumentString = (ss: GraphQLDocumentObject) => {
let docString = ``
docString += `query {
return `query ${toGraphQLDocumentSelectionSet(ss)}`
}

export const toGraphQLDocumentSelectionSet = (ss: GraphQLDocumentObject) => {
return `{
${selectionSet(ss)}
}`
return docString
}

const directiveArgs = (config: object) => {
Expand Down
5 changes: 2 additions & 3 deletions src/client/client.customScalar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { create } from './client.js'
const ctx = setupMockServer()
const data = { fooBarUnion: { int: 1 } }

// @ts-ignore infinite depth
const client = () => create<Index>({ url: ctx.url, schemaIndex })
const client = () => create<Index>({ schema: ctx.url, schemaIndex })

describe(`output`, () => {
test(`query field`, async () => {
Expand Down Expand Up @@ -79,7 +78,7 @@ describe(`input`, () => {
})
const clientExpected = (expectedDocument: (document: any) => void) => {
const client = create<Index>({
url: ctx.url,
schema: ctx.url,
schemaIndex,
hooks: {
documentEncode: (input, run) => {
Expand Down
74 changes: 74 additions & 0 deletions src/client/client.document.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, expectTypeOf, test } from 'vitest'
import * as Schema from '../../tests/_/schema/schema.js'
import * as SchemaMutationOnly from '../../tests/_/schemaMutationOnly/schema.js'
import * as SchemaQueryOnly from '../../tests/_/schemaQueryOnly/schema.js'
import { create } from './client.js'

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

test(`requires input`, () => {
// @ts-expect-error missing input
client.document()
// @ts-expect-error empty object
client.document({})
})

describe(`input`, () => {
test(`document with one query`, () => {
const run = client.document({ foo: { query: { id: true } } }).run
expectTypeOf(run).toMatchTypeOf<(...params: ['foo'] | [] | [undefined]) => Promise<any>>()
})

test(`document with two queries`, () => {
const run = client.document({
foo: { query: { id: true } },
bar: { query: { id: true } },
}).run
expectTypeOf(run).toMatchTypeOf<(name: 'foo' | 'bar') => Promise<any>>()
})

test(`root operation not available if it is not in schema`, () => {
const clientQueryOnly = create<SchemaQueryOnly.Index>({
schema: SchemaQueryOnly.schema,
schemaIndex: SchemaQueryOnly.$Index,
})
clientQueryOnly.document({
foo: { query: { id: true } },
// @ts-expect-error mutation not in schema
bar: { mutation: { id: true } },
})
const clientMutationOnly = create<SchemaMutationOnly.Index>({
schema: SchemaMutationOnly.schema,
schemaIndex: SchemaMutationOnly.$Index,
})
clientMutationOnly.document({
// @ts-expect-error query not in schema
foo: { query: { id: true } },
bar: { mutation: { id: true } },
})
})
})

describe(`output`, () => {
test(`document with one query`, async () => {
{
const result = await client.document({ foo: { query: { id: true } } }).run()
expectTypeOf(result).toEqualTypeOf<{ id: string | null }>()
}
{
const result = await client.document({ foo: { query: { id: true } } }).run(`foo`)
expectTypeOf(result).toEqualTypeOf<{ id: string | null }>()
}
{
const result = await client.document({ foo: { query: { id: true } } }).run(undefined)
expectTypeOf(result).toEqualTypeOf<{ id: string | null }>()
}
})
test(`document with two queries`, async () => {
const result = await client.document({
foo: { query: { id: true } },
bar: { query: { id: true } },
}).run(`foo`)
expectTypeOf(result).toEqualTypeOf<{ id: string | null }>()
})
})
70 changes: 70 additions & 0 deletions src/client/client.document.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, test } from 'vitest'
import type { Index } from '../../tests/_/schema/generated/Index.js'
import { $Index } from '../../tests/_/schema/generated/SchemaRuntime.js'
import { db, schema } from '../../tests/_/schema/schema.js'
import { create } from './client.js'

const client = create<Index>({ schema, schemaIndex: $Index })

describe(`document with two queries`, () => {
const withTwo = client.document({
foo: {
query: { id: true },
},
bar: {
query: { idNonNull: true },
},
})

test(`works`, async () => {
const { run } = withTwo
await expect(run(`foo`)).resolves.toEqual({ data: { id: db.id1 } })
await expect(run(`bar`)).resolves.toEqual({ data: { idNonNull: db.id1 } })
})
test(`error if no operation name is provided`, async () => {
const { run } = withTwo
// @ts-expect-error
await expect(run()).resolves.toMatchObject({
errors: [{ message: `Must provide operation name if query contains multiple operations.` }],
})
})
test(`error if wrong operation name is provided`, async () => {
const { run } = withTwo
// @ts-expect-error
await expect(run(`boo`)).resolves.toMatchObject({ errors: [{ message: `Unknown operation named "boo".` }] })
})
test(`error if invalid name in document`, async () => {
// @ts-expect-error
const { run } = client.document({ foo$: { query: { id: true } } })
await expect(run(`foo$`)).resolves.toMatchObject({
errors: [{ message: `Syntax Error: Expected "{", found "$".` }],
})
})
})

test(`document with one query`, async () => {
const { run } = client.document({ foo: { query: { id: true } } })
await expect(run(`foo`)).resolves.toEqual({ data: { id: db.id1 } })
await expect(run()).resolves.toEqual({ data: { id: db.id1 } })
await expect(run(undefined)).resolves.toEqual({ data: { id: db.id1 } })
})

test(`document with one mutation`, async () => {
const { run } = client.document({ foo: { mutation: { id: true } } })
await expect(run(`foo`)).resolves.toEqual({ data: { id: db.id1 } })
await expect(run()).resolves.toEqual({ data: { id: db.id1 } })
await expect(run(undefined)).resolves.toEqual({ data: { id: db.id1 } })
})

test(`document with one mutation and one query`, async () => {
const { run } = client.document({
foo: {
mutation: { id: true },
},
bar: {
query: { idNonNull: true },
},
})
await expect(run(`foo`)).resolves.toEqual({ data: { id: db.id1 } })
await expect(run(`bar`)).resolves.toEqual({ data: { idNonNull: db.id1 } })
})

0 comments on commit 73adae5

Please sign in to comment.