elysia 2.0.0-beta.25 → 2.0.0-beta.27

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 (127) hide show
  1. package/dist/adapter/bun/index.js +5 -21
  2. package/dist/adapter/bun/index.mjs +5 -21
  3. package/dist/adapter/origin.js +0 -57
  4. package/dist/adapter/origin.mjs +0 -57
  5. package/dist/adapter/utils.js +1 -25
  6. package/dist/adapter/utils.mjs +1 -25
  7. package/dist/adapter/web-standard/handler.js +3 -1
  8. package/dist/adapter/web-standard/handler.mjs +3 -1
  9. package/dist/base.d.ts +7 -3
  10. package/dist/base.js +29 -143
  11. package/dist/base.mjs +30 -144
  12. package/dist/compile/aot-capture.js +1 -3
  13. package/dist/compile/aot-capture.mjs +1 -3
  14. package/dist/compile/aot-emit.js +0 -13
  15. package/dist/compile/aot-emit.mjs +0 -13
  16. package/dist/compile/aot-reconstruct.js +0 -26
  17. package/dist/compile/aot-reconstruct.mjs +0 -26
  18. package/dist/compile/aot.js +0 -7
  19. package/dist/compile/aot.mjs +0 -7
  20. package/dist/compile/handler/frozen-validator.js +0 -11
  21. package/dist/compile/handler/frozen-validator.mjs +0 -11
  22. package/dist/compile/handler/index.js +0 -9
  23. package/dist/compile/handler/index.mjs +0 -9
  24. package/dist/compile/handler/jit.d.ts +5 -1
  25. package/dist/compile/handler/jit.js +7 -18
  26. package/dist/compile/handler/jit.mjs +8 -19
  27. package/dist/compile/handler/params.js +0 -3
  28. package/dist/compile/handler/params.mjs +0 -3
  29. package/dist/compile/handler/utils.js +3 -14
  30. package/dist/compile/handler/utils.mjs +3 -14
  31. package/dist/compiled.js +0 -9
  32. package/dist/compiled.mjs +0 -9
  33. package/dist/constants.js +18 -2
  34. package/dist/constants.mjs +18 -2
  35. package/dist/cookie/crypto.d.ts +1 -1
  36. package/dist/cookie/crypto.js +8 -7
  37. package/dist/cookie/crypto.mjs +8 -7
  38. package/dist/cookie/utils.js +0 -13
  39. package/dist/cookie/utils.mjs +0 -13
  40. package/dist/error.js +0 -73
  41. package/dist/error.mjs +0 -73
  42. package/dist/handler/error.js +0 -26
  43. package/dist/handler/error.mjs +0 -26
  44. package/dist/handler/fetch.js +14 -25
  45. package/dist/handler/fetch.mjs +14 -25
  46. package/dist/handler/utils.js +5 -5
  47. package/dist/handler/utils.mjs +5 -5
  48. package/dist/package.js +1 -1
  49. package/dist/package.mjs +1 -1
  50. package/dist/parse-query.js +0 -4
  51. package/dist/parse-query.mjs +0 -4
  52. package/dist/plugin/aot/bun.js +0 -26
  53. package/dist/plugin/aot/bun.mjs +0 -26
  54. package/dist/plugin/aot/core.d.ts +1 -1
  55. package/dist/plugin/aot/core.js +8 -34
  56. package/dist/plugin/aot/core.mjs +8 -34
  57. package/dist/plugin/aot/esbuild.js +0 -19
  58. package/dist/plugin/aot/esbuild.mjs +0 -19
  59. package/dist/plugin/aot/rspack-loader.js +0 -16
  60. package/dist/plugin/aot/rspack-loader.mjs +0 -16
  61. package/dist/plugin/aot/rspack.js +0 -14
  62. package/dist/plugin/aot/rspack.mjs +0 -14
  63. package/dist/plugin/aot/unplugin.js +0 -19
  64. package/dist/plugin/aot/unplugin.mjs +0 -19
  65. package/dist/plugin/aot/vite.js +0 -14
  66. package/dist/plugin/aot/vite.mjs +0 -14
  67. package/dist/plugin/trace.js +0 -16
  68. package/dist/plugin/trace.mjs +0 -16
  69. package/dist/plugin/websocket.js +0 -21
  70. package/dist/plugin/websocket.mjs +0 -21
  71. package/dist/schema-snapshot.js +0 -4
  72. package/dist/schema-snapshot.mjs +0 -4
  73. package/dist/sucrose.js +0 -4
  74. package/dist/sucrose.mjs +0 -4
  75. package/dist/trace.js +0 -1
  76. package/dist/trace.mjs +0 -1
  77. package/dist/type/bridge-live.d.ts +1 -1
  78. package/dist/type/bridge-live.js +1 -1
  79. package/dist/type/bridge-live.mjs +1 -1
  80. package/dist/type/bridge.d.ts +1 -1
  81. package/dist/type/bridge.js +22 -20
  82. package/dist/type/bridge.mjs +22 -20
  83. package/dist/type/coerce-plan.js +0 -1
  84. package/dist/type/coerce-plan.mjs +0 -1
  85. package/dist/type/coerce.js +0 -4
  86. package/dist/type/coerce.mjs +0 -4
  87. package/dist/type/constants.d.ts +1 -1
  88. package/dist/type/elysia/numeric-enum.js +0 -4
  89. package/dist/type/elysia/numeric-enum.mjs +0 -4
  90. package/dist/type/elysia/problem.js +0 -6
  91. package/dist/type/elysia/problem.mjs +0 -6
  92. package/dist/type/index.js +0 -11
  93. package/dist/type/index.mjs +0 -11
  94. package/dist/type/shared.js +0 -6
  95. package/dist/type/shared.mjs +0 -6
  96. package/dist/type/sync-require.js +0 -7
  97. package/dist/type/sync-require.mjs +0 -7
  98. package/dist/type/typebox-type.js +0 -6
  99. package/dist/type/typebox-type.mjs +0 -6
  100. package/dist/type/typebox-value-live.d.ts +2 -2
  101. package/dist/type/typebox-value-live.js +2 -2
  102. package/dist/type/typebox-value-live.mjs +2 -2
  103. package/dist/type/typebox-value-ops.d.ts +2 -2
  104. package/dist/type/typebox-value-ops.js +2 -2
  105. package/dist/type/typebox-value-ops.mjs +2 -2
  106. package/dist/type/typebox-value.d.ts +6 -6
  107. package/dist/type/typebox-value.js +0 -3
  108. package/dist/type/typebox-value.mjs +0 -3
  109. package/dist/type/validator/default-precompute.js +0 -7
  110. package/dist/type/validator/default-precompute.mjs +0 -7
  111. package/dist/type/validator/exact-mirror-live.js +0 -7
  112. package/dist/type/validator/exact-mirror-live.mjs +1 -7
  113. package/dist/type/validator/exact-mirror.js +15 -7
  114. package/dist/type/validator/exact-mirror.mjs +15 -7
  115. package/dist/type/validator/index.js +1 -4
  116. package/dist/type/validator/index.mjs +1 -4
  117. package/dist/universal/file.js +0 -7
  118. package/dist/universal/file.mjs +0 -7
  119. package/dist/utils.js +10 -90
  120. package/dist/utils.mjs +10 -90
  121. package/dist/ws/context.js +14 -15
  122. package/dist/ws/context.mjs +14 -15
  123. package/dist/ws/parser.js +2 -2
  124. package/dist/ws/parser.mjs +2 -2
  125. package/dist/ws/route.js +112 -52
  126. package/dist/ws/route.mjs +113 -53
  127. package/package.json +1 -1
