@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.
- package/README.md +119 -0
- package/dist/context.d.ts +45 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +21 -0
- package/dist/context.js.map +1 -0
- package/dist/http.d.ts +53 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +221 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/pubsub.d.ts +21 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/pubsub.js +83 -0
- package/dist/pubsub.js.map +1 -0
- package/dist/ws.d.ts +56 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +85 -0
- package/dist/ws.js.map +1 -0
- package/package.json +76 -0
- package/src/context.ts +56 -0
- package/src/http.ts +326 -0
- package/src/index.ts +100 -0
- package/src/pubsub.ts +98 -0
- package/src/ws.ts +136 -0
|
@@ -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":"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"}
|
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
|
package/dist/ws.d.ts.map
ADDED
|
@@ -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
|
+
}
|