stitchkit 0.60.1 → 0.61.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.
Files changed (39) hide show
  1. package/dist/application/kernel.d.ts +23 -0
  2. package/dist/application/kernel.d.ts.map +1 -1
  3. package/dist/application/server-resource.d.ts.map +1 -1
  4. package/dist/application.d.ts +1 -1
  5. package/dist/application.d.ts.map +1 -1
  6. package/dist/application.js +11 -6
  7. package/dist/browser/socket-io.d.ts +47 -0
  8. package/dist/browser/socket-io.d.ts.map +1 -1
  9. package/dist/browser/stream.d.ts +23 -0
  10. package/dist/browser/stream.d.ts.map +1 -1
  11. package/dist/contract/index.js +1 -1
  12. package/dist/{index-t8xrqc9g.js → index-413xk7ga.js} +1 -1
  13. package/dist/{index-82e74yfx.js → index-eabpd4tb.js} +56 -7
  14. package/dist/{index-2cgbdckv.js → index-s1tywej8.js} +88 -19
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +213 -17
  18. package/dist/internal/optional-peer.d.ts +14 -0
  19. package/dist/internal/optional-peer.d.ts.map +1 -0
  20. package/dist/node.js +1 -1
  21. package/dist/realtime/contract.d.ts +7 -1
  22. package/dist/realtime/contract.d.ts.map +1 -1
  23. package/dist/realtime/index.d.ts +2 -1
  24. package/dist/realtime/index.d.ts.map +1 -1
  25. package/dist/realtime/rejected-frame.d.ts +86 -0
  26. package/dist/realtime/rejected-frame.d.ts.map +1 -0
  27. package/dist/realtime/request.d.ts +24 -0
  28. package/dist/realtime/request.d.ts.map +1 -1
  29. package/dist/realtime/socket.d.ts.map +1 -1
  30. package/dist/server/index.d.ts +1 -0
  31. package/dist/server/index.d.ts.map +1 -1
  32. package/dist/server/index.js +196 -3
  33. package/dist/server/socket-io.d.ts.map +1 -1
  34. package/dist/server/stream.d.ts.map +1 -1
  35. package/dist/server/streaming-route.d.ts +130 -0
  36. package/dist/server/streaming-route.d.ts.map +1 -0
  37. package/dist/testing.js +1 -1
  38. package/llms-full.txt +364 -23
  39. package/package.json +2 -2
@@ -7,7 +7,7 @@ import {
7
7
  parseMultipart,
8
8
  socketIoLane,
9
9
  webSocketLane
10
- } from "../index-2cgbdckv.js";
10
+ } from "../index-s1tywej8.js";
11
11
  import {
12
12
  composeAuthHooks,
13
13
  createAuthHook,
@@ -973,9 +973,11 @@ function streamSSE(generator) {
973
973
  controller.close();
974
974
  }
975
975
  },
976
- async cancel() {
976
+ cancel() {
977
977
  cancelled = true;
978
- await generator.return(undefined);
978
+ generator.return(undefined).catch(() => {
979
+ return;
980
+ });
979
981
  }
980
982
  });
