Skip to content

Commit

Permalink
feat(ts-client): optional custom scalars (#768)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Apr 12, 2024
1 parent fdca0a7 commit 33a0278
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 32 deletions.
19 changes: 14 additions & 5 deletions src/generator/code/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export interface Input {
importPaths?: {
customScalarCodecs?: string
}
/**
* The GraphQL SDL source code.
*/
schemaSource: string
options?: {
/**
Expand All @@ -32,16 +35,19 @@ export interface Input {

export interface Config {
schema: GraphQLSchema
typeMapByKind: TypeMapByKind
libraryPaths: {
schema: string
scalars: string
}
importPaths: {
customScalarCodecs: string
}
typeMapByKind: TypeMapByKind
TSDoc: {
noDocPolicy: 'message' | 'ignore'
options: {
customScalars: boolean
TSDoc: {
noDocPolicy: 'message' | 'ignore'
}
}
}

Expand All @@ -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`,
},
},
}
}
Expand Down
26 changes: 23 additions & 3 deletions src/generator/code/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions src/generator/code/schemaBuildtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand All @@ -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
Expand Down
35 changes: 13 additions & 22 deletions src/generator/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ 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 {
outputDirPath: string
code?: Omit<GenerateInput, 'schemaSource' | 'sourceDirPath'>
code?: Omit<GenerateInput, 'schemaSource' | 'sourceDirPath' | 'options'>
sourceDirPath?: string
schemaPath?: string
format?: boolean
Expand All @@ -19,38 +19,29 @@ 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` })
await fs.writeFile(`${input.outputDirPath}/SchemaBuildtime.ts`, code.schemaBuildtime, { encoding: `utf8` })
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)
}),
)
}
13 changes: 13 additions & 0 deletions src/lib/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,16 @@ export type GetKeyOr<T, Key, Or> = Key extends keyof T ? T[Key] : Or
import type { ConditionalSimplifyDeep } from 'type-fest/source/conditional-simplify.js'

export type SimplifyDeep<T> = ConditionalSimplifyDeep<T, Function | Iterable<unknown> | 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)
}),
)
}

0 comments on commit 33a0278

Please sign in to comment.