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): generated namespace and client ctor #815

Merged
merged 8 commits into from
Apr 29, 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
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"@graphql-typed-document-node/core": "^3.2.0",
"@molt/command": "^0.9.0",
"dprint": "^0.45.1",
"zod": "^3.23.4"
"zod": "^3.23.5"
},
"peerDependencies": {
"graphql": "14 - 16"
Expand All @@ -98,8 +98,8 @@
"@types/express": "^4.17.21",
"@types/json-bigint": "^1.0.4",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"apollo-server-express": "^3.13.0",
"body-parser": "^1.20.2",
"doctoc": "^2.2.1",
Expand All @@ -120,9 +120,9 @@
"happy-dom": "^14.7.1",
"json-bigint": "^1.0.0",
"tsx": "^4.7.3",
"type-fest": "^4.17.0",
"type-fest": "^4.18.0",
"typescript": "^5.4.5",
"typescript-eslint": "^7.7.1",
"typescript-eslint": "^7.8.0",
"vitest": "^1.5.2"
}
}
290 changes: 145 additions & 145 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/entrypoints/alpha/client.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from '../../layers/5_client/client.js'
export { Client, create, createPrefilled, Input, InputPrefilled } from '../../layers/5_client/client.js'
export { create as createSelect, select } from '../../layers/5_select/select.js'
37 changes: 32 additions & 5 deletions src/layers/2_generator/__snapshots__/files.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`schema2 1`] = `
"import { createPrefilled } from '../../../../src/entrypoints/alpha/client.js'

import { $Index } from './SchemaRuntime.js'

export const create = createPrefilled(\`default\`, $Index)
"
`;

exports[`schema2 2`] = `
"export * as Graffle from './_.js'
"
`;

exports[`schema2 3`] = `
"export { create } from './Client.js'
export { isError } from './Error.js'
export * as Select from './Select.js'
"
`;

exports[`schema2 4`] = `
"type Include<T, U> = Exclude<T, Exclude<T, U>>

type ObjectWithTypeName = { __typename: string }
Expand All @@ -21,10 +42,16 @@ export const isError = <$Value>(value: $Value): value is Include<$Value, ErrorOb
"
`;

exports[`schema2 2`] = `
exports[`schema2 5`] = `
"import { ResultSet, SelectionSet } from '../../../../src/entrypoints/alpha/schema.js'
import { Index } from './Index.js'

// Runtime
// -------

import { createSelect } from '../../../../src/entrypoints/alpha/client.js'
export const Select = createSelect('default')

// Root Types
// ----------

