elysia 2.0.0-beta.3 → 2.0.0-beta.4

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 (69) hide show
  1. package/dist/adapter/bun/index.d.ts +1 -1
  2. package/dist/adapter/bun/index.js +320 -56
  3. package/dist/adapter/bun/index.mjs +321 -57
  4. package/dist/adapter/utils.js +48 -27
  5. package/dist/adapter/utils.mjs +48 -27
  6. package/dist/base.d.ts +16 -5
  7. package/dist/base.js +171 -74
  8. package/dist/base.mjs +172 -75
  9. package/dist/compile/aot-capture.js +1 -1
  10. package/dist/compile/aot-capture.mjs +1 -1
  11. package/dist/compile/aot.d.ts +1 -0
  12. package/dist/compile/aot.js +3 -0
  13. package/dist/compile/aot.mjs +3 -0
  14. package/dist/compile/handler/frozen-validator.d.ts +4 -1
  15. package/dist/compile/handler/frozen-validator.js +8 -3
  16. package/dist/compile/handler/frozen-validator.mjs +5 -3
  17. package/dist/compile/handler/index.d.ts +1 -1
  18. package/dist/compile/handler/index.js +3 -3
  19. package/dist/compile/handler/index.mjs +3 -3
  20. package/dist/compile/handler/reconstruct.d.ts +1 -1
  21. package/dist/compile/handler/reconstruct.js +4 -3
  22. package/dist/compile/handler/reconstruct.mjs +4 -3
  23. package/dist/compile/jit-probe.d.ts +2 -2
  24. package/dist/context.d.ts +1 -1
  25. package/dist/context.js +5 -1
  26. package/dist/context.mjs +5 -1
  27. package/dist/package.js +1 -1
  28. package/dist/package.mjs +1 -1
  29. package/dist/plugin/aot/core.d.ts +11 -5
  30. package/dist/plugin/aot/core.js +86 -38
  31. package/dist/plugin/aot/core.mjs +87 -41
  32. package/dist/plugin/aot/esbuild.js +8 -2
  33. package/dist/plugin/aot/esbuild.mjs +9 -3
  34. package/dist/plugin/aot/hooks.d.ts +2 -1
  35. package/dist/plugin/aot/hooks.js +3 -3
  36. package/dist/plugin/aot/hooks.mjs +4 -4
  37. package/dist/plugin/aot/source.d.ts +4 -1
  38. package/dist/plugin/aot/source.js +21 -14
  39. package/dist/plugin/aot/source.mjs +21 -14
  40. package/dist/plugin/aot/worker.js +2 -2
  41. package/dist/plugin/aot/worker.mjs +2 -2
  42. package/dist/route-table.d.ts +3 -0
  43. package/dist/route-table.js +4 -1
  44. package/dist/route-table.mjs +4 -1
  45. package/dist/schema-snapshot.js +17 -22
  46. package/dist/schema-snapshot.mjs +17 -22
  47. package/dist/type/bridge-live.js +1 -1
  48. package/dist/type/bridge-live.mjs +1 -1
  49. package/dist/type/coerce.d.ts +1 -1
  50. package/dist/type/constants.d.ts +1 -1
  51. package/dist/type/typebox-value-live.d.ts +2 -2
  52. package/dist/type/typebox-value-live.js +2 -2
  53. package/dist/type/typebox-value-live.mjs +2 -2
  54. package/dist/type/typebox-value.d.ts +6 -6
  55. package/dist/type/validator/index.d.ts +1 -1
  56. package/dist/type/validator/index.js +10 -7
  57. package/dist/type/validator/index.mjs +10 -7
  58. package/dist/type/validator/validator-cache.d.ts +1 -1
  59. package/dist/type/validator/validator-cache.js +17 -9
  60. package/dist/type/validator/validator-cache.mjs +17 -9
  61. package/dist/utils.d.ts +10 -1
  62. package/dist/utils.js +8 -1
  63. package/dist/utils.mjs +6 -1
  64. package/dist/ws/context.d.ts +4 -1
  65. package/dist/ws/context.js +21 -3
  66. package/dist/ws/context.mjs +20 -3
  67. package/dist/ws/route.js +57 -12
  68. package/dist/ws/route.mjs +59 -14
  69. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import { createAdapter } from "../index.mjs";
