elysia 2.0.0-beta.26 → 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 (115) hide show
  1. package/dist/adapter/bun/index.js +0 -19
  2. package/dist/adapter/bun/index.mjs +0 -19
  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.js +21 -136
  10. package/dist/base.mjs +22 -137
  11. package/dist/compile/aot-capture.js +0 -2
  12. package/dist/compile/aot-capture.mjs +0 -2
  13. package/dist/compile/aot-emit.js +0 -13
  14. package/dist/compile/aot-emit.mjs +0 -13
  15. package/dist/compile/aot-reconstruct.js +0 -26
  16. package/dist/compile/aot-reconstruct.mjs +0 -26
  17. package/dist/compile/aot.js +0 -7
  18. package/dist/compile/aot.mjs +0 -7
  19. package/dist/compile/handler/frozen-validator.js +0 -11
  20. package/dist/compile/handler/frozen-validator.mjs +0 -11
  21. package/dist/compile/handler/index.js +0 -9
  22. package/dist/compile/handler/index.mjs +0 -9
  23. package/dist/compile/handler/jit.d.ts +5 -1
  24. package/dist/compile/handler/jit.js +2 -15
  25. package/dist/compile/handler/jit.mjs +3 -16
  26. package/dist/compile/handler/params.js +0 -3
  27. package/dist/compile/handler/params.mjs +0 -3
  28. package/dist/compile/handler/utils.js +3 -14
  29. package/dist/compile/handler/utils.mjs +3 -14
  30. package/dist/compiled.js +0 -9
  31. package/dist/compiled.mjs +0 -9
  32. package/dist/constants.js +18 -2
  33. package/dist/constants.mjs +18 -2
  34. package/dist/cookie/crypto.d.ts +1 -1
  35. package/dist/cookie/crypto.js +8 -7
  36. package/dist/cookie/crypto.mjs +8 -7
  37. package/dist/cookie/utils.js +0 -13
  38. package/dist/cookie/utils.mjs +0 -13
  39. package/dist/error.js +0 -73
  40. package/dist/error.mjs +0 -73
  41. package/dist/handler/error.js +0 -26
  42. package/dist/handler/error.mjs +0 -26
  43. package/dist/handler/fetch.js +12 -23
  44. package/dist/handler/fetch.mjs +12 -23
  45. package/dist/handler/utils.js +5 -5
  46. package/dist/handler/utils.mjs +5 -5
  47. package/dist/package.js +1 -1
  48. package/dist/package.mjs +1 -1
  49. package/dist/parse-query.js +0 -4
  50. package/dist/parse-query.mjs +0 -4
  51. package/dist/plugin/aot/bun.js +0 -26
  52. package/dist/plugin/aot/bun.mjs +0 -26
  53. package/dist/plugin/aot/core.d.ts +1 -1
  54. package/dist/plugin/aot/core.js +0 -32
  55. package/dist/plugin/aot/core.mjs +0 -32
  56. package/dist/plugin/aot/esbuild.js +0 -19
  57. package/dist/plugin/aot/esbuild.mjs +0 -19
  58. package/dist/plugin/aot/rspack-loader.js +0 -16
  59. package/dist/plugin/aot/rspack-loader.mjs +0 -16
  60. package/dist/plugin/aot/rspack.js +0 -14
  61. package/dist/plugin/aot/rspack.mjs +0 -14
  62. package/dist/plugin/aot/unplugin.js +0 -19
  63. package/dist/plugin/aot/unplugin.mjs +0 -19
  64. package/dist/plugin/aot/vite.js +0 -14
  65. package/dist/plugin/aot/vite.mjs +0 -14
  66. package/dist/plugin/trace.js +0 -16
  67. package/dist/plugin/trace.mjs +0 -16
  68. package/dist/plugin/websocket.js +0 -21
  69. package/dist/plugin/websocket.mjs +0 -21
  70. package/dist/schema-snapshot.js +0 -4
  71. package/dist/schema-snapshot.mjs +0 -4
  72. package/dist/sucrose.js +0 -4
  73. package/dist/sucrose.mjs +0 -4
  74. package/dist/trace.js +0 -1
  75. package/dist/trace.mjs +0 -1
  76. package/dist/type/bridge.js +22 -20
  77. package/dist/type/bridge.mjs +22 -20
  78. package/dist/type/coerce-plan.js +0 -1
  79. package/dist/type/coerce-plan.mjs +0 -1
  80. package/dist/type/coerce.js +0 -4
  81. package/dist/type/coerce.mjs +0 -4
  82. package/dist/type/constants.d.ts +1 -1
  83. package/dist/type/elysia/numeric-enum.js +0 -4
  84. package/dist/type/elysia/numeric-enum.mjs +0 -4
  85. package/dist/type/elysia/problem.js +0 -6
  86. package/dist/type/elysia/problem.mjs +0 -6
  87. package/dist/type/index.js +0 -11
  88. package/dist/type/index.mjs +0 -11
  89. package/dist/type/shared.js +0 -6
  90. package/dist/type/shared.mjs +0 -6
  91. package/dist/type/sync-require.js +0 -7
  92. package/dist/type/sync-require.mjs +0 -7
  93. package/dist/type/typebox-type.js +0 -6
  94. package/dist/type/typebox-type.mjs +0 -6
  95. package/dist/type/typebox-value.js +0 -3
  96. package/dist/type/typebox-value.mjs +0 -3
  97. package/dist/type/validator/default-precompute.js +0 -7
  98. package/dist/type/validator/default-precompute.mjs +0 -7
  99. package/dist/type/validator/exact-mirror-live.js +0 -7
  100. package/dist/type/validator/exact-mirror-live.mjs +1 -7
  101. package/dist/type/validator/exact-mirror.js +15 -7
  102. package/dist/type/validator/exact-mirror.mjs +15 -7
  103. package/dist/type/validator/index.js +1 -4
  104. package/dist/type/validator/index.mjs +1 -4
  105. package/dist/universal/file.js +0 -7
  106. package/dist/universal/file.mjs +0 -7
  107. package/dist/utils.js +10 -90
  108. package/dist/utils.mjs +10 -90
  109. package/dist/ws/context.js +14 -15
  110. package/dist/ws/context.mjs +14 -15
  111. package/dist/ws/parser.js +2 -2
  112. package/dist/ws/parser.mjs +2 -2
  113. package/dist/ws/route.js +112 -52
  114. package/dist/ws/route.mjs +113 -53
  115. 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,16 +83,6 @@ 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;
