@znight/graphql 0.2.2 → 0.4.0
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 +262 -17
- package/dist/compiler.d.mts +21 -1
- package/dist/compiler.mjs +116 -15
- package/dist/compiler.mjs.map +1 -1
- package/dist/{types-DQ713UgI.d.mts → driver-DgbpFj4-.d.mts} +193 -9
- package/dist/http-BWcayUtH.mjs +2165 -0
- package/dist/http-BWcayUtH.mjs.map +1 -0
- package/dist/index.d.mts +52 -51
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-CR_wVfRU.mjs → plugin-BssB7wtn.mjs} +242 -1778
- package/dist/plugin-BssB7wtn.mjs.map +1 -0
- package/dist/testing.d.mts +40 -1
- package/dist/testing.mjs +649 -4
- package/dist/testing.mjs.map +1 -1
- package/dist/yoga.d.mts +64 -0
- package/dist/yoga.mjs +379 -0
- package/dist/yoga.mjs.map +1 -0
- package/package.json +19 -10
- package/src/compiler/analyze.ts +27 -0
- package/src/compiler/data.ts +20 -1
- package/src/compiler/generate.ts +137 -19
- package/src/conformance.ts +700 -0
- package/src/context.ts +13 -0
- package/src/driver.ts +63 -0
- package/src/engine.ts +146 -0
- package/src/execution.ts +602 -67
- package/src/index.ts +7 -0
- package/src/limits.ts +22 -1
- package/src/metadata.ts +20 -4
- package/src/options.ts +53 -4
- package/src/pipeline.ts +72 -9
- package/src/plugin.ts +172 -29
- package/src/testing.ts +3 -0
- package/src/transport/builtin.ts +57 -0
- package/src/transport/fetch.ts +57 -0
- package/src/transport/http.ts +12 -9
- package/src/types.ts +22 -4
- package/src/yoga.ts +471 -0
- package/dist/plugin-CR_wVfRU.mjs.map +0 -1
package/src/context.ts
CHANGED
|
@@ -44,6 +44,10 @@ export interface ContextOptions {
|
|
|
44
44
|
readonly http?: HttpContext
|
|
45
45
|
readonly signal?: AbortSignal
|
|
46
46
|
readonly requestId?: string
|
|
47
|
+
/** The driver that received the operation (RFC 0009); `'builtin'` when absent. */
|
|
48
|
+
readonly driver?: string
|
|
49
|
+
/** The driver's context (`ExecutionArgs.contextValue` given to the engine). */
|
|
50
|
+
readonly serverContext?: unknown
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const NO_VARIABLES: Readonly<Record<string, unknown>> = Object.freeze({})
|
|
@@ -88,6 +92,15 @@ export class OperationContext implements GraphQLContext {
|
|
|
88
92
|
return this.#options.transport ?? 'direct'
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
/** The driver that received the operation: `'builtin'` for the built-in transport and direct calls. */
|
|
96
|
+
get driver(): string {
|
|
97
|
+
return this.#options.driver ?? 'builtin'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get serverContext(): unknown {
|
|
101
|
+
return this.#options.serverContext
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
get operationType(): GraphQLOperationKind {
|
|
92
105
|
return this.#operation.operation
|
|
93
106
|
}
|
package/src/driver.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL driver contract (RFC 0009 §4.3): a GraphQL server (GraphQL Yoga, Apollo Server, a
|
|
3
|
+
* third party's) serving the schema of `GraphQLPlugin` on top of its {@link GraphQLEngine}. Znight
|
|
4
|
+
* owns the schema, the execution and the lifecycle; the driver owns the protocol, its plugins and
|
|
5
|
+
* its subscription transport. Types only.
|
|
6
|
+
*/
|
|
7
|
+
import type { Container, CoreLogger } from '@znight/core'
|
|
8
|
+
import type { HttpPlugin, PluginRoute } from '@znight/http'
|
|
9
|
+
import type { GraphQLEngine } from './engine.ts'
|
|
10
|
+
|
|
11
|
+
/** What {@link GraphQLDriver.routes} receives. */
|
|
12
|
+
export interface GraphQLDriverRoutesContext {
|
|
13
|
+
/** The `path` option: the endpoint path, relative to the `HttpPlugin` prefix (`/graphql`). */
|
|
14
|
+
readonly path: string
|
|
15
|
+
/** The absolute endpoint path, the prefix included (`/api/graphql`). */
|
|
16
|
+
readonly endpoint: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A route of a driver: a plugin route of `HttpPlugin` without `meta`, `guards` and `bodyLimit`,
|
|
21
|
+
* which `GraphQLPlugin` adds from its `http` option (`bodyLimit` on the routes that are not `GET`).
|
|
22
|
+
*/
|
|
23
|
+
export type GraphQLDriverRoute = Pick<PluginRoute, 'method' | 'path' | 'name' | 'handler'>
|
|
24
|
+
|
|
25
|
+
/** What {@link GraphQLDriver.start} receives besides the engine. */
|
|
26
|
+
export interface GraphQLDriverContext {
|
|
27
|
+
/** The application logger. */
|
|
28
|
+
readonly logger: CoreLogger
|
|
29
|
+
readonly container: Container
|
|
30
|
+
/** The `HttpPlugin` of the application, when registered (its adapter, for server-mounted drivers). */
|
|
31
|
+
readonly http: HttpPlugin | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A GraphQL driver, given to `GraphQLPlugin.configure({ driver })`. A route-mounted driver returns
|
|
36
|
+
* its routes, which run in `HttpPlugin`'s pipeline (request scope, hooks, guards, CORS, drain) and
|
|
37
|
+
* call the engine from there; a server-mounted one has no routes and attaches itself to the
|
|
38
|
+
* adapter in `start()`. One driver serves one application at a time.
|
|
39
|
+
*/
|
|
40
|
+
export interface GraphQLDriver {
|
|
41
|
+
/**
|
|
42
|
+
* The driver's name (`yoga`, `apollo`...): `GraphQLOperationEvent.driver`, logs and errors.
|
|
43
|
+
* `builtin` is reserved for the built-in transport.
|
|
44
|
+
*/
|
|
45
|
+
readonly name: string
|
|
46
|
+
/**
|
|
47
|
+
* The routes `HttpPlugin` serves for the driver, with the `http` option's meta, guards and body
|
|
48
|
+
* limit applied by the plugin. Called once per application, possibly before {@link start}: the
|
|
49
|
+
* handlers MUST NOT use the engine before the first request. Not called with `http: false`.
|
|
50
|
+
*/
|
|
51
|
+
routes?(context: GraphQLDriverRoutesContext): readonly GraphQLDriverRoute[]
|
|
52
|
+
/**
|
|
53
|
+
* Called in `GraphQLPlugin.onBootstrap`, once the engine is built: create the server. A failure
|
|
54
|
+
* fails the boot (ZNIGHT066, the original error as `cause`).
|
|
55
|
+
*/
|
|
56
|
+
start(engine: GraphQLEngine, context: GraphQLDriverContext): void | Promise<void>
|
|
57
|
+
/**
|
|
58
|
+
* Stops the server, after the engine ended its subscriptions: in `GraphQLPlugin.onDispose` for a
|
|
59
|
+
* route-mounted driver (once `HttpPlugin` drained its requests, which the driver keeps serving),
|
|
60
|
+
* in `GraphQLPlugin.onStop` for a server-mounted one (drain and stop the server).
|
|
61
|
+
*/
|
|
62
|
+
stop?(): void | Promise<void>
|
|
63
|
+
}
|
package/src/engine.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GraphQL engine (RFC 0009 §4.2): what a GraphQL server (a driver) calls to run Znight's
|
|
3
|
+
* schema. Its functions have the shape of graphql-js's (`parse`, `validate`, `execute`,
|
|
4
|
+
* `subscribe`), so they fit Envelop's `useEngine`, `graphql-ws`, `graphql-sse` and `graphql-http`
|
|
5
|
+
* handlers as they are, and they keep everything `GraphQLPlugin` guarantees: the document cache and
|
|
6
|
+
* limits, the request scope and `GraphQLContext`, guards, loaders, result mappers, error mapping
|
|
7
|
+
* and masking, the subscriptions cap and the `graphql:operation` hook.
|
|
8
|
+
*
|
|
9
|
+
* A layer over the internal `GraphQLExecutor`: the built-in transport keeps calling the executor
|
|
10
|
+
* directly, so the engine costs nothing to an application without driver.
|
|
11
|
+
*/
|
|
12
|
+
import type { ByteSize } from '@znight/http'
|
|
13
|
+
import type {
|
|
14
|
+
DocumentNode,
|
|
15
|
+
ExecutionArgs,
|
|
16
|
+
ExecutionResult,
|
|
17
|
+
GraphQLError,
|
|
18
|
+
GraphQLSchema,
|
|
19
|
+
Source,
|
|
20
|
+
ValidationRule,
|
|
21
|
+
} from 'graphql'
|
|
22
|
+
import type { EngineStream, ExecuteOptions, GraphQLExecutor } from './execution.ts'
|
|
23
|
+
|
|
24
|
+
/** A value of type `T` or a promise of it (graphql-js `PromiseOrValue`). */
|
|
25
|
+
type PromiseOrValue<T> = T | Promise<T>
|
|
26
|
+
|
|
27
|
+
/** Options of {@link GraphQLEngine.execute} and {@link GraphQLEngine.subscribe}. */
|
|
28
|
+
export interface EngineExecuteOptions {
|
|
29
|
+
/**
|
|
30
|
+
* The protocol that carried the operation (`GraphQLOperationEvent.transport`,
|
|
31
|
+
* `GraphQLContext.transport`): `'http'` (default), `'sse'` for an event stream, `'ws'` for
|
|
32
|
+
* `graphql-ws`.
|
|
33
|
+
*/
|
|
34
|
+
readonly transport?: 'http' | 'sse' | 'ws'
|
|
35
|
+
/**
|
|
36
|
+
* Cancels the operation (graphql-js `abortSignal`, `GraphQLContext.signal`). Default:
|
|
37
|
+
* `ExecutionArgs.abortSignal`, else the signal of the HTTP request of the current scope.
|
|
38
|
+
*/
|
|
39
|
+
readonly signal?: AbortSignal
|
|
40
|
+
/** `GraphQLContext.requestId`. Default: the request id of the HTTP request of the scope. */
|
|
41
|
+
readonly requestId?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The resolved options a GraphQL server needs to configure itself (read-only). */
|
|
45
|
+
export interface GraphQLEngineSettings {
|
|
46
|
+
/** The endpoint path, the `HttpPlugin` prefix included (`/api/graphql`). */
|
|
47
|
+
readonly path: string
|
|
48
|
+
readonly introspection: boolean
|
|
49
|
+
/** The `graphiql` option: serve the server's IDE. */
|
|
50
|
+
readonly ide: boolean
|
|
51
|
+
readonly exposeErrors: boolean
|
|
52
|
+
/** `limits.maxTokens`, for servers that parse documents themselves. */
|
|
53
|
+
readonly maxTokens: number | undefined
|
|
54
|
+
readonly subscriptions: false | { readonly maxActive: number }
|
|
55
|
+
readonly http: false | { readonly get: boolean; readonly bodyLimit: ByteSize }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The engine of a bootstrapped `GraphQLPlugin` (`plugin.engine`, and the first argument of
|
|
60
|
+
* `GraphQLDriver.start()`). Its functions are bound: pass them around as they are.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const handler = createHandler({
|
|
64
|
+
* schema: engine.schema,
|
|
65
|
+
* parse: engine.parse,
|
|
66
|
+
* validate: engine.validate,
|
|
67
|
+
* execute: args => engine.execute(args),
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export interface GraphQLEngine {
|
|
72
|
+
/** The executable schema: its resolvers are Znight's pipelines. */
|
|
73
|
+
readonly schema: GraphQLSchema
|
|
74
|
+
readonly settings: GraphQLEngineSettings
|
|
75
|
+
/**
|
|
76
|
+
* graphql-js `parse` through the document cache: `maxTokens`, then the depth, aliases and
|
|
77
|
+
* directives limits. Throws a `GraphQLError` (`GRAPHQL_PARSE_FAILED`, `QUERY_TOO_COMPLEX`), like
|
|
78
|
+
* graphql-js. A text the engine validated returns the same `DocumentNode`.
|
|
79
|
+
*/
|
|
80
|
+
parse(source: string | Source): DocumentNode
|
|
81
|
+
/**
|
|
82
|
+
* graphql-js `validate` with `rules` (default: graphql-js `specifiedRules`), to which the engine
|
|
83
|
+
* adds its own ({@link rules}), with `hideSuggestions` when introspection is off. With the
|
|
84
|
+
* default rules on {@link schema}, a document is validated once (then cached).
|
|
85
|
+
*/
|
|
86
|
+
validate(schema: GraphQLSchema, document: DocumentNode, rules?: readonly ValidationRule[]): readonly GraphQLError[]
|
|
87
|
+
/** The engine's own rules (limits, introspection), for servers that validate documents themselves. */
|
|
88
|
+
readonly rules: readonly ValidationRule[]
|
|
89
|
+
/**
|
|
90
|
+
* Executes a validated query or mutation in the current request scope (or a scope of its own),
|
|
91
|
+
* with the operation's `GraphQLContext` as `contextValue` (`args.contextValue` becomes
|
|
92
|
+
* `GraphQLContext.serverContext`). Errors are mapped and masked, as `GraphQLError` instances.
|
|
93
|
+
*/
|
|
94
|
+
execute(args: ExecutionArgs, options?: EngineExecuteOptions): PromiseOrValue<ExecutionResult>
|
|
95
|
+
/**
|
|
96
|
+
* Starts a validated subscription: a stream of results, or a single error result (rejected, past
|
|
97
|
+
* `subscriptions.maxActive`, a source that could not start). The plugin ends the stream when the
|
|
98
|
+
* application stops.
|
|
99
|
+
*/
|
|
100
|
+
subscribe(
|
|
101
|
+
args: ExecutionArgs,
|
|
102
|
+
options?: EngineExecuteOptions,
|
|
103
|
+
): PromiseOrValue<AsyncGenerator<ExecutionResult, void, void> | ExecutionResult>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The engine of a plugin: bound functions over its executor. */
|
|
107
|
+
export class Engine implements GraphQLEngine {
|
|
108
|
+
readonly schema: GraphQLSchema
|
|
109
|
+
readonly settings: GraphQLEngineSettings
|
|
110
|
+
readonly rules: readonly ValidationRule[]
|
|
111
|
+
readonly parse: (source: string | Source) => DocumentNode
|
|
112
|
+
readonly validate: (
|
|
113
|
+
schema: GraphQLSchema,
|
|
114
|
+
document: DocumentNode,
|
|
115
|
+
rules?: readonly ValidationRule[],
|
|
116
|
+
) => readonly GraphQLError[]
|
|
117
|
+
readonly execute: (args: ExecutionArgs, options?: EngineExecuteOptions) => PromiseOrValue<ExecutionResult>
|
|
118
|
+
readonly subscribe: (args: ExecutionArgs, options?: EngineExecuteOptions) => Promise<EngineStream | ExecutionResult>
|
|
119
|
+
|
|
120
|
+
constructor(executor: GraphQLExecutor, settings: GraphQLEngineSettings, driver: string) {
|
|
121
|
+
this.schema = executor.schema
|
|
122
|
+
this.settings = Object.freeze({ ...settings })
|
|
123
|
+
this.rules = executor.engineRules
|
|
124
|
+
// extra arguments of graphql-js (parse options, validation options) are not used: the engine's apply
|
|
125
|
+
this.parse = source => executor.parseDocument(source, driver)
|
|
126
|
+
this.validate = (schema, document, rules) => executor.validateDocument(schema, document, rules, driver)
|
|
127
|
+
this.execute = (args, options) => executor.executeDocument(args, executeOptions(args, options, driver))
|
|
128
|
+
this.subscribe = (args, options) => executor.subscribeDocument(args, executeOptions(args, options, driver))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function executeOptions(
|
|
133
|
+
args: ExecutionArgs,
|
|
134
|
+
options: EngineExecuteOptions | undefined,
|
|
135
|
+
driver: string,
|
|
136
|
+
): ExecuteOptions {
|
|
137
|
+
const signal = options?.signal ?? args?.abortSignal ?? undefined
|
|
138
|
+
const requestId = options?.requestId
|
|
139
|
+
return {
|
|
140
|
+
transport: options?.transport ?? 'http',
|
|
141
|
+
driver,
|
|
142
|
+
serverContext: args?.contextValue,
|
|
143
|
+
...(signal === undefined ? {} : { signal }),
|
|
144
|
+
...(requestId === undefined ? {} : { requestId }),
|
|
145
|
+
}
|
|
146
|
+
}
|