elysia 2.0.0-beta.13 → 2.0.0-beta.15

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 (90) hide show
  1. package/dist/adapter/bun/index.d.ts +4 -2
  2. package/dist/adapter/bun/index.js +35 -38
  3. package/dist/adapter/bun/index.mjs +35 -39
  4. package/dist/adapter/utils.js +16 -21
  5. package/dist/adapter/utils.mjs +16 -21
  6. package/dist/base.d.ts +28 -0
  7. package/dist/base.js +109 -59
  8. package/dist/base.mjs +110 -60
  9. package/dist/compile/aot.js +14 -21
  10. package/dist/compile/aot.mjs +14 -21
  11. package/dist/compile/handler/frozen-validator.js +51 -42
  12. package/dist/compile/handler/frozen-validator.mjs +51 -42
  13. package/dist/compile/handler/index.js +57 -50
  14. package/dist/compile/handler/index.mjs +57 -50
  15. package/dist/compile/handler/jit.js +36 -26
  16. package/dist/compile/handler/jit.mjs +37 -27
  17. package/dist/compile/handler/params.js +2 -0
  18. package/dist/compile/handler/params.mjs +3 -1
  19. package/dist/compile/handler/utils.d.ts +2 -2
  20. package/dist/compile/handler/utils.js +115 -111
  21. package/dist/compile/handler/utils.mjs +115 -113
  22. package/dist/context.d.ts +3 -1
  23. package/dist/context.js +2 -0
  24. package/dist/context.mjs +3 -1
  25. package/dist/cookie/config.js +6 -7
  26. package/dist/cookie/config.mjs +6 -7
  27. package/dist/error.d.ts +6 -3
  28. package/dist/error.js +26 -28
  29. package/dist/error.mjs +26 -28
  30. package/dist/handler/fetch.js +2 -1
  31. package/dist/handler/fetch.mjs +3 -2
  32. package/dist/handler/utils.d.ts +3 -1
  33. package/dist/handler/utils.js +19 -1
  34. package/dist/handler/utils.mjs +17 -1
  35. package/dist/index.d.ts +3 -3
  36. package/dist/package.js +1 -1
  37. package/dist/package.mjs +1 -1
  38. package/dist/plugin/aot/core.js +4 -0
  39. package/dist/plugin/aot/core.mjs +4 -0
  40. package/dist/sucrose.js +60 -59
  41. package/dist/sucrose.mjs +60 -59
  42. package/dist/trace.js +6 -9
  43. package/dist/trace.mjs +6 -9
  44. package/dist/type/coerce-plan.js +41 -36
  45. package/dist/type/coerce-plan.mjs +41 -36
  46. package/dist/type/coerce.d.ts +1 -1
  47. package/dist/type/constants.d.ts +1 -1
  48. package/dist/type/elysia/cookie.js +8 -17
  49. package/dist/type/elysia/cookie.mjs +8 -17
  50. package/dist/type/elysia/date.js +6 -7
  51. package/dist/type/elysia/date.mjs +6 -7
  52. package/dist/type/elysia/numeric.js +8 -9
  53. package/dist/type/elysia/numeric.mjs +8 -9
  54. package/dist/type/elysia/problem.d.ts +20 -0
  55. package/dist/type/elysia/problem.js +25 -0
  56. package/dist/type/elysia/problem.mjs +24 -0
  57. package/dist/type/exports.d.ts +3 -2
  58. package/dist/type/exports.js +2 -0
  59. package/dist/type/exports.mjs +2 -1
  60. package/dist/type/index.js +2 -0
  61. package/dist/type/index.mjs +2 -0
  62. package/dist/type/validator/clean-safe.d.ts +1 -2
  63. package/dist/type/validator/clean-safe.js +0 -2
  64. package/dist/type/validator/clean-safe.mjs +1 -2
  65. package/dist/type/validator/custom-error.js +37 -26
  66. package/dist/type/validator/custom-error.mjs +37 -26
  67. package/dist/type/validator/default-precompute.js +24 -26
  68. package/dist/type/validator/default-precompute.mjs +24 -26
  69. package/dist/type/validator/exact-mirror.js +6 -8
  70. package/dist/type/validator/exact-mirror.mjs +6 -8
  71. package/dist/type/validator/index.js +64 -66
  72. package/dist/type/validator/index.mjs +65 -67
  73. package/dist/type/validator/validator-cache.d.ts +2 -2
  74. package/dist/type/validator/validator-cache.js +7 -10
  75. package/dist/type/validator/validator-cache.mjs +6 -9
  76. package/dist/types.d.ts +4 -3
  77. package/dist/universal/file.js +2 -6
  78. package/dist/universal/file.mjs +2 -6
  79. package/dist/utils.d.ts +11 -2
  80. package/dist/utils.js +85 -47
  81. package/dist/utils.mjs +82 -48
  82. package/dist/validator/index.js +14 -18
  83. package/dist/validator/index.mjs +14 -18
  84. package/dist/ws/context.js +9 -11
  85. package/dist/ws/context.mjs +9 -11
  86. package/dist/ws/parser.js +23 -20
  87. package/dist/ws/parser.mjs +23 -20
  88. package/dist/ws/route.js +76 -87
  89. package/dist/ws/route.mjs +76 -87
  90. package/package.json +5 -2