@@ -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,16 +83,6 @@ 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;
@@ -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.js CHANGED
@@ -66,17 +66,11 @@ const toModel = (key, value) => {
66
66
  value.$id ??= key;
67
67
  return value;
68
68
  };
69
- const hookKeys = new Set([
70
- ...require_utils.schemaProperties,
71
- ...require_utils.eventProperties,
72
- "detail",
73
- "tags"
74
- ]);
75
69
  const assertScope = (scope) => {
76
70
  if (scope !== void 0 && scope !== "local" && scope !== "plugin" && scope !== "global") throw new Error(`[Elysia] Invalid hook scope ${JSON.stringify(scope)}, expected 'local', 'plugin' or 'global' (1.x 'scoped' is 'plugin')`);
77
71
  };
78
72
  const hasHookKeys = (value) => {
79
- for (const key in value) if (hookKeys.has(key)) return true;
73
+ for (const key in value) if (require_utils.schemaProperties.has(key) || require_utils.eventProperties.has(key) || key === "detail" || key === "tags") return true;
80
74
  return false;
81
75
  };
82
76
  function assertSchemaShape(hook, where) {
@@ -95,13 +89,6 @@ function assertSchemaShape(hook, where) {
95
89
  throw new Error(`[Elysia] ${where}: "${key}" is not a schema — use t.Object({ ... })`);
96
90
  }
97
91
  }
