@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/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
|
+
}
|
package/src/metadata.ts
ADDED
|
@@ -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'
|