From a3761aa5f6428117e58d1adb64ae696c29c77eea Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Thu, 11 Apr 2024 20:39:00 -0400 Subject: [PATCH 1/2] feat: optional custom scalars --- src/generator/code/code.ts | 19 ++++++++++++++----- src/generator/code/scalar.ts | 26 +++++++++++++++++++++++--- src/generator/code/schemaBuildtime.ts | 4 ++-- src/generator/files.ts | 22 ++++++++++++---------- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/generator/code/code.ts b/src/generator/code/code.ts index 90af67ca8..ec6f9839a 100644 --- a/src/generator/code/code.ts +++ b/src/generator/code/code.ts @@ -17,6 +17,9 @@ export interface Input { importPaths?: { customScalarCodecs?: string } + /** + * The GraphQL SDL source code. + */ schemaSource: string options?: { /** @@ -32,6 +35,7 @@ export interface Input { export interface Config { schema: GraphQLSchema + typeMapByKind: TypeMapByKind libraryPaths: { schema: string scalars: string @@ -39,9 +43,11 @@ export interface Config { importPaths: { customScalarCodecs: string } - typeMapByKind: TypeMapByKind - TSDoc: { - noDocPolicy: 'message' | 'ignore' + options: { + customScalars: boolean + TSDoc: { + noDocPolicy: 'message' | 'ignore' + } } } @@ -57,8 +63,11 @@ export const resolveOptions = (input: Input): Config => { schema: input.libraryPaths?.schema ?? `graphql-request/alpha/schema`, }, typeMapByKind: getTypeMapByKind(schema), - TSDoc: { - noDocPolicy: input.options?.TSDoc?.noDocPolicy ?? `ignore`, + options: { + customScalars: input.options?.customScalars ?? false, + TSDoc: { + noDocPolicy: input.options?.TSDoc?.noDocPolicy ?? `ignore`, + }, }, } } diff --git a/src/generator/code/scalar.ts b/src/generator/code/scalar.ts index 3251fbe80..e1eb71325 100644 --- a/src/generator/code/scalar.ts +++ b/src/generator/code/scalar.ts @@ -3,22 +3,42 @@ import type { Config } from './code.js' 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 + code += ` - import type * as CustomScalar from '${config.importPaths.customScalarCodecs}' + + ${config.options.customScalars ? `import type * as CustomScalar from '${config.importPaths.customScalarCodecs}'` : ``} declare global { interface SchemaCustomScalars { ${ config.typeMapByKind.GraphQLScalarTypeCustom .map((_) => { - return `${_.name}: CustomScalar.${_.name}` + return `${_.name}: ${needsDefaultCustomScalarImplementation ? `String` : `CustomScalar.${_.name}`}` }).join(`\n`) } } } export * from '${config.libraryPaths.scalars}' - export * from '${config.importPaths.customScalarCodecs}' + ${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 += ` +${ + config.typeMapByKind.GraphQLScalarTypeCustom + .flatMap((_) => { + return [`export const ${_.name} = String`, `export type ${_.name} = String`] + }).join(`\n`) + } + ` + } + return code } diff --git a/src/generator/code/schemaBuildtime.ts b/src/generator/code/schemaBuildtime.ts index 290915470..a2d1e7ac3 100644 --- a/src/generator/code/schemaBuildtime.ts +++ b/src/generator/code/schemaBuildtime.ts @@ -176,7 +176,7 @@ const concreteRenderers = defineConcreteRenderers({ const getDocumentation = (config: Config, node: Describable) => { const generalDescription = node.description - ?? (config.TSDoc.noDocPolicy === `message` ? defaultDescription(node) : null) + ?? (config.options.TSDoc.noDocPolicy === `message` ? defaultDescription(node) : null) const deprecationDescription = isDeprecatableNode(node) && node.deprecationReason ? `@deprecated ${node.deprecationReason}` @@ -191,7 +191,7 @@ const getDocumentation = (config: Config, node: Describable) => { : null const generalDescription = _.description ? _.description - : config.TSDoc.noDocPolicy === `message` + : config.options.TSDoc.noDocPolicy === `message` ? `Missing description.` : null if (!generalDescription && !deprecationDescription) return null diff --git a/src/generator/files.ts b/src/generator/files.ts index 3eb4f67f2..25820cc73 100644 --- a/src/generator/files.ts +++ b/src/generator/files.ts @@ -8,7 +8,7 @@ import { generateCode, type Input as GenerateInput } from './code/code.js' export interface Input { outputDirPath: string - code?: Omit + code?: Omit sourceDirPath?: string schemaPath?: string format?: boolean @@ -19,23 +19,25 @@ export const generateFiles = async (input: Input) => { const schemaPath = input.schemaPath ?? Path.join(sourceDirPath, `schema.graphql`) const schemaSource = await fs.readFile(schemaPath, `utf8`) - const customScalarCodecsPath = Path.relative(input.outputDirPath, Path.join(sourceDirPath, `customScalarCodecs.js`)) // todo support other extensions: .tsx,.js,.mjs,.cjs - const customScalarCodecsPathExists = await fileExists(customScalarCodecsPath.replace(`.js`, `.ts`)) + const customScalarCodecsFilePath = Path.join(sourceDirPath, `customScalarCodecs.ts`) + const customScalarCodecsImportPath = Path.relative( + input.outputDirPath, + customScalarCodecsFilePath.replace(/\.ts$/, `.js`), + ) + const customScalarCodecsPathExists = await fileExists(customScalarCodecsFilePath) const formatter = (input.format ?? true) ? createFromBuffer(await fs.readFile(getPath())) : undefined - const options: GenerateInput['options'] = { - formatter, - customScalars: customScalarCodecsPathExists, - } - const code = generateCode({ schemaSource, importPaths: { - customScalarCodecs: customScalarCodecsPath, + customScalarCodecs: customScalarCodecsImportPath, }, ...input.code, - options, + options: { + formatter, + customScalars: customScalarCodecsPathExists, + }, }) await fs.mkdir(input.outputDirPath, { recursive: true }) await fs.writeFile(`${input.outputDirPath}/Index.ts`, code.index, { encoding: `utf8` }) From 3b65769949862d2a9e889b4b7d708a7a01813df7 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Thu, 11 Apr 2024 20:56:19 -0400 Subject: [PATCH 2/2] refactor --- src/generator/files.ts | 13 +------------ src/lib/prelude.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/generator/files.ts b/src/generator/files.ts index 25820cc73..1c33a13fd 100644 --- a/src/generator/files.ts +++ b/src/generator/files.ts @@ -3,7 +3,7 @@ import { getPath } from '@dprint/typescript' import _ from 'json-bigint' import fs from 'node:fs/promises' import * as Path from 'node:path' -import { errorFromMaybeError } from '../lib/prelude.js' +import { fileExists } from '../lib/prelude.js' import { generateCode, type Input as GenerateInput } from './code/code.js' export interface Input { @@ -45,14 +45,3 @@ export const generateFiles = async (input: Input) => { await fs.writeFile(`${input.outputDirPath}/Scalar.ts`, code.scalars, { encoding: `utf8` }) await fs.writeFile(`${input.outputDirPath}/SchemaRuntime.ts`, code.schemaRuntime, { encoding: `utf8` }) } - -const fileExists = async (path: string) => { - return Boolean( - await fs.stat(path).catch((_: unknown) => { - const error = errorFromMaybeError(_) - return `code` in error && typeof error.code === `string` && error.code === `ENOENT` - ? null - : Promise.reject(error) - }), - ) -} diff --git a/src/lib/prelude.ts b/src/lib/prelude.ts index e720bb0b7..c9a92ed0f 100644 --- a/src/lib/prelude.ts +++ b/src/lib/prelude.ts @@ -173,3 +173,16 @@ export type GetKeyOr = Key extends keyof T ? T[Key] : Or import type { ConditionalSimplifyDeep } from 'type-fest/source/conditional-simplify.js' export type SimplifyDeep = ConditionalSimplifyDeep | Date, object> + +import fs from 'node:fs/promises' + +export const fileExists = async (path: string) => { + return Boolean( + await fs.stat(path).catch((_: unknown) => { + const error = errorFromMaybeError(_) + return `code` in error && typeof error.code === `string` && error.code === `ENOENT` + ? null + : Promise.reject(error) + }), + ) +}