98
- /**
99
- * Every key a single route must answer to, in registration order.
100
- *
101
- * `registerLoose` is the caller's call, not this helper's: HTTP gates it on
102
- * `canRegisterLoose` because its lookup retries the trailing slash, while WS
103
- * has no such retry and must own both directions outright
104
- */
105
92
  function expandPaths(path, needsEncode, registerLoose, explicit) {
106
93
  const variants = [path];
107
94
  if (needsEncode) {
@@ -172,31 +159,10 @@ var Elysia = class Elysia {
172
159
  get ["~programId"]() {
173
160
  return this;
174
161
  }
162
+ static #nextScopeChild;
175
163
  constructor(config) {
176
- /**
177
- * ! Don't tune
178
- *
179
- * Interesting JSC internal: These `= undefined` shouldn't be removed
180
- *
181
- * Inline slots live inside the object cell, so JSC must pick their
182
- * count at allocation before any initializer runs
183
- *
184
- * Its only signal is counting `this.x =` in the ctor body;
185
- * class-field initializers compile into a separate hidden function
186
- * the guess never sees
187
- *
188
- * The cell can't grow afterwards, so once inline slots run out every
189
- * later property spills into the butterfly, and capacity is by n^2
190
- *
191
- * Fields run first before ctor (here), so they take the inline slots
192
- * these assignments reserve
193
- *
194
- * Don't omitted field either for monomorphic access
195
- *
196
- * @see test/memory/instance-footprint.test.ts
197
- * Est. overflow 37 -> 21 = butterfly 64 -> 32 slots = 738B -> 450B;
198
- * reusing the app as its program identity brings the current cell to ~386B.
199
- **/
164
+ const scopeChild = Elysia.#nextScopeChild;
165
+ Elysia.#nextScopeChild = void 0;
200
166
  this["~config"] = config;
201
167
  this["~ext"] = void 0;
202
168
  this["~hookChain"] = void 0;
@@ -213,7 +179,7 @@ var Elysia = class Elysia {
213
179
  this["~compilerSession"] = void 0;
214
180
  this["~generation"] = void 0;
215
181
  this["~introspect"] = void 0;
216
- this["~scopeChild"] = void 0;
182
+ this["~scopeChild"] = scopeChild;
217
183
  this["~scopeChildren"] = void 0;
218
184
  if (config) {
219
185
  const { prefix, name, seed, adapter } = config;
@@ -222,12 +188,6 @@ var Elysia = class Elysia {
222
188
  if (adapter?.setup) this.#useFn(adapter.setup);
223
189
  } else this["~Prefix"] = void 0;
224
190
  }
225
- /**
226
- * Every registered route with its merged hooks.
227
- * Development shares a frozen cache.
228
- * Production rebuilds the list on every read
229
- * so its metadata can be released after startup.
230
- */
231
191
  get routes() {
232
192
  const generation = this["~generation"];
233
193
  if (this.#isCacheable && this.cachedGeneration === generation && this.cachedEpoch === mutationEpoch && this.cachedRoutes !== void 0) return this.cachedRoutes;
@@ -346,7 +306,7 @@ var Elysia = class Elysia {
346
306
  parent,
347
307
  refs,
348
308
  scope,
349
- owner: this,
309
+ owner: this["~scopeChild"] ? this : void 0,
350
310
  origin: this.hash
351
311
  };
352
312
  }
@@ -369,20 +329,6 @@ var Elysia = class Elysia {
369
329
  parsers[name] = fn;
370
330
  return this;
371
331
  }
372
- /**
373
- * ### setup | Life cycle event
374
- * Called after server is ready for serving
375
- *
376
- * ---
377
- * @example
378
- * ```typescript
379
- * new Elysia()
380
- * .setup(({ server }) => {
381
- * console.log("Running at ${server?.url}:${server?.port}")
382
- * })
383
- * .listen(3000)
384
- * ```
385
- */
386
332
  setup(handler) {
387
333
  this.#assertMutable("setup");
388
334
  (this.#ext.setup ??= []).push(...[].concat(handler));
@@ -482,14 +428,15 @@ var Elysia = class Elysia {
482
428
  const run = arguments[2];
483
429
  const isSchema = typeof schemaOrRun === "object";
484
430
  const callback = isSchema ? run : schemaOrRun;
485
- const child = new Elysia(this["~config"] || prefix ? {
431
+ const config = this["~config"] || prefix ? {
486
432
  ...this["~config"],
487
433
  name: void 0,
488
434
  seed: void 0,
489
435
  as: void 0,
490
436
  prefix
491
- } : void 0);
492
- child["~scopeChild"] = true;
437
+ } : void 0;
438
+ Elysia.#nextScopeChild = true;
439
+ const child = new Elysia(config);
493
440
  child.scopeParent = this;
494
441
  (this["~scopeChildren"] ??= []).push(child);
495
442
  const src = this["~ext"];
@@ -512,7 +459,6 @@ var Elysia = class Elysia {
512
459
  get #ext() {
513
460
  return this["~ext"] ??= require_utils.nullObject();
514
461
  }
515
- /** A group child starts or takes up its parent's table, see `group()` */
516
462
  #table(field) {
517
463
  return this.#ext[field] ??= this.scopeParent ? this.scopeParent.#table(field) : require_utils.nullObject();
518
464
  }
@@ -745,10 +691,6 @@ var Elysia = class Elysia {
745
691
  }
746
692
  return addedByThisCall;
747
693
  }
748
- /**
749
- * Reject a second, unrelated definition of an already-registered macro.
750
- * Split out of `#use` for the same reason as `#absorbExt`.
751
- */
752
694
  #assertMacroUnique(app, addedByThisCall) {
753
695
  const incomingMacro = app["~ext"]?.macro;
754
696
  const existingMacro = this["~ext"]?.macro;
@@ -763,14 +705,6 @@ var Elysia = class Elysia {
763
705
  throw new Error(`[Elysia] Macro "${macroName}" can be only define once`);
764
706
  }
765
707
  }
766
- /**
767
- * Merge the absorbed app's `~ext` container into this one.
768
- *
769
- * `#use` needed a frame wide enoughfor every local in this block + `#propagateHooks`
770
- * JSC pays that width on *entry*, before a single field is read
771
- *
772
- * Keep the cold blocks in their own frames.
773
- */
774
708
  #absorbExt(app, addedByThisCall) {
775
709
  const source = app["~ext"];
776
710
  const { decorator, store, headers, models, parser, macro, hoc, setup, cleanup, capability } = source;
@@ -857,10 +791,6 @@ var Elysia = class Elysia {
857
791
  }
858
792
  }
859
793
  }
860
- /**
861
- * Republish the absorbed app's plugin/global hooks onto this instance's chain.
862
- * Split out from #use, see #absorbExt
863
- */
864
794
  #propagateHooks(app, hookChain, addedByThisCall) {
865
795
  const nodes = useNodesBuffer;
866
796
  nodes.length = 0;
@@ -920,7 +850,7 @@ var Elysia = class Elysia {
920
850
  delete added.schema;
921
851
  if (dropRequest) delete added.request;
922
852
  }
923
- if (require_utils.isNotEmpty(added)) this.#pushChainNode(added, scope, registration, origin, app);
853
+ if (require_utils.isNotEmpty(added)) this.#pushChainNode(added, scope, registration, origin, scopeChild ? app : void 0);
924
854
  }
925
855
  if (lifting) liftedRequests.set(this, lifting);
926
856
  nodes.length = 0;
@@ -1090,10 +1020,6 @@ var Elysia = class Elysia {
1090
1020
  if (this["~generation"] === void 0) return;
1091
1021
  throw new Error(`[Elysia] .${api}() called after the app was sealed by its first request, listen or compile`);
1092
1022
  }
1093
- /**
1094
- * `#assertMutable` for the tables a group child shares with its parents:
1095
- * a child kept past its callback must not write into a sealed one
1096
- */
1097
1023
  #assertTableMutable(api) {
1098
1024
  for (let app = this; app; app = app.scopeParent) app.#assertMutable(api);
1099
1025
  }
@@ -1151,9 +1077,6 @@ var Elysia = class Elysia {
1151
1077
  return this;
1152
1078
  }
1153
1079
  }
1154
- /**
1155
- * Registered reusable models (via `.model()`), keyed by name.
1156
- */
1157
1080
  get models() {
1158
1081
  return this["~ext"]?.models ?? this.scopeParent?.models ?? require_utils.nullObject();
1159
1082
  }
@@ -1245,10 +1168,6 @@ var Elysia = class Elysia {
1245
1168
  OPTIONS: void 0
1246
1169
  };
1247
1170
  }