package/dist/base.js CHANGED
@@ -44,6 +44,23 @@ const mergeExtCallbacks = (target, incoming) => {
44
44
  }
45
45
  index.indexedLength = target.length;
46
46
  };
47
+ const recordDisposable = (ext, value) => {
48
+ if (!require_utils.isDisposable(value)) return;
49
+ const disposable = ext.disposable ??= [];
50
+ if (!disposable.includes(value)) disposable.push(value);
51
+ };
52
+ const recordDisposables = (ext, source) => {
53
+ const stored = ext.decorator;
54
+ for (const key in source) {
55
+ const value = stored?.[key];
56
+ recordDisposable(ext, value);
57
+ if (!require_utils.isDisposable(value)) recordDisposable(ext, source[key]);
58
+ }
59
+ };
60
+ const markStoredSingletons = (table, source) => {
61
+ if (!table) return;
62
+ for (const key in source) require_utils.markSingletons(table[key]);
63
+ };
47
64
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
48
65
  const isSchemaValue = (value) => {
49
66
  if (value == null || typeof value === "string") return true;
@@ -263,15 +280,31 @@ var Elysia = class Elysia {
263
280
  const existing = target[name];
264
281
  if (!fresh && name in target && !!existing && typeof existing === "object" && !Array.isArray(existing) && !Array.isArray(value)) target[name] = require_utils.mergeDeep(target[name], value, void 0, as === "override");
265
282
  else target[name] = value;
283
+ if (field === "decorator") {
284
+ const stored = target[name];
285
+ recordDisposable(ext, stored);
286
+ if (!require_utils.isDisposable(stored)) recordDisposable(ext, value);
287
+ }
288
+ require_utils.markSingletons(target[name]);
266
289
  return this;
267
290
  }
268
291
  if (fresh) Object.assign(target, value);
269
292
  else ext[field] = require_utils.mergeDeep(target, value, void 0, as === "override");
293
+ if (field === "decorator") recordDisposables(ext, value);
294
+ markStoredSingletons(ext[field], value);
270
295
  return this;
271
296
  case "function":
272
297
  if (name) {
273
- if (as === "override" || !(name in target)) target[name] = value;
274
- } else ext[field] = value(target);
298
+ if (as === "override" || !(name in target)) {
299
+ target[name] = value;
300
+ if (field === "decorator") recordDisposable(ext, value);
301
+ require_utils.markSingletons(value);
302
+ }
303
+ } else {
304
+ ext[field] = value(target);
305
+ if (field === "decorator") recordDisposables(ext, ext[field]);
306
+ markStoredSingletons(ext[field], ext[field]);
307
+ }
275
308
  return this;
276
309
  default:
277
310
  if (as === "override" || !(name in target)) target[name] = value;
@@ -810,12 +843,18 @@ var Elysia = class Elysia {
810
843
  * Keep the cold blocks in their own frames.
811
844
  */
812
845
  #absorbExt(app) {
813
- const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, capability } = app["~ext"];
846
+ const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, disposable, capability } = app["~ext"];
814
847
  const ext = this["~ext"] ??= require_utils.nullObject();
815
- if (decorator) if (ext.decorator) require_utils.mergeDeep(ext.decorator, decorator, void 0, true, false, void 0, { map: void 0 });
816
- else ext.decorator = require_utils.clonePlainDecorators(decorator);
817
- if (store) if (ext.store) require_utils.mergeDeep(ext.store, store);
818
- else ext.store = Object.assign(require_utils.nullObject(), store);
848
+ if (decorator) {
849
+ if (ext.decorator) require_utils.mergeDeep(ext.decorator, decorator, void 0, true, false, void 0, { map: void 0 });
850
+ else ext.decorator = require_utils.clonePlainDecorators(decorator);
851
+ markStoredSingletons(ext.decorator, decorator);
852
+ }
853
+ if (store) {
854
+ if (ext.store) require_utils.mergeDeep(ext.store, store);
855
+ else ext.store = Object.assign(require_utils.nullObject(), store);
856
+ markStoredSingletons(ext.store, store);
857
+ }
819
858
  if (headers) if (ext.headers) Object.assign(ext.headers, headers);
