elysia 2.0.0-beta.11 → 2.0.0-beta.13

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 (99) hide show
  1. package/dist/adapter/bun/index.js +36 -21
  2. package/dist/adapter/bun/index.mjs +36 -21
  3. package/dist/adapter/utils.js +1 -0
  4. package/dist/adapter/utils.mjs +1 -0
  5. package/dist/adapter/web-standard/handler.js +8 -2
  6. package/dist/adapter/web-standard/handler.mjs +8 -2
  7. package/dist/base.d.ts +5 -0
  8. package/dist/base.js +109 -4
  9. package/dist/base.mjs +111 -6
  10. package/dist/compile/aot-capture.d.ts +2 -13
  11. package/dist/compile/aot-capture.js +5 -27
  12. package/dist/compile/aot-capture.mjs +6 -24
  13. package/dist/compile/aot-emit.js +1 -1
  14. package/dist/compile/aot-emit.mjs +1 -1
  15. package/dist/compile/aot.d.ts +1 -1
  16. package/dist/compile/aot.js +2 -2
  17. package/dist/compile/aot.mjs +2 -2
  18. package/dist/compile/handler/descriptor.d.ts +4 -8
  19. package/dist/compile/handler/descriptor.js +8 -21
  20. package/dist/compile/handler/descriptor.mjs +9 -21
  21. package/dist/compile/handler/index.d.ts +4 -3
  22. package/dist/compile/handler/index.js +16 -6
  23. package/dist/compile/handler/index.mjs +16 -7
  24. package/dist/compile/handler/jit.js +82 -76
  25. package/dist/compile/handler/jit.mjs +83 -77
  26. package/dist/compile/handler/params.d.ts +1 -4
  27. package/dist/compile/handler/params.js +3 -4
  28. package/dist/compile/handler/params.mjs +5 -5
  29. package/dist/compile/handler/reconstruct.d.ts +1 -1
  30. package/dist/compile/handler/static-clone-resolver.d.ts +6 -0
  31. package/dist/compile/handler/static-clone-resolver.js +8 -0
  32. package/dist/compile/handler/static-clone-resolver.mjs +7 -0
  33. package/dist/compile/handler/utils.d.ts +7 -7
  34. package/dist/compile/handler/utils.js +24 -20
  35. package/dist/compile/handler/utils.mjs +24 -20
  36. package/dist/compile/utils.js +7 -4
  37. package/dist/compile/utils.mjs +7 -4
  38. package/dist/context.js +3 -21
  39. package/dist/context.mjs +4 -22
  40. package/dist/cookie/index.d.ts +1 -2
  41. package/dist/cookie/index.js +0 -2
  42. package/dist/cookie/index.mjs +1 -2
  43. package/dist/cookie/lib.js +2 -1
  44. package/dist/cookie/lib.mjs +2 -1
  45. package/dist/cookie/utils.d.ts +3 -7
  46. package/dist/cookie/utils.js +9 -28
  47. package/dist/cookie/utils.mjs +11 -29
  48. package/dist/error.d.ts +40 -83
  49. package/dist/error.js +18 -19
  50. package/dist/error.mjs +18 -20
  51. package/dist/handler/error.js +4 -5
  52. package/dist/handler/error.mjs +4 -5
  53. package/dist/handler/fetch.js +31 -17
  54. package/dist/handler/fetch.mjs +32 -18
  55. package/dist/handler/utils.d.ts +3 -3
  56. package/dist/handler/utils.js +28 -17
  57. package/dist/handler/utils.mjs +28 -17
  58. package/dist/index.d.ts +3 -3
  59. package/dist/package.js +1 -1
  60. package/dist/package.mjs +1 -1
  61. package/dist/plugin/aot/core.d.ts +3 -7
  62. package/dist/plugin/aot/core.js +8 -18
  63. package/dist/plugin/aot/core.mjs +9 -17
  64. package/dist/plugin/aot/hooks.js +5 -1
  65. package/dist/plugin/aot/hooks.mjs +5 -1
  66. package/dist/trace.js +23 -10
  67. package/dist/trace.mjs +23 -10
  68. package/dist/type/bridge-live.d.ts +3 -2
  69. package/dist/type/bridge-live.js +4 -2
  70. package/dist/type/bridge-live.mjs +3 -2
  71. package/dist/type/bridge.d.ts +6 -4
  72. package/dist/type/bridge.js +9 -1
  73. package/dist/type/bridge.mjs +3 -1
  74. package/dist/type/coerce-plan.d.ts +2 -2
  75. package/dist/type/coerce-plan.js +0 -1
  76. package/dist/type/coerce-plan.mjs +2 -2
  77. package/dist/type/compat.js +2 -1
  78. package/dist/type/compat.mjs +3 -2
  79. package/dist/type/constants.d.ts +1 -1
  80. package/dist/type/shared.d.ts +1 -3
  81. package/dist/type/shared.js +0 -3
  82. package/dist/type/shared.mjs +1 -3
  83. package/dist/type/typebox-value-live.d.ts +4 -3
  84. package/dist/type/typebox-value-live.js +5 -3
  85. package/dist/type/typebox-value-live.mjs +4 -3
  86. package/dist/type/typebox-value.d.ts +11 -7
  87. package/dist/type/typebox-value.js +2 -1
  88. package/dist/type/typebox-value.mjs +1 -1
  89. package/dist/type/validator/index.d.ts +1 -1
  90. package/dist/type/validator/validator-cache.d.ts +1 -1
  91. package/dist/types.d.ts +10 -17
  92. package/dist/universal/file.d.ts +1 -3
  93. package/dist/universal/file.js +0 -2
  94. package/dist/universal/file.mjs +1 -2
  95. package/dist/universal/is-production.d.ts +1 -2
  96. package/dist/universal/is-production.js +1 -3
  97. package/dist/universal/is-production.mjs +2 -3
  98. package/dist/utils.d.ts +0 -1
  99. package/package.json +13 -6
