@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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1346 -0
  3. package/dist/compiler.d.mts +114 -0
  4. package/dist/compiler.mjs +4599 -0
  5. package/dist/compiler.mjs.map +1 -0
  6. package/dist/index.d.mts +583 -0
  7. package/dist/index.mjs +134 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/names-BlzDM5cF.mjs +111 -0
  10. package/dist/names-BlzDM5cF.mjs.map +1 -0
  11. package/dist/plugin-CR_wVfRU.mjs +3445 -0
  12. package/dist/plugin-CR_wVfRU.mjs.map +1 -0
  13. package/dist/testing.d.mts +101 -0
  14. package/dist/testing.mjs +306 -0
  15. package/dist/testing.mjs.map +1 -0
  16. package/dist/types-DQ713UgI.d.mts +266 -0
  17. package/package.json +97 -1
  18. package/src/compiler/analyze.ts +727 -0
  19. package/src/compiler/builder.ts +1561 -0
  20. package/src/compiler/checks.ts +105 -0
  21. package/src/compiler/conformance.ts +1068 -0
  22. package/src/compiler/constants.ts +80 -0
  23. package/src/compiler/data.ts +83 -0
  24. package/src/compiler/defaults.ts +92 -0
  25. package/src/compiler/discriminators.ts +53 -0
  26. package/src/compiler/docs.ts +55 -0
  27. package/src/compiler/generate.ts +419 -0
  28. package/src/compiler/graphql-js.ts +42 -0
  29. package/src/compiler/ids.ts +128 -0
  30. package/src/compiler/index.ts +98 -0
  31. package/src/compiler/locate.ts +82 -0
  32. package/src/compiler/model.ts +137 -0
  33. package/src/compiler/options.ts +85 -0
  34. package/src/compiler/scalars.ts +138 -0
  35. package/src/compiler/schema-first.ts +595 -0
  36. package/src/compiler/sdl-source.ts +529 -0
  37. package/src/compiler/sdl-view.ts +61 -0
  38. package/src/compiler/suggest.ts +39 -0
  39. package/src/context.ts +181 -0
  40. package/src/decisions.ts +169 -0
  41. package/src/decorators.ts +202 -0
  42. package/src/errors.ts +89 -0
  43. package/src/execution.ts +1064 -0
  44. package/src/format.ts +285 -0
  45. package/src/index.ts +89 -0
  46. package/src/limits.ts +177 -0
  47. package/src/loaders.ts +149 -0
  48. package/src/memory-pubsub.ts +133 -0
  49. package/src/metadata.ts +222 -0
  50. package/src/names.ts +47 -0
  51. package/src/options.ts +331 -0
  52. package/src/pipeline.ts +338 -0
  53. package/src/plugin.ts +579 -0
  54. package/src/pubsub.ts +36 -0
  55. package/src/results.ts +206 -0
  56. package/src/scalars.ts +138 -0
  57. package/src/schema-hash.ts +18 -0
  58. package/src/schema.ts +635 -0
  59. package/src/sdl.ts +530 -0
  60. package/src/testing.ts +454 -0
  61. package/src/tokens.ts +22 -0
  62. package/src/transport/abort.ts +80 -0
  63. package/src/transport/accept.ts +155 -0
  64. package/src/transport/graphiql.ts +106 -0
  65. package/src/transport/http.ts +373 -0
  66. package/src/transport/sse.ts +45 -0
  67. package/src/type-ref.ts +89 -0
  68. package/src/types.ts +225 -0
  69. package/src/validate-args.ts +48 -0