820
859
  else ext.headers = Object.assign(require_utils.nullObject(), headers);
821
860
  if (models) if (ext.models) Object.assign(ext.models, models);
@@ -850,6 +889,8 @@ var Elysia = class Elysia {
850
889
  else ext.setup = setup.slice();
851
890
  if (cleanup) if (ext.cleanup) mergeExtCallbacks(ext.cleanup, cleanup);
852
891
  else ext.cleanup = cleanup.slice();
892
+ if (disposable) if (ext.disposable) mergeExtCallbacks(ext.disposable, disposable);
893
+ else ext.disposable = disposable.slice();
853
894
  if (capability) {
854
895
  const target = ext.capability ??= require_utils.nullObject();
855
896
  if (capability.trace) {
@@ -1069,28 +1110,27 @@ var Elysia = class Elysia {
1069
1110
  }).then(() => {}, (err) => {
1070
1111
  this._error ??= { error: err };
1071
1112
  console.error(err);
1072
- }).finally(() => this.#tryDrain(next));
1113
+ }).finally(() => {
1114
+ if (this._pending > 0) return;
1115
+ if (this.ready !== next) return;
1116
+ const previousCompiled = this.compiled;
1117
+ const previousJitColdRemaining = this.jitColdRemaining;
1118
+ this.ready = void 0;
1119
+ this.compiled = void 0;
1120
+ this.fetchFn = void 0;
1121
+ this.routerBuilt = false;
1122
+ require_context.clearContextCache(this);
1123
+ try {
1124
+ this.#buildRouter(false);
1125
+ } catch (error) {
1126
+ this.compiled = previousCompiled;
1127
+ this.jitColdRemaining = previousJitColdRemaining;
1128
+ this._error ??= { error };
1129
+ }
1130
+ });
1073
1131
  this.ready = next;
1074
1132
  return this;
1075
1133
  }
1076
- #tryDrain(sentinel) {
1077
- if (this._pending > 0) return;
1078
- if (this.ready !== sentinel) return;
1079
- const previousCompiled = this.compiled;
1080
- const previousJitColdRemaining = this.jitColdRemaining;
1081
- this.ready = void 0;
1082
- this.compiled = void 0;
1083
- this.fetchFn = void 0;
1084
- this.routerBuilt = false;
1085
- require_context.clearContextCache(this);
1086
- try {
1087
- this.#buildRouter(false);
1088
- } catch (error) {
1089
- this.compiled = previousCompiled;
1090
- this.jitColdRemaining = previousJitColdRemaining;
1091
- this._error ??= { error };
1092
- }
1093
- }
1094
1134
  #add(method, path, hookOrFn, fn, hasHook = fn !== void 0) {
1095
1135
  if (this["~Prefix"]) path = require_utils.joinPath(this["~Prefix"], path);
1096
1136
  else if (path && path.charCodeAt(0) !== 47) path = "/" + path;
@@ -1234,6 +1274,9 @@ var Elysia = class Elysia {
1234
1274
  options(path, hookOrFn, fn) {
1235
1275
  return this.#add("OPTIONS", path, hookOrFn, fn);
1236
1276
  }
1277
+ query(path, hookOrFn, fn) {
1278
+ return this.#add("QUERY", path, hookOrFn, fn);
1279
+ }
1237
1280
  head(path, hookOrFn, fn) {
1238
1281
  return this.#add("HEAD", path, hookOrFn, fn);
1239
1282
  }
@@ -1337,11 +1380,9 @@ var Elysia = class Elysia {
1337
1380
  if (indexedTable) this.#satisfyJit(indexedTable, index);
1338
1381
  return handler;
1339
1382
  }
1340
- return this.#jitHandler(index, route ?? (indexedTable ? void 0 : this["~routes"][index]), precomputedStatic, aliases, indexedTable);
1341
- }
1342
- #jitHandler(index, route, precomputedStatic, aliases, table) {
1343
- if (table !== void 0) this.jitTable = table;
1344
- if (route !== void 0) (this.jitRoute ??= [])[index] = route;
1383
+ const jitRoute = route ?? (indexedTable ? void 0 : this["~routes"][index]);
1384
+ if (indexedTable !== void 0) this.jitTable = indexedTable;
1385
+ if (jitRoute !== void 0) (this.jitRoute ??= [])[index] = jitRoute;
1345
1386
  if (precomputedStatic !== void 0) (this.jitStatic ??= [])[index] = precomputedStatic;
1346
1387
  if (aliases !== void 0) (this.jitAliases ??= [])[index] = aliases;
1347
1388
  return (context) => this.#jitDispatch(index, context);
@@ -1483,31 +1524,6 @@ var Elysia = class Elysia {
1483
1524
  }
