Skip to content

Commit 33a0278

Browse files
authored
feat(ts-client): optional custom scalars (#768)
1 parent fdca0a7 commit 33a0278

File tree

5 files changed

+65
-32
lines changed

5 files changed

+65
-32
lines changed

src/generator/code/code.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export interface Input {
1717
importPaths?: {
1818
customScalarCodecs?: string
1919
}
20+
/**
21+
* The GraphQL SDL source code.
22+
*/
2023
schemaSource: string
2124
options?: {
2225
/**
@@ -32,16 +35,19 @@ export interface Input {
3235

3336
export interface Config {
3437
schema: GraphQLSchema
38+
typeMapByKind: TypeMapByKind
3539
libraryPaths: {
3640
schema: string
3741
scalars: string
3842
}
3943
importPaths: {
4044
customScalarCodecs: string
4145
}
42-
typeMapByKind: TypeMapByKind
43-
TSDoc: {
44-
noDocPolicy: 'message' | 'ignore'
46+
options: {
47+
customScalars: boolean
48+
TSDoc: {
49+
noDocPolicy: 'message' | 'ignore'
50+
}
4551
}
4652
}
4753

@@ -57,8 +63,11 @@ export const resolveOptions = (input: Input): Config => {
5763
schema: input.libraryPaths?.schema ?? `graphql-request/alpha/schema`,
5864
},
5965
typeMapByKind: getTypeMapByKind(schema),
60-
TSDoc: {
61-
noDocPolicy: input.options?.TSDoc?.noDocPolicy ?? `ignore`,
66+
options: {
67+
customScalars: input.options?.customScalars ?? false,
68+
TSDoc: {
69+
noDocPolicy: input.options?.TSDoc?.noDocPolicy ?? `ignore`,
70+
},
6271
},
6372
}
6473
}

src/generator/code/scalar.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,42 @@ import type { Config } from './code.js'
33
export const generateScalar = (config: Config) => {
44
let code = ``
55

6+
// todo test case for when this is true
7+
const needsDefaultCustomScalarImplementation = config.typeMapByKind.GraphQLScalarTypeCustom.length > 0
8+
&& !config.options.customScalars
9+
610
code += `
7-
import type * as CustomScalar from '${config.importPaths.customScalarCodecs}'
11+
12+
${config.options.customScalars ? `import type * as CustomScalar from '${config.importPaths.customScalarCodecs}'` : ``}
813
914
declare global {
1015
interface SchemaCustomScalars {
1116
${
1217
config.typeMapByKind.GraphQLScalarTypeCustom
1318
.map((_) => {
14-
return `${_.name}: CustomScalar.${_.name}`
19+
return `${_.name}: ${needsDefaultCustomScalarImplementation ? `String` : `CustomScalar.${_.name}`}`
1520
}).join(`\n`)
1621
}
1722
}
1823
}
1924
2025
export * from '${config.libraryPaths.scalars}'
21-
export * from '${config.importPaths.customScalarCodecs}'
26+
${config.options.customScalars ? `export * from '${config.importPaths.customScalarCodecs}'` : ``}
2227
`
28+
29+
if (needsDefaultCustomScalarImplementation) {
30+
console.log(
31+
`WARNING: Custom scalars detected in the schema, but you have not created a custom scalars module to import implementations from.`,
32+
)
33+
code += `
34+
${
35+
config.typeMapByKind.GraphQLScalarTypeCustom
36+
.flatMap((_) => {
37+
return [`export const ${_.name} = String`, `export type ${_.name} = String`]
38+
}).join(`\n`)
39+
}
40+
`
41+
}
42+
2343
return code
2444
}

src/generator/code/schemaBuildtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ const concreteRenderers = defineConcreteRenderers({
176176

177177
const getDocumentation = (config: Config, node: Describable) => {
178178
const generalDescription = node.description
179-
?? (config.TSDoc.noDocPolicy === `message` ? defaultDescription(node) : null)
179+
?? (config.options.TSDoc.noDocPolicy === `message` ? defaultDescription(node) : null)
180180

181181
const deprecationDescription = isDeprecatableNode(node) && node.deprecationReason
182182
? `@deprecated ${node.deprecationReason}`
@@ -191,7 +191,7 @@ const getDocumentation = (config: Config, node: Describable) => {
191191
: null
192192
const generalDescription = _.description
193193
? _.description
194-
: config.TSDoc.noDocPolicy === `message`
194+
: config.options.TSDoc.noDocPolicy === `message`
195195
? `Missing description.`
196196
: null
197197
if (!generalDescription && !deprecationDescription) return null

src/generator/files.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { getPath } from '@dprint/typescript'
33
import _ from 'json-bigint'
44
import fs from 'node:fs/promises'
55
import * as Path from 'node:path'
6-
import { errorFromMaybeError } from '../lib/prelude.js'
6+
import { fileExists } from '../lib/prelude.js'
77
import { generateCode, type Input as GenerateInput } from './code/code.js'
88

99
export interface Input {
1010
outputDirPath: string
11-
code?: Omit<GenerateInput, 'schemaSource' | 'sourceDirPath'>
11+
code?: Omit<GenerateInput, 'schemaSource' | 'sourceDirPath' | 'options'>
1212
sourceDirPath?: string
1313
schemaPath?: string
1414
format?: boolean
@@ -19,38 +19,29 @@ export const generateFiles = async (input: Input) => {
1919
const schemaPath = input.schemaPath ?? Path.join(sourceDirPath, `schema.graphql`)
2020
const schemaSource = await fs.readFile(schemaPath, `utf8`)
2121

22-
const customScalarCodecsPath = Path.relative(input.outputDirPath, Path.join(sourceDirPath, `customScalarCodecs.js`))
2322
// todo support other extensions: .tsx,.js,.mjs,.cjs
24-
const customScalarCodecsPathExists = await fileExists(customScalarCodecsPath.replace(`.js`, `.ts`))
23+
const customScalarCodecsFilePath = Path.join(sourceDirPath, `customScalarCodecs.ts`)
24+
const customScalarCodecsImportPath = Path.relative(
25+
input.outputDirPath,
26+
customScalarCodecsFilePath.replace(/\.ts$/, `.js`),
27+
)
28+
const customScalarCodecsPathExists = await fileExists(customScalarCodecsFilePath)
2529
const formatter = (input.format ?? true) ? createFromBuffer(await fs.readFile(getPath())) : undefined
2630

27-
const options: GenerateInput['options'] = {
28-
formatter,
29-
customScalars: customScalarCodecsPathExists,
30-
}
31-
3231
const code = generateCode({
3332
schemaSource,
3433
importPaths: {
35-
customScalarCodecs: customScalarCodecsPath,
34+
customScalarCodecs: customScalarCodecsImportPath,
3635
},
3736
...input.code,
38-
options,
37+
options: {
38+
formatter,
39+
customScalars: customScalarCodecsPathExists,
40+
},
3941
})
4042
await fs.mkdir(input.outputDirPath, { recursive: true })
4143
await fs.writeFile(`${input.outputDirPath}/Index.ts`, code.index, { encoding: `utf8` })
4244
await fs.writeFile(`${input.outputDirPath}/SchemaBuildtime.ts`, code.schemaBuildtime, { encoding: `utf8` })
4345
await fs.writeFile(`${input.outputDirPath}/Scalar.ts`, code.scalars, { encoding: `utf8` })
4446
await fs.writeFile(`${input.outputDirPath}/SchemaRuntime.ts`, code.schemaRuntime, { encoding: `utf8` })
4547
}
46-
47-
const fileExists = async (path: string) => {
48-
return Boolean(
49-
await fs.stat(path).catch((_: unknown) => {
50-
const error = errorFromMaybeError(_)
51-
return `code` in error && typeof error.code === `string` && error.code === `ENOENT`
52-
? null
53-
: Promise.reject(error)
54-
}),
55-
)
56-
}

src/lib/prelude.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,16 @@ export type GetKeyOr<T, Key, Or> = Key extends keyof T ? T[Key] : Or
173173
import type { ConditionalSimplifyDeep } from 'type-fest/source/conditional-simplify.js'
174174

175175
export type SimplifyDeep<T> = ConditionalSimplifyDeep<T, Function | Iterable<unknown> | Date, object>
176+
177+
import fs from 'node:fs/promises'
178+
179+
export const fileExists = async (path: string) => {
180+
return Boolean(
181+
await fs.stat(path).catch((_: unknown) => {
182+
const error = errorFromMaybeError(_)
183+
return `code` in error && typeof error.code === `string` && error.code === `ENOENT`
184+
? null
185+
: Promise.reject(error)
186+
}),
187+
)
188+
}

0 commit comments

Comments
 (0)