elysia 2.0.0-beta.10 → 2.0.0-beta.12

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 (86) hide show
  1. package/dist/adapter/bun/index.js +8 -4
  2. package/dist/adapter/bun/index.mjs +8 -4
  3. package/dist/adapter/web-standard/handler.js +11 -5
  4. package/dist/adapter/web-standard/handler.mjs +11 -5
  5. package/dist/base.d.ts +5 -0
  6. package/dist/base.js +109 -4
  7. package/dist/base.mjs +111 -6
  8. package/dist/compile/aot-capture.d.ts +2 -13
  9. package/dist/compile/aot-capture.js +5 -27
  10. package/dist/compile/aot-capture.mjs +6 -24
  11. package/dist/compile/handler/descriptor.d.ts +2 -5
  12. package/dist/compile/handler/descriptor.js +2 -5
  13. package/dist/compile/handler/descriptor.mjs +2 -5
  14. package/dist/compile/handler/index.d.ts +3 -2
  15. package/dist/compile/handler/index.js +1 -0
  16. package/dist/compile/handler/index.mjs +1 -1
  17. package/dist/compile/handler/jit.js +14 -13
  18. package/dist/compile/handler/jit.mjs +14 -13
  19. package/dist/compile/handler/params.d.ts +1 -4
  20. package/dist/compile/handler/params.js +0 -2
  21. package/dist/compile/handler/params.mjs +1 -2
  22. package/dist/compile/handler/reconstruct.d.ts +1 -1
  23. package/dist/compile/handler/utils.d.ts +1 -2
  24. package/dist/compile/handler/utils.js +1 -2
  25. package/dist/compile/handler/utils.mjs +2 -2
  26. package/dist/context.js +3 -21
  27. package/dist/context.mjs +4 -22
  28. package/dist/cookie/error.d.ts +1 -2
  29. package/dist/cookie/error.js +6 -3
  30. package/dist/cookie/error.mjs +6 -3
  31. package/dist/cookie/index.d.ts +1 -2
  32. package/dist/cookie/index.js +0 -2
  33. package/dist/cookie/index.mjs +1 -2
  34. package/dist/cookie/utils.d.ts +2 -6
  35. package/dist/cookie/utils.js +0 -5
  36. package/dist/cookie/utils.mjs +2 -6
  37. package/dist/error.d.ts +110 -97
  38. package/dist/error.js +85 -45
  39. package/dist/error.mjs +84 -46
  40. package/dist/handler/error.js +9 -7
  41. package/dist/handler/error.mjs +9 -7
  42. package/dist/handler/fetch.js +14 -4
  43. package/dist/handler/fetch.mjs +15 -5
  44. package/dist/handler/utils.d.ts +3 -3
  45. package/dist/handler/utils.js +28 -16
  46. package/dist/handler/utils.mjs +28 -16
  47. package/dist/index.d.ts +3 -3
  48. package/dist/package.js +1 -1
  49. package/dist/package.mjs +1 -1
  50. package/dist/plugin/aot/core.d.ts +1 -7
  51. package/dist/plugin/aot/core.js +2 -18
  52. package/dist/plugin/aot/core.mjs +3 -17
  53. package/dist/trace.js +9 -5
  54. package/dist/trace.mjs +9 -5
  55. package/dist/type/bridge-live.d.ts +3 -2
  56. package/dist/type/bridge-live.js +4 -2
  57. package/dist/type/bridge-live.mjs +3 -2
  58. package/dist/type/bridge.d.ts +6 -4
  59. package/dist/type/bridge.js +9 -1
  60. package/dist/type/bridge.mjs +3 -1
  61. package/dist/type/coerce-plan.d.ts +2 -2
  62. package/dist/type/coerce-plan.js +0 -1
  63. package/dist/type/coerce-plan.mjs +2 -2
  64. package/dist/type/compat.js +2 -1
  65. package/dist/type/compat.mjs +3 -2
  66. package/dist/type/shared.d.ts +1 -3
  67. package/dist/type/shared.js +0 -3
  68. package/dist/type/shared.mjs +1 -3
  69. package/dist/type/typebox-value-live.d.ts +4 -3
  70. package/dist/type/typebox-value-live.js +5 -3
  71. package/dist/type/typebox-value-live.mjs +4 -3
  72. package/dist/type/typebox-value.d.ts +11 -7
  73. package/dist/type/typebox-value.js +2 -1
  74. package/dist/type/typebox-value.mjs +1 -1
  75. package/dist/type/validator/index.d.ts +1 -1
  76. package/dist/type/validator/validator-cache.d.ts +1 -1
  77. package/dist/types.d.ts +19 -17
  78. package/dist/universal/file.d.ts +1 -3
  79. package/dist/universal/file.js +0 -2
  80. package/dist/universal/file.mjs +1 -2
  81. package/dist/universal/is-production.d.ts +1 -2
  82. package/dist/universal/is-production.js +1 -3
  83. package/dist/universal/is-production.mjs +2 -3
  84. package/dist/ws/route.js +2 -6
  85. package/dist/ws/route.mjs +3 -7
  86. package/package.json +10 -6