1248
- /**
1249
- * Force all route handlers to compile immediately (sets `precompile: true`
1250
- * on the config and triggers a fresh build of the fetch handler).
1251
- */
1252
1171
  compile() {
1253
1172
  this["~config"] ??= require_utils.nullObject();
1254
1173
  this["~config"].precompile = true;
@@ -1567,14 +1486,17 @@ var Elysia = class Elysia {
1567
1486
  break;
1568
1487
  }
1569
1488
  }
1570
- if (hasTypeBoxSchema) try {
1571
- require_type_bridge.warmTypebox();
1572
- } catch {}
1573
1489
  let markDuplicates = false;
1490
+ let eager = false;
1574
1491
  if (length) {
1575
1492
  const programId = this["~programId"];
1576
1493
  if (!require_compile_aot.Compiled.claim(programId, this["~aotFingerprint"] = require_compile_aot.createAotFingerprint())) require_compile_aot.Compiled.assertUncontested(programId);
1577
1494
  markDuplicates = require_compile_aot.Compiled.hasProgram(programId) || precompile !== true && require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv();
1495
+ if (hasTypeBoxSchema) try {
1496
+ require_type_bridge.warmTypebox();
1497
+ } catch {
1498
+ if (!require_type_bridge.isBridgeLive() && !require_compile_aot.Compiled.hasProgram(programId)) eager = markDuplicates = true;
1499
+ }
1578
1500
  }
1579
1501
  const isLoose = this["~config"]?.strictPath !== true;
1580
1502
  const collectExplicit = isLoose && table.hasLoose;
@@ -1624,7 +1546,7 @@ var Elysia = class Elysia {
1624
1546
  }
1625
1547
  continue;
1626
1548
  }