2
2
  import { isDynamicRegex, needEncodeRegex } from "../../constants.mjs";
3
- import { flattenChain, getLoosePath, nullObject } from "../../utils.mjs";
3
+ import { flattenChain, getLoosePath, isSocketQuiet, nullObject, throwLifecycleErrors } from "../../utils.mjs";
4
4
  import { WebStandardAdapter } from "../web-standard/index.mjs";
5
5
  import { frozenRootOf, resolvedWsOf } from "../../generation.mjs";
6
6
  import { origin } from "../origin.mjs";
@@ -8,6 +8,77 @@ import { buildNativeStaticResponse } from "../../compile/handler/index.mjs";
8
8
  import { routeRow } from "../../route-table.mjs";
9
9
 
10
10
  //#region src/adapter/bun/index.ts
11
+ function runLifecycle(epoch, callback, ...args) {
12
+ const invoking = epoch.invoking;
13
+ epoch.invoking = true;
14
+ try {
15
+ return callback(...args);
16
+ } finally {
17
+ epoch.invoking = invoking;
18
+ }
19
+ }
20
+ const unavailableFetch = () => new Response(null, {
21
+ headers: { connection: "close" },
22
+ status: 503
23
+ });
24
+ function reloadServer(server, serve) {
25
+ try {
26
+ server.reload(serve);
27
+ } catch (error) {
28
+ if (!serve.routes) throw error;
29
+ delete serve.routes;
30
+ console.warn("[Elysia] Native static promotion was skipped:", error);
31
+ try {
32
+ server.reload(serve);
33
+ } catch (fallbackError) {
34
+ console.error("[Elysia] Failed to reload Bun server:", fallbackError);
35
+ throw fallbackError;
36
+ }
37
+ }
38
+ }
39
+ function releaseLifecycle(app, ext, createdExt, stop, registerCleanup) {
40
+ if (ext.stop === stop) delete ext.stop;
41
+ if (ext.cleanupEpoch === registerCleanup) delete ext.cleanupEpoch;
42
+ if (createdExt && app["~ext"] === ext && !Object.keys(ext).length) app["~ext"] = void 0;
43
+ }
44
+ function clearAppServer(app, server) {
45
+ if (app.server === server) app.server = void 0;
46
+ }
47
+ async function waitForServerRequests(server, abandoned) {
48
+ while (server.pendingRequests && !abandoned()) await Bun.sleep(1);
49
+ }
50
+ async function settleServerWebSockets(lifecycle, errors) {
51
+ if (!lifecycle) return true;
52
+ lifecycle.closing = true;
53
+ let failed = false;
54
+ do {
55
+ for (const socket of lifecycle.sockets) if (socket.readyState < 3) try {
56
+ socket.terminate();
57
+ } catch (error) {
58
+ errors.push(error);
59
+ failed = true;
60
+ }
61
+ if (failed) return false;
62
+ await Bun.sleep(1);
63
+ const abandon = lifecycle.abandon === true;
64
+ for (const socket of lifecycle.sockets) if (abandon || isSocketQuiet(socket)) {
65
+ delete socket.data["~lifecycleRun"];
66
+ lifecycle.sockets.delete(socket);
67
+ }
68
+ } while (lifecycle.sockets.size);
69
+ return true;
70
+ }
71
+ const closeServerIdle = (server, errors) => {
72
+ const close = server.closeIdleConnections;
73
+ if (!close) return true;
74
+ try {
75
+ close.call(server);
76
+ return true;
77
+ } catch (error) {
78
+ errors.push(error);
79
+ return false;
80
+ }
81
+ };
11
82
  /**
12
83
  * ! This may looks like it would cause race condition, but it is not
13
84
  * Bun is single-threaded and synchronous, so the `finally` block will
@@ -25,15 +96,6 @@ const withOrigin = (fetch) => (request, server) => {
25
96
  origin.request = void 0;
26
97
  }
27
98
  };
28
- const nativeStaticMethods = new Set([
29
- "GET",
30
- "POST",
31
- "PUT",
32
- "DELETE",
33
- "PATCH",
34
- "HEAD",
35
- "OPTIONS"
36
- ]);
37
99
  function collectStaticRoutes(app) {
38
100
  if (app["~config"]?.nativeStaticResponse === false) return;
39
101
  app.fetch;
@@ -43,9 +105,16 @@ function collectStaticRoutes(app) {
43
105
  const table = app["~generation"]?.routeTable ?? app["~routeTable"];
44
106
  const length = table?.length ?? 0;
45
107
  if (!table || !length) return;
46
- const methods = table.method;
47
- const paths = table.path;
48
- const handlers = table.handler;
108
+ const { method: methods, path: paths, handler: handlers } = table;
109
+ const nativeStaticMethods = new Set([
110
+ "GET",
111
+ "POST",
112
+ "PUT",
113
+ "DELETE",
114
+ "PATCH",
115
+ "HEAD",
116
+ "OPTIONS"
117
+ ]);
49
118
  let hasCandidate = false;
50
119
  for (let i = 0; i < length; i++) {
51
120
  const h = handlers[i];
@@ -84,18 +153,19 @@ function collectStaticRoutes(app) {
84
153
  const path = paths[i];
85
154
  if (seen.get(method + " " + path) !== i) continue;
86
155
  if (!nativeStaticMethods.has(method)) continue;
156
+ if (isDynamicRegex.test(path)) continue;
87
157
  const h = handlers[i];
88
158
  if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
89
159
  const value = buildNativeStaticResponse(routeRow(table, i), app);
90
160
  if (!value) continue;
91
161
  add(method, path, value);
92
- if (!strictPath && !isDynamicRegex.test(path)) {
162
+ if (!strictPath) {
93
163
  const loose = getLoosePath(path);
94
164
  if (loose !== path && !explicitPaths?.get(method)?.has(loose)) add(method, loose, value);
95
165
  }
96
166
  }
97
167
  if (!Object.keys(ready).length) return;
98
- return [ready, []];
168
+ return ready;
99
169
  }
100
170
  const BunAdapter = createAdapter({
101
171
  name: "bun",
@@ -104,11 +174,18 @@ const BunAdapter = createAdapter({
104
174
  parse: WebStandardAdapter.parse,
105
175
  response: WebStandardAdapter.response,
106
176
  listen(app, options, callback) {
177
+ if (app.server || app["~ext"]?.stop) throw new Error("[Elysia] Cannot call listen() while a server or teardown is active");
107
178
  const _config = app["~config"]?.serve;
108
179
  const optionsIsObject = typeof options === "object";
109
180
  let live;
110
- const gatedFetch = (request, server) => live ? live(request, server) : Promise.resolve(ready).then(() => {
111
- if (!live) throw new Error("[Elysia] Server was stopped before it was ready");
181
+ let cancelled = false;
182
+ let startupShutdown;
183
+ let shutdownAttempt;
184
+ const gatedFetch = (request, server) => live ? live(request, server) : Promise.resolve(ready).then(async () => {
185
+ if (!live) {
186
+ await startupShutdown;
187
+ throw new Error("[Elysia] Server was stopped before it was ready");
188
+ }
112
189
  return live(request, server);
113
190
  });
114
191
  const _options = optionsIsObject ? { ...options } : {
@@ -121,6 +198,8 @@ const BunAdapter = createAdapter({
121
198
  ..._options
122
199
  } : _options;
123
200
  let ready;
201
+ let wsLifecycle;
202
+ let modulesReady;
124
203
  const build = () => {
125
204
  const fetch = app.fetch;
126
205
  let routes;
@@ -133,7 +212,17 @@ const BunAdapter = createAdapter({
133
212
  if (app["~hasWS"]) {
134
213
  const resolved = resolvedWsOf(app);
135
214
  if (!resolved) throw new Error("[Elysia] internal: WebSocket routes are present but no capability provider was resolved");
136
- websocket = resolved.config ? Object.assign(resolved.provider.buildGlobalWSHandler(), resolved.config) : resolved.provider.buildGlobalWSHandler();
215
+ const handler = resolved.provider.buildGlobalWSHandler();
216
+ websocket = resolved.config ? Object.assign(handler, resolved.config) : handler;
217
+ const setter = Object.getOwnPropertyDescriptor(handler, "~lifecycle")?.set;
218
+ if (setter) {
219
+ wsLifecycle = {
220
+ closing: false,
221
+ sockets: /* @__PURE__ */ new Set(),
222
+ run: runLifecycle.bind(null, epoch)
223
+ };
224
+ setter.call(handler, wsLifecycle);
225
+ }
137
226
  }
