Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ts-client): document method #774

Merged
merged 12 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 } })
})
Loading