nitro-graphql 1.6.1 → 1.7.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ecosystem/nuxt.mjs +3 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +23 -1
- package/dist/rollup.mjs +2 -2
- package/dist/routes/apollo-server-ws.d.mts +6 -0
- package/dist/routes/apollo-server-ws.mjs +298 -0
- package/dist/routes/apollo-server.d.mts +2 -2
- package/dist/routes/apollo-server.mjs +2 -2
- package/dist/routes/debug.d.mts +2 -2
- package/dist/routes/graphql-yoga-ws.d.mts +6 -0
- package/dist/routes/graphql-yoga-ws.mjs +298 -0
- package/dist/routes/graphql-yoga.d.mts +2 -2
- package/dist/routes/health.d.mts +2 -2
- package/dist/subscribe/index.d.mts +146 -0
- package/dist/subscribe/index.mjs +830 -0
- package/dist/templates/subscribe-client.mjs +59 -0
- package/dist/types/index.d.mts +16 -1
- package/dist/utils/apollo.d.mts +1 -1
- package/dist/utils/apollo.mjs +1 -1
- package/dist/utils/client-codegen.d.mts +16 -2
- package/dist/utils/client-codegen.mjs +410 -10
- package/dist/utils/define.d.mts +1 -1
- package/dist/utils/type-generation.mjs +8 -4
- package/dist/utils/ws-protocol.d.mts +25 -0
- package/dist/utils/ws-protocol.mjs +99 -0
- package/dist/utils/ws-schema.d.mts +6 -0
- package/dist/utils/ws-schema.mjs +58 -0
- package/package.json +14 -9
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { parse, subscribe, validate } from "graphql";
|
|
2
|
+
import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
|
|
3
|
+
import { importedConfig } from "#nitro-internal-virtual/graphql-config";
|
|
4
|
+
import { moduleConfig } from "#nitro-internal-virtual/module-config";
|
|
5
|
+
import { directives } from "#nitro-internal-virtual/server-directives";
|
|
6
|
+
import { resolvers } from "#nitro-internal-virtual/server-resolvers";
|
|
7
|
+
import { schemas } from "#nitro-internal-virtual/server-schemas";
|
|
8
|
+
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
9
|
+
import { defineWebSocketHandler } from "h3";
|
|
10
|
+
|
|
11
|
+
//#region src/routes/graphql-yoga-ws.ts
|
|
12
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
13
|
+
function devLog(message, ...args) {
|
|
14
|
+
if (isDev) console.log(message, ...args);
|
|
15
|
+
}
|
|
16
|
+
const DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER = 20;
|
|
17
|
+
const DEFAULT_PING_INTERVAL_MS = 15e3;
|
|
18
|
+
const DEFAULT_PONG_TIMEOUT_MS = 5e3;
|
|
19
|
+
function sendMessage(peer, message) {
|
|
20
|
+
peer.send(JSON.stringify(message));
|
|
21
|
+
}
|
|
22
|
+
function sendErrorMessage(peer, id, errors) {
|
|
23
|
+
sendMessage(peer, {
|
|
24
|
+
id,
|
|
25
|
+
type: "error",
|
|
26
|
+
payload: errors
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function sendNextMessage(peer, id, payload) {
|
|
30
|
+
sendMessage(peer, {
|
|
31
|
+
id,
|
|
32
|
+
type: "next",
|
|
33
|
+
payload
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function sendCompleteMessage(peer, id) {
|
|
37
|
+
sendMessage(peer, {
|
|
38
|
+
id,
|
|
39
|
+
type: "complete"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function handleConnectionInit(peer, payload) {
|
|
43
|
+
if (payload) peer.context.connectionParams = payload;
|
|
44
|
+
const onConnect = importedConfig?.websocket?.onConnect;
|
|
45
|
+
if (onConnect) try {
|
|
46
|
+
const result = await onConnect({
|
|
47
|
+
connectionParams: payload || {},
|
|
48
|
+
headers: Object.fromEntries(peer.request.headers.entries()),
|
|
49
|
+
peerId: peer.id,
|
|
50
|
+
remoteAddress: peer.remoteAddress
|
|
51
|
+
});
|
|
52
|
+
if (result === false) {
|
|
53
|
+
sendMessage(peer, {
|
|
54
|
+
type: "connection_error",
|
|
55
|
+
payload: { message: "Connection rejected by server" }
|
|
56
|
+
});
|
|
57
|
+
peer.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (result && typeof result === "object") peer.context.connectionContext = result;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
sendMessage(peer, {
|
|
63
|
+
type: "connection_error",
|
|
64
|
+
payload: { message: error instanceof Error ? error.message : "Connection validation failed" }
|
|
65
|
+
});
|
|
66
|
+
peer.close();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
sendMessage(peer, { type: "connection_ack" });
|
|
70
|
+
startKeepAlive(peer);
|
|
71
|
+
}
|
|
72
|
+
function handlePing(peer) {
|
|
73
|
+
sendMessage(peer, { type: "pong" });
|
|
74
|
+
}
|
|
75
|
+
function handlePong(peer) {
|
|
76
|
+
if (peer.context.pongTimeout) {
|
|
77
|
+
clearTimeout(peer.context.pongTimeout);
|
|
78
|
+
peer.context.pongTimeout = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function startKeepAlive(peer) {
|
|
82
|
+
stopKeepAlive(peer);
|
|
83
|
+
peer.context.pingInterval = setInterval(() => {
|
|
84
|
+
sendMessage(peer, { type: "ping" });
|
|
85
|
+
peer.context.pongTimeout = setTimeout(() => {
|
|
86
|
+
devLog("[GraphQL WS] Peer did not respond to ping, closing connection");
|
|
87
|
+
peer.close();
|
|
88
|
+
}, DEFAULT_PONG_TIMEOUT_MS);
|
|
89
|
+
}, DEFAULT_PING_INTERVAL_MS);
|
|
90
|
+
}
|
|
91
|
+
function stopKeepAlive(peer) {
|
|
92
|
+
if (peer.context.pingInterval) {
|
|
93
|
+
clearInterval(peer.context.pingInterval);
|
|
94
|
+
peer.context.pingInterval = null;
|
|
95
|
+
}
|
|
96
|
+
if (peer.context.pongTimeout) {
|
|
97
|
+
clearTimeout(peer.context.pongTimeout);
|
|
98
|
+
peer.context.pongTimeout = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function handleSubscribe(peer, msg, schema) {
|
|
102
|
+
if (!msg.id || !msg.payload) {
|
|
103
|
+
sendErrorMessage(peer, msg.id, [{ message: "Invalid subscribe message" }]);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const subscriptions = peer.context.subscriptions;
|
|
107
|
+
const connectionParams = peer.context.connectionParams;
|
|
108
|
+
const connectionContext = peer.context.connectionContext;
|
|
109
|
+
if (subscriptions.has(msg.id)) {
|
|
110
|
+
sendErrorMessage(peer, msg.id, [{ message: `Subscription with ID "${msg.id}" already exists` }]);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (subscriptions.size >= DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER) {
|
|
114
|
+
sendErrorMessage(peer, msg.id, [{ message: `Maximum subscriptions limit (${DEFAULT_MAX_SUBSCRIPTIONS_PER_PEER}) reached` }]);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const { query, variables, operationName } = msg.payload;
|
|
119
|
+
const document = typeof query === "string" ? parse(query) : query;
|
|
120
|
+
const validationErrors = validate(schema, document);
|
|
121
|
+
if (validationErrors.length > 0) {
|
|
122
|
+
sendErrorMessage(peer, msg.id, validationErrors.map((err) => ({
|
|
123
|
+
message: err.message,
|
|
124
|
+
locations: err.locations,
|
|
125
|
+
path: err.path
|
|
126
|
+
})));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const result = await subscribe({
|
|
130
|
+
schema,
|
|
131
|
+
document,
|
|
132
|
+
variableValues: variables,
|
|
133
|
+
operationName,
|
|
134
|
+
contextValue: {
|
|
135
|
+
connectionParams,
|
|
136
|
+
...connectionContext,
|
|
137
|
+
headers: Object.fromEntries(peer.request.headers.entries()),
|
|
138
|
+
authorization: peer.request.headers.get("authorization") || connectionParams?.authorization,
|
|
139
|
+
peerId: peer.id,
|
|
140
|
+
remoteAddress: peer.remoteAddress
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (Symbol.asyncIterator in result) {
|
|
144
|
+
const abortController = new AbortController();
|
|
145
|
+
const trackedSub = {
|
|
146
|
+
iterator: result,
|
|
147
|
+
abortController
|
|
148
|
+
};
|
|
149
|
+
subscriptions.set(msg.id, trackedSub);
|
|
150
|
+
const iterateSubscription = async () => {
|
|
151
|
+
const subscriptionId = msg.id;
|
|
152
|
+
const signal = abortController.signal;
|
|
153
|
+
try {
|
|
154
|
+
for await (const value of result) {
|
|
155
|
+
if (signal.aborted) break;
|
|
156
|
+
sendNextMessage(peer, subscriptionId, value);
|
|
157
|
+
}
|
|
158
|
+
if (!signal.aborted) sendCompleteMessage(peer, subscriptionId);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (signal.aborted) return;
|
|
161
|
+
console.error("[GraphQL WS] Subscription error:", error);
|
|
162
|
+
sendErrorMessage(peer, subscriptionId, [{ message: error instanceof Error ? error.message : "Subscription error" }]);
|
|
163
|
+
} finally {
|
|
164
|
+
subscriptions.delete(subscriptionId);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
iterateSubscription();
|
|
168
|
+
} else {
|
|
169
|
+
sendNextMessage(peer, msg.id, result);
|
|
170
|
+
sendCompleteMessage(peer, msg.id);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("[GraphQL WS] Operation error:", error);
|
|
174
|
+
sendErrorMessage(peer, msg.id, [{ message: error instanceof Error ? error.message : "Operation failed" }]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function handleComplete(peer, msg) {
|
|
178
|
+
if (!msg.id) return;
|
|
179
|
+
const subscriptions = peer.context.subscriptions;
|
|
180
|
+
const tracked = subscriptions.get(msg.id);
|
|
181
|
+
if (tracked) {
|
|
182
|
+
tracked.abortController.abort();
|
|
183
|
+
if (typeof tracked.iterator.return === "function") try {
|
|
184
|
+
await tracked.iterator.return();
|
|
185
|
+
} catch {}
|
|
186
|
+
subscriptions.delete(msg.id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function cleanupSubscriptions(peer, sendComplete = false) {
|
|
190
|
+
const subscriptions = peer.context.subscriptions;
|
|
191
|
+
if (!subscriptions) return;
|
|
192
|
+
for (const [id, tracked] of subscriptions.entries()) {
|
|
193
|
+
if (sendComplete) try {
|
|
194
|
+
sendCompleteMessage(peer, id);
|
|
195
|
+
} catch {}
|
|
196
|
+
tracked.abortController.abort();
|
|
197
|
+
if (typeof tracked.iterator.return === "function") try {
|
|
198
|
+
await tracked.iterator.return();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(`[GraphQL WS] Error cleaning up subscription ${id}:`, error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
subscriptions.clear();
|
|
204
|
+
}
|
|
205
|
+
let buildSubgraphSchema = null;
|
|
206
|
+
async function loadFederationSupport() {
|
|
207
|
+
if (buildSubgraphSchema !== null) return buildSubgraphSchema;
|
|
208
|
+
try {
|
|
209
|
+
buildSubgraphSchema = (await import("@apollo/subgraph")).buildSubgraphSchema;
|
|
210
|
+
} catch {
|
|
211
|
+
buildSubgraphSchema = false;
|
|
212
|
+
}
|
|
213
|
+
return buildSubgraphSchema;
|
|
214
|
+
}
|
|
215
|
+
async function createMergedSchema() {
|
|
216
|
+
const typeDefs = mergeTypeDefs([schemas.map((schema$1) => schema$1.def).join("\n\n")], {
|
|
217
|
+
throwOnConflict: true,
|
|
218
|
+
commentDescriptions: true,
|
|
219
|
+
sort: true
|
|
220
|
+
});
|
|
221
|
+
const mergedResolvers = mergeResolvers(resolvers.map((r) => r.resolver));
|
|
222
|
+
const federationEnabled = moduleConfig.federation?.enabled;
|
|
223
|
+
let schema;
|
|
224
|
+
if (federationEnabled) {
|
|
225
|
+
const buildSubgraph = await loadFederationSupport();
|
|
226
|
+
if (buildSubgraph) schema = buildSubgraph({
|
|
227
|
+
typeDefs: typeof typeDefs === "string" ? parse(typeDefs) : typeDefs,
|
|
228
|
+
resolvers: mergedResolvers
|
|
229
|
+
});
|
|
230
|
+
else {
|
|
231
|
+
console.warn("[GraphQL WS] Federation enabled but @apollo/subgraph not available");
|
|
232
|
+
schema = makeExecutableSchema({
|
|
233
|
+
typeDefs,
|
|
234
|
+
resolvers: mergedResolvers
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} else schema = makeExecutableSchema({
|
|
238
|
+
typeDefs,
|
|
239
|
+
resolvers: mergedResolvers
|
|
240
|
+
});
|
|
241
|
+
if (directives && directives.length > 0) {
|
|
242
|
+
for (const { directive } of directives) if (directive.transformer) schema = directive.transformer(schema);
|
|
243
|
+
}
|
|
244
|
+
return schema;
|
|
245
|
+
}
|
|
246
|
+
let schemaPromise = null;
|
|
247
|
+
async function getSchema() {
|
|
248
|
+
if (!schemaPromise) schemaPromise = createMergedSchema();
|
|
249
|
+
return schemaPromise;
|
|
250
|
+
}
|
|
251
|
+
var graphql_yoga_ws_default = defineWebSocketHandler({
|
|
252
|
+
async open(peer) {
|
|
253
|
+
devLog("[GraphQL WS] Client connected");
|
|
254
|
+
peer.context.subscriptions = /* @__PURE__ */ new Map();
|
|
255
|
+
},
|
|
256
|
+
async message(peer, message) {
|
|
257
|
+
try {
|
|
258
|
+
const data = message.text();
|
|
259
|
+
const msg = JSON.parse(data);
|
|
260
|
+
const currentSchema = await getSchema();
|
|
261
|
+
switch (msg.type) {
|
|
262
|
+
case "connection_init":
|
|
263
|
+
await handleConnectionInit(peer, msg.payload);
|
|
264
|
+
break;
|
|
265
|
+
case "ping":
|
|
266
|
+
handlePing(peer);
|
|
267
|
+
break;
|
|
268
|
+
case "pong":
|
|
269
|
+
handlePong(peer);
|
|
270
|
+
break;
|
|
271
|
+
case "subscribe":
|
|
272
|
+
await handleSubscribe(peer, msg, currentSchema);
|
|
273
|
+
break;
|
|
274
|
+
case "complete":
|
|
275
|
+
await handleComplete(peer, msg);
|
|
276
|
+
break;
|
|
277
|
+
default: devLog("[GraphQL WS] Unknown message type:", msg.type);
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error("[GraphQL WS] Message handling error:", error);
|
|
281
|
+
sendMessage(peer, {
|
|
282
|
+
type: "error",
|
|
283
|
+
payload: [{ message: "Invalid message format" }]
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
async close(peer, details) {
|
|
288
|
+
devLog("[GraphQL WS] Client disconnected:", details);
|
|
289
|
+
stopKeepAlive(peer);
|
|
290
|
+
await cleanupSubscriptions(peer, true);
|
|
291
|
+
},
|
|
292
|
+
async error(_peer, error) {
|
|
293
|
+
console.error("[GraphQL WS] WebSocket error:", error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
export { graphql_yoga_ws_default as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as h37 from "h3";
|
|
2
2
|
|
|
3
3
|
//#region src/routes/graphql-yoga.d.ts
|
|
4
|
-
declare const _default:
|
|
4
|
+
declare const _default: h37.EventHandler<h37.EventHandlerRequest, Promise<Response>>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { _default as default };
|
package/dist/routes/health.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as h31 from "h3";
|
|
2
2
|
|
|
3
3
|
//#region src/routes/health.d.ts
|
|
4
|
-
declare const _default:
|
|
4
|
+
declare const _default: h31.EventHandler<h31.EventHandlerRequest, Promise<{
|
|
5
5
|
status: string;
|
|
6
6
|
message: string;
|
|
7
7
|
timestamp: string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
//#region src/subscribe/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* GraphQL Subscription Client
|
|
4
|
+
* Framework-agnostic subscription client supporting WebSocket and SSE transports
|
|
5
|
+
*
|
|
6
|
+
* Uses native browser WebSocket API and EventSource for maximum compatibility.
|
|
7
|
+
*
|
|
8
|
+
* @example WebSocket (default)
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createSubscriptionClient } from 'nitro-graphql/subscribe'
|
|
11
|
+
*
|
|
12
|
+
* const client = createSubscriptionClient({ wsEndpoint: '/api/graphql/ws' })
|
|
13
|
+
*
|
|
14
|
+
* // Simple subscription
|
|
15
|
+
* client.subscribe(
|
|
16
|
+
* 'subscription { countdown(from: 10) }',
|
|
17
|
+
* {},
|
|
18
|
+
* (data) => console.log(data),
|
|
19
|
+
* (error) => console.error(error)
|
|
20
|
+
* )
|
|
21
|
+
*
|
|
22
|
+
* // Multiplexed session (multiple subscriptions, single connection)
|
|
23
|
+
* const session = client.createSession()
|
|
24
|
+
* session.subscribe(query1, vars1, onData1)
|
|
25
|
+
* session.subscribe(query2, vars2, onData2)
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example SSE Transport (same API, different transport)
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Use SSE when WebSocket is blocked (corporate firewalls, etc.)
|
|
31
|
+
* client.subscribe(
|
|
32
|
+
* 'subscription { countdown(from: 10) }',
|
|
33
|
+
* {},
|
|
34
|
+
* (data) => console.log(data),
|
|
35
|
+
* (error) => console.error(error),
|
|
36
|
+
* { transport: 'sse' }
|
|
37
|
+
* )
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Auto Transport (WebSocket first, SSE fallback)
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // Automatically fallback to SSE if WebSocket fails
|
|
43
|
+
* client.subscribe(
|
|
44
|
+
* 'subscription { countdown(from: 10) }',
|
|
45
|
+
* {},
|
|
46
|
+
* (data) => console.log(data),
|
|
47
|
+
* (error) => console.error(error),
|
|
48
|
+
* { transport: 'auto' }
|
|
49
|
+
* )
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @module nitro-graphql/subscribe
|
|
53
|
+
*/
|
|
54
|
+
type ConnectionState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'error';
|
|
55
|
+
/** Transport type for subscriptions */
|
|
56
|
+
type SubscriptionTransport = 'websocket' | 'sse' | 'auto';
|
|
57
|
+
/** Transport options for subscribe calls */
|
|
58
|
+
interface TransportOptions {
|
|
59
|
+
/** Transport type: 'websocket' (default), 'sse', or 'auto' (WS first, SSE fallback) */
|
|
60
|
+
transport?: SubscriptionTransport;
|
|
61
|
+
/** Shorthand for { transport: 'sse' } */
|
|
62
|
+
sse?: boolean;
|
|
63
|
+
}
|
|
64
|
+
interface SubscriptionOptions<TVariables = Record<string, unknown>> {
|
|
65
|
+
query: string;
|
|
66
|
+
variables?: TVariables;
|
|
67
|
+
onData?: (data: unknown) => void;
|
|
68
|
+
onError?: (error: Error) => void;
|
|
69
|
+
onConnected?: () => void;
|
|
70
|
+
onReconnected?: () => void;
|
|
71
|
+
onDisconnected?: () => void;
|
|
72
|
+
onRetrying?: (attempt: number, maxAttempts: number) => void;
|
|
73
|
+
onMaxRetriesReached?: () => void;
|
|
74
|
+
onStateChange?: (state: ConnectionState) => void;
|
|
75
|
+
maxRetries?: number;
|
|
76
|
+
connectionTimeoutMs?: number;
|
|
77
|
+
connectionParams?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
interface SubscriptionHandle {
|
|
80
|
+
unsubscribe: () => void;
|
|
81
|
+
readonly isConnected: boolean;
|
|
82
|
+
readonly state: ConnectionState;
|
|
83
|
+
readonly id: string;
|
|
84
|
+
/** The active transport type */
|
|
85
|
+
readonly transport: 'websocket' | 'sse';
|
|
86
|
+
}
|
|
87
|
+
type StateChangeCallback = (state: ConnectionState, subscriptionCount: number) => void;
|
|
88
|
+
interface SubscriptionSession {
|
|
89
|
+
subscribe: <TData = unknown, TVariables = Record<string, unknown>>(query: string, variables?: TVariables, onData?: (data: TData) => void, onError?: (error: Error) => void) => SubscriptionHandle;
|
|
90
|
+
readonly state: ConnectionState;
|
|
91
|
+
readonly isConnected: boolean;
|
|
92
|
+
readonly subscriptionCount: number;
|
|
93
|
+
close: () => void;
|
|
94
|
+
onStateChange: (callback: StateChangeCallback) => () => void;
|
|
95
|
+
}
|
|
96
|
+
interface SubscriptionClientConfig {
|
|
97
|
+
/** WebSocket endpoint (default: '/api/graphql/ws') */
|
|
98
|
+
wsEndpoint?: string;
|
|
99
|
+
/** SSE endpoint for SSE transport (default: '/api/graphql') */
|
|
100
|
+
sseEndpoint?: string;
|
|
101
|
+
/** Connection parameters for WebSocket handshake */
|
|
102
|
+
connectionParams?: Record<string, unknown> | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
103
|
+
/** Connection timeout in ms (default: 10000) */
|
|
104
|
+
connectionTimeoutMs?: number;
|
|
105
|
+
/** Maximum retry attempts (default: 5) */
|
|
106
|
+
maxRetries?: number;
|
|
107
|
+
}
|
|
108
|
+
interface SubscriptionClient {
|
|
109
|
+
subscribe: <TData = unknown, TVariables = Record<string, unknown>>(query: string, variables?: TVariables, onData?: (data: TData) => void, onError?: (error: Error) => void, transportOptions?: TransportOptions) => SubscriptionHandle;
|
|
110
|
+
subscribeAsync: <_TData = unknown, TVariables = Record<string, unknown>>(options: SubscriptionOptions<TVariables>, transportOptions?: TransportOptions) => Promise<SubscriptionHandle>;
|
|
111
|
+
createSession: () => SubscriptionSession;
|
|
112
|
+
}
|
|
113
|
+
declare function createSubscriptionClient(config?: SubscriptionClientConfig): SubscriptionClient;
|
|
114
|
+
interface SseSubscriptionOptions<TVariables = Record<string, unknown>> {
|
|
115
|
+
query: string;
|
|
116
|
+
variables?: TVariables;
|
|
117
|
+
onData?: (data: unknown) => void;
|
|
118
|
+
onError?: (error: Error) => void;
|
|
119
|
+
onConnected?: () => void;
|
|
120
|
+
onReconnected?: () => void;
|
|
121
|
+
onDisconnected?: () => void;
|
|
122
|
+
onStateChange?: (state: ConnectionState) => void;
|
|
123
|
+
}
|
|
124
|
+
interface SseSubscriptionHandle {
|
|
125
|
+
close: () => void;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create an SSE subscription using native EventSource (legacy API)
|
|
129
|
+
* Use this when WebSocket is blocked (corporate firewalls, etc.)
|
|
130
|
+
*
|
|
131
|
+
* @deprecated Use the unified client with { transport: 'sse' } instead
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const handle = createSseSubscription('/api/graphql', {
|
|
136
|
+
* query: 'subscription { countdown(from: 10) }',
|
|
137
|
+
* onData: (data) => console.log(data),
|
|
138
|
+
* })
|
|
139
|
+
*
|
|
140
|
+
* // Later...
|
|
141
|
+
* handle.close()
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
declare function createSseSubscription<TData = unknown, TVariables = Record<string, unknown>>(endpoint: string, options: SseSubscriptionOptions<TVariables>): SseSubscriptionHandle;
|
|
145
|
+
//#endregion
|
|
146
|
+
export { ConnectionState, SseSubscriptionHandle, SseSubscriptionOptions, StateChangeCallback, SubscriptionClient, SubscriptionClientConfig, SubscriptionHandle, SubscriptionOptions, SubscriptionSession, SubscriptionTransport, TransportOptions, createSseSubscription, createSubscriptionClient };
|