1484
1525
  return false;
1485
1526
  }
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
- }
1500
- #routeMayHaveModelRef(table, i) {
1501
- const macroScope = table.macroScope?.get(i);
1502
- const owner = table.owner[i];
1503
- const candidate = macroScope ?? owner ?? this;
1504
- if ((candidate === this ? this : require_compile_handler_index.localMacroRoot(candidate, this))["~ext"]?.macro) return true;
1505
- if (Elysia.#hookHasString(table.localHook[i])) return true;
1506
- const appHook = table.appHook[i];
1507
- if (appHook !== void 0 && appHook.refs) return true;
1508
- const inheritedChain = table.inheritedChain[i];
1509
- return inheritedChain !== void 0 && inheritedChain.refs;
1510
- }
1511
1527
  #assertRouteModelRefs(route, method) {
1512
1528
  const models = this["~ext"]?.models;
1513
1529
  const path = route[1];
@@ -1649,8 +1665,37 @@ var Elysia = class Elysia {
1649
1665
  const length = table.length;
1650
1666
  const appChain = this["~hookChain"];
1651
1667
  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]);
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 {
1668
+ else for (let i = 0; i < length; i++) {
1669
+ const macroScope = table.macroScope?.get(i);
1670
+ const owner = table.owner[i];
1671
+ const candidate = macroScope ?? owner ?? this;
1672
+ const localRoot = candidate === this ? this : require_compile_handler_index.localMacroRoot(candidate, this);
1673
+ let hasModelRef;
1674
+ if (localRoot["~ext"]?.macro) hasModelRef = true;
1675
+ else if (Elysia.#hookHasString(table.localHook[i])) hasModelRef = true;
1676
+ else {
1677
+ const appHook = table.appHook[i];
1678
+ if (appHook !== void 0 && appHook.refs) hasModelRef = true;
1679
+ else {
1680
+ const inheritedChain = table.inheritedChain[i];
1681
+ hasModelRef = inheritedChain !== void 0 && inheritedChain.refs;
1682
+ }
1683
+ }
1684
+ if (hasModelRef) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1685
+ }
1686
+ let hasTypeBoxSchema = false;
1687
+ if (length) {
1688
+ const models = this["~ext"]?.models;
1689
+ const resolve = require_compile_handler_index.chainResolver(this);
1690
+ const rootChain = this["~hookChain"];
1691
+ const seen = /* @__PURE__ */ new Set();
1692
+ const { localHook, appHook, inheritedChain, owner } = table;
1693
+ 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)) {
1694
+ hasTypeBoxSchema = true;
1695
+ break;
1696
+ }
1697
+ }
1698
+ if (hasTypeBoxSchema) try {
1654
1699
  require_type_bridge.warmTypebox();
1655
1700
  } catch {}
1656
1701
  let markDuplicates = false;
@@ -1816,7 +1861,7 @@ var Elysia = class Elysia {
1816
1861
  this.server = void 0;
1817
1862
  }
1818
1863
  const handlers = this["~ext"]?.cleanup;
1819
- if (!errors.length && !handlers?.length) return result && typeof result.then === "function" ? result : void 0;
1864
+ if (!errors.length && !handlers?.length && !this["~ext"]?.disposable?.length) return result && typeof result.then === "function" ? result : void 0;
1820
1865
  return (async () => {
1821
1866
  try {
1822
1867
  await result;
@@ -1828,6 +1873,11 @@ var Elysia = class Elysia {
1828
1873
  } catch (error) {
1829
1874
  errors.push(error);
1830
1875
  }
1876
+ try {
1877
+ await require_utils.disposeDecorators(this);
1878
+ } catch (error) {
1879
+ errors.push(error);
1880
+ }
1831
1881
  require_utils.throwLifecycleErrors(errors);
1832
1882
  })();
1833
1883
  }
package/dist/base.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isBun } from "./universal/constants.mjs";
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";
2
+ import { clonePlainDecorators, clonePlainDeep, coalesceSchemas, createErrorEventHandler, disposeDecorators, eventProperties, fnOrigin, fnv1a, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isDisposable, isEmpty, isNotEmpty, isRecordNumber, joinPath, macroOrigin, mapDeriveEntry, markSingletons, mergeDeep, mergeResponse, nullObject, pushField, schemaProperties, serializeMacroSeed, throwLifecycleErrors } from "./utils.mjs";
3
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";
@@ -41,6 +41,23 @@ const mergeExtCallbacks = (target, incoming) => {
41
41
  }
42
42
  index.indexedLength = target.length;
43
43
  };