Expand Down Expand Up @@ -128,7 +155,7 @@ export type Interface<$SelectionSet extends SelectionSet.Interface<Index['interf
"
`;

exports[`schema2 3`] = `
exports[`schema2 6`] = `
"/* eslint-disable */

import type * as Schema from './SchemaBuildtime.js'
Expand Down Expand Up @@ -188,7 +215,7 @@ export interface Index {
"
`;

exports[`schema2 4`] = `
exports[`schema2 7`] = `
"import type * as $ from '../../../../src/entrypoints/alpha/schema.js'
import type * as $Scalar from './Scalar.ts'

Expand Down Expand Up @@ -524,13 +551,13 @@ export namespace Union {
"
`;

exports[`schema2 5`] = `
exports[`schema2 8`] = `
"export * from '../../../../src/layers/1_Schema/Hybrid/types/Scalar/Scalar.js'
export * from '../../customScalarCodecs.js'
"
`;

exports[`schema2 6`] = `
exports[`schema2 9`] = `
"/* eslint-disable */

import * as $ from '../../../../src/entrypoints/alpha/schema.js'
Expand Down
18 changes: 18 additions & 0 deletions src/layers/2_generator/code/Client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createCodeGenerator } from '../createCodeGenerator.js'
import { moduleNameSchemaRuntime } from './SchemaRuntime.js'

export const { generate: generateClient, moduleName: moduleNameClient } = createCodeGenerator(
`Client`,
(config) => {
const code: string[] = []

code.push(
`import { createPrefilled } from '${config.libraryPaths.client}'`,
`import { $Index } from './${moduleNameSchemaRuntime}.js'`,
``,
`export const create = createPrefilled(\`${config.name}\`, $Index)`,
)

return code.join(`\n\n`)
},
)
34 changes: 16 additions & 18 deletions src/layers/2_generator/code/Error.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { Config } from './generateCode.js'
import { createCodeGenerator } from '../createCodeGenerator.js'

export const moduleNameError = `Error`
export const { generate: generateError, moduleName: moduleNameError } = createCodeGenerator(
`Error`,
(config) => {
const code: string[] = []

export const generateError = (config: Config) => {
const code: string[] = []
code.push(
`type Include<T, U> = Exclude<T, Exclude<T, U>>`,
`type ObjectWithTypeName = { __typename: string }`,
)

code.push(
`type Include<T, U> = Exclude<T, Exclude<T, U>>`,
`type ObjectWithTypeName = { __typename: string }`,
)

code.push(`
code.push(`
const ErrorObjectsTypeNameSelectedEnum = {
${config.error.objects.map(_ => `${_.name}: { __typename: '${_.name}' }`).join(`,\n`)}
} as ${config.error.objects.length > 0 ? `const` : `Record<string,ObjectWithTypeName>`}
Expand All @@ -20,15 +20,13 @@ export const generateError = (config: Config) => {
type ErrorObjectsTypeNameSelected = (typeof ErrorObjectsTypeNameSelected)[number]
`)

code.push(
`export const isError = <$Value>(value:$Value): value is Include<$Value, ErrorObjectsTypeNameSelected> => {
code.push(
`export const isError = <$Value>(value:$Value): value is Include<$Value, ErrorObjectsTypeNameSelected> => {
return typeof value === 'object' && value !== null && '__typename' in value &&
ErrorObjectsTypeNameSelected.some(_ => _.__typename === value.__typename)
}`,
)
)

return {
code: code.join(`\n\n`),
moduleName: moduleNameError,
}
}
return code.join(`\n\n`)
},
)
106 changes: 52 additions & 54 deletions src/layers/2_generator/code/Index.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,67 @@
import { isUnionType } from 'graphql'
import { Code } from '../../../lib/Code.js'
import { hasMutation, hasQuery, hasSubscription, unwrapToNamed } from '../../../lib/graphql.js'
import type { Config } from './generateCode.js'
import { createCodeGenerator } from '../createCodeGenerator.js'
import { moduleNameSchemaBuildtime } from './SchemaBuildtime.js'

export const moduleNameIndex = `Index`
export const { generate: generateIndex, moduleName: moduleNameIndex } = createCodeGenerator(
`Index`,
(config) => {
const namespace = `Schema`
const code = []
code.push(`/* eslint-disable */\n`)
code.push(`import type * as ${namespace} from './${moduleNameSchemaBuildtime}.js'\n`)

export const generateIndex = (config: Config) => {
const namespace = `Schema`
const code = []
code.push(`/* eslint-disable */\n`)
code.push(`import type * as ${namespace} from './${moduleNameSchemaBuildtime}.js'\n`)

code.push(Code.export$(
Code.interface$(
`Index`,
Code.objectFrom({
name: Code.quote(config.name),
Root: {
type: Code.objectFrom({
Query: hasQuery(config.typeMapByKind) ? `${namespace}.Root.Query` : null,
Mutation: hasMutation(config.typeMapByKind) ? `${namespace}.Root.Mutation` : null,
Subscription: hasSubscription(config.typeMapByKind) ? `${namespace}.Root.Subscription` : null,
}),
},
objects: Code.objectFromEntries(
config.typeMapByKind.GraphQLObjectType.map(_ => [_.name, `${namespace}.Object.${_.name}`]),
),
unions: Code.objectFromEntries(
config.typeMapByKind.GraphQLUnionType.map(_ => [_.name, `${namespace}.Union.${_.name}`]),
),
interfaces: Code.objectFromEntries(
config.typeMapByKind.GraphQLInterfaceType.map(_ => [_.name, `${namespace}.Interface.${_.name}`]),
),
// todo jsdoc comment saying:
// Objects that match this pattern name: /.../
error: Code.objectFrom({
code.push(Code.export$(
Code.interface$(
`Index`,
Code.objectFrom({
name: Code.quote(config.name),
Root: {
type: Code.objectFrom({
Query: hasQuery(config.typeMapByKind) ? `${namespace}.Root.Query` : null,
Mutation: hasMutation(config.typeMapByKind) ? `${namespace}.Root.Mutation` : null,
Subscription: hasSubscription(config.typeMapByKind) ? `${namespace}.Root.Subscription` : null,
}),
},
objects: Code.objectFromEntries(
config.error.objects.map(_ => [_.name, `${namespace}.Object.${_.name}`]),
config.typeMapByKind.GraphQLObjectType.map(_ => [_.name, `${namespace}.Object.${_.name}`]),
),
objectsTypename: Code.objectFromEntries(
config.error.objects.map(_ => [_.name, `{ __typename: "${_.name}" }`]),
unions: Code.objectFromEntries(
config.typeMapByKind.GraphQLUnionType.map(_ => [_.name, `${namespace}.Union.${_.name}`]),
),
rootResultFields: `{
interfaces: Code.objectFromEntries(
config.typeMapByKind.GraphQLInterfaceType.map(_ => [_.name, `${namespace}.Interface.${_.name}`]),
),
// todo jsdoc comment saying:
// Objects that match this pattern name: /.../
error: Code.objectFrom({
objects: Code.objectFromEntries(
config.error.objects.map(_ => [_.name, `${namespace}.Object.${_.name}`]),
),
objectsTypename: Code.objectFromEntries(
config.error.objects.map(_ => [_.name, `{ __typename: "${_.name}" }`]),
),
rootResultFields: `{
${
Object.entries(config.rootTypes).map(([rootTypeName, rootType]) => {
if (!rootType) return `${rootTypeName}: {}`
Object.entries(config.rootTypes).map(([rootTypeName, rootType]) => {
if (!rootType) return `${rootTypeName}: {}`

const resultFields = Object.values(rootType.getFields()).filter((field) => {
const type = unwrapToNamed(field.type)
return isUnionType(type)
&& type.getTypes().some(_ => config.error.objects.some(__ => __.name === _.name))
}).map((field) => field.name)
const resultFields = Object.values(rootType.getFields()).filter((field) => {
const type = unwrapToNamed(field.type)
return isUnionType(type)
&& type.getTypes().some(_ => config.error.objects.some(__ => __.name === _.name))
}).map((field) => field.name)

return `${rootType.name}: {\n${resultFields.map(_ => `${_}: "${_}"`).join(`,\n`)} }`
}).join(`\n`)
}
return `${rootType.name}: {\n${resultFields.map(_ => `${_}: "${_}"`).join(`,\n`)} }`
}).join(`\n`)
}
}`,
}),
}),
}),
),
))
),
))

return {
code: code.join(`\n`),
moduleName: moduleNameIndex,
}
}
return code.join(`\n`)
},
)
64 changes: 31 additions & 33 deletions src/layers/2_generator/code/Scalar.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,44 @@
import type { Config } from './generateCode.js'
import { createCodeGenerator } from '../createCodeGenerator.js'

export const moduleNameScalar = `Scalar`
export const { generate: generateScalar, moduleName: moduleNameScalar } = createCodeGenerator(
`Scalar`,
(config) => {
let code = ``

export const generateScalar = (config: Config) => {
let code = ``
// todo test case for when this is true
const needsDefaultCustomScalarImplementation = config.typeMapByKind.GraphQLScalarTypeCustom.length > 0
&& !config.options.customScalars

// todo test case for when this is true
const needsDefaultCustomScalarImplementation = config.typeMapByKind.GraphQLScalarTypeCustom.length > 0
&& !config.options.customScalars

const StandardScalarNamespace = `StandardScalar`
code += `
const StandardScalarNamespace = `StandardScalar`
code += `

${
needsDefaultCustomScalarImplementation
? `import * as ${StandardScalarNamespace} from '${config.libraryPaths.scalars}'`
: ``
}
needsDefaultCustomScalarImplementation
? `import * as ${StandardScalarNamespace} from '${config.libraryPaths.scalars}'`
: ``
}

export * from '${config.libraryPaths.scalars}'
${config.options.customScalars ? `export * from '${config.importPaths.customScalarCodecs}'` : ``}
`

if (needsDefaultCustomScalarImplementation) {
console.log(
`WARNING: Custom scalars detected in the schema, but you have not created a custom scalars module to import implementations from.`,
)
code += `
if (needsDefaultCustomScalarImplementation) {
console.log(
`WARNING: Custom scalars detected in the schema, but you have not created a custom scalars module to import implementations from.`,
)
code += `
${
config.typeMapByKind.GraphQLScalarTypeCustom
.flatMap((_) => {
return [
`export const ${_.name} = ${StandardScalarNamespace}.String`,
`export type ${_.name} = ${StandardScalarNamespace}.String`,
]
}).join(`\n`)
}
config.typeMapByKind.GraphQLScalarTypeCustom
.flatMap((_) => {
return [
`export const ${_.name} = ${StandardScalarNamespace}.String`,
`export type ${_.name} = ${StandardScalarNamespace}.String`,
]
}).join(`\n`)
}
`
}
}

return {
code,
moduleName: moduleNameScalar,
}
}
return code
},
)
Loading