@nifrajs/graphql 3.2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../src/pubsub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wGAAwG;AACxG,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,OAAO;IAC5C,wDAAwD;IACxD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,IAAI,CAAA;IAC5C,mGAAmG;IACnG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAA;CACvD;AAED,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,KAAK,GAAG,OAAO,KAAK,aAAa,CAAC,KAAK,CAAC,CAyBpE"}
package/dist/pubsub.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * A trivial in-memory pub/sub for `subscription` resolvers - the reference implementation of the
3
+ * subscription source seam. A resolver's `subscribe` returns `pubsub.subscribe(topic)` (an async
4
+ * iterator); anything in the process calls `pubsub.publish(topic, payload)` to push an event.
5
+ *
6
+ * This is deliberately in-process and unbounded-fan-in only within one server instance - the same
7
+ * single-instance contract as core's `TopicRegistry`. A durable, multi-instance, or tenant-scoped
8
+ * pub/sub (Redis, a Durable Object, NATS) is an operated concern: implement the same
9
+ * `{ publish, subscribe }` shape over your bus and pass it in. Keeping only the in-memory impl here is
10
+ * the guardrail - a durable payload-carrying sink does not belong in a public package.
11
+ */
12
+ /** Create an in-memory {@link GraphqlPubSub}. */
13
+ export function createPubSub() {
14
+ const topics = new Map();
15
+ const publish = (topic, payload) => {
16
+ const sinks = topics.get(topic);
17
+ if (sinks === undefined)
18
+ return;
19
+ for (const sink of sinks)
20
+ sink.push(payload);
21
+ };
22
+ const subscribe = (topic) => {
23
+ let sinks = topics.get(topic);
24
+ if (sinks === undefined) {
25
+ sinks = new Set();
26
+ topics.set(topic, sinks);
27
+ }
28
+ const set = sinks;
29
+ const sink = createSink(() => {
30
+ set.delete(sink);
31
+ if (set.size === 0)
32
+ topics.delete(topic);
33
+ });
34
+ set.add(sink);
35
+ return sink.iterator;
36
+ };
37
+ return { publish, subscribe };
38
+ }
39
+ function createSink(onClose) {
40
+ const queue = [];
41
+ const pulls = [];
42
+ let done = false;
43
+ const push = (payload) => {
44
+ if (done)
45
+ return;
46
+ const pull = pulls.shift();
47
+ if (pull !== undefined) {
48
+ pull({ value: payload, done: false });
49
+ return;
50
+ }
51
+ queue.push(payload);
52
+ };
53
+ const finish = () => {
54
+ if (!done) {
55
+ done = true;
56
+ onClose();
57
+ for (const pull of pulls.splice(0))
58
+ pull({ value: undefined, done: true });
59
+ }
60
+ return { value: undefined, done: true };
61
+ };
62
+ const iterator = {
63
+ next() {
64
+ if (queue.length > 0)
65
+ return Promise.resolve({ value: queue.shift(), done: false });
66
+ if (done)
67
+ return Promise.resolve({ value: undefined, done: true });
68
+ return new Promise((resolve) => pulls.push(resolve));
69
+ },
70
+ return() {
71
+ return Promise.resolve(finish());
72
+ },
73
+ throw(err) {
74
+ finish();
75
+ return Promise.reject(err);
76
+ },
77
+ [Symbol.asyncIterator]() {
78
+ return this;
79
+ },
80
+ };
81
+ return { push, iterator };
82
+ }
83
+ //# sourceMappingURL=pubsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../src/pubsub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,iDAAiD;AACjD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAA;IAElD,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,OAAc,EAAQ,EAAE;QACtD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAM;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9C,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,KAAa,EAAgC,EAAE;QAChE,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,GAAG,IAAI,GAAG,EAAE,CAAA;YACjB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC1B,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAA;QACjB,MAAM,IAAI,GAAG,UAAU,CAAQ,GAAG,EAAE;YAClC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAChB,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;QACF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACb,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC,CAAA;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AAC/B,CAAC;AAQD,SAAS,UAAU,CAAQ,OAAmB;IAC5C,MAAM,KAAK,GAAY,EAAE,CAAA;IACzB,MAAM,KAAK,GAAmD,EAAE,CAAA;IAChE,IAAI,IAAI,GAAG,KAAK,CAAA;IAEhB,MAAM,IAAI,GAAG,CAAC,OAAc,EAAQ,EAAE;QACpC,IAAI,IAAI;YAAE,OAAM;QAChB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YACrC,OAAM;QACR,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACrB,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,GAA0B,EAAE;QACzC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,IAAI,CAAA;YACX,OAAO,EAAE,CAAA;YACT,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5E,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACzC,CAAC,CAAA;IAED,MAAM,QAAQ,GAAiC;QAC7C,IAAI;YACF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;YAC5F,IAAI,IAAI;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAClE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QACtD,CAAC;QACD,MAAM;YACJ,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QAClC,CAAC;QACD,KAAK,CAAC,GAAa;YACjB,MAAM,EAAE,CAAA;YACR,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5B,CAAC;QACD,CAAC,MAAM,CAAC,aAAa,CAAC;YACpB,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAC3B,CAAC"}
package/dist/ws.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GraphQL subscriptions over nifra's native WebSocket lane, speaking the `graphql-transport-ws`
3
+ * protocol (the modern `graphql-ws` wire protocol). This adapts `graphql-ws`'s transport-agnostic
4
+ * server (`makeServer`) onto a nifra `app.ws()` route, so subscription operations run through the same
5
+ * socket runtime as every other nifra WebSocket - no separate socket server, no second upgrade path.
6
+ *
7
+ * `graphql-ws` owns the per-operation async-iterator lifecycle (one `subscribe` iterator per client
8
+ * `Subscribe` message, drained to that client's socket). The event *source* those iterators pull from
9
+ * is yours - typically a {@link ./pubsub.ts | GraphqlPubSub}, whose in-memory reference impl mirrors
10
+ * core's `TopicRegistry` and can be swapped for a durable bus without touching this transport.
11
+ *
12
+ * `graphql-ws` is an optional peer dependency: import this module only when you need subscriptions, and
13
+ * the dependency (and its bundle weight) stays off every app that doesn't.
14
+ */
15
+ import type { WebSocketHandler } from "@nifrajs/core/ws";
16
+ import { type GraphQLSchema } from "graphql";
17
+ import { type ConnectionInitMessage, type ServerOptions } from "graphql-ws";
18
+ import { type GraphqlContextBuilder } from "./context.js";
19
+ /** Per-connection state carried on `ws.data` for the graphql-ws bridge. */
20
+ interface GraphqlWsConnection {
21
+ readonly request: Request;
22
+ /** Feed one raw inbound frame into graphql-ws (registered via its `onMessage`). */
23
+ dispatch?: (message: string) => void | Promise<void>;
24
+ /** Tear the graphql-ws connection down (graphql-ws's `opened()` return value). */
25
+ closed?: (code: number, reason: string) => void | Promise<void>;
26
+ }
27
+ export interface GraphqlWsOptions<Context extends Record<string, unknown> = Record<string, unknown>> {
28
+ /** The executable schema. */
29
+ readonly schema: GraphQLSchema;
30
+ /** Build the resolver `contextValue` from the upgrade request. Shared with the HTTP transport. */
31
+ readonly context?: GraphqlContextBuilder<Context>;
32
+ /**
33
+ * Called once per connection with the client's `connection_init` payload; return `false` (or throw)
34
+ * to reject the connection. The seam for authenticating a socket from an init token.
35
+ */
36
+ readonly onConnect?: (payload: ConnectionInitMessage["payload"], request: Request) => boolean | Promise<boolean>;
37
+ /** Additional raw `graphql-ws` server options merged last (advanced escape hatch). */
38
+ readonly serverOptions?: Partial<ServerOptions>;
39
+ }
40
+ /**
41
+ * Build a nifra {@link WebSocketHandler} that terminates the `graphql-transport-ws` protocol. Register
42
+ * it on a route enabled with `.use(websocket())`:
43
+ *
44
+ * ```ts
45
+ * import { websocket } from "@nifrajs/core/ws"
46
+ * import { graphqlWebSocket } from "@nifrajs/graphql/ws"
47
+ *
48
+ * const app = server().use(websocket()).ws("/graphql", graphqlWebSocket({ schema, context }))
49
+ * ```
50
+ *
51
+ * The route MUST negotiate the `graphql-transport-ws` subprotocol at the WebSocket layer for a browser
52
+ * `graphql-ws` client to connect.
53
+ */
54
+ export declare function graphqlWebSocket<Context extends Record<string, unknown> = Record<string, unknown>>(options: GraphqlWsOptions<Context>): WebSocketHandler<GraphqlWsConnection>;
55
+ export {};
56
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAoC,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EAAW,KAAK,aAAa,EAAa,MAAM,SAAS,CAAA;AAChE,OAAO,EACL,KAAK,qBAAqB,EAI1B,KAAK,aAAa,EACnB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAgB,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEvE,2EAA2E;AAC3E,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,kFAAkF;IAClF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChE;AAED,MAAM,WAAW,gBAAgB,CAC/B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjE,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IACjD;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CACnB,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC,EACzC,OAAO,EAAE,OAAO,KACb,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;CAChD;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChG,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,GACjC,gBAAgB,CAAC,mBAAmB,CAAC,CA4DvC"}
package/dist/ws.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * GraphQL subscriptions over nifra's native WebSocket lane, speaking the `graphql-transport-ws`
3
+ * protocol (the modern `graphql-ws` wire protocol). This adapts `graphql-ws`'s transport-agnostic
4
+ * server (`makeServer`) onto a nifra `app.ws()` route, so subscription operations run through the same
5
+ * socket runtime as every other nifra WebSocket - no separate socket server, no second upgrade path.
6
+ *
7
+ * `graphql-ws` owns the per-operation async-iterator lifecycle (one `subscribe` iterator per client
8
+ * `Subscribe` message, drained to that client's socket). The event *source* those iterators pull from
9
+ * is yours - typically a {@link ./pubsub.ts | GraphqlPubSub}, whose in-memory reference impl mirrors
10
+ * core's `TopicRegistry` and can be swapped for a durable bus without touching this transport.
11
+ *
12
+ * `graphql-ws` is an optional peer dependency: import this module only when you need subscriptions, and
13
+ * the dependency (and its bundle weight) stays off every app that doesn't.
14
+ */
15
+ import { execute, subscribe } from "graphql";
16
+ import { GRAPHQL_TRANSPORT_WS_PROTOCOL, makeServer, } from "graphql-ws";
17
+ import { buildContext } from "./context.js";
18
+ /** A frame is a string for JSON control messages; graphql-ws only speaks text frames. */
19
+ function asText(data) {
20
+ return typeof data === "string" ? data : new TextDecoder().decode(data);
21
+ }
22
+ /**
23
+ * Build a nifra {@link WebSocketHandler} that terminates the `graphql-transport-ws` protocol. Register
24
+ * it on a route enabled with `.use(websocket())`:
25
+ *
26
+ * ```ts
27
+ * import { websocket } from "@nifrajs/core/ws"
28
+ * import { graphqlWebSocket } from "@nifrajs/graphql/ws"
29
+ *
30
+ * const app = server().use(websocket()).ws("/graphql", graphqlWebSocket({ schema, context }))
31
+ * ```
32
+ *
33
+ * The route MUST negotiate the `graphql-transport-ws` subprotocol at the WebSocket layer for a browser
34
+ * `graphql-ws` client to connect.
35
+ */
36
+ export function graphqlWebSocket(options) {
37
+ const gqlServer = makeServer({
38
+ schema: options.schema,
39
+ execute,
40
+ subscribe,
41
+ onConnect: options.onConnect
42
+ ? async (ctx) => {
43
+ return await options.onConnect?.(ctx.connectionParams, ctx.extra.request);
44
+ }
45
+ : undefined,
46
+ context: options.context
47
+ ? async (ctx) => await buildContext(options.context, { request: ctx.extra.request })
48
+ : undefined,
49
+ ...options.serverOptions,
50
+ });
51
+ return {
52
+ upgrade(c) {
53
+ return { request: c.req };
54
+ },
55
+ open(ws) {
56
+ const protocol = ws.raw?.protocol ?? GRAPHQL_TRANSPORT_WS_PROTOCOL;
57
+ let onFrame;
58
+ const closed = gqlServer.opened({
59
+ protocol,
60
+ send: async (data) => {
61
+ ws.send(data);
62
+ },
63
+ close: (code, reason) => {
64
+ ws.close(code, reason);
65
+ },
66
+ onMessage: (cb) => {
67
+ onFrame = cb;
68
+ },
69
+ }, { request: ws.data.request });
70
+ ws.data.closed = closed;
71
+ ws.data.dispatch = (message) => onFrame?.(message);
72
+ },
73
+ message(ws, data) {
74
+ // Fire-and-forget: graphql-ws's per-message handler does not resolve until a subscription's
75
+ // iterator completes (`await for await`), so awaiting it here would block every later frame on
76
+ // this socket - including the client's `Complete` that stops the subscription. graphql-ws drives
77
+ // its own sends and error frames from inside that loop; we only feed it inbound frames.
78
+ void ws.data.dispatch?.(asText(data));
79
+ },
80
+ close(ws, code, reason) {
81
+ void ws.data.closed?.(code, reason);
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=ws.js.map
package/dist/ws.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../src/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,OAAO,EAAsB,SAAS,EAAE,MAAM,SAAS,CAAA;AAChE,OAAO,EAEL,6BAA6B,EAE7B,UAAU,GAEX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAA8B,MAAM,cAAc,CAAA;AA8BvE,yFAAyF;AACzF,SAAS,MAAM,CAAC,IAAyB;IACvC,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AACzE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAkC;IAElC,MAAM,SAAS,GAAG,UAAU,CAAgD;QAC1E,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO;QACP,SAAS;QACT,SAAS,EAAE,OAAO,CAAC,SAAS;YAC1B,CAAC,CAAC,KAAK,EAAE,GAAoE,EAAE,EAAE;gBAC7E,OAAO,MAAM,OAAO,CAAC,SAAS,EAAE,CAC9B,GAAG,CAAC,gBAAoD,EACxD,GAAG,CAAC,KAAK,CAAC,OAAO,CAClB,CAAA;YACH,CAAC;YACH,CAAC,CAAC,SAAS;QACb,OAAO,EAAE,OAAO,CAAC,OAAO;YACtB,CAAC,CAAC,KAAK,EAAE,GAAoE,EAAE,EAAE,CAC7E,MAAM,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACvE,CAAC,CAAC,SAAS;QACb,GAAG,OAAO,CAAC,aAAa;KACzB,CAAC,CAAA;IAEF,OAAO;QACL,OAAO,CAAC,CAAmB;YACzB,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;QAC3B,CAAC;QAED,IAAI,CAAC,EAAuC;YAC1C,MAAM,QAAQ,GACX,EAAE,CAAC,GAAyC,EAAE,QAAQ,IAAI,6BAA6B,CAAA;YAC1F,IAAI,OAAgE,CAAA;YACpE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAC7B;gBACE,QAAQ;gBACR,IAAI,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;oBAC3B,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACf,CAAC;gBACD,KAAK,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;oBACtC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;gBACxB,CAAC;gBACD,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;oBAChB,OAAO,GAAG,EAAE,CAAA;gBACd,CAAC;aACF,EACD,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAC7B,CAAA;YACD,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;YACvB,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAA;QAC5D,CAAC;QAED,OAAO,CAAC,EAAuC,EAAE,IAAyB;YACxE,4FAA4F;YAC5F,+FAA+F;YAC/F,iGAAiG;YACjG,wFAAwF;YACxF,KAAK,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,CAAC,EAAuC,EAAE,IAAY,EAAE,MAAc;YACzE,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACrC,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@nifrajs/graphql",
3
+ "version": "3.2.0",
4
+ "description": "Mount a GraphQL endpoint on a nifra app - a spec-compliant GraphQL-over-HTTP handler you mount at POST /graphql, graphql-ws subscriptions over nifra's native WebSocket lane, and typed nifra context piped straight into your resolvers. Uses graphql's own execute/subscribe; no server framework re-bundled.",
5
+ "keywords": [
6
+ "bun",
7
+ "typescript",
8
+ "nifra",
9
+ "graphql",
10
+ "graphql-http",
11
+ "graphql-ws",
12
+ "subscriptions",
13
+ "resolvers",
14
+ "api"
15
+ ],
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "bun": "./src/index.ts",
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./http": {
27
+ "bun": "./src/http.ts",
28
+ "types": "./dist/http.d.ts",
29
+ "default": "./dist/http.js"
30
+ },
31
+ "./ws": {
32
+ "bun": "./src/ws.ts",
33
+ "types": "./dist/ws.d.ts",
34
+ "default": "./dist/ws.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "src"
40
+ ],
41
+ "sideEffects": false,
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
44
+ },
45
+ "peerDependencies": {
46
+ "@nifrajs/core": "^3.2.0",
47
+ "graphql": "^17.0.0",
48
+ "graphql-ws": "^6.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@nifrajs/core": {
52
+ "optional": true
53
+ },
54
+ "graphql-ws": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@nifrajs/core": "workspace:*",
60
+ "graphql": "17.0.2",
61
+ "graphql-ws": "6.2.1"
62
+ },
63
+ "license": "MIT",
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/nifrajs/nifra.git",
70
+ "directory": "packages/graphql"
71
+ },
72
+ "bugs": {
73
+ "url": "https://github.com/nifrajs/nifra/issues"
74
+ },
75
+ "homepage": "https://github.com/nifrajs/nifra#readme"
76
+ }
package/src/context.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The context-injection seam - the one thing a hand-rolled `app.all('/graphql', yoga.fetch)` mount
3
+ * cannot do. A GraphQL executor builds an opaque `contextValue` from the raw `Request`; it never sees
4
+ * the nifra request context (auth subject, params, env, `waitUntil`). This contract lets a resolver's
5
+ * `contextValue` be derived from whatever the surrounding nifra handler already resolved.
6
+ *
7
+ * The builder is called once per operation (per HTTP request, or once per WS `Subscribe` message),
8
+ * before `execute`/`subscribe`. It receives the raw `Request` and, on the HTTP path, the nifra route
9
+ * context `c` when the endpoint is mounted with `mountGraphql` (which has `c` in scope); on the WS path
10
+ * it receives the upgrade request. Return the object your resolvers read as their third argument.
11
+ *
12
+ * MOAT NOTE: this seam only *passes through* context the host already owns. It never persists it. A
13
+ * tenant-aware or durable resolver-context store is an operated concern and lives outside this package.
14
+ */
15
+
16
+ /** What the builder is handed. `request` is always present; `nifra` is the route context when known. */
17
+ export interface GraphqlContextInput<Env = unknown> {
18
+ readonly request: Request
19
+ /**
20
+ * The nifra route context (`c`) when the endpoint was mounted via `mountGraphql`, otherwise
21
+ * `undefined` (e.g. `respondGraphql` called standalone, or the WS transport before a route context
22
+ * exists). Typed loosely on purpose so this package never imports core's context type at the value
23
+ * level - keeping the peer dependency truly optional.
24
+ */
25
+ readonly nifra?: NifraContextLike<Env>
26
+ }
27
+
28
+ /**
29
+ * The subset of a nifra route context this package surfaces to a resolver-context builder. Structural,
30
+ * so the real `c` satisfies it without a nominal import. Kept to the non-body accessors a resolver
31
+ * legitimately needs; the raw body belongs to the GraphQL executor, not the builder.
32
+ */
33
+ export interface NifraContextLike<Env = unknown> {
34
+ readonly req: Request
35
+ readonly params: Record<string, string>
36
+ readonly env: Env
37
+ readonly signal: AbortSignal
38
+ readonly waitUntil: (promise: Promise<unknown>) => void
39
+ }
40
+
41
+ /**
42
+ * Build the resolver `contextValue` for one operation. Sync or async; a throw is reported as a GraphQL
43
+ * error envelope rather than crashing the transport. Omit it and resolvers get `contextValue: {}`.
44
+ */
45
+ export type GraphqlContextBuilder<Context = unknown, Env = unknown> = (
46
+ input: GraphqlContextInput<Env>,
47
+ ) => Context | Promise<Context>
48
+
49
+ /** Resolve a context builder to a concrete value, defaulting to an empty object. */
50
+ export async function buildContext<Context, Env>(
51
+ builder: GraphqlContextBuilder<Context, Env> | undefined,
52
+ input: GraphqlContextInput<Env>,
53
+ ): Promise<Context> {
54
+ if (builder === undefined) return {} as Context
55
+ return await builder(input)
56
+ }