44
+ const recordDisposable = (ext, value) => {
45
+ if (!isDisposable(value)) return;
46
+ const disposable = ext.disposable ??= [];
47
+ if (!disposable.includes(value)) disposable.push(value);
48
+ };
49
+ const recordDisposables = (ext, source) => {
50
+ const stored = ext.decorator;
51
+ for (const key in source) {
52
+ const value = stored?.[key];
53
+ recordDisposable(ext, value);
54
+ if (!isDisposable(value)) recordDisposable(ext, source[key]);
55
+ }
56
+ };
57
+ const markStoredSingletons = (table, source) => {
58
+ if (!table) return;
59
+ for (const key in source) markSingletons(table[key]);
60
+ };
44
61
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
45
62
  const isSchemaValue = (value) => {
46
63
  if (value == null || typeof value === "string") return true;
@@ -260,15 +277,31 @@ var Elysia = class Elysia {
260
277
  const existing = target[name];
261
278
  if (!fresh && name in target && !!existing && typeof existing === "object" && !Array.isArray(existing) && !Array.isArray(value)) target[name] = mergeDeep(target[name], value, void 0, as === "override");
262
279
  else target[name] = value;
280
+ if (field === "decorator") {
281
+ const stored = target[name];
282
+ recordDisposable(ext, stored);
283
+ if (!isDisposable(stored)) recordDisposable(ext, value);
284
+ }
285
+ markSingletons(target[name]);
263
286
  return this;
264
287
  }
265
288
  if (fresh) Object.assign(target, value);
266
289
  else ext[field] = mergeDeep(target, value, void 0, as === "override");
290
+ if (field === "decorator") recordDisposables(ext, value);
291
+ markStoredSingletons(ext[field], value);
267
292
  return this;
268
293
  case "function":
269
294
  if (name) {
270
- if (as === "override" || !(name in target)) target[name] = value;
271
- } else ext[field] = value(target);
295
+ if (as === "override" || !(name in target)) {
296
+ target[name] = value;
297
+ if (field === "decorator") recordDisposable(ext, value);
298
+ markSingletons(value);
299
+ }
300
+ } else {
301
+ ext[field] = value(target);
302
+ if (field === "decorator") recordDisposables(ext, ext[field]);
303
+ markStoredSingletons(ext[field], ext[field]);
304
+ }
272
305
  return this;
273
306
  default:
274
307
  if (as === "override" || !(name in target)) target[name] = value;
@@ -807,12 +840,18 @@ var Elysia = class Elysia {
807
840
  * Keep the cold blocks in their own frames.
808
841
  */
809
842
  #absorbExt(app) {
810
- const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, capability } = app["~ext"];
843
+ const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, disposable, capability } = app["~ext"];
811
844
  const ext = this["~ext"] ??= nullObject();
812
- if (decorator) if (ext.decorator) mergeDeep(ext.decorator, decorator, void 0, true, false, void 0, { map: void 0 });
813
- else ext.decorator = clonePlainDecorators(decorator);
814
- if (store) if (ext.store) mergeDeep(ext.store, store);
815
- else ext.store = Object.assign(nullObject(), store);
845
+ if (decorator) {
846
+ if (ext.decorator) mergeDeep(ext.decorator, decorator, void 0, true, false, void 0, { map: void 0 });
847
+ else ext.decorator = clonePlainDecorators(decorator);
848
+ markStoredSingletons(ext.decorator, decorator);
849
+ }
850
+ if (store) {
851
+ if (ext.store) mergeDeep(ext.store, store);
852
+ else ext.store = Object.assign(nullObject(), store);
853
+ markStoredSingletons(ext.store, store);
854
+ }
816
855
  if (headers) if (ext.headers) Object.assign(ext.headers, headers);
817
856
  else ext.headers = Object.assign(nullObject(), headers);
818
857
  if (models) if (ext.models) Object.assign(ext.models, models);
@@ -847,6 +886,8 @@ var Elysia = class Elysia {
847
886
  else ext.setup = setup.slice();
848
887
  if (cleanup) if (ext.cleanup) mergeExtCallbacks(ext.cleanup, cleanup);
849
888
  else ext.cleanup = cleanup.slice();
889
+ if (disposable) if (ext.disposable) mergeExtCallbacks(ext.disposable, disposable);
890
+ else ext.disposable = disposable.slice();
850
891
  if (capability) {
851
892
  const target = ext.capability ??= nullObject();
852
893
  if (capability.trace) {
@@ -1066,28 +1107,27 @@ var Elysia = class Elysia {
1066
1107
  }).then(() => {}, (err) => {
1067
1108
  this._error ??= { error: err };
1068
1109
  console.error(err);
1069
- }).finally(() => this.#tryDrain(next));
1110
+ }).finally(() => {
1111
+ if (this._pending > 0) return;
1112
+ if (this.ready !== next) return;
1113
+ const previousCompiled = this.compiled;
1114
+ const previousJitColdRemaining = this.jitColdRemaining;
1115
+ this.ready = void 0;
1116
+ this.compiled = void 0;
1117
+ this.fetchFn = void 0;
1118
+ this.routerBuilt = false;
1119
+ clearContextCache(this);
1120
+ try {
1121
+ this.#buildRouter(false);
1122
+ } catch (error) {
1123
+ this.compiled = previousCompiled;
1124
+ this.jitColdRemaining = previousJitColdRemaining;
1125
+ this._error ??= { error };
1126
+ }
1127
+ });
1070
1128
  this.ready = next;
1071
1129
  return this;
1072
1130
  }