1627
- const lazy = !precompile && !this.compiled?.[i];
1549
+ const lazy = (eager ? (routeFlags & require_route_table.RouteFlag.ExactDuplicate) !== 0 : !precompile) && !this.compiled?.[i];
1628
1550
  if (lazy) {
1629
1551
  this.compiled ??= new Array(length);
1630
1552
  this.jitTable = table;
@@ -1634,14 +1556,14 @@ var Elysia = class Elysia {
1634
1556
  const registerLoose = isLoose && canRegisterLoose(routePath, isDynamic);
1635
1557
  if (!isDynamic && !needsEncode && !registerLoose) {
1636
1558
  const map = methods[routeMethod] ??= require_utils.nullObject();
1637
- map[routePath] = lazy ? ~i : this.handler(i, precompile, void 0, void 0, table);
1559
+ map[routePath] = lazy ? ~i : this.handler(i, true, void 0, void 0, table);
1638
1560
  continue;
1639
1561
  }
1640
1562
  const paths = expandPaths(routePath, needsEncode, registerLoose, registerLoose ? explicitPaths?.get(routeMethod) : void 0);
1641
1563
  if (isDynamic) {
1642
1564
  if (require_utils.isHTMLBundle(table.handler[i])) continue;
1643
1565
  const router = this["~router"] ??= new memoirist.default({ loosePath: isLoose });
1644
- const store = lazy ? i : this.handler(i, precompile, void 0, void 0, table);
1566
+ const store = lazy ? i : this.handler(i, true, void 0, void 0, table);
1645
1567
  for (let p = 0; p < paths.length; p++) router.add(routeMethod, paths[p], store);
1646
1568
  } else {
1647
1569
  const map = methods[routeMethod] ??= require_utils.nullObject();
@@ -1649,7 +1571,7 @@ var Elysia = class Elysia {
1649
1571
  method: routeMethod,
1650
1572
  paths
1651
1573
  };
1652
- const handler = lazy ? ~i : this.handler(i, precompile, void 0, void 0, table);
1574
+ const handler = lazy ? ~i : this.handler(i, true, void 0, void 0, table);
1653
1575
  for (let p = 0; p < paths.length; p++) map[paths[p]] = handler;
1654
1576
  }
1655
1577
  }
@@ -1680,21 +1602,6 @@ var Elysia = class Elysia {
1680
1602
  if (!require_compile_aot.Capture.isAotBuildEnv()) listen(this, options, callback);
1681
1603
  return this;
1682
1604
  }
1683
- /**
1684
- * Add a higher order function over Elysia.fetch
1685
- *
1686
- * @example
1687
- * ```ts
1688
- * const ctx = new AsyncLocalStorage<{ counter: number }>()
1689
- *
1690
- * new Elysia()
1691
- * .wrap(
1692
- * (fetch) => (request, ...rest) =>
1693
- * ctx.run({ counter: 0 }, () => fetch(request, ...rest))
1694
- * )
1695
- * .get('/', () => ctx.getStore())
1696
- * ```
1697
- */
1698
1605
  wrap(callback) {
1699
1606
  this.#assertMutable("wrap");
1700
1607
  if (this.fetchFn && !this.#pending) console.warn("[Elysia] .wrap() was called after the fetch handler was built");
@@ -1703,34 +1610,12 @@ var Elysia = class Elysia {
1703
1610
  (ext.hocOrigin ??= []).push(this.hash ?? {});
1704
1611
  return this;
1705
1612
  }
1706
- /**
1707
- * ### cleanup | Life cycle event
1708
- * Called after server stop serving request
1709
- *
1710
- * ---
1711
- * @example
1712
- * ```typescript
1713
- * new Elysia()
1714
- * .cleanup((app) => {
1715
- * closeDatabase()
1716
- * })
1717
- * ```
1718
- */
1719
1613
  cleanup(handler) {
1720
1614
  if (this["~ext"]?.cleanupEpoch?.(handler)) return this;
1721
1615
  this.#assertMutable("cleanup");
1722
1616
  (this.#ext.cleanup ??= []).push(...[].concat(handler));
1723
1617
  return this;
1724
1618
  }
1725
- /**
1726
- * Stop the underlying server (if any), then run every `cleanup` handler
1727
- *
1728
- * Awaiting `stop()` from inside a `setup`, `cleanup` or WebSocket lifecycle
1729
- * callback is only supported while that callback is still synchronous
1730
- *
1731
- * @param closeActiveConnections Pass `true` to terminate active
1732
- * transports. Omit (or pass `false`) to drain safely.
1733
- */
1734
1619
  stop(closeActiveConnections) {
1735
1620
  const stop = this["~ext"]?.stop;
1736
1621
  if (stop) return stop(closeActiveConnections);