@znight/graphql 0.2.1 → 0.3.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 +238 -18
- package/dist/{types-DQ713UgI.d.mts → driver-Cnze8Oyr.d.mts} +184 -7
- package/dist/http-BWcayUtH.mjs +2165 -0
- package/dist/http-BWcayUtH.mjs.map +1 -0
- package/dist/index.d.mts +37 -49
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{plugin-CR_wVfRU.mjs → plugin-q4oz7d6s.mjs} +214 -1774
- package/dist/plugin-q4oz7d6s.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 +25 -11
- 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/options.ts +53 -4
- package/src/plugin.ts +170 -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 +12 -2
- package/src/yoga.ts +471 -0
- package/dist/plugin-CR_wVfRU.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -44,7 +44,8 @@ npm install -D @znight/compiler vite
|
|
|
44
44
|
|
|
45
45
|
Runs on Node.js >= 24 and Bun >= 1.3 (ESM only). `@znight/http` and `graphql` 17 are required peer
|
|
46
46
|
dependencies, `dataloader` is a dependency, `@znight/testing` an optional peer (for
|
|
47
|
-
`@znight/graphql/testing`)
|
|
47
|
+
`@znight/graphql/testing`), `graphql-yoga` 6 an optional peer (for the Yoga driver,
|
|
48
|
+
`@znight/graphql/yoga`).
|
|
48
49
|
|
|
49
50
|
## Contents
|
|
50
51
|
|
|
@@ -61,6 +62,7 @@ dependencies, `dataloader` is a dependency, `@znight/testing` an optional peer (
|
|
|
61
62
|
- [Errors as data](#errors-as-data)
|
|
62
63
|
- [Plugin options](#plugin-options)
|
|
63
64
|
- [GraphQL over HTTP and SSE](#graphql-over-http-and-sse)
|
|
65
|
+
- [Drivers and the engine](#drivers-and-the-engine)
|
|
64
66
|
- [Security defaults and limits](#security-defaults-and-limits)
|
|
65
67
|
- [Testing](#testing)
|
|
66
68
|
- [The generated SDL and the compiler options](#the-generated-sdl-and-the-compiler-options)
|
|
@@ -425,8 +427,11 @@ curl -N -H 'accept: text/event-stream' -H 'content-type: application/json' \
|
|
|
425
427
|
# data: {"data":{"bookAdded":{"title":"The Hobbit"}}}
|
|
426
428
|
```
|
|
427
429
|
|
|
428
|
-
|
|
429
|
-
`Events
|
|
430
|
+
The events of a subscription are born elsewhere (a mutation, a scheduled job, a message consumer),
|
|
431
|
+
while the stream stays open. `PubSub<Events>` is the mailbox between them: producers
|
|
432
|
+
`publish(topic, payload)`, every open subscription `subscribe(topic, { signal })`s and receives what
|
|
433
|
+
is published on the topic from then on (nothing is stored or replayed). It is an injectable abstract
|
|
434
|
+
class (singleton, provided by `GraphQLPlugin`), where `Events` maps topics to payload types:
|
|
430
435
|
|
|
431
436
|
```ts
|
|
432
437
|
/** Topics of the application PubSub and their payloads. */
|
|
@@ -443,8 +448,14 @@ for await (const book of this.events.subscribe('bookAdded', { signal: ctx.signal
|
|
|
443
448
|
subscriber (`subscriptions.bufferSize`, 100 events). A subscriber that falls behind gets a final
|
|
444
449
|
`SUBSCRIPTION_OVERFLOW` error and its stream ends, rather than losing events silently or buffering
|
|
445
450
|
without bound. The plugin closes the `MemoryPubSub` it created when the application is disposed.
|
|
446
|
-
-
|
|
447
|
-
|
|
451
|
+
- `MemoryPubSub` reaches the subscribers of its own process only. Several instances need a shared
|
|
452
|
+
implementation (Redis pub/sub, NATS, Postgres `LISTEN`/`NOTIFY`, a broker): extend `PubSub` and
|
|
453
|
+
pass an instance as the `pubsub` option. `subscribe()` returns its iterator synchronously, ends
|
|
454
|
+
when `signal` aborts or `return()` is called, buffers a bounded number of events per subscriber
|
|
455
|
+
(`SUBSCRIPTION_OVERFLOW` beyond), and payloads must survive serialization. The plugin does not
|
|
456
|
+
close a `PubSub` it did not create. The guide's
|
|
457
|
+
[Write your own PubSub](../../docs/guide/graphql.md#custom-pubsub) recipe is a tested Redis
|
|
458
|
+
implementation (ioredis).
|
|
448
459
|
- `subscribe()` ends when its `signal` aborts: the client left, the application shuts down, the
|
|
449
460
|
stream reached `subscriptions.maxDuration`, or (`plugin.subscribe()`) its `return()` was called.
|
|
450
461
|
Always pass `ctx.signal`.
|
|
@@ -512,13 +523,14 @@ Built-in scalars appear in the schema only when used: `String`, `Float`, `Int`,
|
|
|
512
523
|
| Member | What |
|
|
513
524
|
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
514
525
|
| `requestId` | Id of the request carrying the operation (`HttpContext.requestId` over HTTP, a UUID or `requestId` option otherwise) |
|
|
515
|
-
| `transport` | `'http'`, `'sse'` or `'direct'` (`plugin.execute()` / `subscribe()`)
|
|
526
|
+
| `transport` | `'http'`, `'sse'` or `'direct'` (`plugin.execute()` / `subscribe()`); `'ws'` is reserved for `graphql-ws` |
|
|
516
527
|
| `http` | The `HttpContext` of the request, `undefined` for direct executions outside a request |
|
|
517
528
|
| `operationType` / `operationName` | `'query'`, `'mutation'` or `'subscription'`; the selected operation name |
|
|
518
529
|
| `document` / `variables` | The parsed document (shared by the document cache: never mutate it) and the coerced variables |
|
|
519
530
|
| `signal` | Over HTTP, the request's `ctx.signal`: client gone, SSE stream closed; passed to graphql-js as `abortSignal` |
|
|
520
531
|
| `state` | A `Map` for per-operation storage |
|
|
521
532
|
| `loader(Class \| token)` | The DataLoader of the current execution for a loader |
|
|
533
|
+
| `serverContext` | With a [driver](#drivers-and-the-engine), the context its server gave the engine; `undefined` otherwise |
|
|
522
534
|
|
|
523
535
|
- Resolvers receive it with `@Ctx()`; request-scoped providers inject `GraphQLContext`, singletons
|
|
524
536
|
`Lazy<GraphQLContext>`, exactly like `HttpContext`.
|
|
@@ -777,12 +789,13 @@ ZNIGHT066.
|
|
|
777
789
|
| `resultMappers` | `[]` | `GraphQLResultMapper` classes or instances, tried in order: errors as data on the fields whose union holds one of their `types` ([Errors as data](#errors-as-data)) |
|
|
778
790
|
| `exposeErrors` | `false` | Unmasked messages for unexpected errors (development) |
|
|
779
791
|
| `introspection` | `NODE_ENV !== 'production'` | Off: graphql-js `NoSchemaIntrospectionCustomRule`, and suggestions are hidden |
|
|
780
|
-
| `graphiql` | `NODE_ENV === 'development'` | `GET path` with `accept: text/html` serves GraphiQL
|
|
792
|
+
| `graphiql` | `NODE_ENV === 'development'` | `GET path` with `accept: text/html` serves GraphiQL; with a `driver`, the driver's IDE |
|
|
781
793
|
| `limits` | `{ maxTokens: 2000, maxDepth: 12, maxAliases: 30, maxDirectives: 50 }` | Request limits (introspection fields exempt from `maxDepth`); `false` disables one |
|
|
782
794
|
| `documentCache` | `1000` | Distinct documents kept parsed and validated (LRU); `0` disables |
|
|
783
795
|
| `subscriptions` | `{ maxActive: 10_000, bufferSize: 100 }` | `false` disables subscriptions over SSE. `heartbeat` (ms or `false`) and `maxDuration` (ms) are passed to `HttpResponse.sse()` (default: the `HttpPlugin` `sse` options) |
|
|
784
796
|
| `pubsub` | a `MemoryPubSub` | The `PubSub` provided to the container |
|
|
785
797
|
| `enforcement` | `'error'` | How the boot check of field decisions reports a problem: `'error'` fails the boot (ZNIGHT023), `'warn'` logs |
|
|
798
|
+
| `driver` | the built-in transport | The GraphQL server serving the schema ([Drivers and the engine](#drivers-and-the-engine)); `heartbeat` and `maxDuration` are refused with one (ZNIGHT066) |
|
|
786
799
|
|
|
787
800
|
`HttpPlugin` must be registered unless `http: false` (ZNIGHT066). The plugin instance exposes:
|
|
788
801
|
|
|
@@ -794,10 +807,15 @@ ZNIGHT066.
|
|
|
794
807
|
| `subscribe(request, options?)` | A stream of results, one per event, until the source ends, `options.signal` aborts, `return()` is called or the application stops |
|
|
795
808
|
| `pubsub` | The `PubSub` of the application |
|
|
796
809
|
| `activeSubscriptions` | Active subscriptions, every transport |
|
|
797
|
-
| `
|
|
810
|
+
| `engine` | The `GraphQLEngine` drivers call (after bootstrap): [Drivers and the engine](#drivers-and-the-engine) |
|
|
811
|
+
| `initRoots()` / `httpRoutes()` | The `PluginInitRoots` contract (`TestApp`) and the `PluginHttpRoutes` contract of `@znight/http`, `onHttpDrain()` included |
|
|
798
812
|
|
|
799
813
|
`request` is `{ query, operationName?, variables?, extensions? }`; `options` is `{ signal?,
|
|
800
|
-
requestId? }`. A direct execution has no HTTP guards
|
|
814
|
+
requestId? }`. A direct execution has no HTTP guards of its own: inside a controller, it runs in the
|
|
815
|
+
route's request scope, after the route's guards. With `http: false`, a controller can hand its
|
|
816
|
+
requests to another GraphQL server that delegates the execution to `execute()` and `subscribe()`;
|
|
817
|
+
a [driver](#drivers-and-the-engine) does it without the copying, on the parsed document (GraphQL
|
|
818
|
+
Yoga: [`yogaDriver()`](#graphql-yoga)).
|
|
801
819
|
|
|
802
820
|
```ts
|
|
803
821
|
const plugin = app.getPlugin<GraphQLPlugin>('graphql')
|
|
@@ -805,10 +823,13 @@ const result = await plugin?.execute({ query: '{ books { total } }' })
|
|
|
805
823
|
```
|
|
806
824
|
|
|
807
825
|
`onStart` logs `GraphQL endpoint ready` with the path, the number of types and the schema hash
|
|
808
|
-
(`GraphQL schema ready` with `http: false`). On shutdown, `onStop` ends the direct subscriptions
|
|
809
|
-
`HttpPlugin` drains the requests in flight and closes the SSE streams
|
|
810
|
-
`'shutdown'`, then the stream ends with a jittered `retry` and **no**
|
|
811
|
-
reconnect to another instance).
|
|
826
|
+
(`GraphQL schema ready` with `http: false`). On shutdown, `onStop` ends the direct subscriptions
|
|
827
|
+
and those of the engine; `HttpPlugin` drains the requests in flight and closes the SSE streams
|
|
828
|
+
(their `ctx.signal` aborts with `'shutdown'`, then the stream ends with a jittered `retry` and **no**
|
|
829
|
+
`complete` event, so clients reconnect to another instance). When `HttpPlugin` starts draining, it
|
|
830
|
+
calls `GraphQLPlugin.onHttpDrain()`, which ends the engine's subscriptions too (a driver serves them
|
|
831
|
+
as streams `HttpPlugin` does not close) and refuses new ones (503). A route-mounted driver stops in
|
|
832
|
+
`onDispose`, once the drain is over: requests in flight are served. Plugin order does not matter.
|
|
812
833
|
|
|
813
834
|
## GraphQL over HTTP and SSE
|
|
814
835
|
|
|
@@ -850,6 +871,201 @@ Adapters need nothing specific. With Express, do not mount `express.json()` befo
|
|
|
850
871
|
(the body would be consumed). In tests, `TestApp.inject()` runs queries and mutations through every
|
|
851
872
|
adapter in memory and `injectStream()` / `injectSse()` stream subscriptions.
|
|
852
873
|
|
|
874
|
+
## Drivers and the engine
|
|
875
|
+
|
|
876
|
+
The built-in GraphQL-over-HTTP and SSE endpoint is the default and the fastest path. A **driver**
|
|
877
|
+
serves the same schema with another GraphQL server, for its protocol features and its plugins
|
|
878
|
+
(RFC 0009): Znight stays the engine, the driver owns the transport.
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
import { yogaDriver } from '@znight/graphql/yoga'
|
|
882
|
+
|
|
883
|
+
GraphQLPlugin.configure({ guards: [AccessGuard], driver: yogaDriver() })
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Resolvers, guards, loaders, result mappers, request-scoped providers and tests do not change: the
|
|
887
|
+
server calls `plugin.engine`, which runs the compiled schema with everything this README describes.
|
|
888
|
+
**GraphQL Yoga** is the first driver ([`@znight/graphql/yoga`](#graphql-yoga)); **Apollo Server**
|
|
889
|
+
follows once it accepts graphql-js 17. Any other graphql-js-shaped server takes the engine as it
|
|
890
|
+
is: a driver is a small object, checked with the [conformance suite](#driver-conformance).
|
|
891
|
+
|
|
892
|
+
**The engine.** `plugin.engine` (after bootstrap, and the first argument of `driver.start()`):
|
|
893
|
+
|
|
894
|
+
| Member | What |
|
|
895
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
896
|
+
| `schema` | The executable schema; its resolvers are Znight's pipelines |
|
|
897
|
+
| `settings` | The resolved options a server needs: `path` (prefix included), `introspection`, `ide` (`graphiql`), `exposeErrors`, `maxTokens`, `subscriptions` (`false` or `{ maxActive }`), `http` (`false` or `{ get, bodyLimit }`) |
|
|
898
|
+
| `parse(source)` | graphql-js `parse` through the document cache, with `maxTokens` and the depth, aliases and directives limits; throws a `GraphQLError` (`GRAPHQL_PARSE_FAILED`, `QUERY_TOO_COMPLEX`) |
|
|
899
|
+
| `validate(schema, doc, rules?)` | graphql-js `validate` with `rules` (default `specifiedRules`) plus the engine's, suggestions hidden without introspection; a valid document is cached (default rules), its errors carry `GRAPHQL_VALIDATION_FAILED` |
|
|
900
|
+
| `rules` | The engine's own rules (the limits, `NoSchemaIntrospectionCustomRule` without introspection), for servers that validate themselves |
|
|
901
|
+
| `execute(args, options?)` | A validated query or mutation, in the current request scope (or one of its own): the `GraphQLContext` is the `contextValue`, `args.contextValue` its `serverContext`; errors mapped and masked, as `GraphQLError` instances |
|
|
902
|
+
| `subscribe(args, options?)` | A validated subscription: a stream of results (loaders renewed per event), or a single error result; the `maxActive` cap is shared with every transport, and the plugin ends the stream when the application stops |
|
|
903
|
+
|
|
904
|
+
`options` is `{ transport?: 'http' | 'sse' | 'ws', signal?, requestId? }`; the signal defaults to
|
|
905
|
+
`args.abortSignal`, then to the HTTP request's. The functions are bound and have graphql-js's
|
|
906
|
+
signatures, so they fit `graphql-http`, `graphql-sse`, `graphql-ws` and Envelop's `useEngine` as
|
|
907
|
+
they are. Every operation produces one `graphql:operation` event with the driver's name, and
|
|
908
|
+
documents rejected by `parse` or `validate` are observed too.
|
|
909
|
+
|
|
910
|
+
**The driver contract.** A `GraphQLDriver` has a `name` (`builtin` is reserved), optional
|
|
911
|
+
`routes()`, `start(engine, context)` and an optional `stop()`:
|
|
912
|
+
|
|
913
|
+
```ts
|
|
914
|
+
import type { HttpContext } from '@znight/http'
|
|
915
|
+
import type { GraphQLDriver } from '@znight/graphql'
|
|
916
|
+
|
|
917
|
+
import { HttpResponse } from '@znight/http'
|
|
918
|
+
import { createHandler, type Handler } from 'graphql-http'
|
|
919
|
+
|
|
920
|
+
/** graphql-http's handler on the engine: GraphQL over HTTP served by the reference implementation. */
|
|
921
|
+
export function graphqlHttpDriver(): GraphQLDriver {
|
|
922
|
+
let handler: Handler<HttpContext> | undefined
|
|
923
|
+
return {
|
|
924
|
+
name: 'graphql-http',
|
|
925
|
+
routes: ({ path }) => {
|
|
926
|
+
const handle = async (ctx: HttpContext) => {
|
|
927
|
+
const body = ctx.method === 'POST' ? new TextDecoder().decode(await ctx.rawBody()) : null
|
|
928
|
+
const [text, init] = await (handler as Handler<HttpContext>)({
|
|
929
|
+
method: ctx.method,
|
|
930
|
+
url: ctx.url,
|
|
931
|
+
headers: { get: name => ctx.header(name.toLowerCase()) ?? null },
|
|
932
|
+
body,
|
|
933
|
+
raw: ctx,
|
|
934
|
+
context: undefined,
|
|
935
|
+
})
|
|
936
|
+
return new HttpResponse(text ?? undefined, { status: init.status, headers: init.headers ?? {} })
|
|
937
|
+
}
|
|
938
|
+
return [
|
|
939
|
+
{ method: 'GET', path, name: 'graphql', handler: handle },
|
|
940
|
+
{ method: 'POST', path, name: 'graphql', handler: handle },
|
|
941
|
+
]
|
|
942
|
+
},
|
|
943
|
+
start(engine) {
|
|
944
|
+
handler = createHandler<HttpContext>({
|
|
945
|
+
schema: engine.schema,
|
|
946
|
+
parse: engine.parse,
|
|
947
|
+
validate: engine.validate,
|
|
948
|
+
execute: args => engine.execute(args),
|
|
949
|
+
})
|
|
950
|
+
},
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
- **Routes.** A route-mounted driver returns its routes: `GraphQLPlugin.httpRoutes()` gives them to
|
|
956
|
+
`HttpPlugin` with the `http` option's `meta` and `guards`, and its `bodyLimit` on the routes that
|
|
957
|
+
are not `GET`. They run in the HTTP pipeline like the built-in endpoint (request id, request
|
|
958
|
+
scope, `http:request` hooks, global and endpoint guards, route decisions, CORS, security headers,
|
|
959
|
+
drain), so the engine finds the request scope: `GraphQLContext.http` is the request, and HTTP
|
|
960
|
+
guards and GraphQL guards see the same request-scoped providers. `routes()` is called once,
|
|
961
|
+
possibly before `start()`. A driver without routes is server-mounted: it attaches itself to
|
|
962
|
+
`context.http.adapter` in `start()` and runs outside the pipeline.
|
|
963
|
+
- **Lifecycle.** `onBootstrap` builds the engine, then awaits `start(engine, { logger, container,
|
|
964
|
+
http })`; a failure fails the boot (ZNIGHT066, the original error as `cause`). The engine's
|
|
965
|
+
subscriptions end when `HttpPlugin` starts draining (`onHttpDrain()`, whatever the plugin order)
|
|
966
|
+
and in `onStop`; new ones are then refused (503). `stop()` is awaited in `onDispose` for a
|
|
967
|
+
route-mounted driver, after `HttpPlugin` drained its requests, and in `onStop` for a
|
|
968
|
+
server-mounted one (`onDispose` does, for an application that never started).
|
|
969
|
+
- **Options.** The engine options (`guards`, `errorMappers`, `resultMappers`, `exposeErrors`,
|
|
970
|
+
`introspection`, `limits`, `documentCache`, `subscriptions: false`, `maxActive`, `bufferSize`,
|
|
971
|
+
`pubsub`, `enforcement`) apply to every driver. `path`, `http` and `graphiql` are read by the
|
|
972
|
+
driver through its routes and `engine.settings`. `subscriptions.heartbeat` and `maxDuration`
|
|
973
|
+
configure the built-in SSE transport only: with a driver they are ZNIGHT066 (use the driver's
|
|
974
|
+
keep-alives). A driver with routes needs `HttpPlugin` and refuses `http: false` (ZNIGHT066).
|
|
975
|
+
- **Errors.** The engine maps and masks errors once; a driver keeps the engine's errors as they
|
|
976
|
+
are. Request errors raised by the server itself (malformed JSON, a persisted query not found) are
|
|
977
|
+
the server's; an unexpected error thrown outside the engine is masked like the engine masks one.
|
|
978
|
+
- **Cost.** Without `driver`, the plugin runs the built-in transport's handlers, which call the
|
|
979
|
+
internal executor directly: an application without driver pays nothing for the abstraction.
|
|
980
|
+
|
|
981
|
+
`executeOperation()` and `subscribeOperation()` of `@znight/graphql/testing` send their requests to
|
|
982
|
+
the driver's routes like to the built-in endpoint. The repository's tests run the `graphql-http`
|
|
983
|
+
audits against the driver above and the Yoga driver on the four adapters, on Node.js and Bun.
|
|
984
|
+
|
|
985
|
+
### GraphQL Yoga
|
|
986
|
+
|
|
987
|
+
`@znight/graphql/yoga` serves the schema with [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
|
|
988
|
+
6 (`graphql-yoga`, an optional peer dependency loaded by this entry only):
|
|
989
|
+
|
|
990
|
+
```ts
|
|
991
|
+
import { GraphQLPlugin } from '@znight/graphql'
|
|
992
|
+
import { yogaDriver } from '@znight/graphql/yoga'
|
|
993
|
+
|
|
994
|
+
GraphQLPlugin.configure({ driver: yogaDriver({ plugins: [usePersistedOperations({ getPersistedOperation })] }) })
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
| Export | What |
|
|
998
|
+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
999
|
+
| `yogaDriver(options?)` | The driver (`name: 'yoga'`); `driver.yoga` is the Yoga server once started |
|
|
1000
|
+
| `YogaDriverOptions` | Yoga's options without those Znight owns (below); `graphiql` takes GraphiQL's options |
|
|
1001
|
+
| `yogaContext(ctx)` | Yoga's context of an operation (`request`, `params`, what plugins added, `znight`: the `HttpContext`), ZNIGHT066 otherwise |
|
|
1002
|
+
| `YogaContext`, `YogaServerContext` | Their types |
|
|
1003
|
+
|
|
1004
|
+
- **The engine is Yoga's engine.** The driver installs Envelop's `useEngine` with the engine's
|
|
1005
|
+
`parse`, `validate`, `execute` and `subscribe`, before the application's plugins: one document
|
|
1006
|
+
cache (Yoga's `parserAndValidationCache` is off), the engine's limits and introspection rule,
|
|
1007
|
+
rejected documents observed by the hook, one `graphql:operation` event per operation with
|
|
1008
|
+
`driver: 'yoga'` (`transport: 'sse'` for subscriptions). Plugins wrapping the execution
|
|
1009
|
+
(`useResponseCache`) call the engine; an operation executed by a plugin that replaces it
|
|
1010
|
+
(`useGraphQlJit`) fails with ZNIGHT066, logged once: its resolvers would not get the
|
|
1011
|
+
`GraphQLContext`.
|
|
1012
|
+
- **Owned options**, refused with ZNIGHT066 (a type error first): `schema`, `context`,
|
|
1013
|
+
`graphqlEndpoint` (`path`), `maskedErrors` (the engine masks once, see below),
|
|
1014
|
+
`cors` (`HttpPlugin`'s), `maxRequestBodySize` (`http.bodyLimit`), `parserAndValidationCache`,
|
|
1015
|
+
`requestId` (Yoga uses `HttpContext.requestId`), `healthCheckEndpoint`, `landingPage`,
|
|
1016
|
+
`fetchAPI` (the runtime's own), `disposeOnProcessTerminate` (the application stops Yoga),
|
|
1017
|
+
`batching` (one operation per request: the operations of a batch would share the request scope,
|
|
1018
|
+
where each one's `GraphQLContext` would replace the other's) and `multipart` (JSON bodies only,
|
|
1019
|
+
below). Yoga's `logging` is off unless set.
|
|
1020
|
+
- **Routes.** `GET` and `POST` on `path`. `GET` serves Yoga's GraphiQL with `graphiql` on, and
|
|
1021
|
+
queries with `http.get` on (405 with `allow: POST` otherwise); a mutation over `GET` is Yoga's 405
|
|
1022
|
+
(`allow: POST`). A `POST` body is `application/json` only, like the built-in transport: any other
|
|
1023
|
+
content type (a form, `multipart/form-data`, `text/plain`, `application/graphql`) is the built-in
|
|
1024
|
+
transport's 415, before Yoga parses it, so a cross-site page cannot run a mutation with the
|
|
1025
|
+
user's cookies (no CSRF: a JSON body always needs a CORS preflight).
|
|
1026
|
+
- **Errors.** The engine's errors (masked already) and `GraphQLError`s thrown on purpose (a plugin's
|
|
1027
|
+
rate limit, Yoga's request errors) are sent as they are. Any other error thrown outside the engine
|
|
1028
|
+
(an Envelop plugin's hook, Yoga itself) is logged once by Znight and masked like the engine masks
|
|
1029
|
+
one: `Unexpected error.` with code `INTERNAL_SERVER_ERROR` (its message with `exposeErrors`), a
|
|
1030
|
+
500 for a request error.
|
|
1031
|
+
- **The Fetch bridge.** On `honoAdapter()` the native Web `Request` is handed to Yoga as is (a `GET`,
|
|
1032
|
+
or a body with a `content-length` within the limit); otherwise a `Request` is built from
|
|
1033
|
+
`HttpContext` with the body read by `ctx.rawBody()`, so a body past `http.bodyLimit` is
|
|
1034
|
+
`HttpPlugin`'s 413 on every adapter. Yoga's `Response` is sent as is by `HttpPlugin`.
|
|
1035
|
+
- **Subscriptions** use Yoga's SSE; the engine caps them (`subscriptions.maxActive`) and ends them
|
|
1036
|
+
when `HttpPlugin` starts draining, whatever the plugin order, when Yoga sends `complete` (not the
|
|
1037
|
+
built-in transport's `retry` hint); a subscription sent during the drain gets a
|
|
1038
|
+
`SERVICE_UNAVAILABLE` error. Queries in flight are served until the drain is over; `stop()` then
|
|
1039
|
+
awaits `yoga.dispose()` (the plugins' `onDispose`), and a request reaching the stopped driver gets
|
|
1040
|
+
a 503.
|
|
1041
|
+
- **Cost.** Yoga's request parsing, its plugin chain and the Web objects cost about 13 to 18 µs per
|
|
1042
|
+
request on Bun and 27 to 45 µs on Node.js in the in-process benchmark (`test/bench/http.bench.ts`),
|
|
1043
|
+
against about 1.6 µs and 5.5 µs for the built-in transport.
|
|
1044
|
+
|
|
1045
|
+
### Driver conformance
|
|
1046
|
+
|
|
1047
|
+
`describeDriverConformance()` of `@znight/graphql/testing` describes the scenarios every driver
|
|
1048
|
+
passes (RFC 0009 §12), with the test framework's own functions (`bun:test`, Vitest, Jest,
|
|
1049
|
+
`node:test`) and `node:assert`: queries, mutations, a field guard reading a request-scoped
|
|
1050
|
+
provider, loaders batching, request-scoped providers per request, result mappers, masking,
|
|
1051
|
+
introspection off, limits, HTTP guards on the endpoint, the hook, `POST` bodies other than JSON
|
|
1052
|
+
(415, never executed) and mutations over `GET` (405), and over SSE (unless `subscriptions: false`)
|
|
1053
|
+
events, completion, client abort, `maxActive` and shutdown in both plugin orders.
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
import { describeDriverConformance } from '@znight/graphql/testing'
|
|
1057
|
+
import { yogaDriver } from '@znight/graphql/yoga'
|
|
1058
|
+
import { expressAdapter } from '@znight/http/express'
|
|
1059
|
+
import { honoAdapter } from '@znight/http/hono'
|
|
1060
|
+
import { afterAll, beforeAll, describe, it } from 'bun:test'
|
|
1061
|
+
|
|
1062
|
+
describeDriverConformance({
|
|
1063
|
+
driver: () => yogaDriver(), // a new driver per application; () => undefined for the built-in transport
|
|
1064
|
+
test: { describe, it, beforeAll, afterAll },
|
|
1065
|
+
adapters: { express: () => expressAdapter(), hono: () => honoAdapter() },
|
|
1066
|
+
})
|
|
1067
|
+
```
|
|
1068
|
+
|
|
853
1069
|
## Security defaults and limits
|
|
854
1070
|
|
|
855
1071
|
- **Introspection** off in production (`NODE_ENV === 'production'`), and suggestions hidden with it
|
|
@@ -1128,8 +1344,9 @@ Boot and execution are those of code-first.
|
|
|
1128
1344
|
## Observability
|
|
1129
1345
|
|
|
1130
1346
|
Every operation goes through the `graphql:operation` hook of `@znight/core` (`GraphQLOperationEvent`:
|
|
1131
|
-
`operationType`, `operationName`, `documentId` (SHA-256 of the query text), `transport`,
|
|
1132
|
-
`
|
|
1347
|
+
`operationType`, `operationName`, `documentId` (SHA-256 of the query text), `transport`, `driver`
|
|
1348
|
+
(`builtin`, or the name of the [driver](#drivers-and-the-engine)), `requestId`, `context`,
|
|
1349
|
+
`startTime`, then `outcome` (`success`, `partial`, `error`, `rejected`) and
|
|
1133
1350
|
`errors` with their codes). A subscription produces one event for its whole life. The event is built
|
|
1134
1351
|
only when the hook has wrappers or listeners, so it costs nothing otherwise.
|
|
1135
1352
|
|
|
@@ -1173,8 +1390,9 @@ on Node.js, use `node --conditions=@znight/source` instead of `bun`):
|
|
|
1173
1390
|
`GraphQLPlugin.execute()` over graphql-js `execute()` for a cached 50-field query (target: under
|
|
1174
1391
|
10 %; measured 0–9 % on Bun 1.4, 1–7 % on Node.js 24).
|
|
1175
1392
|
- `bun --conditions=@znight/source test/bench/http.bench.ts`: the endpoint through the `HttpPlugin`
|
|
1176
|
-
pipeline in process (`GET` and `POST`, both JSON media types), next to bare graphql-js
|
|
1177
|
-
controller route answering a constant
|
|
1393
|
+
pipeline in process (`GET` and `POST`, both JSON media types), next to bare graphql-js, to a
|
|
1394
|
+
controller route answering a constant, and to `GraphQLEngine.execute()` (what a driver calls: on
|
|
1395
|
+
a cached `{ hello }`, within 2 % of `plugin.execute()`). `ABORT_REASON=1` removes the cost graphql-js 17.0 adds to
|
|
1178
1396
|
every execution (a `DOMException` built when it aborts its own `AbortController`, about 3 µs on
|
|
1179
1397
|
Node.js): compare with other servers at the same graphql-js version.
|
|
1180
1398
|
|
|
@@ -1328,11 +1546,13 @@ are emitted by schema-first only: code-first sections, hashes and artifacts are
|
|
|
1328
1546
|
custom internal enum values (an enum value is its SDL name).
|
|
1329
1547
|
- **Subscriptions over SSE only.** No `graphql-ws` (WebSocket), no SSE single connection mode, no
|
|
1330
1548
|
multipart subscriptions.
|
|
1549
|
+
- **Drivers**: the built-in transport and GraphQL Yoga (`@znight/graphql/yoga`); Apollo Server once it
|
|
1550
|
+
accepts graphql-js 17. Yoga plugins that replace the execution (`useGraphQlJit`) are refused.
|
|
1331
1551
|
- No `@defer` / `@stream`, no persisted or trusted documents, no query cost analysis, no response
|
|
1332
1552
|
cache, no federation or schema stitching, no file uploads (use an HTTP upload route), no operation
|
|
1333
1553
|
batching.
|
|
1334
1554
|
- `MemoryPubSub` serves one process; a multi-instance `PubSub` is the application's (`pubsub`
|
|
1335
|
-
option).
|
|
1555
|
+
option, Redis recipe in the guide).
|
|
1336
1556
|
- Types must be written: the compiler reads annotations, it never infers return or property types.
|
|
1337
1557
|
- The SDL artifact and `schemaHash` see the `description` and `specifiedByURL` of a
|
|
1338
1558
|
`ScalarDefinition` written as literals only; a graphql-js `GraphQLScalarType` (`graphql-scalars`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Token } from "@znight/core";
|
|
2
|
-
import { GuardEnforcement, HttpContext, RouteMetaCarrier } from "@znight/http";
|
|
3
|
-
import { ConstValueNode, DocumentNode, GraphQLResolveInfo } from "graphql";
|
|
1
|
+
import { Container, CoreLogger, Token } from "@znight/core";
|
|
2
|
+
import { ByteSize, GuardEnforcement, HttpContext, HttpPlugin, PluginRoute, RouteMetaCarrier } from "@znight/http";
|
|
3
|
+
import { ConstValueNode, DocumentNode, ExecutionArgs, ExecutionResult, FormattedExecutionResult, GraphQLError, GraphQLFormattedError, GraphQLResolveInfo, GraphQLSchema, Source, ValidationRule } from "graphql";
|
|
4
4
|
/**
|
|
5
5
|
* GraphQL errors shown to clients (RFC 0005 §5.10, §8.8): `GraphQLException` and the
|
|
6
6
|
* `extensions.code` values the plugin produces (Apollo conventions, which clients already know).
|
|
@@ -70,8 +70,11 @@ declare class GraphQLException extends Error {
|
|
|
70
70
|
type GraphQLArgs = Readonly<Record<string, unknown>>;
|
|
71
71
|
/** Kind of a GraphQL operation. */
|
|
72
72
|
type GraphQLOperationKind = "query" | "mutation" | "subscription";
|
|
73
|
-
/**
|
|
74
|
-
|
|
73
|
+
/**
|
|
74
|
+
* How an operation reached the server: GraphQL over HTTP, GraphQL over SSE, GraphQL over WebSocket
|
|
75
|
+
* (`ws`, reserved for `graphql-ws`), or `plugin.execute()` / `plugin.subscribe()` (`direct`).
|
|
76
|
+
*/
|
|
77
|
+
type GraphQLTransport = "http" | "sse" | "ws" | "direct";
|
|
75
78
|
/**
|
|
76
79
|
* Options of a loader (§5.5), read once per loader class: the `options` property of a
|
|
77
80
|
* {@link BatchLoad} provider. They are DataLoader's, except `batchScheduleFn` (Znight batches per
|
|
@@ -160,6 +163,13 @@ interface GraphQLContext {
|
|
|
160
163
|
readonly signal: AbortSignal;
|
|
161
164
|
/** Per-operation storage. */
|
|
162
165
|
readonly state: Map<string | symbol, unknown>;
|
|
166
|
+
/**
|
|
167
|
+
* The context of the GraphQL server that received the operation through a driver (RFC 0009): the
|
|
168
|
+
* `contextValue` it passed to `GraphQLEngine.execute()` (Yoga's initial context, Apollo's context
|
|
169
|
+
* value). `undefined` for the built-in transport and direct calls. Resolvers do not need it: the
|
|
170
|
+
* request is `http`, the user a request-scoped provider.
|
|
171
|
+
*/
|
|
172
|
+
readonly serverContext: unknown;
|
|
163
173
|
/** The DataLoader of the current execution for a loader class (`ctx.loader(AuthorsById)`). */
|
|
164
174
|
loader<L extends BatchLoad<unknown, unknown>>(loader: BatchLoadClass<L>): BatchLoader<LoaderKey<L>, LoaderValue<L>>;
|
|
165
175
|
/**
|
|
@@ -262,5 +272,172 @@ interface ScalarDefinition<T> {
|
|
|
262
272
|
/** Literal of the document → internal value. Default: `parseValue` of the literal's JSON value. */
|
|
263
273
|
parseLiteral?(literal: ConstValueNode): T;
|
|
264
274
|
}
|
|
265
|
-
|
|
266
|
-
|
|
275
|
+
/** Options of {@link orderByKeys}. */
|
|
276
|
+
interface OrderByKeysOptions {
|
|
277
|
+
/**
|
|
278
|
+
* What a key without value gets: `'error'` (default) a `NOT_FOUND` `GraphQLException` failing that
|
|
279
|
+
* key only, `'null'` the value `null`.
|
|
280
|
+
*/
|
|
281
|
+
readonly missing?: "error" | "null";
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Reorders `values` (rows in any order, some keys possibly missing) to match `keys`, what a
|
|
285
|
+
* `BatchLoad.load()` must return. Keys are compared with `Map` semantics (`===`, `NaN` equal).
|
|
286
|
+
*
|
|
287
|
+
* ```ts
|
|
288
|
+
* async load(ids: readonly ID[]): Promise<readonly (Author | Error)[]> {
|
|
289
|
+
* return orderByKeys(ids, await this.authors.findMany(ids), author => author.id)
|
|
290
|
+
* }
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
declare function orderByKeys<K, V>(keys: readonly K[], values: Iterable<V>, keyOf: (value: V) => K, options?: OrderByKeysOptions & {
|
|
294
|
+
readonly missing?: "error";
|
|
295
|
+
}): (V | Error)[];
|
|
296
|
+
declare function orderByKeys<K, V>(keys: readonly K[], values: Iterable<V>, keyOf: (value: V) => K, options: OrderByKeysOptions & {
|
|
297
|
+
readonly missing: "null";
|
|
298
|
+
}): (V | null)[];
|
|
299
|
+
/** A GraphQL request: the body of a GraphQL-over-HTTP request, or the argument of `plugin.execute()`. */
|
|
300
|
+
interface GraphQLRequest {
|
|
301
|
+
readonly query: string;
|
|
302
|
+
readonly operationName?: string | null | undefined;
|
|
303
|
+
readonly variables?: Readonly<Record<string, unknown>> | null | undefined;
|
|
304
|
+
/** Request extensions (ignored by the executor; available to transports). */
|
|
305
|
+
readonly extensions?: Readonly<Record<string, unknown>> | null | undefined;
|
|
306
|
+
}
|
|
307
|
+
/** The results of a subscription (one per event), or the single result of a query sent as a stream. */
|
|
308
|
+
interface GraphQLResultStream extends AsyncIterableIterator<FormattedExecutionResult> {
|
|
309
|
+
/** Ends the stream: the source event stream is returned (unsubscribed). */
|
|
310
|
+
return(): Promise<IteratorResult<FormattedExecutionResult>>;
|
|
311
|
+
}
|
|
312
|
+
/** A value of type `T` or a promise of it (graphql-js `PromiseOrValue`). */
|
|
313
|
+
type PromiseOrValue<T> = T | Promise<T>;
|
|
314
|
+
/** Options of {@link GraphQLEngine.execute} and {@link GraphQLEngine.subscribe}. */
|
|
315
|
+
interface EngineExecuteOptions {
|
|
316
|
+
/**
|
|
317
|
+
* The protocol that carried the operation (`GraphQLOperationEvent.transport`,
|
|
318
|
+
* `GraphQLContext.transport`): `'http'` (default), `'sse'` for an event stream, `'ws'` for
|
|
319
|
+
* `graphql-ws`.
|
|
320
|
+
*/
|
|
321
|
+
readonly transport?: "http" | "sse" | "ws";
|
|
322
|
+
/**
|
|
323
|
+
* Cancels the operation (graphql-js `abortSignal`, `GraphQLContext.signal`). Default:
|
|
324
|
+
* `ExecutionArgs.abortSignal`, else the signal of the HTTP request of the current scope.
|
|
325
|
+
*/
|
|
326
|
+
readonly signal?: AbortSignal;
|
|
327
|
+
/** `GraphQLContext.requestId`. Default: the request id of the HTTP request of the scope. */
|
|
328
|
+
readonly requestId?: string;
|
|
329
|
+
}
|
|
330
|
+
/** The resolved options a GraphQL server needs to configure itself (read-only). */
|
|
331
|
+
interface GraphQLEngineSettings {
|
|
332
|
+
/** The endpoint path, the `HttpPlugin` prefix included (`/api/graphql`). */
|
|
333
|
+
readonly path: string;
|
|
334
|
+
readonly introspection: boolean;
|
|
335
|
+
/** The `graphiql` option: serve the server's IDE. */
|
|
336
|
+
readonly ide: boolean;
|
|
337
|
+
readonly exposeErrors: boolean;
|
|
338
|
+
/** `limits.maxTokens`, for servers that parse documents themselves. */
|
|
339
|
+
readonly maxTokens: number | undefined;
|
|
340
|
+
readonly subscriptions: false | {
|
|
341
|
+
readonly maxActive: number;
|
|
342
|
+
};
|
|
343
|
+
readonly http: false | {
|
|
344
|
+
readonly get: boolean;
|
|
345
|
+
readonly bodyLimit: ByteSize;
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* The engine of a bootstrapped `GraphQLPlugin` (`plugin.engine`, and the first argument of
|
|
350
|
+
* `GraphQLDriver.start()`). Its functions are bound: pass them around as they are.
|
|
351
|
+
*
|
|
352
|
+
* ```ts
|
|
353
|
+
* const handler = createHandler({
|
|
354
|
+
* schema: engine.schema,
|
|
355
|
+
* parse: engine.parse,
|
|
356
|
+
* validate: engine.validate,
|
|
357
|
+
* execute: args => engine.execute(args),
|
|
358
|
+
* })
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
interface GraphQLEngine {
|
|
362
|
+
/** The executable schema: its resolvers are Znight's pipelines. */
|
|
363
|
+
readonly schema: GraphQLSchema;
|
|
364
|
+
readonly settings: GraphQLEngineSettings;
|
|
365
|
+
/**
|
|
366
|
+
* graphql-js `parse` through the document cache: `maxTokens`, then the depth, aliases and
|
|
367
|
+
* directives limits. Throws a `GraphQLError` (`GRAPHQL_PARSE_FAILED`, `QUERY_TOO_COMPLEX`), like
|
|
368
|
+
* graphql-js. A text the engine validated returns the same `DocumentNode`.
|
|
369
|
+
*/
|
|
370
|
+
parse(source: string | Source): DocumentNode;
|
|
371
|
+
/**
|
|
372
|
+
* graphql-js `validate` with `rules` (default: graphql-js `specifiedRules`), to which the engine
|
|
373
|
+
* adds its own ({@link rules}), with `hideSuggestions` when introspection is off. With the
|
|
374
|
+
* default rules on {@link schema}, a document is validated once (then cached).
|
|
375
|
+
*/
|
|
376
|
+
validate(schema: GraphQLSchema, document: DocumentNode, rules?: readonly ValidationRule[]): readonly GraphQLError[];
|
|
377
|
+
/** The engine's own rules (limits, introspection), for servers that validate documents themselves. */
|
|
378
|
+
readonly rules: readonly ValidationRule[];
|
|
379
|
+
/**
|
|
380
|
+
* Executes a validated query or mutation in the current request scope (or a scope of its own),
|
|
381
|
+
* with the operation's `GraphQLContext` as `contextValue` (`args.contextValue` becomes
|
|
382
|
+
* `GraphQLContext.serverContext`). Errors are mapped and masked, as `GraphQLError` instances.
|
|
383
|
+
*/
|
|
384
|
+
execute(args: ExecutionArgs, options?: EngineExecuteOptions): PromiseOrValue<ExecutionResult>;
|
|
385
|
+
/**
|
|
386
|
+
* Starts a validated subscription: a stream of results, or a single error result (rejected, past
|
|
387
|
+
* `subscriptions.maxActive`, a source that could not start). The plugin ends the stream when the
|
|
388
|
+
* application stops.
|
|
389
|
+
*/
|
|
390
|
+
subscribe(args: ExecutionArgs, options?: EngineExecuteOptions): PromiseOrValue<AsyncGenerator<ExecutionResult, void, void> | ExecutionResult>;
|
|
391
|
+
}
|
|
392
|
+
/** What {@link GraphQLDriver.routes} receives. */
|
|
393
|
+
interface GraphQLDriverRoutesContext {
|
|
394
|
+
/** The `path` option: the endpoint path, relative to the `HttpPlugin` prefix (`/graphql`). */
|
|
395
|
+
readonly path: string;
|
|
396
|
+
/** The absolute endpoint path, the prefix included (`/api/graphql`). */
|
|
397
|
+
readonly endpoint: string;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* A route of a driver: a plugin route of `HttpPlugin` without `meta`, `guards` and `bodyLimit`,
|
|
401
|
+
* which `GraphQLPlugin` adds from its `http` option (`bodyLimit` on the routes that are not `GET`).
|
|
402
|
+
*/
|
|
403
|
+
type GraphQLDriverRoute = Pick<PluginRoute, "method" | "path" | "name" | "handler">;
|
|
404
|
+
/** What {@link GraphQLDriver.start} receives besides the engine. */
|
|
405
|
+
interface GraphQLDriverContext {
|
|
406
|
+
/** The application logger. */
|
|
407
|
+
readonly logger: CoreLogger;
|
|
408
|
+
readonly container: Container;
|
|
409
|
+
/** The `HttpPlugin` of the application, when registered (its adapter, for server-mounted drivers). */
|
|
410
|
+
readonly http: HttpPlugin | undefined;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* A GraphQL driver, given to `GraphQLPlugin.configure({ driver })`. A route-mounted driver returns
|
|
414
|
+
* its routes, which run in `HttpPlugin`'s pipeline (request scope, hooks, guards, CORS, drain) and
|
|
415
|
+
* call the engine from there; a server-mounted one has no routes and attaches itself to the
|
|
416
|
+
* adapter in `start()`. One driver serves one application at a time.
|
|
417
|
+
*/
|
|
418
|
+
interface GraphQLDriver {
|
|
419
|
+
/**
|
|
420
|
+
* The driver's name (`yoga`, `apollo`...): `GraphQLOperationEvent.driver`, logs and errors.
|
|
421
|
+
* `builtin` is reserved for the built-in transport.
|
|
422
|
+
*/
|
|
423
|
+
readonly name: string;
|
|
424
|
+
/**
|
|
425
|
+
* The routes `HttpPlugin` serves for the driver, with the `http` option's meta, guards and body
|
|
426
|
+
* limit applied by the plugin. Called once per application, possibly before {@link start}: the
|
|
427
|
+
* handlers MUST NOT use the engine before the first request. Not called with `http: false`.
|
|
428
|
+
*/
|
|
429
|
+
routes?(context: GraphQLDriverRoutesContext): readonly GraphQLDriverRoute[];
|
|
430
|
+
/**
|
|
431
|
+
* Called in `GraphQLPlugin.onBootstrap`, once the engine is built: create the server. A failure
|
|
432
|
+
* fails the boot (ZNIGHT066, the original error as `cause`).
|
|
433
|
+
*/
|
|
434
|
+
start(engine: GraphQLEngine, context: GraphQLDriverContext): void | Promise<void>;
|
|
435
|
+
/**
|
|
436
|
+
* Stops the server, after the engine ended its subscriptions: in `GraphQLPlugin.onDispose` for a
|
|
437
|
+
* route-mounted driver (once `HttpPlugin` drained its requests, which the driver keeps serving),
|
|
438
|
+
* in `GraphQLPlugin.onStop` for a server-mounted one (drain and stop the server).
|
|
439
|
+
*/
|
|
440
|
+
stop?(): void | Promise<void>;
|
|
441
|
+
}
|
|
442
|
+
export { ScalarDefinition as A, GraphQLResultMapperClass as C, LoaderOptions as D, LoaderKey as E, GraphQLErrorCodeValue as M, GraphQLException as N, LoaderValue as O, GraphQLExceptionOptions as P, GraphQLResultMapper as S, GraphQLTransport as T, GraphQLErrorMapper as _, EngineExecuteOptions as a, GraphQLGuardClass as b, GraphQLRequest as c, orderByKeys as d, BatchLoad as f, GraphQLContext as g, GraphQLArgs as h, GraphQLDriverRoutesContext as i, GraphQLErrorCode as j, ResolverContext as k, GraphQLResultStream as l, BatchLoader as m, GraphQLDriverContext as n, GraphQLEngine as o, BatchLoadClass as p, GraphQLDriverRoute as r, GraphQLEngineSettings as s, GraphQLDriver as t, OrderByKeysOptions as u, GraphQLErrorMapperClass as v, GraphQLResultTypes as w, GraphQLOperationKind as x, GraphQLGuard as y };
|
|
443
|
+
//# sourceMappingURL=driver-Cnze8Oyr.d.mts.map
|