1073
- #tryDrain(sentinel) {
1074
- if (this._pending > 0) return;
1075
- if (this.ready !== sentinel) return;
1076
- const previousCompiled = this.compiled;
1077
- const previousJitColdRemaining = this.jitColdRemaining;
1078
- this.ready = void 0;
1079
- this.compiled = void 0;
1080
- this.fetchFn = void 0;
1081
- this.routerBuilt = false;
1082
- clearContextCache(this);
1083
- try {
1084
- this.#buildRouter(false);
1085
- } catch (error) {
1086
- this.compiled = previousCompiled;
1087
- this.jitColdRemaining = previousJitColdRemaining;
1088
- this._error ??= { error };
1089
- }
1090
- }
1091
1131
  #add(method, path, hookOrFn, fn, hasHook = fn !== void 0) {
1092
1132
  if (this["~Prefix"]) path = joinPath(this["~Prefix"], path);
1093
1133
  else if (path && path.charCodeAt(0) !== 47) path = "/" + path;
@@ -1231,6 +1271,9 @@ var Elysia = class Elysia {
1231
1271
  options(path, hookOrFn, fn) {
1232
1272
  return this.#add("OPTIONS", path, hookOrFn, fn);
1233
1273
  }
1274
+ query(path, hookOrFn, fn) {
1275
+ return this.#add("QUERY", path, hookOrFn, fn);
1276
+ }
1234
1277
  head(path, hookOrFn, fn) {
1235
1278
  return this.#add("HEAD", path, hookOrFn, fn);
1236
1279
  }
@@ -1334,11 +1377,9 @@ var Elysia = class Elysia {
1334
1377
  if (indexedTable) this.#satisfyJit(indexedTable, index);
1335
1378
  return handler;
1336
1379
  }
1337
- return this.#jitHandler(index, route ?? (indexedTable ? void 0 : this["~routes"][index]), precomputedStatic, aliases, indexedTable);
1338
- }
1339
- #jitHandler(index, route, precomputedStatic, aliases, table) {
1340
- if (table !== void 0) this.jitTable = table;
1341
- if (route !== void 0) (this.jitRoute ??= [])[index] = route;
1380
+ const jitRoute = route ?? (indexedTable ? void 0 : this["~routes"][index]);
1381
+ if (indexedTable !== void 0) this.jitTable = indexedTable;
1382
+ if (jitRoute !== void 0) (this.jitRoute ??= [])[index] = jitRoute;
1342
1383
  if (precomputedStatic !== void 0) (this.jitStatic ??= [])[index] = precomputedStatic;
1343
1384
  if (aliases !== void 0) (this.jitAliases ??= [])[index] = aliases;
1344
1385
  return (context) => this.#jitDispatch(index, context);
@@ -1480,31 +1521,6 @@ var Elysia = class Elysia {
1480
1521
  }
1481
1522
  return false;
1482
1523
  }
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
- }
1497
- #routeMayHaveModelRef(table, i) {
1498
- const macroScope = table.macroScope?.get(i);
1499
- const owner = table.owner[i];
1500
- const candidate = macroScope ?? owner ?? this;
1501
- if ((candidate === this ? this : localMacroRoot(candidate, this))["~ext"]?.macro) return true;
1502
- if (Elysia.#hookHasString(table.localHook[i])) return true;
1503
- const appHook = table.appHook[i];
1504
- if (appHook !== void 0 && appHook.refs) return true;
1505
- const inheritedChain = table.inheritedChain[i];
1506
- return inheritedChain !== void 0 && inheritedChain.refs;
1507
- }
1508
1524
  #assertRouteModelRefs(route, method) {
1509
1525
  const models = this["~ext"]?.models;
1510
1526
  const path = route[1];
