Skip to content

Commit

Permalink
feat(ts-client): generated namespace and client ctor (#815)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Apr 29, 2024
1 parent 5410032 commit fb715d9
Show file tree
Hide file tree
Showing 49 changed files with 838 additions and 635 deletions.
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
},
)

0 comments on commit fb715d9

Please sign in to comment.