package/src/loaders.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Loaders (RFC 0005 §5.5, §8.6): the DataLoader of a `BatchLoad` provider for one execution, and
3
+ * `orderByKeys()`.
4
+ *
5
+ * The registry of an execution (`Map<token, DataLoader>`) lives in its `GraphQLContext` and is
6
+ * created on the first `loader()` call: operations that use no loader allocate nothing.
7
+ */
8
+ import type { Container, Token } from '@znight/core'
9
+ import type { BatchLoad, BatchLoader, LoaderOptions } from './types.ts'
10
+
11
+ import { ErrorCode, ZnightError } from '@znight/core'
12
+ import DataLoader from 'dataloader'
13
+
14
+ import { GraphQLErrorCode, GraphQLException } from './errors.ts'
15
+
16
+ /** DataLoader options of a loader class, read once. */
17
+ type ResolvedLoaderOptions = DataLoader.Options<unknown, unknown, unknown>
18
+
19
+ /**
20
+ * Creates the per-execution DataLoaders of loader providers: the provider is resolved in the
21
+ * current scope (loaders may be request-scoped), its `options` are read once per token.
22
+ */
23
+ export class LoaderFactory {
24
+ readonly #container: Container
25
+ readonly #options = new Map<Token, ResolvedLoaderOptions>()
26
+
27
+ constructor(container: Container) {
28
+ this.#container = container
29
+ }
30
+
31
+ /** A new DataLoader for the loader provider `token`, bound to the instance of the current scope. */
32
+ create(token: Token): BatchLoader<unknown, unknown> {
33
+ const provider = this.#container.get(token) as Partial<BatchLoad<unknown, unknown>> | undefined
34
+ if (provider === null || typeof provider !== 'object' || typeof provider.load !== 'function') {
35
+ throw new ZnightError(
36
+ ErrorCode.InvalidLoader,
37
+ `The loader ${token} does not implement BatchLoad (no load(keys) method)`,
38
+ { hints: ['A loader is a provider implementing BatchLoad<K, V>: async load(keys) { ... }'] },
39
+ )
40
+ }
41
+ let options = this.#options.get(token)
42
+ if (options === undefined) {
43
+ options = readOptions(token, provider.options)
44
+ this.#options.set(token, options)
45
+ }
46
+ return new DataLoader<unknown, unknown, unknown>(
47
+ keys => loadBatch(token, keys, provider as BatchLoad<unknown, unknown>),
48
+ options,
49
+ ) as BatchLoader<unknown, unknown>
50
+ }
51
+ }
52
+
53
+ /**
54
+ * The values of a batch, or ZNIGHT065 (failing every key) when there is not one value per key.
55
+ * `async` so that a `load()` throwing synchronously rejects with its own error (DataLoader would
56
+ * replace it with a TypeError).
57
+ */
58
+ async function loadBatch(
59
+ token: Token,
60
+ keys: readonly unknown[],
61
+ provider: BatchLoad<unknown, unknown>,
62
+ ): Promise<readonly unknown[]> {
63
+ const values: unknown = await provider.load(keys)
64
+ if (Array.isArray(values) && values.length === keys.length) return values
65
+ throw new ZnightError(
66
+ ErrorCode.InvalidLoader,
67
+ `The loader ${token} returned ${Array.isArray(values) ? `${values.length} values` : 'no array'} for ${keys.length} keys`,
68
+ { hints: ['load(keys) returns one value (or Error) per key, in the order of the keys: use orderByKeys()'] },
69
+ )
70
+ }
71
+
72
+ function readOptions(token: Token, options: LoaderOptions | undefined): ResolvedLoaderOptions {
73
+ if (options === undefined) return {}
74
+ const invalid = (reason: string): ZnightError =>
75
+ new ZnightError(ErrorCode.InvalidLoader, `Invalid options of the loader ${token}: ${reason}`, {
76
+ hints: ['options: { maxBatchSize?: positive integer, cache?: boolean, cacheKeyFn?: (key) => unknown }'],
77
+ })
78
+ if (options === null || typeof options !== 'object') throw invalid('expected an object')
79
+ const { maxBatchSize, cache, cacheKeyFn } = options
80
+ if (maxBatchSize !== undefined && !(Number.isSafeInteger(maxBatchSize) && maxBatchSize >= 1)) {
81
+ throw invalid(`maxBatchSize must be a positive integer (received ${String(maxBatchSize)})`)
82
+ }
83
+ if (cache !== undefined && typeof cache !== 'boolean') throw invalid('cache must be a boolean')
84
+ if (cacheKeyFn !== undefined && typeof cacheKeyFn !== 'function') throw invalid('cacheKeyFn must be a function')
85
+ return {
86
+ ...(maxBatchSize === undefined ? {} : { maxBatchSize }),
87
+ ...(cache === undefined ? {} : { cache }),
88
+ ...(cacheKeyFn === undefined ? {} : { cacheKeyFn: (key: unknown) => cacheKeyFn.call(options, key) }),
89
+ }
90
+ }
91
+
92
+ /** Options of {@link orderByKeys}. */
93
+ export interface OrderByKeysOptions {
94
+ /**
95
+ * What a key without value gets: `'error'` (default) a `NOT_FOUND` `GraphQLException` failing that
96
+ * key only, `'null'` the value `null`.
97
+ */
98
+ readonly missing?: 'error' | 'null'
99
+ }
100
+
101
+ /**
102
+ * Reorders `values` (rows in any order, some keys possibly missing) to match `keys`, what a
103
+ * `BatchLoad.load()` must return. Keys are compared with `Map` semantics (`===`, `NaN` equal).
104
+ *
105
+ * ```ts
106
+ * async load(ids: readonly ID[]): Promise<readonly (Author | Error)[]> {
107
+ * return orderByKeys(ids, await this.authors.findMany(ids), author => author.id)
108
+ * }
109
+ * ```
110
+ */
111
+ export function orderByKeys<K, V>(
112
+ keys: readonly K[],
113
+ values: Iterable<V>,
114
+ keyOf: (value: V) => K,
115
+ options?: OrderByKeysOptions & { readonly missing?: 'error' },
116
+ ): (V | Error)[]
117
+ export function orderByKeys<K, V>(
118
+ keys: readonly K[],
119
+ values: Iterable<V>,
120
+ keyOf: (value: V) => K,
121
+ options: OrderByKeysOptions & { readonly missing: 'null' },
122
+ ): (V | null)[]
123
+ export function orderByKeys<K, V>(
124
+ keys: readonly K[],
125
+ values: Iterable<V>,
126
+ keyOf: (value: V) => K,
127
+ options: OrderByKeysOptions = {},
128
+ ): (V | Error | null)[] {
129
+ const missing = options.missing ?? 'error'
130
+ if (missing !== 'error' && missing !== 'null') {
131
+ throw new TypeError(`orderByKeys(): missing must be 'error' or 'null' (received ${String(missing)})`)
132
+ }
133
+ const byKey = new Map<K, V>()
134
+ for (const value of values) byKey.set(keyOf(value), value)
135
+ const result: (V | Error | null)[] = new Array(keys.length)
136
+ for (let index = 0; index < keys.length; index++) {
137
+ const key = keys[index] as K
138
+ const value = byKey.get(key)
139
+ if (value !== undefined || byKey.has(key)) result[index] = value as V
140
+ else result[index] = missing === 'null' ? null : notFound(key)
141
+ }
142
+ return result
143
+ }
144
+
145
+ function notFound(key: unknown): GraphQLException {
146
+ return new GraphQLException(`Not found: ${typeof key === 'string' ? key : String(key)}`, {
147
+ code: GraphQLErrorCode.NotFound,
148
+ })
149
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * `MemoryPubSub` (RFC 0005 §5.6, §8.7): the in-process `PubSub` of `GraphQLPlugin`, one RFC 0006
3
+ * `EventChannel` per subscriber.
4
+ */
5
+ import type { SubscribeOptions } from './pubsub.ts'
6
+
7
+ import { EventChannel } from '@znight/http'
8
+
9
+ import { GraphQLErrorCode, GraphQLException } from './errors.ts'
10
+ import { PubSub } from './pubsub.ts'
11
+
12
+ /** Options of {@link MemoryPubSub}. */
13
+ export interface MemoryPubSubOptions {
14
+ /**
15
+ * Events buffered per subscriber. A subscriber that falls further behind gets a final
16
+ * `SUBSCRIPTION_OVERFLOW` error and its subscription ends. Default 100.
17
+ */
18
+ readonly bufferSize?: number
19
+ }
20
+
21
+ const DEFAULT_BUFFER_SIZE = 100
22
+ const PUBLISHED: Promise<void> = Promise.resolve()
23
+
24
+ /** One subscriber: its channel, and whether it overflowed. */
25
+ class Subscriber<T> implements AsyncIterableIterator<T> {
26
+ readonly channel: EventChannel<T>
27
+ readonly #iterator: AsyncIterator<T>
28
+ overflowed = false
29
+
30
+ constructor(channel: EventChannel<T>) {
31
+ this.channel = channel
32
+ this.#iterator = channel[Symbol.asyncIterator]()
33
+ }
34
+
35
+ async next(): Promise<IteratorResult<T>> {
36
+ const step = await this.#iterator.next()
37
+ if (step.done === true && this.overflowed) {
38
+ this.overflowed = false
39
+ throw new GraphQLException('The subscriber fell behind the events: the subscription ended', {
40
+ code: GraphQLErrorCode.SubscriptionOverflow,
41
+ })
42
+ }
43
+ return step
44
+ }
45
+
46
+ return(): Promise<IteratorResult<T>> {
47
+ this.overflowed = false
48
+ return this.#iterator.return?.() ?? Promise.resolve({ done: true, value: undefined })
49
+ }
50
+
51
+ [Symbol.asyncIterator](): AsyncIterableIterator<T> {
52
+ return this
53
+ }
54
+ }
55
+
56
+ /**
57
+ * In-process fan-out of events to subscribers: what `GraphQLPlugin` provides as `PubSub` by default.
58
+ * Deployments with several instances supply their own `PubSub` (`pubsub` option).
59
+ *
60
+ * ```ts
61
+ * GraphQLPlugin.configure({ pubsub: new MemoryPubSub({ bufferSize: 500 }) })
62
+ * ```
63
+ */
64
+ export class MemoryPubSub<Events extends object = Record<string, unknown>> extends PubSub<Events> {
65
+ readonly #bufferSize: number
66
+ readonly #topics = new Map<string, Set<Subscriber<unknown>>>()
67
+ #closed = false
68
+
69
+ constructor(options: MemoryPubSubOptions = {}) {
70
+ super()
71
+ const bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE
72
+ if (!(Number.isSafeInteger(bufferSize) && bufferSize >= 1)) {
73
+ throw new TypeError(`MemoryPubSub: bufferSize must be a positive integer (received ${String(bufferSize)})`)
74
+ }
75
+ this.#bufferSize = bufferSize
76
+ }
77
+
78
+ /** Subscribers of every topic. */
79
+ get subscribers(): number {
80
+ let count = 0
81
+ for (const subscribers of this.#topics.values()) count += subscribers.size
82
+ return count
83
+ }
84
+
85
+ publish<K extends keyof Events & string>(topic: K, payload: Events[K]): Promise<void> {
86
+ const subscribers = this.#topics.get(topic)
87
+ if (subscribers !== undefined) {
88
+ for (const subscriber of subscribers) {
89
+ const channel = subscriber.channel
90
+ // `false` from an open channel is an overflow ('close'): it closes after its buffered events
91
+ if (!channel.closed && !channel.push(payload)) subscriber.overflowed = true
92
+ }
93
+ }
94
+ return PUBLISHED
95
+ }
96
+
97
+ subscribe<K extends keyof Events & string>(
98
+ topic: K,
99
+ options: SubscribeOptions = {},
100
+ ): AsyncIterableIterator<Events[K]> {
101
+ const channel = new EventChannel<Events[K]>({
102
+ capacity: this.#bufferSize,
103
+ overflow: 'close',
104
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
105
+ })
106
+ const subscriber = new Subscriber(channel)
107
+ if (this.#closed) {
108
+ channel.close()
109
+ return subscriber
110
+ }
111
+ let subscribers = this.#topics.get(topic)
112
+ if (subscribers === undefined) {
113
+ subscribers = new Set()
114
+ this.#topics.set(topic, subscribers)
115
+ }
116
+ const set = subscribers
117
+ set.add(subscriber as Subscriber<unknown>)
118
+ channel.onClose(() => {
119
+ set.delete(subscriber as Subscriber<unknown>)
120
+ if (set.size === 0 && this.#topics.get(topic) === set) this.#topics.delete(topic)
121
+ })
122
+ return subscriber
123
+ }
124
+
125
+ /** Ends every subscription; later subscriptions end at once. */
126
+ close(): void {
127
+ this.#closed = true
128
+ for (const subscribers of [...this.#topics.values()]) {
129
+ for (const subscriber of [...subscribers]) subscriber.channel.close()
130
+ }
131
+ this.#topics.clear()
132
+ }
133
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * GraphQL section of the generated metadata (`__ZNIGHT_METADATA__.extensions.graphql`, RFC 0005
3
+ * §7), produced by the `@znight/graphql/compiler` extension and consumed by `GraphQLPlugin`, which
4
+ * validates it at boot (ZNIGHT066), because metadata can be hand-written.
5
+ *
6
+ * Data, not code, except `invoke` (one arrow per resolver) and the scalar implementations imported
7
+ * from user code. The SDL printer (`sdl.ts`) reads the same shape without its functions.
8
+ *
9
+ * Import-free: types, type-only imports and one constant, shared by the compiler extension and the
10
+ * runtime.
11
+ */
12
+ import type { Token } from '@znight/core'
13
+ import type { HttpRouteGroupDefinition } from '@znight/http'
14
+ import type { GraphQLResolveInfo, GraphQLScalarType } from 'graphql'
15
+ import type { GraphQLArgs, GraphQLContext, ScalarDefinition } from './types.ts'
16
+
17
+ /** Format version of `metadata.extensions.graphql` (another version is ZNIGHT066 at boot). */
18
+ export const GRAPHQL_METADATA_VERSION = 1
19
+
20
+ /** The schema of the application. The section is omitted when the application has no resolver. */
21
+ export interface GraphQLMetadata {
22
+ /** {@link GRAPHQL_METADATA_VERSION}. */
23
+ readonly version: number
24
+ /** First 16 hexadecimal characters of the SHA-256 of the SDL (`printSdl`): logs, caches. */
25
+ readonly schemaHash: string
26
+ readonly roots: GraphQLRootTypes
27
+ /** Every named type except the graphql-js specified scalars, sorted by name (code-unit order). */
28
+ readonly types: readonly GraphQLTypeDefinition[]
29
+ /**
30
+ * Route decorator groups used on resolvers that must be known at runtime (required, or holding
31
+ * several keys), by group name: the shape of `extensions.http.groups` (RFC 0001).
32
+ */
33
+ readonly groups?: Readonly<Record<string, HttpRouteGroupDefinition>>
34
+ }
35
+
36
+ /** Names of the root operation types (`Query`, `Mutation`, `Subscription`). */
37
+ export interface GraphQLRootTypes {
38
+ readonly query: string
39
+ readonly mutation?: string
40
+ readonly subscription?: string
41
+ }
42
+
43
+ /** A named type of the schema. */
44
+ export type GraphQLTypeDefinition =
45
+ | GraphQLObjectTypeDefinition
46
+ | GraphQLInterfaceTypeDefinition
47
+ | GraphQLInputTypeDefinition
48
+ | GraphQLEnumTypeDefinition
49
+ | GraphQLUnionTypeDefinition
50
+ | GraphQLBuiltinScalarDefinition
51
+ | GraphQLCustomScalarDefinition
52
+
53
+ /** An object type: resolver return types, their fields, the root types. */
54
+ export interface GraphQLObjectTypeDefinition {
55
+ readonly kind: 'object'
56
+ readonly name: string
57
+ readonly description?: string
58
+ /** Interfaces the type implements (schema-first, RFC 0007), in declaration order. Absent: none. */
59
+ readonly interfaces?: readonly string[]
60
+ /** Declaration order (base members first), then field resolvers by resolver token and source order. */
61
+ readonly fields: readonly GraphQLFieldDefinition[]
62
+ }
63
+
64
+ /**
65
+ * An interface type (schema-first, RFC 0007; code-first emits none). Its fields have no
66
+ * `resolver`: graphql-js resolves the fields of the concrete object type.
67
+ */
68
+ export interface GraphQLInterfaceTypeDefinition {
69
+ readonly kind: 'interface'
70
+ readonly name: string
71
+ readonly description?: string
72
+ /** Interfaces the interface implements, in declaration order. Absent: none. */
73
+ readonly interfaces?: readonly string[]
74
+ readonly fields: readonly GraphQLFieldDefinition[]
75
+ /** How the runtime tells the implementing object types apart (`resolveType`). */
76
+ readonly discriminator: GraphQLDiscriminator
77
+ }
78
+
79
+ /** An input object type. */
80
+ export interface GraphQLInputTypeDefinition {
81
+ readonly kind: 'input'
82
+ readonly name: string
83
+ readonly description?: string
84
+ readonly fields: readonly GraphQLInputFieldDefinition[]
85
+ }
86
+
87
+ /** An enum: a TypeScript `enum`, or a named alias of a union of string literals. */
88
+ export interface GraphQLEnumTypeDefinition {
89
+ readonly kind: 'enum'
90
+ readonly name: string
91
+ readonly description?: string
92
+ /** Declaration order. */
93
+ readonly values: readonly GraphQLEnumValueDefinition[]
94
+ }
95
+
96
+ /** A union of object types. */
97
+ export interface GraphQLUnionTypeDefinition {
98
+ readonly kind: 'union'
99
+ readonly name: string
100
+ readonly description?: string
101
+ /** Member type names, sorted. */
102
+ readonly members: readonly string[]
103
+ readonly discriminator: GraphQLDiscriminator
104
+ }
105
+
106
+ /** A scalar implemented by `@znight/graphql`: `DateTime`, `BigInt` or `JSON` (`names.ts`). */
107
+ export interface GraphQLBuiltinScalarDefinition {
108
+ readonly kind: 'scalar'
109
+ readonly name: string
110
+ readonly description?: string
111
+ readonly builtin: true
112
+ }
113
+
114
+ /** A scalar declared with `scalar<T>(name, definition)`: the definition is imported from user code. */
115
+ export interface GraphQLCustomScalarDefinition {
116
+ readonly kind: 'scalar'
117
+ readonly name: string
118
+ /** Wins over the description of the definition (schema-first: the SDL's). */
119
+ readonly description?: string
120
+ /** Wins over the `specifiedByURL` of the definition (schema-first: the SDL's `@specifiedBy`). */
121
+ readonly specifiedByURL?: string
122
+ /**
123
+ * Schema-first (RFC 0007): `description` and `specifiedByURL` are the SDL's and replace the
124
+ * definition's, also when absent, so that the served schema is the SDL.
125
+ */
126
+ readonly sdl?: true
127
+ /** `ScalarDefinition<T>` of any `T` (its methods are bivariant), or a graphql-js scalar named `name`. */
128
+ readonly scalar: ScalarDefinition<unknown> | GraphQLScalarType
129
+ }
130
+
131
+ /** A field of an object type. */
132
+ export interface GraphQLFieldDefinition {
133
+ readonly name: string
134
+ /** Type reference (`'Book!'`, `'[Book!]!'`, `'Int'`), parsed by `type-ref.ts`. */
135
+ readonly type: string
136
+ readonly description?: string
137
+ readonly deprecationReason?: string
138
+ readonly args?: readonly GraphQLArgumentDefinition[]
139
+ /** Absent: the graphql-js default resolver (`parent[name]`). */
140
+ readonly resolver?: GraphQLResolverBinding
141
+ }
142
+
143
+ /** An argument of a field. */
144
+ export interface GraphQLArgumentDefinition {
145
+ readonly name: string
146
+ readonly type: string
147
+ readonly description?: string
148
+ readonly deprecationReason?: string
149
+ /**
150
+ * Default value, as an external value: JSON-compatible, enum values by GraphQL name (passed to
151
+ * graphql-js as `default: { value }`). Absent: no default.
152
+ */
153
+ readonly defaultValue?: unknown
154
+ /** The TypeScript type does not accept `null`: a `null` sent by the client becomes `undefined` (§5.4). */
155
+ readonly nullAsUndefined?: true
156
+ }
157
+
158
+ /** A field of an input object type. */
159
+ export interface GraphQLInputFieldDefinition extends GraphQLArgumentDefinition {}
160
+
161
+ /** A value of an enum. */
162
+ export interface GraphQLEnumValueDefinition {
163
+ /** GraphQL name (the member name, or the literal). */
164
+ readonly name: string
165
+ /** Internal value (the member value, or the literal), what resolvers receive and return. */
166
+ readonly value: string | number
167
+ readonly description?: string
168
+ readonly deprecationReason?: string
169
+ }
170
+
171
+ /** How the runtime tells the members of a union or the implementations of an interface apart (`resolveType`). */
172
+ export type GraphQLDiscriminator =
173
+ | {
174
+ readonly by: 'property'
175
+ readonly property: string
176
+ /** Literal value of the property (as a string) → member type name. */
177
+ readonly values: Readonly<Record<string, string>>
178
+ }
179
+ | {
180
+ readonly by: 'class'
181
+ /** Member classes and their type names, subclasses first (`instanceof` order). */
182
+ readonly classes: readonly (readonly [cls: abstract new (...args: never[]) => unknown, name: string])[]
183
+ }
184
+ | { readonly by: 'typename' }
185
+
186
+ /** Kind of field a resolver method provides. */
187
+ export type GraphQLResolverOperation = 'query' | 'mutation' | 'subscription' | 'field'
188
+
189
+ /** The resolver method behind a field. */
190
+ export interface GraphQLResolverBinding {
191
+ /** Provider token of the resolver class. */
192
+ readonly token: Token
193
+ readonly method: string
194
+ readonly operation: GraphQLResolverOperation
195
+ /** Guard provider tokens: class `@UseGuard` guards, then method ones. */
196
+ readonly guards: readonly Token[]
197
+ /**
198
+ * Effective field metadata (`createRouteDecorator` values, method decisions over class ones, one
199
+ * decision per group). Empty: no metadata, so global guards skip a field resolver (§5.9).
200
+ */
201
+ readonly meta: Readonly<Record<string, unknown>>
202
+ /** Informational: loader provider tokens the method injects. */
203
+ readonly loaders?: readonly Token[]
204
+ /**
205
+ * Calls the method: maps the graphql-js arguments to the method parameters (§7.3). Synchronous
206
+ * unless an argument is validated with a Standard Schema.
207
+ */
208
+ readonly invoke: (
209
+ // biome-ignore lint/suspicious/noExplicitAny: generated invokers receive the concrete resolver class
210
+ resolver: any,
211
+ parent: unknown,
212
+ args: GraphQLArgs,
213
+ context: GraphQLContext,
214
+ info: GraphQLResolveInfo,
215
+ ) => unknown
216
+ }
217
+
218
+ declare module '@znight/core' {
219
+ interface ZnightExtensionsMetadata {
220
+ readonly graphql?: GraphQLMetadata
221
+ }
222
+ }
package/src/names.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * GraphQL naming rules and built-in types (RFC 0005 §5.7, §6.4).
3
+ *
4
+ * Import-free: shared by the compiler extension (naming diagnostics, ZNIGHT061) and the runtime
5
+ * (structural validation of `metadata.extensions.graphql` and the built-in scalars, ZNIGHT066).
6
+ */
7
+
8
+ /** Grammar of a GraphQL name (types, fields, arguments, enum values). */
9
+ export const GRAPHQL_NAME: RegExp = /^[_A-Za-z][_0-9A-Za-z]*$/
10
+
11
+ /** `true` when `name` is a GraphQL name that user types may take (not reserved with `__`). */
12
+ export function isGraphQLName(name: string): boolean {
13
+ return GRAPHQL_NAME.test(name) && !name.startsWith('__')
14
+ }
15
+
16
+ /** Names an enum value may not take (GraphQL spec §3.9). */
17
+ export const RESERVED_ENUM_VALUES: ReadonlySet<string> = new Set(['true', 'false', 'null'])
18
+
19
+ /** Names of the root operation types; user types may not take them (ZNIGHT061). */
20
+ export const ROOT_TYPE_NAMES: {
21
+ readonly query: 'Query'
22
+ readonly mutation: 'Mutation'
23
+ readonly subscription: 'Subscription'
24
+ } = Object.freeze({ query: 'Query', mutation: 'Mutation', subscription: 'Subscription' })
25
+
26
+ /** Scalars of the GraphQL specification: always present, never declared in the metadata nor printed. */
27
+ export const SPECIFIED_SCALARS: ReadonlySet<string> = new Set(['String', 'Int', 'Float', 'Boolean', 'ID'])
28
+
29
+ /** A scalar `@znight/graphql` implements itself (`kind: 'scalar', builtin: true` in the metadata). */
30
+ export interface BuiltinScalarInfo {
31
+ /** `@specifiedBy(url:)`. Absent: none. */
32
+ readonly specifiedByURL?: string
33
+ }
34
+
35
+ /**
36
+ * The scalars `@znight/graphql` implements (§5.7): `DateTime` (`Date`), `BigInt` (`bigint`) and
37
+ * `JSON` (`JSONValue`). They have no description. The runtime builds them with exactly these
38
+ * properties and without `valueToLiteral`, so that graphql-js `printSchema` and `printSdl()` agree.
39
+ */
40
+ export const BUILTIN_SCALARS: ReadonlyMap<string, BuiltinScalarInfo> = new Map<string, BuiltinScalarInfo>([
41
+ ['BigInt', {}],
42
+ ['DateTime', { specifiedByURL: 'https://datatracker.ietf.org/doc/html/rfc3339' }],
43
+ ['JSON', {}],
44
+ ])
45
+
46
+ /** The default `@deprecated` reason of graphql-js: printed as a bare `@deprecated`. */
47
+ export const DEFAULT_DEPRECATION_REASON = 'No longer supported'