@@ -1646,8 +1662,37 @@ var Elysia = class Elysia {
1646
1662
  const length = table.length;
1647
1663
  const appChain = this["~hookChain"];
1648
1664
  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]);
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 {
1665
+ else for (let i = 0; i < length; i++) {
1666
+ const macroScope = table.macroScope?.get(i);
1667
+ const owner = table.owner[i];
1668
+ const candidate = macroScope ?? owner ?? this;
1669
+ const localRoot = candidate === this ? this : localMacroRoot(candidate, this);
1670
+ let hasModelRef;
1671
+ if (localRoot["~ext"]?.macro) hasModelRef = true;
1672
+ else if (Elysia.#hookHasString(table.localHook[i])) hasModelRef = true;
1673
+ else {
1674
+ const appHook = table.appHook[i];
1675
+ if (appHook !== void 0 && appHook.refs) hasModelRef = true;
1676
+ else {
1677
+ const inheritedChain = table.inheritedChain[i];
1678
+ hasModelRef = inheritedChain !== void 0 && inheritedChain.refs;
1679
+ }
1680
+ }
1681
+ if (hasModelRef) this.#assertRouteModelRefs(routeRow(table, i), method[i]);
1682
+ }
1683
+ let hasTypeBoxSchema = false;
1684
+ if (length) {
1685
+ const models = this["~ext"]?.models;
1686
+ const resolve = chainResolver(this);
1687
+ const rootChain = this["~hookChain"];
1688
+ const seen = /* @__PURE__ */ new Set();
1689
+ const { localHook, appHook, inheritedChain, owner } = table;
1690
+ 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)) {
1691
+ hasTypeBoxSchema = true;
1692
+ break;
1693
+ }
1694
+ }
1695
+ if (hasTypeBoxSchema) try {
1651
1696
  warmTypebox();
1652
1697
  } catch {}
1653
1698
  let markDuplicates = false;
@@ -1813,7 +1858,7 @@ var Elysia = class Elysia {
1813
1858
  this.server = void 0;
1814
1859
  }
1815
1860
  const handlers = this["~ext"]?.cleanup;
1816
- if (!errors.length && !handlers?.length) return result && typeof result.then === "function" ? result : void 0;
1861
+ if (!errors.length && !handlers?.length && !this["~ext"]?.disposable?.length) return result && typeof result.then === "function" ? result : void 0;
1817
1862
  return (async () => {
1818
1863
  try {
1819
1864
  await result;
@@ -1825,6 +1870,11 @@ var Elysia = class Elysia {
1825
1870
  } catch (error) {
1826
1871
  errors.push(error);
1827
1872
  }
1873
+ try {
1874
+ await disposeDecorators(this);
1875
+ } catch (error) {
1876
+ errors.push(error);
1877
+ }
1828
1878
  throwLifecycleErrors(errors);
1829
1879
  })();
1830
1880
  }
@@ -46,13 +46,6 @@ let registered;
46
46
  let claimed;
47
47
  let programs = /* @__PURE__ */ new WeakMap();
48
48
  const programFor = (id) => id ? programs.get(id) : void 0;