@@ -233,6 +233,7 @@ const BunAdapter = require_adapter_index.createAdapter({
233
233
  };
234
234
  let startupFailure;
235
235
  let forceRequested = false;
236
+ const force = Promise.withResolvers();
236
237
  let forceDone = false;
237
238
  let nativeStopped = false;
238
239
  let abandoned = false;
@@ -362,7 +363,10 @@ const BunAdapter = require_adapter_index.createAdapter({
362
363
  const stop = (closeActiveConnections, failure) => {
363
364
  const reentrant = epoch.invoking;
364
365
  if (failure) startupFailure ??= failure;
365
- if (closeActiveConnections === true && !forceDone) forceRequested = true;
366
+ if (closeActiveConnections === true) {
367
+ force.resolve();
368
+ if (!forceDone) forceRequested = true;
369
+ }
366
370
  cancelled = true;
367
371
  clearAppServer(app, server);
368
372
  let outcome;
@@ -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
  }
@@ -461,10 +465,10 @@ const BunAdapter = require_adapter_index.createAdapter({
461
465
  publish();
462
466
  };
463
467
  try {
464
- ready = (modulesReady = app.modules).then(start);
465
- ready = ready.catch((error) => stop(true, { error }));
468
+ ready = (modulesReady = app.modules).then(start).catch((error) => stop(true, { error }));
466
469
  ready.catch((error) => {
467
470
  console.error("[Elysia] listen() failed:", error);
471
+ if (typeof process !== "undefined") process.exitCode = 1;
468
472
  });
469
473
  } catch (error) {
470
474
  stop(true, { error }).catch(console.error);
@@ -232,6 +232,7 @@ const BunAdapter = createAdapter({
232
232
  };
233
233
  let startupFailure;
234
234
  let forceRequested = false;
235
+ const force = Promise.withResolvers();
235
236
  let forceDone = false;
236
237
  let nativeStopped = false;
237
238
  let abandoned = false;
@@ -361,7 +362,10 @@ const BunAdapter = createAdapter({
361
362
  const stop = (closeActiveConnections, failure) => {
362
363
  const reentrant = epoch.invoking;
363
364
  if (failure) startupFailure ??= failure;
364
- if (closeActiveConnections === true && !forceDone) forceRequested = true;
365
+ if (closeActiveConnections === true) {
366
+ force.resolve();
367
+ if (!forceDone) forceRequested = true;
368
+ }
365
369
  cancelled = true;
366
370
  clearAppServer(app, server);
367
371
  let outcome;
@@ -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
  }
@@ -460,10 +464,10 @@ const BunAdapter = createAdapter({
460
464
  publish();
461
465
  };
462
466
  try {
463
- ready = (modulesReady = app.modules).then(start);
464
- ready = ready.catch((error) => stop(true, { error }));
467
+ ready = (modulesReady = app.modules).then(start).catch((error) => stop(true, { error }));
465
468
  ready.catch((error) => {
466
469
  console.error("[Elysia] listen() failed:", error);
470
+ if (typeof process !== "undefined") process.exitCode = 1;
467
471
  });
468
472
  } catch (error) {
469
473
  stop(true, { error }).catch(console.error);
@@ -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');
@@ -46,7 +47,7 @@ function mapResponseWithSet(response, set, request, owned) {
46
47
  case "File":
47
48
  case "Blob": return require_adapter_utils.handleFile(response, set, request);
48
49
  case "ElysiaStatus":
49
- set.status = response.code;
50
+ set.status = response.status;
50
51
  if (response.headers) Object.assign(require_adapter_utils.materializeSetHeaders(set), response.headers);
51
52
  return mapResponseWithSet(response.response, set, request, owned);
52
53
  case void 0: return response ? Response.json(response, set) : new Response(null, set);
@@ -58,11 +59,16 @@ 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) {
65
- set.status = response.code;
71
+ set.status = response.status;
66
72
  if (response.headers) Object.assign(set.headers, response.headers);
67
73
  return mapResponse(response.response, set, request, owned);
68
74
  }
@@ -84,7 +90,7 @@ function mapCompactResponse(response, request, owned) {
84
90
  case "File":
85
91
  case "Blob": return require_adapter_utils.handleFile(response, void 0, request);
86
92
  case "ElysiaStatus": return mapResponse(response.response, {
87
- status: response.code,
93
+ status: response.status,
88
94
  headers: response.headers ? Object.assign(require_utils.nullObject(), response.headers) : require_utils.nullObject()
89
95
  }, request, owned);
90
96
  case void 0: return response ? Response.json(response) : new Response("");
@@ -121,14 +127,14 @@ function mapFallback(response, set, request, owned = false) {
121
127
  if (response instanceof Promise) return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
122
128
  if (response instanceof Error) return errorToResponse(response, set, request, owned);
123
129
  if (response instanceof require_error.ElysiaStatus) if (set) {
124
- set.status = response.code;
130
+ set.status = response.status;
125
131
  if (response.headers) set.headers = {
126
132
  ...set.headers,
127
133
  ...response.headers
128
134
  };
129
135
  return mapResponse(response.response, set, request, owned);
130
136
  } else return mapResponse(response.response, {
131
- status: response.code,
137
+ status: response.status,
132
138
  headers: response.headers ? { ...response.headers } : {}
133
139
  }, request, owned);
134
140
  if (response instanceof require_universal_file.ElysiaFile) return handleElysiaFile(response, void 0, request);
@@ -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";
@@ -45,7 +46,7 @@ function mapResponseWithSet(response, set, request, owned) {
45
46
  case "File":
46
47
  case "Blob": return handleFile(response, set, request);
47
48
  case "ElysiaStatus":
48
- set.status = response.code;
49
+ set.status = response.status;
49
50
  if (response.headers) Object.assign(materializeSetHeaders(set), response.headers);
50
51
  return mapResponseWithSet(response.response, set, request, owned);
51
52
  case void 0: return response ? Response.json(response, set) : new Response(null, set);
@@ -57,11 +58,16 @@ 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) {
64
- set.status = response.code;
70
+ set.status = response.status;
65
71
  if (response.headers) Object.assign(set.headers, response.headers);
66
72
  return mapResponse(response.response, set, request, owned);
67
73
  }
@@ -83,7 +89,7 @@ function mapCompactResponse(response, request, owned) {
83
89
  case "File":
84
90
  case "Blob": return handleFile(response, void 0, request);
85
91
  case "ElysiaStatus": return mapResponse(response.response, {
86
- status: response.code,
92
+ status: response.status,
87
93
  headers: response.headers ? Object.assign(nullObject(), response.headers) : nullObject()
88
94
  }, request, owned);
89
95
  case void 0: return response ? Response.json(response) : new Response("");
@@ -120,14 +126,14 @@ function mapFallback(response, set, request, owned = false) {
120
126
  if (response instanceof Promise) return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
121
127
  if (response instanceof Error) return errorToResponse(response, set, request, owned);
122
128
  if (response instanceof ElysiaStatus) if (set) {
123
- set.status = response.code;
129
+ set.status = response.status;
124
130
  if (response.headers) set.headers = {
125
131
  ...set.headers,
126
132
  ...response.headers
127
133
  };
128
134
  return mapResponse(response.response, set, request, owned);
129
135
  } else return mapResponse(response.response, {
130
- status: response.code,
136
+ status: response.status,
131
137
  headers: response.headers ? { ...response.headers } : {}
132
138
  }, request, owned);
133
139
  if (response instanceof ElysiaFile) return handleElysiaFile(response, void 0, request);
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
  }
package/dist/base.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { isBun } from "./universal/constants.mjs";
2
2
  import { clonePlainDecorators, clonePlainDeep, coalesceSchemas, createErrorEventHandler, eventProperties, fnOrigin, fnv1a, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isEmpty, isNotEmpty, isRecordNumber, joinPath, macroOrigin, mapDeriveEntry, mergeDeep, mergeResponse, nullObject, pushField, schemaProperties, serializeMacroSeed, throwLifecycleErrors } from "./utils.mjs";
3
- import { Ref } from "./type/bridge.mjs";
3
+ import { Ref, warmTypebox } from "./type/bridge.mjs";
4
4
  import { isProduction } from "./universal/is-production.mjs";
5
5
  import { Capture, Compiled, beginCompilerSession, createAotFingerprint, endCompilerSession } from "./compile/aot.mjs";
6
6
  import { traceCapabilityRequired, wsCapabilityRequired } from "./generation.mjs";
7
- import { compileHandler, composeRouteHook, localMacroRoot, resolveLocalHook } from "./compile/handler/index.mjs";
7
+ import { chainResolver, compileHandler, composeRouteHook, localMacroRoot, resolveLocalHook } from "./compile/handler/index.mjs";
8
8
  import { RouteFlag, buildRouteTable, routeRow } from "./route-table.mjs";
9
9
  import { BunAdapter } from "./adapter/bun/index.mjs";
10
10
  import { clearContextCache } from "./context.mjs";
@@ -42,6 +42,42 @@ const mergeExtCallbacks = (target, incoming) => {
42
42
  index.indexedLength = target.length;
43
43
  };
44
44
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
45
+ const isSchemaValue = (value) => {
46
+ if (value == null || typeof value === "string") return true;
47
+ if (typeof value !== "object" && typeof value !== "function") return false;
48
+ return "~kind" in value || "~standard" in value || "~elyAcl" in value || typeof value.Check === "function";
49
+ };
50
+ const hookKeys = new Set([
51
+ ...schemaProperties,
52
+ ...eventProperties,
53
+ "detail",
54
+ "tags"
55
+ ]);
56
+ const hasHookKeys = (value) => {
57
+ for (const key in value) if (hookKeys.has(key)) return true;
58
+ return false;
59
+ };
60
+ const isPlainObject = (value) => {
61
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
62
+ const proto = Object.getPrototypeOf(value);
63
+ return proto === Object.prototype || proto === null;
64
+ };
65
+ const assertSchemaShape = (hook, where) => {
66
+ for (const key in hook) {
67
+ if (!schemaProperties.has(key)) continue;
68
+ const value = hook[key];
69
+ if (isSchemaValue(value)) continue;
70
+ if (key === "response" && isPlainObject(value)) {
71
+ let isStatusMap = true;
72
+ for (const status in value) if (status !== "default" && isNaN(+status) || !isSchemaValue(value[status])) {
73
+ isStatusMap = false;
74
+ break;
75
+ }
76
+ if (isStatusMap) continue;
77
+ }
78
+ throw new Error(`[Elysia] ${where}: "${key}" is not a schema — use t.Object({ ... })`);
79
+ }
80
+ };
45
81
  /**
46
82
  * Every key a single route must answer to, in registration order.
47
83
  *
@@ -170,6 +206,11 @@ var Elysia = class Elysia {
170
206
  if (adapter?.setup) this.#useFn(adapter.setup);
171
207
  } else this["~Prefix"] = void 0;
172
208
  }
209
+ /**
210
+ * Every registered route with its merged hooks.
211
+ * Development shares a frozen cache. Production rebuilds the list so its
212
+ * metadata can be released after startup.
213
+ */
173
214
  get routes() {
174
215
  const generation = this["~generation"];
175
216
  if (this.#isCacheable && this.cachedGeneration === generation && this.cachedEpoch === mutationEpoch && this.cachedRoutes !== void 0) return this.cachedRoutes;
@@ -189,7 +230,8 @@ var Elysia = class Elysia {
189
230
  hooks: merged
190
231
  };
191
232
  });
192
- if (this.#isCacheable) {
233
+ if (this.#isCacheable && !isProduction()) {
234
+ Object.freeze(routes);
193
235
  this.#cacheRoutes(generation);
194
236
  this.cachedRoutes = routes;
195
237
  }
@@ -425,6 +467,7 @@ var Elysia = class Elysia {
425
467
  return this;
426
468
  }
427
469
  #guard(scope, hook) {
470
+ if (hook) assertSchemaShape(hook, ".guard()");
428
471
  hook = snapshotHookSchemas(hook);
429
472
  hookToGuard(hook);
430
473
  const trackFn = (fn) => {
@@ -537,13 +580,17 @@ var Elysia = class Elysia {
537
580
  };
538
581
  return this;
539
582
  }
540
- macro(macro) {
583
+ macro(macro, definition) {
541
584
  this.#assertMutable("macro");
542
585
  if (typeof macro === "function") throw new Error("use `.macro({ name: fn })` instead of `.macro(fn)`");
586
+ if (definition != null || typeof macro === "string") throw new Error("[Elysia] .macro(name, definition) was removed in 2.0 — use .macro({ [name]: definition })");
543
587
  const m = this.#ensureMacroTable();
544
588
  const baseline = this.macroBaseline;
545
589
  for (const key in macro) {
546
- if (typeof macro[key] === "object") macro[key] = hookToGuard(macro[key]);
590
+ if (typeof macro[key] === "object") {
591
+ if (macro[key]) assertSchemaShape(macro[key], `.macro(${key})`);
592
+ macro[key] = hookToGuard(macro[key]);
593
+ }
547
594
  if (this.hash !== void 0 && !macroOrigin.has(macro[key])) macroOrigin.set(macro[key], this.hash);
548
595
  if (baseline?.has(key) && key in m && m[key] !== macro[key]) throw new Error(`[Elysia] Macro "${key}" can be only define once`);
549
596
  }
@@ -1044,6 +1091,8 @@ var Elysia = class Elysia {
1044
1091
  #add(method, path, hookOrFn, fn, hasHook = fn !== void 0) {
1045
1092
  if (this["~Prefix"]) path = joinPath(this["~Prefix"], path);
1046
1093
  else if (path && path.charCodeAt(0) !== 47) path = "/" + path;
1094
+ 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`);
1095
+ if (hasHook && hookOrFn) assertSchemaShape(hookOrFn, `${method} ${path}`);
1047
1096
  const handler = hasHook ? fn : hookOrFn;
1048
1097
  const hook = hasHook ? snapshotHookSchemas(hookOrFn) : void 0;
1049
1098
  const appHook = this["~hookChain"];
@@ -1395,6 +1444,56 @@ var Elysia = class Elysia {
1395
1444
  }
1396
1445
  return false;
1397
1446
  }
1447
+ static #slotHasTypeBox(value, models, record) {
1448
+ if (typeof value === "string") value = models?.[value];
1449
+ if (!value || typeof value !== "object") return false;
1450
+ if ("~kind" in value || "~elyAcl" in value) return true;
1451
+ if (!record || "~standard" in value) return false;
1452
+ for (const status in value) if (Elysia.#slotHasTypeBox(value[status], models)) return true;
1453
+ return false;
1454
+ }
1455
+ static #hookHasTypeBox(h, models) {
1456
+ if (!h || typeof h !== "object") return false;
1457
+ for (const key in h) {
1458
+ if (key === "schemas") {
1459
+ const schemas = h[key];
1460
+ if (Array.isArray(schemas)) {
1461
+ for (let s = 0; s < schemas.length; s++) if (Elysia.#hookHasTypeBox(schemas[s], models)) return true;
1462
+ }
1463
+ continue;
1464
+ }
1465
+ if (schemaProperties.has(key) && Elysia.#slotHasTypeBox(h[key], models, key === "response")) return true;
1466
+ }
1467
+ return false;
1468
+ }
1469
+ static #chainHasTypeBox(node, models, seen, resolve) {
1470
+ while (node) {
1471
+ if (seen.has(node)) return false;
1472
+ seen.add(node);
1473
+ if ("added" in node) {
1474
+ if (Elysia.#hookHasTypeBox(resolve ? resolve(node) : node.added, models)) return true;
1475
+ node = node.parent;
1476
+ } else {
1477
+ if (Elysia.#chainHasTypeBox(node.combine, models, seen, resolve)) return true;
1478
+ node = node.over;
1479
+ }
1480
+ }
1481
+ return false;
1482
+ }
1483
+ /**
1484
+ * Scan each route and its inherited hooks for TypeBox schemas.
1485
+ * This may warm TypeBox after a closer schema overrides it, avoiding a full
1486
+ * hook merge during build.
1487
+ */
1488
+ #hasTypeBoxSchema(table) {
1489
+ const models = this["~ext"]?.models;
1490
+ const resolve = chainResolver(this);
1491
+ const rootChain = this["~hookChain"];
1492
+ const seen = /* @__PURE__ */ new Set();
1493
+ const { localHook, appHook, inheritedChain, owner } = table;
1494
+ 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;
1495
+ return false;
1496
+ }
1398
1497
  #routeMayHaveModelRef(table, i) {
1399
1498
  const macroScope = table.macroScope?.get(i);
1400
1499
  const owner = table.owner[i];
@@ -1548,6 +1647,9 @@ var Elysia = class Elysia {
1548
1647
  const appChain = this["~hookChain"];
1549
1648
  if (this["~ext"]?.macro || this["~scopeChildren"] || appChain !== void 0 && appChain.refs) for (let i = 0; i < length; i++) this.#assertRouteModelRefs(routeRow(table, i), method[i]);
1550
1649
  else for (let i = 0; i < length; i++) if (this.#routeMayHaveModelRef(table, i)) this.#assertRouteModelRefs(routeRow(table, i), method[i]);
1650
+ if (length && this.#hasTypeBoxSchema(table)) try {
1651
+ warmTypebox();
1652
+ } catch {}
1551
1653
  let markDuplicates = false;
1552
1654
  if (length) {
1553
1655
  const programId = this["~programId"];
@@ -1637,7 +1739,10 @@ var Elysia = class Elysia {
1637
1739
  }
1638
1740
  listen(options, callback) {
1639
1741
  const listen = (this["~config"]?.adapter ?? (isBun ? BunAdapter : void 0))?.listen;
1640
- if (!listen) throw new Error("No adapter provided for listen()");
1742
+ if (!listen) {
1743
+ const deno = typeof Deno !== "undefined";
1744
+ 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"}`);
1745
+ }
1641
1746
  if (!Capture.isAotBuildEnv()) listen(this, options, callback);
1642
1747
  return this;
1643
1748
  }
@@ -1,24 +1,13 @@
1
- import { CaptureImpl, CapturedHandler, CapturedValidator, CompiledSnapshot } from "./aot.js";
1
+ import { CapturedHandler, CapturedValidator, CompiledSnapshot } from "./aot.js";
2
2
 
3
3
  //#region src/compile/aot-capture.d.ts
4
- declare function resetCompactErrorWarnings(): void;
5
4
  declare function beginValidatorCapture(): void;
6
5
  declare function abortCapture(): void;
7
6
  declare function endValidatorCapture(): CapturedValidator[];
8
7
  declare function endHandlerCapture(): CapturedHandler[];
9
- /** @internal deterministic session/capture assertions. */
10
- declare const getCompilerSessionDiagnostics: () => {
11
- active: boolean;
12
- appAttached: boolean;
13
- validators: number;
14
- handlers: number;
15
- sucrose: number;
16
- };
17
8
  /** @internal preserve registry around in-process AOT analysis */
18
9
  declare const snapshotCompiled: () => CompiledSnapshot;
19
10
  /** @internal restore registry after in-process AOT analysis */
20
11
  declare function restoreCompiled(snapshot: CompiledSnapshot): void;
21
- declare const impl: CaptureImpl;
22
- declare function installCaptureImpl(): void;
23
12
  //#endregion
24
- export { abortCapture, beginValidatorCapture, impl as captureImplementation, endHandlerCapture, endValidatorCapture, getCompilerSessionDiagnostics, installCaptureImpl, resetCompactErrorWarnings, restoreCompiled, snapshotCompiled };
13
+ export { abortCapture, beginValidatorCapture, endHandlerCapture, endValidatorCapture, restoreCompiled, snapshotCompiled };