981
983
  return new Response(stream, {
@@ -1019,10 +1021,198 @@ async function* parseSSE(response, options) {
1019
1021
  reader.releaseLock();
1020
1022
  }
1021
1023
  }
1024
+ // src/server/streaming-route.ts
1025
+ var DEFAULT_STREAM_HEARTBEAT_MS = 5000;
1026
+ var FRAMINGS = {
1027
+ ndjson: {
1028
+ contentType: "application/x-ndjson",
1029
+ keepAlive: `
1030
+ `,
1031
+ frame: (value) => `${JSON.stringify(value)}
1032
+ `
1033
+ },
1034
+ sse: {
1035
+ contentType: "text/event-stream",
1036
+ keepAlive: `: keep-alive
1037
+
1038
+ `,
1039
+ frame: (value) => `data: ${JSON.stringify(value)}
1040
+
1041
+ `,
1042
+ done: `data: [DONE]
1043
+
1044
+ `
1045
+ }
1046
+ };
1047
+ function applyIdleTimeout(server, request, seconds) {
1048
+ if (typeof server !== "object" || server === null)
1049
+ return;
1050
+ const timeout = Reflect.get(server, "timeout");
1051
+ if (typeof timeout !== "function")
1052
+ return;
1053
+ try {
1054
+ Reflect.apply(timeout, server, [request, seconds]);
1055
+ } catch {}
1056
+ }
1057
+ function unrefTimer(timer) {
1058
+ if (typeof timer !== "object" || timer === null)
1059
+ return;
1060
+ const unref = Reflect.get(timer, "unref");
1061
+ if (typeof unref === "function")
1062
+ Reflect.apply(unref, timer, []);
1063
+ }
1064
+ var MAX_BUFFERED_FRAMES = 16;
1065
+ function responseHeaders(framing, extra) {
1066
+ const headers = new Headers(extra);
1067
+ headers.set("Content-Type", framing.contentType);
1068
+ headers.set("Cache-Control", "no-cache");
1069
+ headers.set("Connection", "keep-alive");
1070
+ headers.set("X-Accel-Buffering", "no");
1071
+ return headers;
1072
+ }
1073
+ function streamingRoute(options) {
1074
+ const heartbeatMs = options.heartbeatMs ?? DEFAULT_STREAM_HEARTBEAT_MS;
1075
+ if (!Number.isFinite(heartbeatMs) || heartbeatMs <= 0) {
1076
+ throw new TypeError("heartbeatMs must be a finite positive number of milliseconds");
1077
+ }
1078
+ const idleTimeoutSeconds = options.idleTimeoutSeconds ?? 0;
1079
+ if (!Number.isInteger(idleTimeoutSeconds) || idleTimeoutSeconds < 0) {
1080
+ throw new TypeError("idleTimeoutSeconds must be a non-negative integer");
1081
+ }
1082
+ const framing = FRAMINGS[options.format ?? "ndjson"];
1083
+ return {
1084
+ method: options.method ?? "GET",
1085
+ path: options.path,
1086
+ handler: async (request, context) => {
1087
+ applyIdleTimeout(context.server, request, idleTimeoutSeconds);
1088
+ const departed = new AbortController;
1089
+ const iterable = await options.source(request, { ...context, signal: departed.signal });
1090
+ const iterator = iterable[Symbol.asyncIterator]();
1091
+ const encoder = new TextEncoder;
1092
+ let heartbeat = null;
1093
+ let closed = false;
1094
+ const stopHeartbeat = () => {
1095
+ if (heartbeat === null)
1096
+ return;
1097
+ clearInterval(heartbeat);
1098
+ heartbeat = null;
1099
+ };
1100
+ const release = () => {
1101
+ if (closed)
1102
+ return;
1103
+ closed = true;
1104
+ try {
1105
+ stopHeartbeat();
1106
+ signalDemand();
1107
+ departed.abort();
1108
+ Promise.resolve(iterator.return?.(undefined)).catch(() => {
1109
+ return;
1110
+ });
1111
+ } catch {}
1112
+ };
1113
+ let demand = null;
1114
+ const signalDemand = () => {
1115
+ const resolve2 = demand;
1116
+ demand = null;
1117
+ resolve2?.();
1118
+ };
1119
+ const stream = new ReadableStream({
1120
+ start(controller) {
1121
+ const send = (text) => {
1122
+ if (closed)
1123
+ return false;
1124
+ try {
1125
+ controller.enqueue(encoder.encode(text));
1126
+ return true;
1127
+ } catch {
1128
+ release();
1129
+ return false;
1130
+ }
1131
+ };
1132
+ const finish = () => {
1133
+ try {
1134
+ controller.close();
1135
+ } catch {}
1136
+ };
1137
+ if (request.signal.aborted) {
1138
+ release();
1139
+ finish();
1140
+ return;
1141
+ }
1142
+ request.signal.addEventListener("abort", () => {
1143
+ release();
1144
+ finish();
1145
+ });
1146
+ send(framing.keepAlive);
1147
+ heartbeat = setInterval(() => {
1148
+ if ((controller.desiredSize ?? 1) > 0)
1149
+ send(framing.keepAlive);
1150
+ }, heartbeatMs);
1151
+ unrefTimer(heartbeat);
1152
+ const awaitDemand = async () => {
1153
+ while (!closed && (controller.desiredSize ?? 1) <= 0) {
1154
+ await new Promise((resolve2) => {
1155
+ demand = resolve2;
1156
+ });
1157
+ }
1158
+ };
1159
+ let sinceYield = 0;
1160
+ (async () => {
1161
+ try {
1162
+ for (;; ) {
1163
+ await awaitDemand();
1164
+ if (closed)
1165
+ return;
1166
+ const next = await iterator.next();
1167
+ if (closed)
1168
+ return;
1169
+ if (next.done)
1170
+ break;
1171
+ if (!send(framing.frame(next.value)))
1172
+ return;
1173
+ sinceYield += 1;
1174
+ if (sinceYield >= MAX_BUFFERED_FRAMES) {
1175
+ sinceYield = 0;
1176
+ await new Promise((resolve2) => {
1177
+ setTimeout(resolve2, 0);
1178
+ });
1179
+ if (closed)
1180
+ return;
1181
+ }
1182
+ }
1183
+ if (framing.done)
1184
+ send(framing.done);
1185
+ } catch (error) {
1186
+ if (!closed)
1187
+ send(framing.frame(normalizeError(error).toJSON()));
1188
+ } finally {
1189
+ release();
1190
+ finish();
1191
+ }
1192
+ })();
1193
+ },
1194
+ pull() {
1195
+ signalDemand();
1196
+ },
1197
+ cancel() {
1198
+ release();
1199
+ }
1200
+ }, { highWaterMark: MAX_BUFFERED_FRAMES });
1201
+ return new Response(stream, { headers: responseHeaders(framing, options.headers) });
1202
+ }
1203
+ };
1204
+ }
1205
+ function ndjsonRoute(options) {
1206
+ return streamingRoute({ ...options, format: "ndjson" });
1207
+ }
1208
+ function sseRoute(options) {
1209
+ return streamingRoute({ ...options, format: "sse" });
1210
+ }
1022
1211
  export {
1023
1212
  AppError,
1024
1213
  DEFAULT_CORS_ALLOW_HEADERS,
1025
1214
  DEFAULT_CORS_EXPOSE_HEADERS,
1215
+ DEFAULT_STREAM_HEARTBEAT_MS,
1026
1216
  STITCH_ERROR_STATUS,
1027
1217
  ShutdownOptionsSchema,
1028
1218
  ShutdownResultSchema,
@@ -1069,6 +1259,7 @@ export {
1069
1259
  implementRegistry,
1070
1260
  isStitchErrorCode,
1071
1261
  isWithinDir,
1262
+ ndjsonRoute,
1072
1263
  normalizeError,
1073
1264
  notFound,
1074
1265
  openApiRoute,
@@ -1085,8 +1276,10 @@ export {
1085
1276
  serveFile,
1086
1277
  signJwt,
1087
1278
  socketIoLane,
1279
+ sseRoute,
1088
1280
  staticRoute,
1089
1281
  streamSSE,
1282
+ streamingRoute,
1090
1283
  unauthorized,
1091
1284
  verifyJwt,
1092
1285
  verifyPkce,
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/server/socket-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EACV,MAAM,IAAI,SAAS,EAEpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EACV,gBAAgB,EAChB,MAAM,IAAI,cAAc,EAGzB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAA2B,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,aAAa,CAAC;AAc/D,YAAY,EACV,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACzD;;;;;OAKG;IACH,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,6EAA6E;IAC7E,aAAa,IAAI,IAAI,CAAC;IACtB,8EAA8E;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CACnC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,KAAK,GAAG,GAAG,CACX,SAAQ,uBAAuB;IAC/B,+EAA+E;IAC/E,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;CAC3E;AA0ID,wBAAsB,oBAAoB,CACxC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EAKpC,OAAO,GAAG,GAAG,EACb,KAAK,GAAG,OAAO,EAEf,MAAM,EAAE,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,OAAO,CAAC,oBAAoB,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAmKpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,oBAAoB,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,WAAW,CAAC,GAC3E,YAAY,CAMd"}
1
+ {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/server/socket-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EACV,MAAM,IAAI,SAAS,EAEpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EACV,gBAAgB,EAChB,MAAM,IAAI,cAAc,EAGzB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAA2B,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,aAAa,CAAC;AAc/D,YAAY,EACV,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACzD;;;;;OAKG;IACH,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,6EAA6E;IAC7E,aAAa,IAAI,IAAI,CAAC;IACtB,8EAA8E;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CACnC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,KAAK,GAAG,GAAG,CACX,SAAQ,uBAAuB;IAC/B,+EAA+E;IAC/E,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;CAC3E;AA8HD,wBAAsB,oBAAoB,CACxC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EAKpC,OAAO,GAAG,GAAG,EACb,KAAK,GAAG,OAAO,EAEf,MAAM,EAAE,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,OAAO,CAAC,oBAAoB,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAmKpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,oBAAoB,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,WAAW,CAAC,GAC3E,YAAY,CAMd"}
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/server/stream.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,QAAQ,CAmCtE;AAED,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACpD;AAED;;;;GAIG;AACH,wBAAuB,QAAQ,CAAC,CAAC,EAC/B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,eAAe,GACxB,cAAc,CAAC,CAAC,CAAC,CAmCnB"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/server/stream.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,QAAQ,CA2CtE;AAED,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACpD;AAED;;;;GAIG;AACH,wBAAuB,QAAQ,CAAC,CAAC,EAC/B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,eAAe,GACxB,cAAc,CAAC,CAAC,CAAC,CAmCnB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * A long-lived response, with the three things everyone has to remember.
3
+ *
4
+ * `RawRoute` + `ctx.server` already gave every capability needed to serve a
5
+ * continuing NDJSON or SSE body. The problem was never capability — it was that
6
+ * the author of each such route had to independently remember three unrelated
7
+ * things, and forgetting any one of them breaks the stream **silently**:
8
+ *
9
+ * 1. **Clear the generic HTTP idle timeout.** Without `server.timeout(req, 0)`
10
+ * Bun resets the connection after ten seconds. For a stream whose normal
11
+ * state is silence — a subscription to a rare event, the log of an idle
12
+ * process — that is a healthy connection being severed on a schedule.
13
+ * 2. **Send a heartbeat.** Even with the timeout cleared, intermediate proxies
14
+ * and client stacks are under no obligation to hold a connection carrying no
15
+ * bytes.
16
+ * 3. **Flush the headers at open.** A runtime does not send the response until
17
+ * the body produces something, so the consumer's `fetch` does not return
18
+ * until the first frame. On a quiet stream "subscribed and silent" becomes
19
+ * indistinguishable from "not answering" — and there is nothing to inspect,
20
+ * because there is no response yet.
21
+ *
22
+ * Each is obvious alone. Together they are a checklist that lived in the head
23
+ * of whoever wrote the route rather than in the types — and a route written
24
+ * later, doing the same job for another plane, got none of the three. Reviews
25
+ * did not catch it and neither did the tests, because every test published an
26
+ * event immediately and never lived long enough to reach the threshold. The
27
+ * shape of the defect is the point: not "done wrong", but "done incompletely,
28
+ * and the incompleteness is invisible".
29
+ *
30
+ * They are not independent, and the measurement is worth keeping: with a
31
+ * heartbeat under the threshold, point 1 does not change the outcome in
32
+ * process — either measure alone kept a connection alive through twelve
33
+ * seconds of silence, and only dropping both killed it. Point 1 earns its place
34
+ * against what a heartbeat cannot reach: a proxy or a client stack applying its
35
+ * own idle rule. Points 2 and 3 are each load-bearing on their own.
36
+ *
37
+ * A fourth thing is handled here that is easy to not even think of:
38
+ * **cancellation reaches the source.** When the consumer goes away, the async
39
+ * iterable is returned, so a departed subscriber does not leave live work
40
+ * running on the server.
41
+ */
42
+ import type { HttpMethod } from '../contract/define';
43
+ import type { RawRoute, RawRouteContext } from './types';
44
+ /** How a value becomes bytes on the wire. The only thing the two formats differ in. */
45
+ export type StreamingFormat = 'ndjson' | 'sse';
46
+ /**
47
+ * Default heartbeat, deliberately well under Bun's ten-second idle threshold.
48
+ *
49
+ * A default at or near the threshold does not protect anything: the first
50
+ * pulse would arrive at about the moment the connection was already being
51
+ * dropped, so the option would look configured and do nothing.
52
+ */
53
+ export declare const DEFAULT_STREAM_HEARTBEAT_MS = 5000;
54
+ export interface StreamingRouteOptions<TServer = unknown> {
55
+ path: string;
56
+ /** Default `GET` — a subscription is a read. */
57
+ method?: HttpMethod | 'ALL';
58
+ /** Wire framing. Default `ndjson`. */
59
+ format?: StreamingFormat;
60
+ /**
61
+ * Interval between keep-alive frames while the source is silent. Default
62
+ * `DEFAULT_STREAM_HEARTBEAT_MS`. Must be finite and positive.
63
+ */
64
+ heartbeatMs?: number;
65
+ /**
66
+ * Per-request idle timeout in **seconds**, applied through the runtime's own
67
+ * per-request control where it has one (Bun). Default `0` — no timeout.
68
+ *
69
+ * Zero by default and not by accident. The generic timeout exists to reap
70
+ * connections whose peer has gone quiet, and a stream whose normal state is
71
+ * silence is the one case where that inference is wrong — which is the reason
72
+ * this primitive is being reached for at all. It is still a field, because
73
+ * removing a general limit is a decision worth being able to see and to
74
+ * override.
75
+ */
76
+ idleTimeoutSeconds?: number;
77
+ /**
78
+ * The frames. Returned as an async iterable so a generator is the natural
79
+ * way to write one.
80
+ *
81
+ * **A source that waits must honour `context.signal`.** See
82
+ * `StreamingSourceContext` — this is the one part of cancellation the
83
+ * primitive cannot do on the source's behalf.
84
+ */
85
+ source: (request: Request, context: StreamingSourceContext<TServer>) => AsyncIterable<unknown> | Promise<AsyncIterable<unknown>>;
86
+ /** Extra response headers. The framing headers below cannot be overridden. */
87
+ headers?: Record<string, string>;
88
+ }
89
+ /**
90
+ * What a source is given, and the reason the signal is not optional.
91
+ *
92
+ * Closing an async iterator is not enough to stop a source, and the mechanism
93
+ * is worth stating because the failure is silent. An async generator serialises
94
+ * its requests: `return()` issued while a `next()` is still in flight is
95
+ * QUEUED behind it. A subscription source's `next()` is in flight almost all
96
+ * the time — that is what a subscription is — so the return is queued behind a
97
+ * promise that will only settle when the next event arrives, which for a quiet
98
+ * plane may be never. `iterator.return()` is still called, because it does
99
+ * close a source suspended at a `yield`; it simply cannot interrupt one that is
100
+ * waiting.
101
+ *
102
+ * The signal is what can. It is aborted the moment the consumer goes away —
103
+ * through either route, a request abort or a stream cancel, which are not the
104
+ * same event — and a source that awaits on it stops immediately:
105
+ *
106
+ * ```ts
107
+ * ndjsonRoute({
108
+ * path: '/events/subscribe',
109
+ * source: async function* (request, { signal }) {
110
+ * for await (const event of subscribe({ signal })) yield event
111
+ * },
112
+ * })
113
+ * ```
114
+ */
115
+ export interface StreamingSourceContext<TServer = unknown> extends RawRouteContext<TServer> {
116
+ /** Aborted when the consumer goes away. A waiting source must honour it. */
117
+ signal: AbortSignal;
118
+ }
119
+ /**
120
+ * A `RawRoute` serving a continuing body.
121
+ *
122
+ * @see ndjsonRoute
123
+ * @see sseRoute
124
+ */
125
+ export declare function streamingRoute<TServer = unknown>(options: StreamingRouteOptions<TServer>): RawRoute<TServer>;
126
+ /** A `streamingRoute` framed as newline-delimited JSON. Keep-alive is a blank line. */
127
+ export declare function ndjsonRoute<TServer = unknown>(options: Omit<StreamingRouteOptions<TServer>, 'format'>): RawRoute<TServer>;
128
+ /** A `streamingRoute` framed as Server-Sent Events — readable by `parseSSE`. */
129
+ export declare function sseRoute<TServer = unknown>(options: Omit<StreamingRouteOptions<TServer>, 'format'>): RawRoute<TServer>;
130
+ //# sourceMappingURL=streaming-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-route.d.ts","sourceRoot":"","sources":["../../src/server/streaming-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEzD,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,OAAO;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,MAAM,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IAC5B,sCAAsC;IACtC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,MAAM,EAAE,CACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,KACrC,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO,GAAG,OAAO,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IACzF,4EAA4E;IAC5E,MAAM,EAAE,WAAW,CAAC;CACrB;AAiGD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAG,OAAO,EAC9C,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,QAAQ,CAAC,OAAO,CAAC,CAgNnB;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,GAAG,OAAO,EAC3C,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,OAAO,GAAG,OAAO,EACxC,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,GACtD,QAAQ,CAAC,OAAO,CAAC,CAEnB"}
package/dist/testing.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-vtjgx3vv.js";
5
5
  import {
6
6
  createApplication
7
- } from "./index-82e74yfx.js";
7
+ } from "./index-eabpd4tb.js";
8
8
  import"./index-8eywc9zv.js";
9
9
  import {
10
10
  RealtimeRequestDisconnectedError,