49
- const fingerprintMismatch = (manifest, actual) => {
50
- const differences = [];
51
- const expected = manifest.fingerprint;
52
- if (manifest.bf !== 1) differences.push(`bf (manifest ${manifest.bf}, app 1)`);
53
- if (expected.abi !== actual.abi) differences.push(`abi (manifest ${expected.abi}, app ${actual.abi})`);
54
- return differences;
55
- };
56
49
  var Compiled = class {
57
50
  static register(manifest) {
58
51
  registered = manifest;
@@ -61,7 +54,10 @@ var Compiled = class {
61
54
  static claim(id, fingerprint) {
62
55
  if (!registered || claimed) return false;
63
56
  const manifest = registered;
64
- const differences = fingerprintMismatch(manifest, fingerprint);
57
+ const differences = [];
58
+ const expected = manifest.fingerprint;
59
+ if (manifest.bf !== 1) differences.push(`bf (manifest ${manifest.bf}, app 1)`);
60
+ if (expected.abi !== fingerprint.abi) differences.push(`abi (manifest ${expected.abi}, app ${fingerprint.abi})`);
65
61
  if (differences.length) throw new Error(`[elysia-aot] Registered manifest fingerprint mismatch: ${differences.join("; ")}.`);
66
62
  claimed = new WeakRef(id);
67
63
  registered = void 0;
@@ -155,7 +151,16 @@ const CompilerState = {
155
151
  }
156
152
  };
157
153
  const EMPTY_EXTERNALS = Object.freeze([]);
158
- function captureEntry({ method, path, slot }) {
154
+ /** @internal shared with `aot-capture.ts` (`beginValidatorCapture`). */
155
+ const aotActivationError = () => /* @__PURE__ */ new Error("Elysia AOT capture module is not activated.");
156
+ function captureHandler(v) {
157
+ if (!isValidatorCapturing()) return;
158
+ const session = activeSession;
159
+ if (!session) return;
160
+ (session.handlerCapture ??= /* @__PURE__ */ new Map()).set(`${v.method}\0${v.path}`, v);
161
+ }
162
+ function captureSet(loc, partial) {
163
+ const { method, path, slot } = loc;
159
164
  if (!isValidatorCapturing()) return;
160
165
  const capture = activeSession?.capture;
161
166
  if (!capture) return;
@@ -166,18 +171,6 @@ function captureEntry({ method, path, slot }) {
166
171
  path,
167
172
  slot
168
173
  });
169
- return e;
170
- }
171
- /** @internal shared with `aot-capture.ts` (`beginValidatorCapture`). */
172
- const aotActivationError = () => /* @__PURE__ */ new Error("Elysia AOT capture module is not activated.");
173
- function captureHandler(v) {
174
- if (!isValidatorCapturing()) return;
175
- const session = activeSession;
176
- if (!session) return;
177
- (session.handlerCapture ??= /* @__PURE__ */ new Map()).set(`${v.method}\0${v.path}`, v);
178
- }
179
- function captureSet(loc, partial) {
180
- const e = captureEntry(loc);
181
174
  if (e) Object.assign(e, partial);
182
175
  }
183
176
  const captureGet = (loc) => activeSession?.capture?.get(`${loc.method}_${loc.path}_${loc.slot}`);
@@ -45,13 +45,6 @@ let registered;
45
45
  let claimed;
46
46
  let programs = /* @__PURE__ */ new WeakMap();
47
47
  const programFor = (id) => id ? programs.get(id) : void 0;
48
- const fingerprintMismatch = (manifest, actual) => {
49
- const differences = [];
50
- const expected = manifest.fingerprint;
51
- if (manifest.bf !== 1) differences.push(`bf (manifest ${manifest.bf}, app 1)`);
52
- if (expected.abi !== actual.abi) differences.push(`abi (manifest ${expected.abi}, app ${actual.abi})`);
53
- return differences;
54
- };
55
48
  var Compiled = class {
56
49
  static register(manifest) {
57
50
  registered = manifest;
@@ -60,7 +53,10 @@ var Compiled = class {
60
53
  static claim(id, fingerprint) {
61
54
  if (!registered || claimed) return false;
62
55
  const manifest = registered;
63
- const differences = fingerprintMismatch(manifest, fingerprint);
56
+ const differences = [];
57
+ const expected = manifest.fingerprint;
58
+ if (manifest.bf !== 1) differences.push(`bf (manifest ${manifest.bf}, app 1)`);
59
+ if (expected.abi !== fingerprint.abi) differences.push(`abi (manifest ${expected.abi}, app ${fingerprint.abi})`);
64
60
  if (differences.length) throw new Error(`[elysia-aot] Registered manifest fingerprint mismatch: ${differences.join("; ")}.`);
65
61
  claimed = new WeakRef(id);
66
62
  registered = void 0;
@@ -154,7 +150,16 @@ const CompilerState = {
154
150
  }
155
151
  };
156
152
  const EMPTY_EXTERNALS = Object.freeze([]);
157
- function captureEntry({ method, path, slot }) {
153
+ /** @internal shared with `aot-capture.ts` (`beginValidatorCapture`). */
154
+ const aotActivationError = () => /* @__PURE__ */ new Error("Elysia AOT capture module is not activated.");
155
+ function captureHandler(v) {
156
+ if (!isValidatorCapturing()) return;
157
+ const session = activeSession;
158
+ if (!session) return;
159
+ (session.handlerCapture ??= /* @__PURE__ */ new Map()).set(`${v.method}\0${v.path}`, v);
160
+ }
161
+ function captureSet(loc, partial) {
162
+ const { method, path, slot } = loc;
158
163
  if (!isValidatorCapturing()) return;
159
164
  const capture = activeSession?.capture;
160
165
  if (!capture) return;
@@ -165,18 +170,6 @@ function captureEntry({ method, path, slot }) {
165
170
  path,
166
171
  slot
167
172
  });
168
- return e;
169
- }
170
- /** @internal shared with `aot-capture.ts` (`beginValidatorCapture`). */
171
- const aotActivationError = () => /* @__PURE__ */ new Error("Elysia AOT capture module is not activated.");
172
- function captureHandler(v) {
173
- if (!isValidatorCapturing()) return;
174
- const session = activeSession;
175
- if (!session) return;
176
- (session.handlerCapture ??= /* @__PURE__ */ new Map()).set(`${v.method}\0${v.path}`, v);
177
- }
178
- function captureSet(loc, partial) {
179
- const e = captureEntry(loc);
180
173
  if (e) Object.assign(e, partial);
181
174
  }
182
175
  const captureGet = (loc) => activeSession?.capture?.get(`${loc.method}_${loc.path}_${loc.slot}`);