@znight/graphql 0.2.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/README.md +288 -5
- package/dist/compiler.d.mts +24 -3
- package/dist/compiler.mjs +1876 -109
- package/dist/compiler.mjs.map +1 -1
- package/dist/index.d.mts +49 -8
- package/dist/index.mjs +10 -4
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-BdBxr5NQ.mjs → plugin-CR_wVfRU.mjs} +247 -24
- package/dist/plugin-CR_wVfRU.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CgbV9LjD.d.mts → types-DQ713UgI.d.mts} +34 -2
- package/package.json +8 -8
- package/src/compiler/analyze.ts +129 -10
- package/src/compiler/builder.ts +6 -85
- package/src/compiler/checks.ts +105 -0
- package/src/compiler/conformance.ts +1068 -0
- package/src/compiler/constants.ts +6 -0
- package/src/compiler/data.ts +10 -0
- package/src/compiler/generate.ts +41 -27
- package/src/compiler/graphql-js.ts +42 -0
- package/src/compiler/index.ts +8 -3
- package/src/compiler/locate.ts +39 -1
- package/src/compiler/model.ts +16 -1
- package/src/compiler/options.ts +39 -8
- package/src/compiler/schema-first.ts +595 -0
- package/src/compiler/sdl-source.ts +529 -0
- package/src/compiler/sdl-view.ts +3 -1
- package/src/compiler/suggest.ts +39 -0
- package/src/decorators.ts +9 -3
- package/src/index.ts +5 -0
- package/src/metadata.ts +28 -1
- package/src/options.ts +23 -1
- package/src/pipeline.ts +66 -5
- package/src/plugin.ts +27 -5
- package/src/results.ts +206 -0
- package/src/scalars.ts +10 -3
- package/src/schema.ts +90 -12
- package/src/sdl.ts +17 -2
- package/src/types.ts +35 -0
- package/dist/plugin-BdBxr5NQ.mjs.map +0 -1
|
@@ -25,6 +25,10 @@ export const Codes: {
|
|
|
25
25
|
readonly UnresolvableAbstractType: ErrorCode
|
|
26
26
|
/** ZNIGHT065: `@Loader()` parameters and loader providers. */
|
|
27
27
|
readonly InvalidLoader: ErrorCode
|
|
28
|
+
/** ZNIGHT067: schema-first SDL files (RFC 0007): reading, syntax, validation, unsupported constructs. */
|
|
29
|
+
readonly InvalidSdl: ErrorCode
|
|
30
|
+
/** ZNIGHT068: schema-first resolvers against the SDL (RFC 0007): coverage and type conformance. */
|
|
31
|
+
readonly SchemaMismatch: ErrorCode
|
|
28
32
|
} = Object.freeze({
|
|
29
33
|
InvalidDecorator: 'ZNIGHT005',
|
|
30
34
|
MissingRouteDecision: 'ZNIGHT023',
|
|
@@ -34,6 +38,8 @@ export const Codes: {
|
|
|
34
38
|
InvalidInput: 'ZNIGHT063',
|
|
35
39
|
UnresolvableAbstractType: 'ZNIGHT064',
|
|
36
40
|
InvalidLoader: 'ZNIGHT065',
|
|
41
|
+
InvalidSdl: 'ZNIGHT067',
|
|
42
|
+
SchemaMismatch: 'ZNIGHT068',
|
|
37
43
|
})
|
|
38
44
|
|
|
39
45
|
/** Root reason given to `AnalyzeContext.addRoot()`. */
|
package/src/compiler/data.ts
CHANGED
|
@@ -20,6 +20,16 @@ export interface ResolverData {
|
|
|
20
20
|
readonly scope: Scope
|
|
21
21
|
/** Location of `@Resolver`. */
|
|
22
22
|
readonly location: Location
|
|
23
|
+
/**
|
|
24
|
+
* `@Resolver('Event')`: the SDL object type its `@ResolveField` methods belong to (schema-first,
|
|
25
|
+
* RFC 0007), with the location of the argument.
|
|
26
|
+
*/
|
|
27
|
+
readonly typeName?: { readonly name: string; readonly location: Location }
|
|
28
|
+
/**
|
|
29
|
+
* Schema-first: a class the analysis rejected (an error was reported); it only tells which fields
|
|
30
|
+
* its methods name (never bound, never a provider).
|
|
31
|
+
*/
|
|
32
|
+
readonly invalid?: true
|
|
23
33
|
/** Class `@UseGuard` guards. */
|
|
24
34
|
readonly guards: readonly ClassRef[]
|
|
25
35
|
/** One entry per field decorator of the class's methods, in source order. */
|
package/src/compiler/generate.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { GenerateContext, GenInput, GenNode, GenParam } from '@znight/compi
|
|
|
17
17
|
import type { Token } from '@znight/core'
|
|
18
18
|
import type { MetaEntry } from '@znight/http/compiler'
|
|
19
19
|
import type { FieldData, GraphQLData, ParamData, ResolverData } from './data.ts'
|
|
20
|
-
import type { ArgModel, FieldModel, GraphQLModel, TypeModel } from './model.ts'
|
|
20
|
+
import type { ArgModel, DiscriminatorModel, FieldModel, GraphQLModel, TypeModel } from './model.ts'
|
|
21
21
|
import type { ResolvedGraphQLCompilerOptions } from './options.ts'
|
|
22
22
|
|
|
23
23
|
import { GRAPHQL_METADATA_VERSION } from '../metadata.ts'
|
|
@@ -106,7 +106,13 @@ function typeNode(ctx: Context, imports: Imports, type: TypeModel): GenNode {
|
|
|
106
106
|
if (type.description !== undefined && type.kind !== 'scalar') head.push(['description', type.description])
|
|
107
107
|
switch (type.kind) {
|
|
108
108
|
case 'object':
|
|
109
|
-
|
|
109
|
+
case 'interface': {
|
|
110
|
+
const entries: [string, GenInput][] = [...head]
|
|
111
|
+
if (type.interfaces.length > 0) entries.push(['interfaces', ctx.lit([...type.interfaces])])
|
|
112
|
+
entries.push(['fields', ctx.arr(type.fields.map(field => fieldNode(ctx, imports, field)))])
|
|
113
|
+
if (type.kind === 'interface') entries.push(['discriminator', discriminatorNode(ctx, type.discriminator)])
|
|
114
|
+
return ctx.obj(entries)
|
|
115
|
+
}
|
|
110
116
|
case 'input':
|
|
111
117
|
return ctx.obj([...head, ['fields', ctx.arr(type.fields.map(field => inputValueNode(ctx, field)))]])
|
|
112
118
|
case 'enum':
|
|
@@ -127,36 +133,44 @@ function typeNode(ctx: Context, imports: Imports, type: TypeModel): GenNode {
|
|
|
127
133
|
),
|
|
128
134
|
],
|
|
129
135
|
])
|
|
130
|
-
case 'union':
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
],
|
|
147
|
-
])
|
|
136
|
+
case 'union':
|
|
137
|
+
return ctx.obj([
|
|
138
|
+
...head,
|
|
139
|
+
['members', ctx.lit([...type.members])],
|
|
140
|
+
['discriminator', discriminatorNode(ctx, type.discriminator)],
|
|
141
|
+
])
|
|
142
|
+
case 'scalar': {
|
|
143
|
+
// built-in scalars carry no description (the runtime builds them, sdl.ts prints them alike);
|
|
144
|
+
// code-first user scalars bring theirs with the imported definition, schema-first ones take
|
|
145
|
+
// the SDL's (they win over the definition's)
|
|
146
|
+
if (type.definition === undefined) return ctx.obj([...head, ['builtin', ctx.lit(true)]])
|
|
147
|
+
const entries: [string, GenInput][] = [...head, ['scalar', ctx.importValue(type.definition)]]
|
|
148
|
+
if (type.fromSdl === true) {
|
|
149
|
+
if (type.description !== undefined) entries.push(['description', type.description])
|
|
150
|
+
if (type.specifiedByURL !== undefined) entries.push(['specifiedByURL', type.specifiedByURL])
|
|
151
|
+
entries.push(['sdl', ctx.lit(true)])
|
|
148
152
|
}
|
|
149
|
-
return ctx.obj(
|
|
153
|
+
return ctx.obj(entries)
|
|
150
154
|
}
|
|
151
|
-
case 'scalar':
|
|
152
|
-
// built-in scalars carry no description (the runtime builds them, sdl.ts prints them alike);
|
|
153
|
-
// user scalars bring theirs with the imported definition
|
|
154
|
-
return type.definition === undefined
|
|
155
|
-
? ctx.obj([...head, ['builtin', ctx.lit(true)]])
|
|
156
|
-
: ctx.obj([...head, ['scalar', ctx.importValue(type.definition)]])
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
157
|
|
|
158
|
+
/** How the runtime tells the members of a union or the implementations of an interface apart. */
|
|
159
|
+
function discriminatorNode(ctx: Context, discriminator: DiscriminatorModel | undefined): GenNode {
|
|
160
|
+
if (discriminator === undefined || discriminator.by === 'typename') return ctx.obj([['by', 'typename']])
|
|
161
|
+
if (discriminator.by === 'property') {
|
|
162
|
+
return ctx.obj([
|
|
163
|
+
['by', 'property'],
|
|
164
|
+
['property', discriminator.property],
|
|
165
|
+
['values', ctx.obj(Object.entries(discriminator.values))],
|
|
166
|
+
])
|
|
167
|
+
}
|
|
168
|
+
return ctx.obj([
|
|
169
|
+
['by', 'class'],
|
|
170
|
+
['classes', ctx.arr(discriminator.classes.map(entry => ctx.arr([ctx.importValue(entry.identity), entry.name])))],
|
|
171
|
+
])
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
function fieldNode(ctx: Context, imports: Imports, field: FieldModel): GenNode {
|
|
161
175
|
const entries: [string, GenInput][] = [
|
|
162
176
|
['name', field.name],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graphql-js modules schema-first compiles use (RFC 0007 §7): the language (parser, AST), the
|
|
3
|
+
* type system (`validateSchema`, type predicates) and the utilities (`buildASTSchema`,
|
|
4
|
+
* `concatAST`, `valueFromASTUntyped`). Loaded lazily, on the first schema-first compile, with
|
|
5
|
+
* `createRequire`: code-first compiles never load graphql-js, and the execution engine
|
|
6
|
+
* (`graphql/execution`, the `graphql` root entry) and dataloader are never loaded at build time
|
|
7
|
+
* (tests/boundaries.test.ts). graphql is a required peer of `@znight/graphql`.
|
|
8
|
+
*/
|
|
9
|
+
import type * as Language from 'graphql/language'
|
|
10
|
+
import type * as Type from 'graphql/type'
|
|
11
|
+
import type * as Utilities from 'graphql/utilities'
|
|
12
|
+
|
|
13
|
+
import { createRequire } from 'node:module'
|
|
14
|
+
|
|
15
|
+
/** The build-time graphql-js modules. */
|
|
16
|
+
export interface GraphQLBuildModules {
|
|
17
|
+
readonly language: typeof Language
|
|
18
|
+
readonly type: typeof Type
|
|
19
|
+
readonly utilities: typeof Utilities
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The only graphql-js modules the compiler extension may load (checked by tests/boundaries.test.ts). */
|
|
23
|
+
export const GRAPHQL_BUILD_MODULES: readonly string[] = Object.freeze([
|
|
24
|
+
'graphql/language',
|
|
25
|
+
'graphql/type',
|
|
26
|
+
'graphql/utilities',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
let loaded: GraphQLBuildModules | undefined
|
|
30
|
+
|
|
31
|
+
/** The graphql-js modules, loaded on first use. */
|
|
32
|
+
export function graphqlModules(): GraphQLBuildModules {
|
|
33
|
+
if (loaded === undefined) {
|
|
34
|
+
const load = createRequire(import.meta.url)
|
|
35
|
+
loaded = Object.freeze({
|
|
36
|
+
language: load('graphql/language') as typeof Language,
|
|
37
|
+
type: load('graphql/type') as typeof Type,
|
|
38
|
+
utilities: load('graphql/utilities') as typeof Utilities,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
return loaded
|
|
42
|
+
}
|
package/src/compiler/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { buildModel } from './builder.ts'
|
|
|
32
32
|
import { generateGraphQL } from './generate.ts'
|
|
33
33
|
import { EXTENSION_NAME, GraphQLIds } from './ids.ts'
|
|
34
34
|
import { DEFAULT_OPTIONS, resolveOptions } from './options.ts'
|
|
35
|
+
import { buildSchemaFirstModel } from './schema-first.ts'
|
|
35
36
|
|
|
36
37
|
export type { FieldData, FieldOperation, GraphQLData, ParamData, ResolverData } from './data.ts'
|
|
37
38
|
export type { GraphQLCompilerOptions, ResolvedGraphQLCompilerOptions } from './options.ts'
|
|
@@ -41,7 +42,7 @@ const NO_ARGUMENT: DecoratorSpec = { targets: ['parameter'], params: [] }
|
|
|
41
42
|
|
|
42
43
|
/** Decorators owned by the extension, by canonical identity (static / value arguments, §4.6). */
|
|
43
44
|
const decorators: Readonly<Record<string, DecoratorSpec>> = Object.freeze({
|
|
44
|
-
[GraphQLIds.Resolver]: { targets: ['class'], params: ['static'] },
|
|
45
|
+
[GraphQLIds.Resolver]: { targets: ['class'], params: ['static', 'static'] },
|
|
45
46
|
[GraphQLIds.Query]: FIELD,
|
|
46
47
|
[GraphQLIds.Mutation]: FIELD,
|
|
47
48
|
[GraphQLIds.Subscription]: FIELD,
|
|
@@ -74,9 +75,13 @@ function createExtension(options: ResolvedGraphQLCompilerOptions): GraphQLCompil
|
|
|
74
75
|
name: EXTENSION_NAME,
|
|
75
76
|
decorators,
|
|
76
77
|
options,
|
|
77
|
-
analyzeClass: analyzeGraphQLClass,
|
|
78
|
+
analyzeClass: (cls, ctx) => analyzeGraphQLClass(cls, ctx, options),
|
|
78
79
|
validate(ctx) {
|
|
79
|
-
|
|
80
|
+
const schema = options.schema
|
|
81
|
+
models.set(
|
|
82
|
+
ctx.graph.extensions,
|
|
83
|
+
schema === false ? buildModel(ctx, options) : buildSchemaFirstModel(ctx, schema, options.sdl),
|
|
84
|
+
)
|
|
80
85
|
},
|
|
81
86
|
generate(ctx) {
|
|
82
87
|
return generateGraphQL(ctx, modelOf(ctx.graph), options)
|
package/src/compiler/locate.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Source locations of diagnostics. Computed only when a diagnostic is reported: the happy path
|
|
3
3
|
* never reads source text.
|
|
4
4
|
*/
|
|
5
|
-
import type { Location, Project } from '@znight/compiler'
|
|
5
|
+
import type { Location, MethodInfo, Project } from '@znight/compiler'
|
|
6
6
|
|
|
7
7
|
/** From the member name: `name?: ` or `name(): ` (a getter), up to the annotation. */
|
|
8
8
|
const MEMBER_HEAD = /^(?:[\w$]+|'[^']*'|"[^"]*")\s*\??\s*(?:\(\s*\))?\s*:\s*/
|
|
@@ -42,3 +42,41 @@ export function annotationLocation(project: Project, member: Location | undefine
|
|
|
42
42
|
}
|
|
43
43
|
return end > start ? file.location({ start, end }) : member
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The return type annotation of a method, from the location of its name: after the parameter list
|
|
48
|
+
* (balanced brackets), `: T` up to the body; `undefined` when it cannot be found.
|
|
49
|
+
*/
|
|
50
|
+
export function returnTypeLocation(project: Project, method: MethodInfo): Location | undefined {
|
|
51
|
+
const file = project.getFile(method.location.file)
|
|
52
|
+
if (file === undefined) return undefined
|
|
53
|
+
const text = file.text
|
|
54
|
+
let index = text.indexOf('(', method.location.end)
|
|
55
|
+
if (index === -1) return undefined
|
|
56
|
+
let depth = 0
|
|
57
|
+
for (; index < text.length; index++) {
|
|
58
|
+
const char = text[index] as string
|
|
59
|
+
if (OPENING.has(char)) depth++
|
|
60
|
+
else if (CLOSING.has(char) && --depth === 0) break
|
|
61
|
+
}
|
|
62
|
+
const head = /^\s*:\s*/.exec(text.slice(index + 1, index + 64))
|
|
63
|
+
if (head === null) return undefined
|
|
64
|
+
const start = index + 1 + head[0].length
|
|
65
|
+
let end = start
|
|
66
|
+
depth = 0
|
|
67
|
+
for (let cursor = start; cursor < text.length; cursor++) {
|
|
68
|
+
const char = text[cursor] as string
|
|
69
|
+
if (char === '=' && text[cursor + 1] === '>') {
|
|
70
|
+
cursor++
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (depth === 0 && (char === '{' || char === ';') && end > start) break
|
|
74
|
+
if (OPENING.has(char)) depth++
|
|
75
|
+
else if (CLOSING.has(char)) {
|
|
76
|
+
if (depth === 0) break
|
|
77
|
+
depth--
|
|
78
|
+
}
|
|
79
|
+
if (!/\s/.test(char)) end = cursor + 1
|
|
80
|
+
}
|
|
81
|
+
return end > start ? file.location({ start, end }) : undefined
|
|
82
|
+
}
|
package/src/compiler/model.ts
CHANGED
|
@@ -55,6 +55,16 @@ export interface ObjectModel extends BaseModel {
|
|
|
55
55
|
readonly classIdentity: string | undefined
|
|
56
56
|
/** Property names of the declaration, with their literal value when they have one (discriminators). */
|
|
57
57
|
readonly properties: readonly DiscriminantProperty[]
|
|
58
|
+
/** Implemented interfaces (schema-first, RFC 0007), in declaration order; empty in code-first. */
|
|
59
|
+
readonly interfaces: readonly string[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** An interface type (schema-first, RFC 0007). Its fields have no binding. */
|
|
63
|
+
export interface InterfaceModel extends BaseModel {
|
|
64
|
+
readonly kind: 'interface'
|
|
65
|
+
readonly fields: FieldModel[]
|
|
66
|
+
readonly interfaces: readonly string[]
|
|
67
|
+
discriminator: DiscriminatorModel | undefined
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
/** A property of an object type's declaration, as union discriminators see it. */
|
|
@@ -104,10 +114,15 @@ export interface ScalarModel extends BaseModel {
|
|
|
104
114
|
/** Identity of the exported `scalar<T>()` constant; `undefined` for a built-in scalar. */
|
|
105
115
|
readonly definition: string | undefined
|
|
106
116
|
readonly specifiedByURL: string | undefined
|
|
117
|
+
/**
|
|
118
|
+
* Schema-first: the description and `specifiedByURL` come from the SDL and are emitted in the
|
|
119
|
+
* metadata, where they win over the definition's (RFC 0007 §5.2).
|
|
120
|
+
*/
|
|
121
|
+
readonly fromSdl?: true
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
/** A named type of the model. */
|
|
110
|
-
export type TypeModel = ObjectModel | InputModel | EnumModel | UnionModel | ScalarModel
|
|
125
|
+
export type TypeModel = ObjectModel | InterfaceModel | InputModel | EnumModel | UnionModel | ScalarModel
|
|
111
126
|
|
|
112
127
|
/** The schema of the application, built once per compile. */
|
|
113
128
|
export interface GraphQLModel {
|
package/src/compiler/options.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Options of the compiler extension (RFC 0005 §6.7
|
|
3
|
-
* `znight.config.ts`. Import-free.
|
|
2
|
+
* Options of the compiler extension (RFC 0005 §6.7, RFC 0007 §4.1), given with
|
|
3
|
+
* `graphql.configure({ ... })` in `znight.config.ts`. Import-free.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/** Options of `graphql.configure()`. */
|
|
@@ -10,20 +10,32 @@ export interface GraphQLCompilerOptions {
|
|
|
10
10
|
* generated module and checked by `znight generate --check`. Default `false`: no file.
|
|
11
11
|
*/
|
|
12
12
|
readonly sdl?: string | false
|
|
13
|
-
/** GraphQL type of `number`. Default `'Float'`; `'Int'` suits counts and identifiers. */
|
|
13
|
+
/** GraphQL type of `number`. Default `'Float'`; `'Int'` suits counts and identifiers. Code-first only. */
|
|
14
14
|
readonly number?: 'Float' | 'Int'
|
|
15
|
+
/**
|
|
16
|
+
* Schema-first mode (RFC 0007): globs of the `.graphql` files that define the schema, relative to
|
|
17
|
+
* the project root (`'src/**\/*.graphql'`, `!` negations allowed). Resolvers are bound to their
|
|
18
|
+
* fields by name and checked against the SDL. Default `false`: code-first.
|
|
19
|
+
*/
|
|
20
|
+
readonly schema?: string | readonly string[] | false
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
/** {@link GraphQLCompilerOptions} with their defaults. */
|
|
18
24
|
export interface ResolvedGraphQLCompilerOptions {
|
|
19
25
|
readonly sdl: string | false
|
|
20
26
|
readonly number: 'Float' | 'Int'
|
|
27
|
+
/** The schema-first globs; `false`: code-first. */
|
|
28
|
+
readonly schema: readonly string[] | false
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/** The defaults. */
|
|
24
|
-
export const DEFAULT_OPTIONS: ResolvedGraphQLCompilerOptions = Object.freeze({
|
|
32
|
+
export const DEFAULT_OPTIONS: ResolvedGraphQLCompilerOptions = Object.freeze({
|
|
33
|
+
sdl: false,
|
|
34
|
+
number: 'Float',
|
|
35
|
+
schema: false,
|
|
36
|
+
})
|
|
25
37
|
|
|
26
|
-
const KNOWN = new Set(['sdl', 'number'])
|
|
38
|
+
const KNOWN = new Set(['sdl', 'number', 'schema'])
|
|
27
39
|
|
|
28
40
|
/**
|
|
29
41
|
* Merges `options` onto `base`. Throws a `TypeError` naming the option on an invalid value: options
|
|
@@ -34,11 +46,11 @@ export function resolveOptions(
|
|
|
34
46
|
options: GraphQLCompilerOptions,
|
|
35
47
|
): ResolvedGraphQLCompilerOptions {
|
|
36
48
|
if (typeof options !== 'object' || options === null || Array.isArray(options)) {
|
|
37
|
-
throw new TypeError('[znight] graphql.configure() expects an options object: { sdl?, number? }')
|
|
49
|
+
throw new TypeError('[znight] graphql.configure() expects an options object: { sdl?, number?, schema? }')
|
|
38
50
|
}
|
|
39
51
|
for (const key of Object.keys(options)) {
|
|
40
52
|
if (!KNOWN.has(key))
|
|
41
|
-
throw new TypeError(`[znight] graphql.configure(): unknown option "${key}" (options: sdl, number)`)
|
|
53
|
+
throw new TypeError(`[znight] graphql.configure(): unknown option "${key}" (options: sdl, number, schema)`)
|
|
42
54
|
}
|
|
43
55
|
const sdl = options.sdl === undefined ? base.sdl : options.sdl
|
|
44
56
|
if (sdl !== false && (typeof sdl !== 'string' || sdl.trim() === '')) {
|
|
@@ -50,5 +62,24 @@ export function resolveOptions(
|
|
|
50
62
|
if (number !== 'Float' && number !== 'Int') {
|
|
51
63
|
throw new TypeError(`[znight] graphql.configure(): "number" must be 'Float' or 'Int'`)
|
|
52
64
|
}
|
|
53
|
-
return Object.freeze({ sdl, number })
|
|
65
|
+
return Object.freeze({ sdl, number, schema: resolveSchema(base.schema, options.schema) })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveSchema(
|
|
69
|
+
base: readonly string[] | false,
|
|
70
|
+
schema: GraphQLCompilerOptions['schema'],
|
|
71
|
+
): readonly string[] | false {
|
|
72
|
+
if (schema === undefined) return base
|
|
73
|
+
if (schema === false) return false
|
|
74
|
+
const globs = typeof schema === 'string' ? [schema] : schema
|
|
75
|
+
if (
|
|
76
|
+
!Array.isArray(globs) ||
|
|
77
|
+
globs.length === 0 ||
|
|
78
|
+
globs.some(glob => typeof glob !== 'string' || glob.trim() === '')
|
|
79
|
+
) {
|
|
80
|
+
throw new TypeError(
|
|
81
|
+
'[znight] graphql.configure(): "schema" must be a glob or a non-empty list of globs relative to the project root (\'src/**/*.graphql\'), or false',
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
return Object.freeze([...globs])
|
|
54
85
|
}
|