@@ -167,12 +167,10 @@ const BunAdapter = require_adapter_index.createAdapter({
167
167
  response: require_adapter_web_standard_index.WebStandardAdapter.response,
168
168
  listen(app, options, callback) {
169
169
  if (app.server || app["~ext"]?.stop) throw new Error("[Elysia] Cannot call listen() while a server or teardown is active");
170
+ const requestReady = Promise.withResolvers();
170
171
  function gatedFetch(request, server) {
171
- return live ? live(request, server) : Promise.resolve(ready).then(async () => {
172
- if (!live) {
173
- await startupShutdown;
174
- throw new Error("[Elysia] Server was stopped before it was ready");
175
- }
172
+ return live && !cancelled ? live(request, server) : requestReady.promise.then(() => {
173
+ if (cancelled || !live) return unavailableFetch();
176
174
  return live(request, server);
177
175
  });
178
176
  }
@@ -194,7 +192,6 @@ const BunAdapter = require_adapter_index.createAdapter({
194
192
  } : serve);
195
193
  let live;
196
194
  let cancelled = false;
197
- let startupShutdown;
198
195
  let shutdownAttempt;
199
196
  let ready;
200
197
  let wsLifecycle;
@@ -233,6 +230,7 @@ const BunAdapter = require_adapter_index.createAdapter({
233
230
  };
234
231
  let startupFailure;
235
232
  let forceRequested = false;
233
+ const force = Promise.withResolvers();
236
234
  let forceDone = false;
237
235
  let nativeStopped = false;
238
236
  let abandoned = false;
@@ -279,7 +277,9 @@ const BunAdapter = require_adapter_index.createAdapter({
279
277
  };
280
278
  const nativeStop = async (closeActiveConnections, errors) => {
281
279
  try {
282
- await server.stop(closeActiveConnections);
280
+ const stopping = server.stop(closeActiveConnections);
281
+ await (closeActiveConnections ? stopping : Promise.race([stopping, force.promise]));
282
+ if (!closeActiveConnections && forceRequested && !forceDone) return false;
283
283
  nativeStopped = true;
284
284
  if (closeActiveConnections) {
285
285
  forceDone = true;
@@ -362,8 +362,12 @@ const BunAdapter = require_adapter_index.createAdapter({
362
362
  const stop = (closeActiveConnections, failure) => {
363
363
  const reentrant = epoch.invoking;
364
364
  if (failure) startupFailure ??= failure;
365
- if (closeActiveConnections === true && !forceDone) forceRequested = true;
365
+ if (closeActiveConnections === true) {
366
+ force.resolve();
367
+ if (!forceDone) forceRequested = true;
368
+ }
366
369
  cancelled = true;
370
+ requestReady.resolve();
367
371
  clearAppServer(app, server);
368
372
  let outcome;
369
373
  if (shutdownAttempt) {
@@ -381,7 +385,7 @@ const BunAdapter = require_adapter_index.createAdapter({
381
385
  try {
382
386
  await Promise.resolve();
383
387
  if (modulesReady) try {
384
- await modulesReady;
388
+ await Promise.race([modulesReady, force.promise]);
385
389
  } catch (error) {
386
390
  startupFailure ??= { error };
387
391
  }
@@ -432,7 +436,6 @@ const BunAdapter = require_adapter_index.createAdapter({
432
436
  }
433
437
  })().then(resolve, reject);
434
438
  }
435
- if (!published) startupShutdown ??= outcome;
436
439
  if (!reentrant) return outcome;
437
440
  if (observedOutcome !== outcome) {
438
441
  observedOutcome = outcome;
@@ -444,27 +447,39 @@ const BunAdapter = require_adapter_index.createAdapter({
444
447
  };
445
448
  ext.stop = stop;
446
449
  const publish = () => {
447
- if (cancelled || app.server !== server) return;
448
- built ??= build();
449
- live = serve.fetch = withOrigin(built.fetch);
450
- published = true;
451
- if (built.websocket) serve.websocket = built.websocket;
452
- if (built.routes) serve.routes = built.routes;
453
- reloadServer(server, serve);
454
- if (callback) callback(server);
450
+ if (cancelled || app.server !== server) {
451
+ requestReady.resolve();
452
+ return;
453
+ }
454
+ try {
455
+ built ??= build();
456
+ live = serve.fetch = withOrigin(built.fetch);
457
+ published = true;
458
+ if (built.websocket) serve.websocket = built.websocket;
459
+ if (built.routes) serve.routes = built.routes;
460
+ reloadServer(server, serve);
461
+ if (callback) callback(server);
462
+ } catch (error) {
463
+ startupFailure ??= { error };
464
+ throw error;
465
+ }
466
+ requestReady.resolve();
455
467
  };
456
468
  const start = () => {
457
469
  modulesReady = void 0;
458
- if (cancelled || app.server !== server) return;
470
+ if (cancelled || app.server !== server) {
471
+ requestReady.resolve();
472
+ return;
473
+ }
459
474
  const setupReady = setup();
460
475
  if (setupReady && typeof setupReady.then === "function") return Promise.resolve(setupReady).then(publish);
461
476
  publish();
462
477
  };
463
478
  try {
464
- ready = (modulesReady = app.modules).then(start);
465
- ready = ready.catch((error) => stop(true, { error }));
479
+ ready = (modulesReady = app.modules).then(start).catch((error) => stop(true, { error }));
466
480
  ready.catch((error) => {
467
481
  console.error("[Elysia] listen() failed:", error);
482
+ if (typeof process !== "undefined") process.exitCode = 1;
468
483
  });
469
484
  } catch (error) {
470
485
  stop(true, { error }).catch(console.error);
@@ -166,12 +166,10 @@ const BunAdapter = createAdapter({
166
166
  response: WebStandardAdapter.response,
167
167
  listen(app, options, callback) {
168
168
  if (app.server || app["~ext"]?.stop) throw new Error("[Elysia] Cannot call listen() while a server or teardown is active");
169
+ const requestReady = Promise.withResolvers();
169
170
  function gatedFetch(request, server) {
170
- return live ? live(request, server) : Promise.resolve(ready).then(async () => {
171
- if (!live) {
172
- await startupShutdown;
173
- throw new Error("[Elysia] Server was stopped before it was ready");
174
- }
171
+ return live && !cancelled ? live(request, server) : requestReady.promise.then(() => {
172
+ if (cancelled || !live) return unavailableFetch();
175
173
  return live(request, server);
176
174
  });
177
175
  }
@@ -193,7 +191,6 @@ const BunAdapter = createAdapter({
193
191
  } : serve);
194
192
  let live;
195
193
  let cancelled = false;
196
- let startupShutdown;
197
194
  let shutdownAttempt;
198
195
  let ready;
199
196
  let wsLifecycle;
@@ -232,6 +229,7 @@ const BunAdapter = createAdapter({
232
229
  };
233
230
  let startupFailure;
234
231
  let forceRequested = false;
232
+ const force = Promise.withResolvers();
235
233
  let forceDone = false;
236
234
  let nativeStopped = false;
237
235
  let abandoned = false;
@@ -278,7 +276,9 @@ const BunAdapter = createAdapter({
278
276
  };
279
277
  const nativeStop = async (closeActiveConnections, errors) => {
280
278
  try {
281
- await server.stop(closeActiveConnections);
279
+ const stopping = server.stop(closeActiveConnections);
280
+ await (closeActiveConnections ? stopping : Promise.race([stopping, force.promise]));
281
+ if (!closeActiveConnections && forceRequested && !forceDone) return false;
282
282
  nativeStopped = true;
283
283
  if (closeActiveConnections) {
284
284
  forceDone = true;
@@ -361,8 +361,12 @@ const BunAdapter = createAdapter({
361
361
  const stop = (closeActiveConnections, failure) => {
362
362
  const reentrant = epoch.invoking;
363
363
  if (failure) startupFailure ??= failure;
364
- if (closeActiveConnections === true && !forceDone) forceRequested = true;
364
+ if (closeActiveConnections === true) {
365
+ force.resolve();
366
+ if (!forceDone) forceRequested = true;
367
+ }
365
368
  cancelled = true;
369
+ requestReady.resolve();
366
370
  clearAppServer(app, server);
367
371
  let outcome;
368
372
  if (shutdownAttempt) {
@@ -380,7 +384,7 @@ const BunAdapter = createAdapter({
380
384
  try {
381
385
  await Promise.resolve();
382
386
  if (modulesReady) try {
383
- await modulesReady;
387
+ await Promise.race([modulesReady, force.promise]);
384
388
  } catch (error) {
385
389
  startupFailure ??= { error };
386
390
  }
@@ -431,7 +435,6 @@ const BunAdapter = createAdapter({
431
435
  }
432
436
  })().then(resolve, reject);
433
437
  }
434
- if (!published) startupShutdown ??= outcome;
435
438
  if (!reentrant) return outcome;
436
439
  if (observedOutcome !== outcome) {
437
440
  observedOutcome = outcome;
@@ -443,27 +446,39 @@ const BunAdapter = createAdapter({
443
446
  };
444
447
  ext.stop = stop;
445
448
  const publish = () => {
446
- if (cancelled || app.server !== server) return;
447
- built ??= build();
448
- live = serve.fetch = withOrigin(built.fetch);
449
- published = true;
450
- if (built.websocket) serve.websocket = built.websocket;
451
- if (built.routes) serve.routes = built.routes;
452
- reloadServer(server, serve);
453
- if (callback) callback(server);
449
+ if (cancelled || app.server !== server) {
450
+ requestReady.resolve();
451
+ return;
452
+ }
453
+ try {
454
+ built ??= build();
455
+ live = serve.fetch = withOrigin(built.fetch);
456
+ published = true;
457
+ if (built.websocket) serve.websocket = built.websocket;
458
+ if (built.routes) serve.routes = built.routes;
459
+ reloadServer(server, serve);
460
+ if (callback) callback(server);
461
+ } catch (error) {
462
+ startupFailure ??= { error };
463
+ throw error;
464
+ }
465
+ requestReady.resolve();
454
466
  };
455
467
  const start = () => {
456
468
  modulesReady = void 0;
457
- if (cancelled || app.server !== server) return;
469
+ if (cancelled || app.server !== server) {
470
+ requestReady.resolve();
471
+ return;
472
+ }
458
473
  const setupReady = setup();
459
474
  if (setupReady && typeof setupReady.then === "function") return Promise.resolve(setupReady).then(publish);
460
475
  publish();
461
476
  };
462
477
  try {
463
- ready = (modulesReady = app.modules).then(start);
464
- ready = ready.catch((error) => stop(true, { error }));
478
+ ready = (modulesReady = app.modules).then(start).catch((error) => stop(true, { error }));
465
479
  ready.catch((error) => {
466
480
  console.error("[Elysia] listen() failed:", error);
481
+ if (typeof process !== "undefined") process.exitCode = 1;
467
482
  });
468
483
  } catch (error) {
469
484
  stop(true, { error }).catch(console.error);
@@ -145,6 +145,7 @@ function enqueueBinaryChunk(controller, chunk) {
145
145
  }
146
146
  function createStreamHandler({ mapResponse, mapCompactResponse }) {
147
147
  return async (generator, set, request, skipFormat, owned = false) => {
148
+ if (request === null) return void 0;
148
149
  if (require_utils.isByteStream(generator)) {
149
150
  if (generator.locked) throw new TypeError("Cannot transfer a locked or consumed byte stream");
150
151
  if (set) {
@@ -144,6 +144,7 @@ function enqueueBinaryChunk(controller, chunk) {
144
144
  }
145
145
  function createStreamHandler({ mapResponse, mapCompactResponse }) {
146
146
  return async (generator, set, request, skipFormat, owned = false) => {
147
+ if (request === null) return void 0;
147
148
  if (isByteStream(generator)) {
148
149
  if (generator.locked) throw new TypeError("Cannot transfer a locked or consumed byte stream");
149
150
  if (set) {
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_universal_constants = require('../../universal/constants.js');
3
3
  const require_universal_file = require('../../universal/file.js');
4
4
  const require_utils = require('../../utils.js');
5
+ const require_universal_is_production = require('../../universal/is-production.js');
5
6
  const require_error = require('../../error.js');
6
7
  const require_adapter_default_headers = require('../default-headers.js');
7
8
  const require_adapter_utils = require('../utils.js');
@@ -58,7 +59,12 @@ function mapResponseWithSet(response, set, request, owned) {
58
59
  default: return mapFallback(response, set, request, owned);
59
60
  }
60
61
  }
62
+ const checkRemovedSetRedirect = !require_universal_is_production.isProduction();
61
63
  function mapResponse(response, set, request, owned) {
64
+ if (checkRemovedSetRedirect && Object.hasOwn(set, "redirect") && !require_universal_is_production.isProduction()) {
65
+ delete set.redirect;
66
+ throw new Error("[Elysia] set.redirect was removed in 2.0 — return redirect(url) instead");
67
+ }
62
68
  const headers = set.headers;
63
69
  if (set.status !== void 0 || set.cookie || headers["\0"] === headers || require_utils.isNotEmpty(headers)) return mapResponseWithSet(response, set, request, owned);
64
70
  if (response instanceof require_error.ElysiaStatus) {
@@ -131,11 +137,11 @@ function mapFallback(response, set, request, owned = false) {
131
137
  status: response.status,
132
138
  headers: response.headers ? { ...response.headers } : {}
133
139
  }, request, owned);
134
- if (response instanceof require_universal_file.ElysiaFile) return handleElysiaFile(response, void 0, request);
140
+ if (response instanceof require_universal_file.ElysiaFile) return handleElysiaFile(response, set, request);
135
141
  if (require_utils.isElysiaForm(response)) return new Response(require_utils.formToFormData(response), set);
136
142
  if (typeof response?.next === "function" || response instanceof ReadableStream) return handleStream(response, set, request, void 0, owned);
137
143
  if (typeof response?.then === "function") return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
138
- if (Array.isArray(response)) return Response.json(response);
144
+ if (Array.isArray(response)) return Response.json(response, set);
139
145
  if (typeof response?.toResponse === "function") return set ? mapResponse(response.toResponse(), set, request, owned) : mapCompactResponse(response.toResponse(), request, owned);
140
146
  if (responseTag(response) === "Cookie" && require_cookie_cookie.Cookie.isCookie(response)) return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
141
147
  return new Response(response, set);
@@ -1,6 +1,7 @@
1
1
  import { isBun } from "../../universal/constants.mjs";
2
2
  import { ElysiaFile, mime } from "../../universal/file.mjs";
3
3
  import { formToFormData, isElysiaForm, isNotEmpty, nullObject } from "../../utils.mjs";
4
+ import { isProduction } from "../../universal/is-production.mjs";
4
5
  import { ElysiaStatus, PROBLEM_JSON, internalServerErrorBody } from "../../error.mjs";
5
6
  import { defaultHeaders } from "../default-headers.mjs";
6
7
  import { createResponseHandler, createStreamHandler, handleFile, handleSet, materializeSetHeaders } from "../utils.mjs";
@@ -57,7 +58,12 @@ function mapResponseWithSet(response, set, request, owned) {
57
58
  default: return mapFallback(response, set, request, owned);
58
59
  }
59
60
  }
61
+ const checkRemovedSetRedirect = !isProduction();
60
62
  function mapResponse(response, set, request, owned) {
63
+ if (checkRemovedSetRedirect && Object.hasOwn(set, "redirect") && !isProduction()) {
64
+ delete set.redirect;
65
+ throw new Error("[Elysia] set.redirect was removed in 2.0 — return redirect(url) instead");
66
+ }
61
67
  const headers = set.headers;
62
68
  if (set.status !== void 0 || set.cookie || headers["\0"] === headers || isNotEmpty(headers)) return mapResponseWithSet(response, set, request, owned);
63
69
  if (response instanceof ElysiaStatus) {
@@ -130,11 +136,11 @@ function mapFallback(response, set, request, owned = false) {
130
136
  status: response.status,
131
137
  headers: response.headers ? { ...response.headers } : {}
132
138
  }, request, owned);
133
- if (response instanceof ElysiaFile) return handleElysiaFile(response, void 0, request);
139
+ if (response instanceof ElysiaFile) return handleElysiaFile(response, set, request);
134
140
  if (isElysiaForm(response)) return new Response(formToFormData(response), set);
135
141
  if (typeof response?.next === "function" || response instanceof ReadableStream) return handleStream(response, set, request, void 0, owned);
136
142
  if (typeof response?.then === "function") return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
137
- if (Array.isArray(response)) return Response.json(response);
143
+ if (Array.isArray(response)) return Response.json(response, set);
138
144
  if (typeof response?.toResponse === "function") return set ? mapResponse(response.toResponse(), set, request, owned) : mapCompactResponse(response.toResponse(), request, owned);
139
145
  if (responseTag(response) === "Cookie" && Cookie.isCookie(response)) return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
140
146
  return new Response(response, set);
package/dist/base.d.ts CHANGED
@@ -101,6 +101,11 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
101
101
  '~scopeChild'?: boolean;
102
102
  '~scopeChildren'?: AnyElysia[];
103
103
  constructor(config?: ElysiaConfig<BasePath, Scope>);
104
+ /**
105
+ * Every registered route with its merged hooks.
106
+ * Development shares a frozen cache. Production rebuilds the list so its
107
+ * metadata can be released after startup.
108
+ */
104
109
  get routes(): PublicRoute[];
105
110
  /**
106
111
  * ### decorate
package/dist/base.js CHANGED
@@ -45,6 +45,42 @@ const mergeExtCallbacks = (target, incoming) => {
45
45
  index.indexedLength = target.length;
46
46
  };
47
47
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
48
+ const isSchemaValue = (value) => {
49
+ if (value == null || typeof value === "string") return true;
50
+ if (typeof value !== "object" && typeof value !== "function") return false;
51
+ return "~kind" in value || "~standard" in value || "~elyAcl" in value || typeof value.Check === "function";
52
+ };
53
+ const hookKeys = new Set([
54
+ ...require_utils.schemaProperties,
55
+ ...require_utils.eventProperties,
56
+ "detail",
57
+ "tags"
58
+ ]);
59
+ const hasHookKeys = (value) => {
60
+ for (const key in value) if (hookKeys.has(key)) return true;
61
+ return false;
62
+ };
63
+ const isPlainObject = (value) => {
64
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
65
+ const proto = Object.getPrototypeOf(value);
66
+ return proto === Object.prototype || proto === null;
67
+ };
68
+ const assertSchemaShape = (hook, where) => {
69
+ for (const key in hook) {
70
+ if (!require_utils.schemaProperties.has(key)) continue;
71
+ const value = hook[key];
72
+ if (isSchemaValue(value)) continue;
73
+ if (key === "response" && isPlainObject(value)) {
74
+ let isStatusMap = true;
75
+ for (const status in value) if (status !== "default" && isNaN(+status) || !isSchemaValue(value[status])) {
76
+ isStatusMap = false;
77
+ break;
78
+ }
79
+ if (isStatusMap) continue;
80
+ }
81
+ throw new Error(`[Elysia] ${where}: "${key}" is not a schema — use t.Object({ ... })`);
82
+ }
83
+ };
48
84
  /**
49
85
  * Every key a single route must answer to, in registration order.
50
86
  *
@@ -173,6 +209,11 @@ var Elysia = class Elysia {
173
209
  if (adapter?.setup) this.#useFn(adapter.setup);
174
210
  } else this["~Prefix"] = void 0;
175
211
  }
212
+ /**
213
+ * Every registered route with its merged hooks.
214
+ * Development shares a frozen cache. Production rebuilds the list so its
215
+ * metadata can be released after startup.
216
+ */
176
217
  get routes() {
177
218
  const generation = this["~generation"];
178
219
  if (this.#isCacheable && this.cachedGeneration === generation && this.cachedEpoch === mutationEpoch && this.cachedRoutes !== void 0) return this.cachedRoutes;
@@ -192,7 +233,8 @@ var Elysia = class Elysia {
192
233
  hooks: merged
193
234
  };
194
235
  });
195
- if (this.#isCacheable) {
236
+ if (this.#isCacheable && !require_universal_is_production.isProduction()) {
237
+ Object.freeze(routes);
196
238
  this.#cacheRoutes(generation);
197
239
  this.cachedRoutes = routes;
198
240
  }
@@ -428,6 +470,7 @@ var Elysia = class Elysia {
428
470
  return this;
429
471
  }
430
472
  #guard(scope, hook) {
473
+ if (hook) assertSchemaShape(hook, ".guard()");
431
474
  hook = require_schema_snapshot.snapshotHookSchemas(hook);
432
475
  require_utils.hookToGuard(hook);
433
476
  const trackFn = (fn) => {
@@ -540,13 +583,17 @@ var Elysia = class Elysia {
540
583
  };
541
584
  return this;
542
585
  }
543
- macro(macro) {
586
+ macro(macro, definition) {
544
587
  this.#assertMutable("macro");
545
588
  if (typeof macro === "function") throw new Error("use `.macro({ name: fn })` instead of `.macro(fn)`");
589
+ if (definition != null || typeof macro === "string") throw new Error("[Elysia] .macro(name, definition) was removed in 2.0 — use .macro({ [name]: definition })");
546
590
  const m = this.#ensureMacroTable();
547
591
  const baseline = this.macroBaseline;
548
592
  for (const key in macro) {
549
- if (typeof macro[key] === "object") macro[key] = require_utils.hookToGuard(macro[key]);
593
+ if (typeof macro[key] === "object") {
594
+ if (macro[key]) assertSchemaShape(macro[key], `.macro(${key})`);
595
+ macro[key] = require_utils.hookToGuard(macro[key]);
596
+ }
550
597
  if (this.hash !== void 0 && !require_utils.macroOrigin.has(macro[key])) require_utils.macroOrigin.set(macro[key], this.hash);
551
598
  if (baseline?.has(key) && key in m && m[key] !== macro[key]) throw new Error(`[Elysia] Macro "${key}" can be only define once`);
552
599
  }
@@ -1047,6 +1094,8 @@ var Elysia = class Elysia {
1047
1094
  #add(method, path, hookOrFn, fn, hasHook = fn !== void 0) {
1048
1095
  if (this["~Prefix"]) path = require_utils.joinPath(this["~Prefix"], path);
1049
1096
  else if (path && path.charCodeAt(0) !== 47) path = "/" + path;
1097
+ if (typeof hookOrFn === "function" && !hasHookKeys(hookOrFn) && isPlainObject(fn) && hasHookKeys(fn)) throw new Error(`[Elysia] .${method === "*" ? "all" : method.toLowerCase()}('${path}', handler, hook) is the 1.x order; Elysia 2 takes (path, hook, handler) — see the 2.0 migration guide`);
1098
+ if (hasHook && hookOrFn) assertSchemaShape(hookOrFn, `${method} ${path}`);
1050
1099
  const handler = hasHook ? fn : hookOrFn;
1051
1100
  const hook = hasHook ? require_schema_snapshot.snapshotHookSchemas(hookOrFn) : void 0;
1052
1101
  const appHook = this["~hookChain"];
@@ -1398,6 +1447,56 @@ var Elysia = class Elysia {
1398
1447
  }
1399
1448
  return false;
1400
1449
  }
1450
+ static #slotHasTypeBox(value, models, record) {
1451
+ if (typeof value === "string") value = models?.[value];
1452
+ if (!value || typeof value !== "object") return false;
1453
+ if ("~kind" in value || "~elyAcl" in value) return true;
1454
+ if (!record || "~standard" in value) return false;
1455
+ for (const status in value) if (Elysia.#slotHasTypeBox(value[status], models)) return true;
1456
+ return false;
1457
+ }
1458
+ static #hookHasTypeBox(h, models) {
1459
+ if (!h || typeof h !== "object") return false;
1460
+ for (const key in h) {
1461
+ if (key === "schemas") {
1462
+ const schemas = h[key];
1463
+ if (Array.isArray(schemas)) {
1464
+ for (let s = 0; s < schemas.length; s++) if (Elysia.#hookHasTypeBox(schemas[s], models)) return true;
1465
+ }
1466
+ continue;
1467
+ }
1468
+ if (require_utils.schemaProperties.has(key) && Elysia.#slotHasTypeBox(h[key], models, key === "response")) return true;
1469
+ }
1470
+ return false;
1471
+ }
1472
+ static #chainHasTypeBox(node, models, seen, resolve) {
1473
+ while (node) {
1474
+ if (seen.has(node)) return false;
1475
+ seen.add(node);
1476
+ if ("added" in node) {
1477
+ if (Elysia.#hookHasTypeBox(resolve ? resolve(node) : node.added, models)) return true;
1478
+ node = node.parent;
1479
+ } else {
1480
+ if (Elysia.#chainHasTypeBox(node.combine, models, seen, resolve)) return true;
1481
+ node = node.over;
1482
+ }
1483
+ }
1484
+ return false;
1485
+ }
1486
+ /**
1487
+ * Scan each route and its inherited hooks for TypeBox schemas.
1488
+ * This may warm TypeBox after a closer schema overrides it, avoiding a full
1489
+ * hook merge during build.
1490
+ */
1491
+ #hasTypeBoxSchema(table) {
1492
+ const models = this["~ext"]?.models;
1493
+ const resolve = require_compile_handler_index.chainResolver(this);
1494
+ const rootChain = this["~hookChain"];
1495
+ const seen = /* @__PURE__ */ new Set();
1496
+ const { localHook, appHook, inheritedChain, owner } = table;
1497
+ for (let i = 0; i < table.length; i++) if (Elysia.#hookHasTypeBox(localHook[i], models) || Elysia.#chainHasTypeBox(appHook[i], models, seen, resolve) || Elysia.#chainHasTypeBox(inheritedChain[i], models, seen, resolve) || owner[i] !== this && Elysia.#chainHasTypeBox(rootChain, models, seen, resolve)) return true;
1498
+ return false;
1499
+ }
1401
1500
  #routeMayHaveModelRef(table, i) {
1402
1501
  const macroScope = table.macroScope?.get(i);
1403
1502
  const owner = table.owner[i];
@@ -1551,6 +1650,9 @@ var Elysia = class Elysia {
1551
1650
  const appChain = this["~hookChain"];
1552
1651
  if (this["~ext"]?.macro || this["~scopeChildren"] || appChain !== void 0 && appChain.refs) for (let i = 0; i < length; i++) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1553
1652
  else for (let i = 0; i < length; i++) if (this.#routeMayHaveModelRef(table, i)) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1653
+ if (length && this.#hasTypeBoxSchema(table)) try {
1654
+ require_type_bridge.warmTypebox();
1655
+ } catch {}
1554
1656
  let markDuplicates = false;
1555
1657
  if (length) {
1556
1658
  const programId = this["~programId"];
@@ -1640,7 +1742,10 @@ var Elysia = class Elysia {
1640
1742
  }
1641
1743
  listen(options, callback) {
1642
1744
  const listen = (this["~config"]?.adapter ?? (require_universal_constants.isBun ? require_adapter_bun_index.BunAdapter : void 0))?.listen;
1643
- if (!listen) throw new Error("No adapter provided for listen()");
1745
+ if (!listen) {
1746
+ const deno = typeof Deno !== "undefined";
1747
+ throw new Error(`[Elysia] listen() requires an adapter on ${deno ? "Deno" : typeof process === "undefined" ? "this runtime" : "Node.js"} — https://elysiajs.com/integrations/${deno ? "deno" : "node"}`);
1748
+ }
1644
1749
  if (!require_compile_aot.Capture.isAotBuildEnv()) listen(this, options, callback);
1645
1750
  return this;
1646
1751
  }