@@ -52,15 +52,6 @@ async function settleServerWebSockets(lifecycle, errors) {
52
52
  } while (lifecycle.sockets.size);
53
53
  return true;
54
54
  }
55
- /**
56
- * ! This may looks like it would cause race condition, but it is not
57
- * Bun is single-threaded and synchronous, so the `finally` block will
58
- * always run before the next request comes in
59
- * @see ../origin.ts
60
- *
61
- * `finally` runs on the synchronous return of `fetch` (the returned promise is
62
- * not awaited), so the slot is live only for the handler's synchronous prologue
63
- */
64
55
  const withOrigin = (fetch) => (request, server) => {
65
56
  require_adapter_origin.origin.request = request;
66
57
  try {
@@ -92,20 +83,13 @@ function isNativePath(path) {
92
83
  }
93
84
  return true;
94
85
  }
95
- /**
96
- * HTML bundles served by Bun's native router.
97
- *
98
- * A `:param` / `*` bundle matches before `fetch`, so with a `handoff` every
99
- * other route of that method is registered natively too, pointing at `fetch`;
100
- * Bun's specificity (exact > param > wildcard) then picks the Elysia route over
101
- * the bundle (Elysia's own router skips these bundles, see `#buildRouterUnsafe`).
102
- * If any route can't be expressed in Bun's router, dynamic bundles are skipped
103
- * entirely, since the bundle would shadow that route.
104
- */
105
86
  function collectHTMLBundleRoutes(app, handoff, routes) {
106
87
  let dynamic;
107
88
  let bundles;
108
- for (const route of app["~routes"]) {
89
+ const table = app["~generation"]?.routeTable;
90
+ if (table && !table.handler.some((h, i) => isNativeStaticMethod(table.method[i]) && require_utils.isHTMLBundle(h))) return routes;
91
+ const declared = table ? table.method.map((_, i) => require_route_table.routeRow(table, i)) : app["~routes"];
92
+ for (const route of declared) {
109
93
  const [method, path, handler] = route;
110
94
  if (!isNativeStaticMethod(method) || !require_utils.isHTMLBundle(handler)) continue;
111
95
  if (path.indexOf(":") !== -1 || path.indexOf("*") !== -1) {
@@ -122,7 +106,7 @@ function collectHTMLBundleRoutes(app, handoff, routes) {
122
106
  if (!dynamic) return routes;
123
107
  const strictPath = app["~config"]?.strictPath === true;
124
108
  const handoffs = [];
125
- for (const [method, path, handler] of app["~routes"]) {
109
+ for (const [method, path, handler] of declared) {
126
110
  if (require_utils.isHTMLBundle(handler)) continue;
127
111
  const targets = method === "*" ? dynamic : dynamic.has(method) ? [method] : void 0;
128
112
  if (!targets) continue;
@@ -52,15 +52,6 @@ async function settleServerWebSockets(lifecycle, errors) {
52
52
  } while (lifecycle.sockets.size);
53
53
  return true;
54
54
  }
55
- /**
56
- * ! This may looks like it would cause race condition, but it is not
57
- * Bun is single-threaded and synchronous, so the `finally` block will
58
- * always run before the next request comes in
59
- * @see ../origin.ts
60
- *
61
- * `finally` runs on the synchronous return of `fetch` (the returned promise is
62
- * not awaited), so the slot is live only for the handler's synchronous prologue
63
- */
64
55
  const withOrigin = (fetch) => (request, server) => {
65
56
  origin.request = request;
66
57
  try {
@@ -92,20 +83,13 @@ function isNativePath(path) {
92
83
  }
93
84
  return true;
94
85
  }
95
- /**
96
- * HTML bundles served by Bun's native router.
97
- *
98
- * A `:param` / `*` bundle matches before `fetch`, so with a `handoff` every
99
- * other route of that method is registered natively too, pointing at `fetch`;
100
- * Bun's specificity (exact > param > wildcard) then picks the Elysia route over
101
- * the bundle (Elysia's own router skips these bundles, see `#buildRouterUnsafe`).
102
- * If any route can't be expressed in Bun's router, dynamic bundles are skipped
103
- * entirely, since the bundle would shadow that route.
104
- */
105
86
  function collectHTMLBundleRoutes(app, handoff, routes) {
106
87
  let dynamic;
107
88
  let bundles;
108
- for (const route of app["~routes"]) {
89
+ const table = app["~generation"]?.routeTable;
90
+ if (table && !table.handler.some((h, i) => isNativeStaticMethod(table.method[i]) && isHTMLBundle(h))) return routes;
91
+ const declared = table ? table.method.map((_, i) => routeRow(table, i)) : app["~routes"];
92
+ for (const route of declared) {
109
93
  const [method, path, handler] = route;
110
94
  if (!isNativeStaticMethod(method) || !isHTMLBundle(handler)) continue;
111
95
  if (path.indexOf(":") !== -1 || path.indexOf("*") !== -1) {
@@ -122,7 +106,7 @@ function collectHTMLBundleRoutes(app, handoff, routes) {
122
106
  if (!dynamic) return routes;
123
107
  const strictPath = app["~config"]?.strictPath === true;
124
108
  const handoffs = [];
125
- for (const [method, path, handler] of app["~routes"]) {
109
+ for (const [method, path, handler] of declared) {
126
110
  if (isHTMLBundle(handler)) continue;
127
111
  const targets = method === "*" ? dynamic : dynamic.has(method) ? [method] : void 0;
128
112
  if (!targets) continue;
@@ -1,63 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  //#region src/adapter/origin.ts
4
- /**
5
- * Provenance channel for deferred abort-signal arming.
6
- *
7
- * Bun materializes `Request.signal` lazily (~214ns on the first read), so
8
- * Elysia only pays for it once the pipeline can actually observe an abort,
9
- * that is, after it suspends. Deferring that read is only sound for the exact
10
- * `Request` instance `Bun.serve` handed to `fetch`: any other request
11
- * (`app.handle`, a non-Bun adapter, or a `.wrap()` HOC that substitutes the
12
- * request) may carry a user-controlled signal that is already aborted at entry
13
- * and therefore must be observed synchronously.
14
- *
15
- * The Bun adapter publishes the untouched original here for the *synchronous*
16
- * prologue of `app.fetch` and, on the lane where `fetch` has no request hook
17
- * to run, for the compiled route's own entry probe, which is still inside that
18
- * same synchronous frame, then clears it in `finally`, so the window closes
19
- * before the first suspension and cannot leak across concurrent requests.
20
- * A `.wrap()` HOC that replaces the request or defers calling `next()` past a
21
- * microtask simply misses the window, fetch handler falls back to eager check
22
- *
23
- * ## "A single shared slot that every request writes to, so a race condition?"
24
- *
25
- * It looks like one, but it can't be. Explained like you're five:
26
- *
27
- * JavaScript only ever does ONE thing at a time. When a request comes in, the
28
- * adapter writes its name on the whiteboard (`origin.request = request`),
29
- * walks it down the hall (`fetch(request)` the synchronous part, where the
30
- * one and only identity check happens, in `fetch`'s prologue, or at the
31
- * compiled route's entry when `fetch` had no hook to run first), and erases
32
- * the whiteboard `finally`
33
- * all in one uninterruptible turn. The next request's turn CANNOT start
34
- * until this turn is completely finished, because that is how the event loop
35
- * works: run-to-completion, no preemption. So request B can never see request
36
- * A's name on the board, by the time B's turn starts, the board was already
37
- * erased at the end of A's turn.
38
- *
39
- * "But the handler is async!" the `await`s inside it run LATER, in separate
40
- * turns. That is fine, because the check already happened: the verdict was
41
- * copied onto that request's own context (`'~sig'`) during the synchronous
42
- * part, and the code that runs after an `await` only ever reads the context,
43
- * never this slot.
44
- *
45
- * And if any assumption here ever breaks, a runtime that re-enters, a wrap
46
- * that delays, a future edit that sneaks an `await` in before the check the
47
- * comparison simply MISSES and the request falls back to eager arming, which
48
- * is the exact pre-optimization behavior. Every failure mode makes a request
49
- * slightly slower, never incorrect
50
- *
51
- * Adversarial proof, not just argument: `test/core/abort-race.test.ts` storms
52
- * concurrent servers, in-process `handle()` calls, request-substituting and
53
- * `next()`-delaying wraps at this slot and asserts zero misclassification.
54
- *
55
- * Never infer the Bun runtime from `fetch`'s second argument: Cloudflare
56
- * Workers pass `env` there.
57
- *
58
- * It's insanely stupid, but it works. As Bun is single-threaded and synchronous
59
- * so the `finally` block will always run before the next request comes in.
60
- */
61
4
  const origin = { request: void 0 };
62
5
 
63
6
  //#endregion
@@ -1,62 +1,5 @@
1
1
  // @bun
2
2
  //#region src/adapter/origin.ts
3
- /**
4
- * Provenance channel for deferred abort-signal arming.
5
- *
6
- * Bun materializes `Request.signal` lazily (~214ns on the first read), so
7
- * Elysia only pays for it once the pipeline can actually observe an abort,
8
- * that is, after it suspends. Deferring that read is only sound for the exact
9
- * `Request` instance `Bun.serve` handed to `fetch`: any other request
10
- * (`app.handle`, a non-Bun adapter, or a `.wrap()` HOC that substitutes the
11
- * request) may carry a user-controlled signal that is already aborted at entry
12
- * and therefore must be observed synchronously.
13
- *
14
- * The Bun adapter publishes the untouched original here for the *synchronous*
15
- * prologue of `app.fetch` and, on the lane where `fetch` has no request hook
16
- * to run, for the compiled route's own entry probe, which is still inside that
17
- * same synchronous frame, then clears it in `finally`, so the window closes
18
- * before the first suspension and cannot leak across concurrent requests.
19
- * A `.wrap()` HOC that replaces the request or defers calling `next()` past a
20
- * microtask simply misses the window, fetch handler falls back to eager check
21
- *
22
- * ## "A single shared slot that every request writes to, so a race condition?"
23
- *
24
- * It looks like one, but it can't be. Explained like you're five:
25
- *
26
- * JavaScript only ever does ONE thing at a time. When a request comes in, the
27
- * adapter writes its name on the whiteboard (`origin.request = request`),
28
- * walks it down the hall (`fetch(request)` the synchronous part, where the
29
- * one and only identity check happens, in `fetch`'s prologue, or at the
30
- * compiled route's entry when `fetch` had no hook to run first), and erases
31
- * the whiteboard `finally`
32
- * all in one uninterruptible turn. The next request's turn CANNOT start
33
- * until this turn is completely finished, because that is how the event loop
34
- * works: run-to-completion, no preemption. So request B can never see request
35
- * A's name on the board, by the time B's turn starts, the board was already
36
- * erased at the end of A's turn.
37
- *
38
- * "But the handler is async!" the `await`s inside it run LATER, in separate
39
- * turns. That is fine, because the check already happened: the verdict was
40
- * copied onto that request's own context (`'~sig'`) during the synchronous
41
- * part, and the code that runs after an `await` only ever reads the context,
42
- * never this slot.
43
- *
44
- * And if any assumption here ever breaks, a runtime that re-enters, a wrap
45
- * that delays, a future edit that sneaks an `await` in before the check the
46
- * comparison simply MISSES and the request falls back to eager arming, which
47
- * is the exact pre-optimization behavior. Every failure mode makes a request
48
- * slightly slower, never incorrect
49
- *
50
- * Adversarial proof, not just argument: `test/core/abort-race.test.ts` storms
51
- * concurrent servers, in-process `handle()` calls, request-substituting and
52
- * `next()`-delaying wraps at this slot and asserts zero misclassification.
53
- *
54
- * Never infer the Bun runtime from `fetch`'s second argument: Cloudflare
55
- * Workers pass `env` there.
56
- *
57
- * It's insanely stupid, but it works. As Bun is single-threaded and synchronous
58
- * so the `finally` block will always run before the next request comes in.
59
- */
60
3
  const origin = { request: void 0 };
61
4
 
62
5
  //#endregion
@@ -132,7 +132,7 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
132
132
  const typedSSE = !skipFormat && "sse" in generator && generator.sse === true;
133
133
  const sourceIterator = typeof generator.next === "function" ? generator : void 0;
134
134
  let init = typedSSE ? void 0 : generator.next?.();
135
- if (init instanceof Promise) init = await init;
135
+ if (init instanceof Promise || typeof init?.then === "function") init = await init;
136
136
  const sign = set?.["~sign"];
137
137
  if (sign) try {
138
138
  await sign(set);
@@ -430,12 +430,6 @@ const doneResult = {
430
430
  done: true,
431
431
  value: void 0
432
432
  };
433
- /**
434
- * Tee a returned `ReadableStream` so afterResponse, `defer()` and derive
435
- * dispose wait for it, as for a tee'd generator. A `bytes()` stream is left
436
- * alone (exact-stream contract), and so is a `Response`: reading its `.body`
437
- * would move it off Bun's native send path
438
- */
439
433
  function observeStream(source) {
440
434
  if (source.locked || require_utils.isByteStream(source)) return;
441
435
  const reader = source.getReader();
@@ -467,24 +461,6 @@ function observeStream(source) {
467
461
  value
468
462
  ];
469
463
  }
470
- /**
471
- * Split async source into `branches` independent iterators
472
- *
473
- * A producer drains the source ahead of consumers
474
- *
475
- * To prevent long/infinite stream, the unconsumed window is capped:
476
- * Consumed-by-every-branch entries are trimmed off the front
477
- * Producer backpressures whenever the window hits `cap` ENTRIES or
478
- * `capBytes` bytes, whichever comes first
479
- *
480
- * Streams below both caps buffer eagerly
481
- * Only streams exceeding one gate on the slowest consumer
482
- *
483
- * Branch 0 is the value consumer (response/client)
484
- * When `return()` (client abort / early exit), source is stopped
485
- * so the observer branches can still reach completion instead of spinning
486
- * an infinite source
487
- */
488
464
  function tee(source, branches = 2, cap = 64, capBytes = 1 << 22) {
489
465
  const iterator = source[Symbol.asyncIterator]?.() ?? source[Symbol.iterator]();
490
466
  const buffer = [];
@@ -132,7 +132,7 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
132
132
  const typedSSE = !skipFormat && "sse" in generator && generator.sse === true;
133
133
  const sourceIterator = typeof generator.next === "function" ? generator : void 0;
134
134
  let init = typedSSE ? void 0 : generator.next?.();
135
- if (init instanceof Promise) init = await init;
135
+ if (init instanceof Promise || typeof init?.then === "function") init = await init;
136
136
  const sign = set?.["~sign"];
137
137
  if (sign) try {
138
138
  await sign(set);
@@ -430,12 +430,6 @@ const doneResult = {
430
430
  done: true,
431
431
  value: void 0
432
432
  };
433
- /**
434
- * Tee a returned `ReadableStream` so afterResponse, `defer()` and derive
435
- * dispose wait for it, as for a tee'd generator. A `bytes()` stream is left
436
- * alone (exact-stream contract), and so is a `Response`: reading its `.body`
437
- * would move it off Bun's native send path
438
- */
439
433
  function observeStream(source) {
440
434
  if (source.locked || isByteStream(source)) return;
441
435
  const reader = source.getReader();
@@ -467,24 +461,6 @@ function observeStream(source) {
467
461
  value
468
462
  ];
469
463
  }
470
- /**
471
- * Split async source into `branches` independent iterators
472
- *
473
- * A producer drains the source ahead of consumers
474
- *
475
- * To prevent long/infinite stream, the unconsumed window is capped:
476
- * Consumed-by-every-branch entries are trimmed off the front
477
- * Producer backpressures whenever the window hits `cap` ENTRIES or
478
- * `capBytes` bytes, whichever comes first
479
- *
480
- * Streams below both caps buffer eagerly
481
- * Only streams exceeding one gate on the slowest consumer
482
- *
483
- * Branch 0 is the value consumer (response/client)
484
- * When `return()` (client abort / early exit), source is stopped
485
- * so the observer branches can still reach completion instead of spinning
486
- * an infinite source
487
- */
488
464
  function tee(source, branches = 2, cap = 64, capBytes = 1 << 22) {
489
465
  const iterator = source[Symbol.asyncIterator]?.() ?? source[Symbol.iterator]();
490
466
  const buffer = [];
@@ -86,7 +86,9 @@ function mapCompactResponse(response, request, owned) {
86
86
  case "String": return new Response(response, textHeaders);
87
87
  case "Array": return Response.json(response);
88
88
  case "ElysiaForm": return new Response(require_utils.formToFormData(response));
89
- case "Object": return Response.json(response);
89
+ case "Object":
90
+ if (typeof response?.next === "function") return handleStream(response, void 0, request, void 0, owned);
91
+ return Response.json(response);
90
92
  case "Number":
91
93
  case "Boolean": return new Response(String(response));
92
94
  case "ElysiaFile": return handleElysiaFile(response, void 0, request);
@@ -86,7 +86,9 @@ function mapCompactResponse(response, request, owned) {
86
86
  case "String": return new Response(response, textHeaders);
87
87
  case "Array": return Response.json(response);
88
88
  case "ElysiaForm": return new Response(formToFormData(response));
89
- case "Object": return Response.json(response);
89
+ case "Object":
90
+ if (typeof response?.next === "function") return handleStream(response, void 0, request, void 0, owned);
91
+ return Response.json(response);
90
92
  case "Number":
91
93
  case "Boolean": return new Response(String(response));
92
94
  case "ElysiaFile": return handleElysiaFile(response, void 0, request);
package/dist/base.d.ts CHANGED
@@ -84,8 +84,11 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
84
84
  '~router'?: Memoirist<CompiledHandler | number>;
85
85
  '~map'?: {
86
86
  [method: string]: {
87
- [path: string]: CompiledHandler;
87
+ [path: string]: CompiledHandler | number;
88
88
  } | undefined;
89
+ WS?: {
90
+ [path: string]: CompiledHandler;
91
+ };
89
92
  };
90
93
  '~routeTable'?: RouteTable;
91
94
  '~hasWS'?: boolean;
@@ -102,8 +105,9 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
102
105
  constructor(config?: ElysiaConfig<BasePath, Scope>);
103
106
  /**
104
107
  * Every registered route with its merged hooks.
105
- * Development shares a frozen cache. Production rebuilds the list so its
106
- * metadata can be released after startup.
108
+ * Development shares a frozen cache.
109
+ * Production rebuilds the list on every read
110
+ * so its metadata can be released after startup.
107
111
  */
108
112
  get routes(): PublicRoute[];
109
113
  /**