138
227
  return {
139
228
  fetch,
@@ -142,74 +231,249 @@ const BunAdapter = createAdapter({
142
231
  };
143
232
  };
144
233
  let built;
145
- const server = app.server = Bun.serve(serve);
146
- const reload = () => {
234
+ let pendingSetups;
235
+ const server = app.server = Bun.serve(serve.routes || serve.error ? {
236
+ ...serve,
237
+ error: unavailableFetch,
238
+ routes: {}
239
+ } : serve);
240
+ let startupFailure;
241
+ let forceRequested = false;
242
+ let forceDone = false;
243
+ let nativeStopped = false;
244
+ let abandoned = false;
245
+ let published = false;
246
+ let cleanupCompleted = false;
247
+ let idleRequired = false;
248
+ let cleanupFailures;
249
+ let observedOutcome;
250
+ let persistentCleanup;
251
+ let persistentCleanupLength = -1;
252
+ const epochCleanup = [];
253
+ const createdExt = app["~ext"] === void 0;
254
+ const ext = app["~ext"] ??= nullObject();
255
+ const epoch = {
256
+ setup: false,
257
+ invoking: false
258
+ };
259
+ const snapshotCleanup = () => {
260
+ if (persistentCleanupLength !== -1) return;
261
+ persistentCleanup = ext.cleanup;
262
+ persistentCleanupLength = persistentCleanup?.length ?? 0;
263
+ };
264
+ const registerCleanup = (handler) => {
265
+ if (ext.cleanupEpoch !== registerCleanup) return false;
266
+ if (!epoch.setup) throw new Error("[Elysia] .cleanup() called after its setup epoch settled");
267
+ if (Array.isArray(handler)) epochCleanup.push(...handler);
268
+ else epochCleanup.push(handler);
269
+ return true;
270
+ };
271
+ const gate = () => {
272
+ if (!published) return;
273
+ live = void 0;
274
+ if (wsLifecycle) wsLifecycle.closing = true;
147
275
  try {
148
- server.reload(serve);
276
+ server.reload({
277
+ ...serve,
278
+ fetch: unavailableFetch,
279
+ error: unavailableFetch,
280
+ routes: {}
281
+ });
149
282
  } catch (error) {
150
- if (!serve.routes) throw error;
151
- delete serve.routes;
152
- console.warn("[Elysia] Native static promotion was skipped:", error);
153
- try {
154
- server.reload(serve);
155
- } catch (fallbackError) {
156
- console.error("[Elysia] Failed to reload Bun server:", fallbackError);
157
- throw fallbackError;
283
+ return { error };
284
+ }
285
+ };
286
+ const nativeStop = async (closeActiveConnections, errors) => {
287
+ try {
288
+ await server.stop(closeActiveConnections);
289
+ nativeStopped = true;
290
+ if (closeActiveConnections) {
291
+ forceDone = true;
292
+ forceRequested = false;
158
293
  }
294
+ return true;
295
+ } catch (error) {
296
+ if (closeActiveConnections) forceRequested = true;
297
+ errors.push(error);
298
+ return false;
159
299
  }
160
300
  };
301
+ const isAbandoned = () => abandoned;
302
+ const quiesce = async (force) => {
303
+ const errors = [];
304
+ if (!published) return {
305
+ errors,
306
+ releasable: await nativeStop(true, errors),
307
+ safe: true
308
+ };
309
+ if (force && !idleRequired && !wsLifecycle?.closing) {
310
+ const stopped = await nativeStop(true, errors);
311
+ return {
312
+ errors,
313
+ releasable: stopped,
314
+ safe: stopped
315
+ };
316
+ }
317
+ const gateFailure = gate();
318
+ if (gateFailure) errors.push(gateFailure.error);
319
+ let socketsSettled = await settleServerWebSockets(wsLifecycle, errors);
320
+ await Promise.resolve();
321
+ let close = forceRequested || gateFailure !== void 0 || !socketsSettled;
322
+ if (!close && nativeStopped) await waitForServerRequests(server, isAbandoned);
323
+ idleRequired = true;
324
+ let stopped = true;
325
+ while (!nativeStopped || forceRequested && !forceDone) {
326
+ if (nativeStopped) close = true;
327
+ stopped = await nativeStop(close, errors);
328
+ if (stopped) {
329
+ if (socketsSettled) socketsSettled = await settleServerWebSockets(wsLifecycle, errors);
330
+ continue;
331
+ }
332
+ if (close) break;
333
+ await waitForServerRequests(server, isAbandoned);
334
+ close = true;
335
+ }
336
+ let idleClosed = true;
337
+ if (idleRequired) {
338
+ idleClosed = closeServerIdle(server, errors);
339
+ if (idleClosed && !gateFailure) idleRequired = false;
340
+ }
341
+ const safe = socketsSettled && stopped && idleClosed && (!gateFailure || forceDone);
342
+ return {
343
+ errors,
344
+ releasable: safe,
345
+ safe
346
+ };
347
+ };
161
348
  const setup = () => {
162
- const onSetup = app["~ext"]?.setup;
349
+ snapshotCleanup();
350
+ const onSetup = ext.setup;
163
351
  if (!onSetup) return;
164
- let pendingSetups;
352
+ epoch.setup = true;
353
+ ext.cleanupEpoch = registerCleanup;
165
354
  for (let i = 0; i < onSetup.length; i++) {
166
- const result = onSetup[i](app);
167
- if (result && typeof result.then === "function") (pendingSetups ??= []).push(result);
355
+ if (cancelled) break;
356
+ try {
357
+ const result = runLifecycle(epoch, onSetup[i], app);
358
+ if (result && typeof result.then === "function") (pendingSetups ??= []).push(Promise.resolve(result));
359
+ } catch (error) {
360
+ if (!pendingSetups) epoch.setup = false;
361
+ stop(true, { error }).catch(() => {});
362
+ throw error;
363
+ }
168
364
  }
169
- if (pendingSetups) return Promise.all(pendingSetups);
365
+ if (pendingSetups) return Promise.all(pendingSetups).then(() => epoch.setup = false);
366
+ epoch.setup = false;
170
367
  };
171
- const rollback = (error) => {
172
- if (app.server !== server) return;
173
- try {
174
- server.stop(true);
175
- } catch (stopError) {
176
- console.error(stopError);
177
- } finally {
178
- app.server = void 0;
368
+ const stop = (closeActiveConnections, failure) => {
369
+ const reentrant = epoch.invoking;
370
+ if (failure) startupFailure ??= failure;
371
+ if (closeActiveConnections === true && !forceDone) forceRequested = true;
372
+ cancelled = true;
373
+ clearAppServer(app, server);
374
+ let outcome;
375
+ if (shutdownAttempt) {
376
+ if (closeActiveConnections === true && !forceDone) {
377
+ abandoned = true;
378
+ if (wsLifecycle) wsLifecycle.abandon = true;
379
+ }
380
+ outcome = shutdownAttempt;
381
+ } else {
382
+ const { promise, resolve, reject } = Promise.withResolvers();
383
+ outcome = shutdownAttempt = promise;
384
+ const quiescence = quiesce(forceRequested || !published);
385
+ let releasable = false;
386
+ (async () => {
387
+ try {
388
+ await Promise.resolve();
389
+ if (modulesReady) try {
390
+ await modulesReady;
391
+ } catch (error) {
392
+ startupFailure ??= { error };
393
+ }
394
+ if (pendingSetups) {
395
+ const setupResults = await Promise.allSettled(pendingSetups);
396
+ if (!startupFailure) for (let i = 0; i < setupResults.length; i++) {
397
+ const result = setupResults[i];
398
+ if (result.status === "rejected") {
399
+ startupFailure = { error: result.reason };
400
+ break;
401
+ }
402
+ }
403
+ }
404
+ epoch.setup = false;
405
+ const errors = [];
406
+ const quiesced = await quiescence;
407
+ if (startupFailure) errors.push(startupFailure.error);
408
+ errors.push(...quiesced.errors);
409
+ releasable = quiesced.releasable;
410
+ if (!quiesced.safe) {
411
+ if (cleanupFailures) errors.push(...cleanupFailures);
412
+ throwLifecycleErrors(errors);
413
+ }
414
+ if (!cleanupCompleted) {
415
+ snapshotCleanup();
416
+ if (ext.stop === stop) {
417
+ const length = persistentCleanupLength + epochCleanup.length;
418
+ for (let n = 0; n < length; n++) {
419
+ if (ext.stop !== stop) break;
420
+ const i = startupFailure ? length - 1 - n : n;
421
+ const handler = i < persistentCleanupLength ? persistentCleanup[i] : epochCleanup[i - persistentCleanupLength];
422
+ try {
423
+ await runLifecycle(epoch, handler, app);
424
+ } catch (error) {
425
+ (cleanupFailures ??= []).push(error);
426
+ }
427
+ }
428
+ }
429
+ cleanupCompleted = true;
430
+ epochCleanup.length = 0;
431
+ }
432
+ if (cleanupFailures) errors.push(...cleanupFailures);
433
+ if (releasable) releaseLifecycle(app, ext, createdExt, stop, registerCleanup);
434
+ throwLifecycleErrors(errors);
435
+ } finally {
436
+ epoch.setup = false;
437
+ if (!releasable) shutdownAttempt = void 0;
438
+ }
439
+ })().then(resolve, reject);
179
440
  }
180
- const cleanup = app["~ext"]?.cleanup;
181
- if (cleanup) for (let i = cleanup.length - 1; i >= 0; i--) try {
182
- const result = cleanup[i](app);
183
- if (result && typeof result.then === "function") Promise.resolve(result).catch(console.error);
184
- } catch (cleanupError) {
185
- console.error(cleanupError);
441
+ if (!published) startupShutdown ??= outcome;
442
+ if (!reentrant) return outcome;
443
+ if (observedOutcome !== outcome) {
444
+ observedOutcome = outcome;
445
+ outcome.catch((error) => {
446
+ console.error("[Elysia] stop() failed:", error);
447
+ });
186
448
  }
187
- return error;
449
+ return Promise.resolve();
188
450
  };
451
+ ext.stop = stop;
189
452
  const publish = () => {
190
- if (app.server !== server) return;
453
+ if (cancelled || app.server !== server) return;
191
454
  built ??= build();
192
455
  live = serve.fetch = withOrigin(built.fetch);
456
+ published = true;
193
457
  if (built.websocket) serve.websocket = built.websocket;
194
- if (built.routes) serve.routes = built.routes[0];
195
- reload();
458
+ if (built.routes) serve.routes = built.routes;
459
+ reloadServer(server, serve);
196
460
  if (callback) callback(server);
197
461
  };
198
462
  const start = () => {
199
- if (app.server !== server) return;
463
+ modulesReady = void 0;
464
+ if (cancelled || app.server !== server) return;
200
465
  const setupReady = setup();
201
466
  if (setupReady && typeof setupReady.then === "function") return Promise.resolve(setupReady).then(publish);
202
467
  publish();
203
468
  };
204
469
  try {
205
- if (app.pending) ready = app.modules.then(start);
206
- else ready = Promise.resolve().then(start);
470
+ ready = (modulesReady = app.modules).then(start);
471
+ ready = ready.catch((error) => stop(true, { error }));
207
472
  ready.catch((error) => {
208
473
  console.error("[Elysia] listen() failed:", error);
209
- return rollback(error);
210
474
  });
211
475
  } catch (error) {
212
- rollback(error);
476
+ stop(true, { error }).catch(console.error);
213
477
  throw error;
214
478
  }
215
479
  }
@@ -158,10 +158,12 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
158
158
  return new Response(generator, { headers: { "content-type": "application/octet-stream" } });
159
159
  }
160
160
  const typedSSE = !skipFormat && "sse" in generator && generator.sse === true;
161
+ const sourceIterator = typeof generator.next === "function" ? generator : void 0;
161
162
  let init = typedSSE ? void 0 : generator.next?.();
162
163
  if (set) handleSet(set);
163
164
  if (init instanceof Promise) init = await init;
164
- if (init?.value instanceof ReadableStream) generator = init.value;
165
+ const yieldedStream = init?.value instanceof ReadableStream;
166
+ if (yieldedStream) generator = init.value;
165
167
  else if (init && (typeof init?.done === "undefined" || init?.done)) {
166
168
  if (set) return mapResponse(init.value, set, request, owned);
167
169
  return mapCompactResponse(init.value, request, owned);
@@ -195,8 +197,32 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
195
197
  connection: "keep-alive"
196
198
  }
197
199
  };
198
- const iterator = typeof generator.next === "function" ? generator : generator[Symbol.asyncIterator]();
200
+ const sourceStream = generator instanceof ReadableStream ? generator : void 0;
201
+ let streamReader;
202
+ const iterator = sourceStream ? {
203
+ next: () => (streamReader ??= sourceStream.getReader()).read(),
204
+ async return() {
205
+ if (!streamReader) {
206
+ await sourceStream.cancel();
207
+ return {
208
+ done: true,
209
+ value: void 0
210
+ };
211
+ }
212
+ try {
213
+ await streamReader.cancel();
214
+ } finally {
215
+ streamReader.releaseLock();
216
+ }
217
+ return {
218
+ done: true,
219
+ value: void 0
220
+ };
221
+ }
222
+ } : sourceIterator ?? generator[Symbol.asyncIterator]();
223
+ const outerIterator = yieldedStream && !init.done ? sourceIterator : void 0;
199
224
  let end = false;
225
+ let finalizing;
200
226
  const signal = request?.signal;
201
227
  let onAbort;
202
228
  const cleanupAbort = () => {
@@ -205,17 +231,21 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
205
231
  onAbort = void 0;
206
232
  }
207
233
  };
208
- const safeReturn = () => {
234
+ const safeReturn = async (target) => {
209
235
  try {
210
- const r = iterator.return?.();
211
- if (r && typeof r.then === "function") r.catch(() => {});
236
+ await target?.return?.();
212
237
  } catch {}
213
238
  };
214
- const closeSafely = (controller) => {
239
+ const finalize = (mode, controller, error) => {
240
+ if (end) return finalizing;
241
+ end = true;
242
+ cleanupAbort();
243
+ finalizing = safeReturn(iterator).then(() => safeReturn(outerIterator));
244
+ if (!controller) return finalizing;
215
245
  try {
216
- controller.close();
246
+ if (mode === "error") controller.error(error);
247
+ else controller.close();
217
248
  } catch {}
218
- cleanupAbort();
219
249
  };
220
250
  const enqueueValue = async (controller, value) => {
221
251
  if (value.toSSE) {
@@ -234,42 +264,33 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
234
264
  return new Response(new ReadableStream({
235
265
  async start(controller) {
236
266
  if (signal) {
237
- onAbort = () => {
238
- cleanupAbort();
239
- end = true;
240
- safeReturn();
241
- try {
242
- controller.close();
243
- } catch {}
244
- };
267
+ onAbort = () => finalize("close", controller);
245
268
  if (signal.aborted) onAbort();
246
269
  else signal.addEventListener("abort", onAbort, { once: true });
247
270
  }
248
271
  if (!init || init.value instanceof ReadableStream || init.value === void 0 || init.value === null) return;
249
- await enqueueValue(controller, init.value);
272
+ try {
273
+ await enqueueValue(controller, init.value);
274
+ } catch (error) {
275
+ finalize("error", controller, error);
276
+ }
250
277
  },
251
278
  async pull(controller) {
252
- if (end) {
253
- closeSafely(controller);
254
- return;
255
- }
279
+ if (end) return;
256
280
  try {
257
281
  const { value: chunk, done } = await iterator.next();
258
282
  if (done || end) {
259
- closeSafely(controller);
283
+ finalize("close", controller);
260
284
  return;
261
285
  }
262
286
  if (chunk === void 0 || chunk === null) return;
263
287
  await enqueueValue(controller, chunk);
264
288
  } catch (error) {
265
- cleanupAbort();
266
- controller.error(error);
289
+ finalize("error", controller, error);
267
290
  }
268
291
  },
269
292
  cancel() {
270
- end = true;
271
- cleanupAbort();
272
- safeReturn();
293
+ finalize("close");
273
294
  }
274
295
  }, typedSSE ? { highWaterMark: 0 } : void 0), set);
275
296
  };