@znight/graphql 0.0.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1346 -0
- package/dist/compiler.d.mts +114 -0
- package/dist/compiler.mjs +4599 -0
- package/dist/compiler.mjs.map +1 -0
- package/dist/index.d.mts +583 -0
- package/dist/index.mjs +134 -0
- package/dist/index.mjs.map +1 -0
- package/dist/names-BlzDM5cF.mjs +111 -0
- package/dist/names-BlzDM5cF.mjs.map +1 -0
- package/dist/plugin-CR_wVfRU.mjs +3445 -0
- package/dist/plugin-CR_wVfRU.mjs.map +1 -0
- package/dist/testing.d.mts +101 -0
- package/dist/testing.mjs +306 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-DQ713UgI.d.mts +266 -0
- package/package.json +97 -1
- package/src/compiler/analyze.ts +727 -0
- package/src/compiler/builder.ts +1561 -0
- package/src/compiler/checks.ts +105 -0
- package/src/compiler/conformance.ts +1068 -0
- package/src/compiler/constants.ts +80 -0
- package/src/compiler/data.ts +83 -0
- package/src/compiler/defaults.ts +92 -0
- package/src/compiler/discriminators.ts +53 -0
- package/src/compiler/docs.ts +55 -0
- package/src/compiler/generate.ts +419 -0
- package/src/compiler/graphql-js.ts +42 -0
- package/src/compiler/ids.ts +128 -0
- package/src/compiler/index.ts +98 -0
- package/src/compiler/locate.ts +82 -0
- package/src/compiler/model.ts +137 -0
- package/src/compiler/options.ts +85 -0
- package/src/compiler/scalars.ts +138 -0
- package/src/compiler/schema-first.ts +595 -0
- package/src/compiler/sdl-source.ts +529 -0
- package/src/compiler/sdl-view.ts +61 -0
- package/src/compiler/suggest.ts +39 -0
- package/src/context.ts +181 -0
- package/src/decisions.ts +169 -0
- package/src/decorators.ts +202 -0
- package/src/errors.ts +89 -0
- package/src/execution.ts +1064 -0
- package/src/format.ts +285 -0
- package/src/index.ts +89 -0
- package/src/limits.ts +177 -0
- package/src/loaders.ts +149 -0
- package/src/memory-pubsub.ts +133 -0
- package/src/metadata.ts +222 -0
- package/src/names.ts +47 -0
- package/src/options.ts +331 -0
- package/src/pipeline.ts +338 -0
- package/src/plugin.ts +579 -0
- package/src/pubsub.ts +36 -0
- package/src/results.ts +206 -0
- package/src/scalars.ts +138 -0
- package/src/schema-hash.ts +18 -0
- package/src/schema.ts +635 -0
- package/src/sdl.ts +530 -0
- package/src/testing.ts +454 -0
- package/src/tokens.ts +22 -0
- package/src/transport/abort.ts +80 -0
- package/src/transport/accept.ts +155 -0
- package/src/transport/graphiql.ts +106 -0
- package/src/transport/http.ts +373 -0
- package/src/transport/sse.ts +45 -0
- package/src/type-ref.ts +89 -0
- package/src/types.ts +225 -0
- package/src/validate-args.ts +48 -0
package/src/sdl.ts
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic SDL printer (RFC 0005 §6.7, §7.3).
|
|
3
|
+
*
|
|
4
|
+
* Prints a schema model (the data of `metadata.extensions.graphql`, without its functions) exactly
|
|
5
|
+
* like graphql-js 17 `printSchema()` prints the `GraphQLSchema` the runtime builds from it: same
|
|
6
|
+
* order, same block strings, same default value literals, byte for byte. The compiler hashes this
|
|
7
|
+
* output (`schemaHash`) and writes it as the optional SDL artifact; the runtime can print without
|
|
8
|
+
* graphql-js.
|
|
9
|
+
*
|
|
10
|
+
* Import-free (types and constants of import-free modules only): shared by the compiler extension
|
|
11
|
+
* and the runtime. Equivalence holds when the runtime builds the schema the way this module
|
|
12
|
+
* assumes:
|
|
13
|
+
*
|
|
14
|
+
* - every type of the model passed to `new GraphQLSchema({ types })` in model order (metadata types
|
|
15
|
+
* are sorted by name), so the type map, hence `printSchema`, follows that order;
|
|
16
|
+
* - default values passed as `default: { value }` (external values: enum values by name), printed
|
|
17
|
+
* by graphql-js through `valueToLiteral`;
|
|
18
|
+
* - built-in scalars (`names.ts`) without description, with their `specifiedByURL`, and no scalar
|
|
19
|
+
* with a custom `valueToLiteral` used in a default value.
|
|
20
|
+
*/
|
|
21
|
+
import { BUILTIN_SCALARS, DEFAULT_DEPRECATION_REASON, ROOT_TYPE_NAMES, SPECIFIED_SCALARS } from './names.ts'
|
|
22
|
+
import { type GraphQLTypeRef, parseTypeRef } from './type-ref.ts'
|
|
23
|
+
|
|
24
|
+
/** The schema to print: the shape of `GraphQLMetadata` without resolvers nor scalar implementations. */
|
|
25
|
+
export interface SdlSchema {
|
|
26
|
+
readonly roots: SdlRoots
|
|
27
|
+
/** Printed in this order (graphql-js specified scalars are skipped). */
|
|
28
|
+
readonly types: readonly SdlType[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Names of the root operation types. */
|
|
32
|
+
export interface SdlRoots {
|
|
33
|
+
readonly query: string
|
|
34
|
+
readonly mutation?: string
|
|
35
|
+
readonly subscription?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A named type of the schema. */
|
|
39
|
+
export type SdlType = SdlObjectType | SdlInterfaceType | SdlInputType | SdlEnumType | SdlUnionType | SdlScalarType
|
|
40
|
+
|
|
41
|
+
/** What every named type has. */
|
|
42
|
+
export interface SdlNamedType {
|
|
43
|
+
readonly name: string
|
|
44
|
+
readonly description?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** An object type (`type Book { ... }`). */
|
|
48
|
+
export interface SdlObjectType extends SdlNamedType {
|
|
49
|
+
readonly kind: 'object'
|
|
50
|
+
/** Implemented interfaces, in declaration order. */
|
|
51
|
+
readonly interfaces?: readonly string[]
|
|
52
|
+
readonly fields: readonly SdlField[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An interface type (`interface Node { ... }`). */
|
|
56
|
+
export interface SdlInterfaceType extends SdlNamedType {
|
|
57
|
+
readonly kind: 'interface'
|
|
58
|
+
readonly interfaces?: readonly string[]
|
|
59
|
+
readonly fields: readonly SdlField[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A field of an object type. */
|
|
63
|
+
export interface SdlField {
|
|
64
|
+
readonly name: string
|
|
65
|
+
/** Type reference (`'[Book!]!'`). */
|
|
66
|
+
readonly type: string
|
|
67
|
+
readonly description?: string
|
|
68
|
+
readonly deprecationReason?: string
|
|
69
|
+
readonly args?: readonly SdlInputValue[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** An argument or an input field. */
|
|
73
|
+
export interface SdlInputValue {
|
|
74
|
+
readonly name: string
|
|
75
|
+
readonly type: string
|
|
76
|
+
readonly description?: string
|
|
77
|
+
readonly deprecationReason?: string
|
|
78
|
+
/** External value (JSON-compatible, enum values by name); `undefined`: no default. */
|
|
79
|
+
readonly defaultValue?: unknown
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** An input object type (`input NewBookInput { ... }`). */
|
|
83
|
+
export interface SdlInputType extends SdlNamedType {
|
|
84
|
+
readonly kind: 'input'
|
|
85
|
+
readonly fields: readonly SdlInputValue[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** An enum type. */
|
|
89
|
+
export interface SdlEnumType extends SdlNamedType {
|
|
90
|
+
readonly kind: 'enum'
|
|
91
|
+
readonly values: readonly SdlEnumValue[]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A value of an enum type. */
|
|
95
|
+
export interface SdlEnumValue {
|
|
96
|
+
readonly name: string
|
|
97
|
+
readonly description?: string
|
|
98
|
+
readonly deprecationReason?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A union type. */
|
|
102
|
+
export interface SdlUnionType extends SdlNamedType {
|
|
103
|
+
readonly kind: 'union'
|
|
104
|
+
readonly members: readonly string[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A scalar type: a built-in one of `@znight/graphql` (`builtin: true`) or a user scalar. */
|
|
108
|
+
export interface SdlScalarType extends SdlNamedType {
|
|
109
|
+
readonly kind: 'scalar'
|
|
110
|
+
readonly builtin?: boolean
|
|
111
|
+
/** `@specifiedBy(url:)`. Built-in scalars default to the URL of `BUILTIN_SCALARS`. */
|
|
112
|
+
readonly specifiedByURL?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Prints the SDL of a schema, without a trailing newline (like `printSchema`). Throws an `Error` on
|
|
117
|
+
* a malformed type reference or a default value that does not match its type (the compiler reports
|
|
118
|
+
* these as diagnostics before printing).
|
|
119
|
+
*/
|
|
120
|
+
export function printSdl(schema: SdlSchema): string {
|
|
121
|
+
const types = new Map<string, SdlType>()
|
|
122
|
+
for (const type of schema.types) types.set(type.name, type)
|
|
123
|
+
const printer = new SdlPrinter(types)
|
|
124
|
+
const parts: string[] = []
|
|
125
|
+
const definition = printSchemaDefinition(schema.roots, types)
|
|
126
|
+
if (definition !== undefined) parts.push(definition)
|
|
127
|
+
for (const type of schema.types) {
|
|
128
|
+
if (!SPECIFIED_SCALARS.has(type.name)) parts.push(printer.type(type))
|
|
129
|
+
}
|
|
130
|
+
return parts.join('\n\n')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `schema { ... }`, only when the root types do not have their default names (as graphql-js). */
|
|
134
|
+
function printSchemaDefinition(roots: SdlRoots, types: ReadonlyMap<string, SdlType>): string | undefined {
|
|
135
|
+
// graphql-js compares `getQueryType() == getType('Query')` (and so on): a missing root equals a
|
|
136
|
+
// missing type of the default name.
|
|
137
|
+
const isDefault = (root: string | undefined, name: string): boolean =>
|
|
138
|
+
root === undefined ? !types.has(name) : root === name
|
|
139
|
+
if (
|
|
140
|
+
isDefault(roots.query, ROOT_TYPE_NAMES.query) &&
|
|
141
|
+
isDefault(roots.mutation, ROOT_TYPE_NAMES.mutation) &&
|
|
142
|
+
isDefault(roots.subscription, ROOT_TYPE_NAMES.subscription)
|
|
143
|
+
) {
|
|
144
|
+
return undefined
|
|
145
|
+
}
|
|
146
|
+
return (
|
|
147
|
+
'schema {\n' +
|
|
148
|
+
` query: ${roots.query}\n` +
|
|
149
|
+
(roots.mutation === undefined ? '' : ` mutation: ${roots.mutation}\n`) +
|
|
150
|
+
(roots.subscription === undefined ? '' : ` subscription: ${roots.subscription}\n`) +
|
|
151
|
+
'}'
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A GraphQL value literal (the subset of graphql-js `ConstValueNode` defaults can produce). */
|
|
156
|
+
type Literal =
|
|
157
|
+
| { readonly kind: 'IntValue' | 'FloatValue' | 'StringValue' | 'EnumValue'; readonly value: string }
|
|
158
|
+
| { readonly kind: 'BooleanValue'; readonly value: boolean }
|
|
159
|
+
| { readonly kind: 'NullValue' }
|
|
160
|
+
| { readonly kind: 'ListValue'; readonly values: readonly Literal[] }
|
|
161
|
+
| { readonly kind: 'ObjectValue'; readonly fields: readonly (readonly [name: string, value: Literal])[] }
|
|
162
|
+
|
|
163
|
+
const GRAPHQL_MAX_INT = 2_147_483_647
|
|
164
|
+
const GRAPHQL_MIN_INT = -2_147_483_648
|
|
165
|
+
const INTEGER_STRING = /^-?(?:0|[1-9][0-9]*)$/
|
|
166
|
+
/** graphql-js `print()` breaks lists and objects longer than this. */
|
|
167
|
+
const MAX_LINE_LENGTH = 80
|
|
168
|
+
|
|
169
|
+
class SdlPrinter {
|
|
170
|
+
readonly #types: ReadonlyMap<string, SdlType>
|
|
171
|
+
|
|
172
|
+
constructor(types: ReadonlyMap<string, SdlType>) {
|
|
173
|
+
this.#types = types
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type(type: SdlType): string {
|
|
177
|
+
const head = printDescription(type.description, '', true)
|
|
178
|
+
switch (type.kind) {
|
|
179
|
+
case 'object':
|
|
180
|
+
case 'interface':
|
|
181
|
+
return `${head}${type.kind === 'object' ? 'type' : 'interface'} ${type.name}${printImplements(type.interfaces)}${printBlock(type.fields.map((field, i) => this.#field(type.name, field, i)))}`
|
|
182
|
+
case 'input':
|
|
183
|
+
return `${head}input ${type.name}${printBlock(
|
|
184
|
+
type.fields.map(
|
|
185
|
+
(field, i) =>
|
|
186
|
+
`${printDescription(field.description, ' ', i === 0)} ${this.#inputValue(field, `${type.name}.${field.name}`)}`,
|
|
187
|
+
),
|
|
188
|
+
)}`
|
|
189
|
+
case 'enum':
|
|
190
|
+
return `${head}enum ${type.name}${printBlock(
|
|
191
|
+
type.values.map(
|
|
192
|
+
(value, i) =>
|
|
193
|
+
`${printDescription(value.description, ' ', i === 0)} ${value.name}${printDeprecated(value.deprecationReason)}`,
|
|
194
|
+
),
|
|
195
|
+
)}`
|
|
196
|
+
case 'union':
|
|
197
|
+
return `${head}union ${type.name}${type.members.length === 0 ? '' : ` = ${type.members.join(' | ')}`}`
|
|
198
|
+
case 'scalar': {
|
|
199
|
+
const url =
|
|
200
|
+
type.specifiedByURL ?? (type.builtin === true ? BUILTIN_SCALARS.get(type.name)?.specifiedByURL : undefined)
|
|
201
|
+
return `${head}scalar ${type.name}${url === undefined ? '' : ` @specifiedBy(url: ${printString(url)})`}`
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#field(typeName: string, field: SdlField, index: number): string {
|
|
207
|
+
const coordinate = `${typeName}.${field.name}`
|
|
208
|
+
return (
|
|
209
|
+
`${printDescription(field.description, ' ', index === 0)} ${field.name}` +
|
|
210
|
+
this.#args(coordinate, field.args ?? []) +
|
|
211
|
+
`: ${checkedTypeRef(field.type, coordinate)}${printDeprecated(field.deprecationReason)}`
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#args(coordinate: string, args: readonly SdlInputValue[]): string {
|
|
216
|
+
if (args.length === 0) return ''
|
|
217
|
+
if (args.every(arg => arg.description === undefined)) {
|
|
218
|
+
return `(${args.map(arg => this.#inputValue(arg, `${coordinate}(${arg.name}:)`)).join(', ')})`
|
|
219
|
+
}
|
|
220
|
+
const lines = args.map(
|
|
221
|
+
(arg, i) =>
|
|
222
|
+
`${printDescription(arg.description, ' ', i === 0)} ${this.#inputValue(arg, `${coordinate}(${arg.name}:)`)}`,
|
|
223
|
+
)
|
|
224
|
+
return `(\n${lines.join('\n')}\n )`
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#inputValue(value: SdlInputValue, coordinate: string): string {
|
|
228
|
+
const ref = parseTypeRef(value.type)
|
|
229
|
+
if (ref === undefined) throw new Error(`Invalid type reference '${value.type}' (${coordinate})`)
|
|
230
|
+
let declaration = `${value.name}: ${value.type}`
|
|
231
|
+
if (value.defaultValue !== undefined) {
|
|
232
|
+
const literal = this.#literal(value.defaultValue, ref)
|
|
233
|
+
if (literal === undefined) {
|
|
234
|
+
throw new Error(`Invalid default value for ${coordinate}: ${describe(value.defaultValue)}`)
|
|
235
|
+
}
|
|
236
|
+
declaration += ` = ${printLiteral(literal)}`
|
|
237
|
+
}
|
|
238
|
+
return declaration + printDeprecated(value.deprecationReason)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** graphql-js `valueToLiteral(value, type)`: `undefined` when the value does not fit the type. */
|
|
242
|
+
#literal(value: unknown, ref: GraphQLTypeRef): Literal | undefined {
|
|
243
|
+
if (ref.nonNull && value == null) return undefined
|
|
244
|
+
if (value == null) return { kind: 'NullValue' }
|
|
245
|
+
if (ref.kind === 'list') {
|
|
246
|
+
const item: GraphQLTypeRef = ref.of
|
|
247
|
+
if (!isIterableObject(value)) return this.#literal(value, item)
|
|
248
|
+
const values: Literal[] = []
|
|
249
|
+
for (const entry of value) {
|
|
250
|
+
const literal = this.#literal(entry, item)
|
|
251
|
+
if (literal === undefined) return undefined
|
|
252
|
+
values.push(literal)
|
|
253
|
+
}
|
|
254
|
+
return { kind: 'ListValue', values }
|
|
255
|
+
}
|
|
256
|
+
const type = this.#types.get(ref.name)
|
|
257
|
+
if (type?.kind === 'input') return this.#inputLiteral(value, type)
|
|
258
|
+
if (type?.kind === 'enum') {
|
|
259
|
+
return typeof value === 'string' && type.values.some(entry => entry.name === value)
|
|
260
|
+
? { kind: 'EnumValue', value }
|
|
261
|
+
: undefined
|
|
262
|
+
}
|
|
263
|
+
return scalarLiteral(value, ref.name)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#inputLiteral(value: unknown, type: SdlInputType): Literal | undefined {
|
|
267
|
+
if (typeof value !== 'object' || value === null) return undefined
|
|
268
|
+
const record = value as Readonly<Record<string, unknown>>
|
|
269
|
+
for (const key of Object.keys(record)) {
|
|
270
|
+
if (record[key] !== undefined && !type.fields.some(field => field.name === key)) return undefined
|
|
271
|
+
}
|
|
272
|
+
const fields: (readonly [string, Literal])[] = []
|
|
273
|
+
for (const field of type.fields) {
|
|
274
|
+
const fieldValue = Object.hasOwn(record, field.name) ? record[field.name] : undefined
|
|
275
|
+
const ref = parseTypeRef(field.type)
|
|
276
|
+
if (ref === undefined) return undefined
|
|
277
|
+
if (fieldValue === undefined) {
|
|
278
|
+
// required input field: non-null without default
|
|
279
|
+
if (ref.nonNull && field.defaultValue === undefined) return undefined
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
const literal = this.#literal(fieldValue, ref)
|
|
283
|
+
if (literal === undefined) return undefined
|
|
284
|
+
fields.push([field.name, literal])
|
|
285
|
+
}
|
|
286
|
+
return { kind: 'ObjectValue', fields }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** graphql-js `printImplementedInterfaces`. */
|
|
291
|
+
function printImplements(interfaces: readonly string[] | undefined): string {
|
|
292
|
+
return interfaces === undefined || interfaces.length === 0 ? '' : ` implements ${interfaces.join(' & ')}`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** The `valueToLiteral` of the specified scalars, and graphql-js' default one for the others. */
|
|
296
|
+
function scalarLiteral(value: unknown, name: string): Literal | undefined {
|
|
297
|
+
switch (name) {
|
|
298
|
+
case 'Int': {
|
|
299
|
+
const integer = (typeof value === 'number' && Number.isInteger(value)) || typeof value === 'bigint'
|
|
300
|
+
return integer && value <= GRAPHQL_MAX_INT && value >= GRAPHQL_MIN_INT
|
|
301
|
+
? { kind: 'IntValue', value: String(value) }
|
|
302
|
+
: undefined
|
|
303
|
+
}
|
|
304
|
+
case 'Float': {
|
|
305
|
+
// graphql-js wraps the `valueToLiteral` of a scalar in a try/catch: a value it cannot convert
|
|
306
|
+
// (a function) is a mismatch, not a crash
|
|
307
|
+
try {
|
|
308
|
+
const literal = defaultScalarLiteral(value)
|
|
309
|
+
return literal.kind === 'FloatValue' || literal.kind === 'IntValue' ? literal : undefined
|
|
310
|
+
} catch {
|
|
311
|
+
return undefined
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
case 'String':
|
|
315
|
+
return typeof value === 'string' ? { kind: 'StringValue', value } : undefined
|
|
316
|
+
case 'Boolean':
|
|
317
|
+
return typeof value === 'boolean' ? { kind: 'BooleanValue', value } : undefined
|
|
318
|
+
case 'ID':
|
|
319
|
+
if (typeof value === 'string') {
|
|
320
|
+
return INTEGER_STRING.test(value) ? { kind: 'IntValue', value } : { kind: 'StringValue', value }
|
|
321
|
+
}
|
|
322
|
+
if (typeof value === 'number')
|
|
323
|
+
return Number.isInteger(value) ? { kind: 'IntValue', value: String(value) } : undefined
|
|
324
|
+
return typeof value === 'bigint' ? { kind: 'IntValue', value: String(value) } : undefined
|
|
325
|
+
default:
|
|
326
|
+
return defaultScalarLiteral(value)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** graphql-js `defaultScalarValueToLiteral`. */
|
|
331
|
+
function defaultScalarLiteral(value: unknown): Literal {
|
|
332
|
+
if (value == null) return { kind: 'NullValue' }
|
|
333
|
+
switch (typeof value) {
|
|
334
|
+
case 'boolean':
|
|
335
|
+
return { kind: 'BooleanValue', value }
|
|
336
|
+
case 'string':
|
|
337
|
+
return { kind: 'StringValue', value }
|
|
338
|
+
case 'bigint':
|
|
339
|
+
return { kind: 'IntValue', value: value.toString() }
|
|
340
|
+
case 'number': {
|
|
341
|
+
if (!Number.isFinite(value)) return { kind: 'NullValue' }
|
|
342
|
+
const text = String(value)
|
|
343
|
+
return INTEGER_STRING.test(text) ? { kind: 'IntValue', value: text } : { kind: 'FloatValue', value: text }
|
|
344
|
+
}
|
|
345
|
+
case 'object': {
|
|
346
|
+
if (isIterableObject(value)) return { kind: 'ListValue', values: Array.from(value, defaultScalarLiteral) }
|
|
347
|
+
const record = value as Readonly<Record<string, unknown>>
|
|
348
|
+
const fields: (readonly [string, Literal])[] = []
|
|
349
|
+
for (const key of Object.keys(record)) {
|
|
350
|
+
if (record[key] !== undefined) fields.push([key, defaultScalarLiteral(record[key])])
|
|
351
|
+
}
|
|
352
|
+
return { kind: 'ObjectValue', fields }
|
|
353
|
+
}
|
|
354
|
+
default:
|
|
355
|
+
throw new TypeError(`Cannot convert value to AST: ${describe(value)}.`)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function isIterableObject(value: unknown): value is Iterable<unknown> {
|
|
360
|
+
return (
|
|
361
|
+
typeof value === 'object' && value !== null && typeof (value as Iterable<unknown>)[Symbol.iterator] === 'function'
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** graphql-js `print()` of a value literal. */
|
|
366
|
+
function printLiteral(literal: Literal): string {
|
|
367
|
+
switch (literal.kind) {
|
|
368
|
+
case 'IntValue':
|
|
369
|
+
case 'FloatValue':
|
|
370
|
+
case 'EnumValue':
|
|
371
|
+
return literal.value
|
|
372
|
+
case 'StringValue':
|
|
373
|
+
return printString(literal.value)
|
|
374
|
+
case 'BooleanValue':
|
|
375
|
+
return literal.value ? 'true' : 'false'
|
|
376
|
+
case 'NullValue':
|
|
377
|
+
return 'null'
|
|
378
|
+
case 'ListValue': {
|
|
379
|
+
const values = literal.values.map(printLiteral)
|
|
380
|
+
const line = `[${values.join(', ')}]`
|
|
381
|
+
return line.length > MAX_LINE_LENGTH ? `[\n${indent(values.join('\n'))}\n]` : line
|
|
382
|
+
}
|
|
383
|
+
case 'ObjectValue': {
|
|
384
|
+
const fields = literal.fields.map(([name, value]) => `${name}: ${printLiteral(value)}`)
|
|
385
|
+
const line = `{ ${fields.join(', ')} }`
|
|
386
|
+
if (line.length <= MAX_LINE_LENGTH) return line
|
|
387
|
+
const body = indent(fields.join('\n'))
|
|
388
|
+
return body === '' ? '' : `{\n${body}\n}`
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** graphql-js `indent()` of the printer. */
|
|
394
|
+
function indent(text: string): string {
|
|
395
|
+
return text === '' ? '' : ` ${text.replaceAll('\n', '\n ')}`
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function printBlock(items: readonly string[]): string {
|
|
399
|
+
return items.length === 0 ? '' : ` {\n${items.join('\n')}\n}`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function printDeprecated(reason: string | undefined): string {
|
|
403
|
+
if (reason === undefined) return ''
|
|
404
|
+
return reason === DEFAULT_DEPRECATION_REASON ? ' @deprecated' : ` @deprecated(reason: ${printString(reason)})`
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** graphql-js `printDescription()` of `printSchema`. */
|
|
408
|
+
function printDescription(description: string | undefined, indentation: string, firstInBlock: boolean): string {
|
|
409
|
+
if (description === undefined) return ''
|
|
410
|
+
const text = isPrintableAsBlockString(description) ? printBlockString(description) : printString(description)
|
|
411
|
+
const prefix = indentation !== '' && !firstInBlock ? `\n${indentation}` : indentation
|
|
412
|
+
return `${prefix}${text.replaceAll('\n', `\n${indentation}`)}\n`
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function checkedTypeRef(type: string, coordinate: string): string {
|
|
416
|
+
if (parseTypeRef(type) === undefined) throw new Error(`Invalid type reference '${type}' (${coordinate})`)
|
|
417
|
+
return type
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function describe(value: unknown): string {
|
|
421
|
+
if (value === undefined) return 'undefined'
|
|
422
|
+
if (typeof value === 'bigint') return `${value}n`
|
|
423
|
+
if (typeof value === 'number' && !Number.isFinite(value)) return String(value)
|
|
424
|
+
try {
|
|
425
|
+
return JSON.stringify(value) ?? String(value)
|
|
426
|
+
} catch {
|
|
427
|
+
return String(value)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// --- graphql-js string printers (language/printString.ts, language/blockString.ts) ---
|
|
432
|
+
|
|
433
|
+
/** `\t` and space: the GraphQL `WhiteSpace` characters. */
|
|
434
|
+
function isWhiteSpace(code: number): boolean {
|
|
435
|
+
return code === 0x09 || code === 0x20
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Escape sequence of each code unit graphql-js escapes in a string value. */
|
|
439
|
+
const ESCAPES: readonly string[] = Array.from({ length: 0xa0 }, (_, code) => {
|
|
440
|
+
switch (code) {
|
|
441
|
+
case 0x08:
|
|
442
|
+
return '\\b'
|
|
443
|
+
case 0x09:
|
|
444
|
+
return '\\t'
|
|
445
|
+
case 0x0a:
|
|
446
|
+
return '\\n'
|
|
447
|
+
case 0x0c:
|
|
448
|
+
return '\\f'
|
|
449
|
+
case 0x0d:
|
|
450
|
+
return '\\r'
|
|
451
|
+
case 0x22:
|
|
452
|
+
return '\\"'
|
|
453
|
+
case 0x5c:
|
|
454
|
+
return '\\\\'
|
|
455
|
+
default:
|
|
456
|
+
return code < 0x20 || code >= 0x7f ? `\\u${code.toString(16).toUpperCase().padStart(4, '0')}` : ''
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: graphql-js escapes exactly these code units
|
|
460
|
+
const ESCAPED = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g
|
|
461
|
+
|
|
462
|
+
/** A GraphQL string value (`"..."`), escaped like graphql-js `printString`. */
|
|
463
|
+
export function printString(value: string): string {
|
|
464
|
+
return `"${value.replace(ESCAPED, char => ESCAPES[char.charCodeAt(0)] ?? char)}"`
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** graphql-js `isPrintableAsBlockString`. */
|
|
468
|
+
function isPrintableAsBlockString(value: string): boolean {
|
|
469
|
+
if (value === '') return true
|
|
470
|
+
let isEmptyLine = true
|
|
471
|
+
let hasIndent = false
|
|
472
|
+
let hasCommonIndent = true
|
|
473
|
+
let seenNonEmptyLine = false
|
|
474
|
+
for (let i = 0; i < value.length; ++i) {
|
|
475
|
+
switch (value.codePointAt(i)) {
|
|
476
|
+
case 0x0000:
|
|
477
|
+
case 0x0001:
|
|
478
|
+
case 0x0002:
|
|
479
|
+
case 0x0003:
|
|
480
|
+
case 0x0004:
|
|
481
|
+
case 0x0005:
|
|
482
|
+
case 0x0006:
|
|
483
|
+
case 0x0007:
|
|
484
|
+
case 0x0008:
|
|
485
|
+
case 0x000b:
|
|
486
|
+
case 0x000c:
|
|
487
|
+
case 0x000e:
|
|
488
|
+
case 0x000f:
|
|
489
|
+
case 0x000d:
|
|
490
|
+
return false
|
|
491
|
+
case 0x000a:
|
|
492
|
+
if (isEmptyLine && !seenNonEmptyLine) return false
|
|
493
|
+
seenNonEmptyLine = true
|
|
494
|
+
isEmptyLine = true
|
|
495
|
+
hasIndent = false
|
|
496
|
+
break
|
|
497
|
+
case 0x0009:
|
|
498
|
+
case 0x0020:
|
|
499
|
+
hasIndent ||= isEmptyLine
|
|
500
|
+
break
|
|
501
|
+
default:
|
|
502
|
+
hasCommonIndent &&= hasIndent
|
|
503
|
+
isEmptyLine = false
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (isEmptyLine) return false
|
|
507
|
+
if (hasCommonIndent && seenNonEmptyLine) return false
|
|
508
|
+
return true
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** A GraphQL block string (`"""..."""`), laid out like graphql-js `printBlockString`. */
|
|
512
|
+
export function printBlockString(value: string): string {
|
|
513
|
+
const escaped = value.replaceAll('"""', '\\"""')
|
|
514
|
+
const lines = escaped.split(/\r\n|[\n\r]/g)
|
|
515
|
+
const isSingleLine = lines.length === 1
|
|
516
|
+
const forceLeadingNewLine =
|
|
517
|
+
lines.length > 1 && lines.slice(1).every(line => line.length === 0 || isWhiteSpace(line.charCodeAt(0)))
|
|
518
|
+
const hasTrailingTripleQuotes = escaped.endsWith('\\"""')
|
|
519
|
+
const hasTrailingQuote = value.endsWith('"') && !hasTrailingTripleQuotes
|
|
520
|
+
const hasTrailingSlash = value.endsWith('\\')
|
|
521
|
+
const forceTrailingNewline = hasTrailingQuote || hasTrailingSlash
|
|
522
|
+
const printAsMultipleLines =
|
|
523
|
+
!isSingleLine || value.length > 70 || forceTrailingNewline || forceLeadingNewLine || hasTrailingTripleQuotes
|
|
524
|
+
let result = ''
|
|
525
|
+
const skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCodeAt(0))
|
|
526
|
+
if ((printAsMultipleLines && !skipLeadingNewLine) || forceLeadingNewLine) result += '\n'
|
|
527
|
+
result += escaped
|
|
528
|
+
if (printAsMultipleLines || forceTrailingNewline) result += '\n'
|
|
529
|
+
return `"""${result}"""`
|
|
530
|
+
}
|