elysia 2.0.0-beta.3 → 2.0.0-beta.4

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 (69) hide show
  1. package/dist/adapter/bun/index.d.ts +1 -1
  2. package/dist/adapter/bun/index.js +320 -56
  3. package/dist/adapter/bun/index.mjs +321 -57
  4. package/dist/adapter/utils.js +48 -27
  5. package/dist/adapter/utils.mjs +48 -27
  6. package/dist/base.d.ts +16 -5
  7. package/dist/base.js +171 -74
  8. package/dist/base.mjs +172 -75
  9. package/dist/compile/aot-capture.js +1 -1
  10. package/dist/compile/aot-capture.mjs +1 -1
  11. package/dist/compile/aot.d.ts +1 -0
  12. package/dist/compile/aot.js +3 -0
  13. package/dist/compile/aot.mjs +3 -0
  14. package/dist/compile/handler/frozen-validator.d.ts +4 -1
  15. package/dist/compile/handler/frozen-validator.js +8 -3
  16. package/dist/compile/handler/frozen-validator.mjs +5 -3
  17. package/dist/compile/handler/index.d.ts +1 -1
  18. package/dist/compile/handler/index.js +3 -3
  19. package/dist/compile/handler/index.mjs +3 -3
  20. package/dist/compile/handler/reconstruct.d.ts +1 -1
  21. package/dist/compile/handler/reconstruct.js +4 -3
  22. package/dist/compile/handler/reconstruct.mjs +4 -3
  23. package/dist/compile/jit-probe.d.ts +2 -2
  24. package/dist/context.d.ts +1 -1
  25. package/dist/context.js +5 -1
  26. package/dist/context.mjs +5 -1
  27. package/dist/package.js +1 -1
  28. package/dist/package.mjs +1 -1
  29. package/dist/plugin/aot/core.d.ts +11 -5
  30. package/dist/plugin/aot/core.js +86 -38
  31. package/dist/plugin/aot/core.mjs +87 -41
  32. package/dist/plugin/aot/esbuild.js +8 -2
  33. package/dist/plugin/aot/esbuild.mjs +9 -3
  34. package/dist/plugin/aot/hooks.d.ts +2 -1
  35. package/dist/plugin/aot/hooks.js +3 -3
  36. package/dist/plugin/aot/hooks.mjs +4 -4
  37. package/dist/plugin/aot/source.d.ts +4 -1
  38. package/dist/plugin/aot/source.js +21 -14
  39. package/dist/plugin/aot/source.mjs +21 -14
  40. package/dist/plugin/aot/worker.js +2 -2
  41. package/dist/plugin/aot/worker.mjs +2 -2
  42. package/dist/route-table.d.ts +3 -0
  43. package/dist/route-table.js +4 -1
  44. package/dist/route-table.mjs +4 -1
  45. package/dist/schema-snapshot.js +17 -22
  46. package/dist/schema-snapshot.mjs +17 -22
  47. package/dist/type/bridge-live.js +1 -1
  48. package/dist/type/bridge-live.mjs +1 -1
  49. package/dist/type/coerce.d.ts +1 -1
  50. package/dist/type/constants.d.ts +1 -1
  51. package/dist/type/typebox-value-live.d.ts +2 -2
  52. package/dist/type/typebox-value-live.js +2 -2
  53. package/dist/type/typebox-value-live.mjs +2 -2
  54. package/dist/type/typebox-value.d.ts +6 -6
  55. package/dist/type/validator/index.d.ts +1 -1
  56. package/dist/type/validator/index.js +10 -7
  57. package/dist/type/validator/index.mjs +10 -7
  58. package/dist/type/validator/validator-cache.d.ts +1 -1
  59. package/dist/type/validator/validator-cache.js +17 -9
  60. package/dist/type/validator/validator-cache.mjs +17 -9
  61. package/dist/utils.d.ts +10 -1
  62. package/dist/utils.js +8 -1
  63. package/dist/utils.mjs +6 -1
  64. package/dist/ws/context.d.ts +4 -1
  65. package/dist/ws/context.js +21 -3
  66. package/dist/ws/context.mjs +20 -3
  67. package/dist/ws/route.js +57 -12
  68. package/dist/ws/route.mjs +59 -14
  69. package/package.json +2 -2
@@ -157,10 +157,12 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
157
157
  return new Response(generator, { headers: { "content-type": "application/octet-stream" } });
158
158
  }
159
159
  const typedSSE = !skipFormat && "sse" in generator && generator.sse === true;
160
+ const sourceIterator = typeof generator.next === "function" ? generator : void 0;
160
161
  let init = typedSSE ? void 0 : generator.next?.();
161
162
  if (set) handleSet(set);
162
163
  if (init instanceof Promise) init = await init;
163
- if (init?.value instanceof ReadableStream) generator = init.value;
164
+ const yieldedStream = init?.value instanceof ReadableStream;
165
+ if (yieldedStream) generator = init.value;
164
166
  else if (init && (typeof init?.done === "undefined" || init?.done)) {
165
167
  if (set) return mapResponse(init.value, set, request, owned);
166
168
  return mapCompactResponse(init.value, request, owned);
@@ -194,8 +196,32 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
194
196
  connection: "keep-alive"
195
197
  }
196
198
  };
197
- const iterator = typeof generator.next === "function" ? generator : generator[Symbol.asyncIterator]();
199
+ const sourceStream = generator instanceof ReadableStream ? generator : void 0;
200
+ let streamReader;
201
+ const iterator = sourceStream ? {
202
+ next: () => (streamReader ??= sourceStream.getReader()).read(),
203
+ async return() {
204
+ if (!streamReader) {
205
+ await sourceStream.cancel();
206
+ return {
207
+ done: true,
208
+ value: void 0
209
+ };
210
+ }
211
+ try {
212
+ await streamReader.cancel();
213
+ } finally {
214
+ streamReader.releaseLock();
215
+ }
216
+ return {
217
+ done: true,
218
+ value: void 0
219
+ };
220
+ }
221
+ } : sourceIterator ?? generator[Symbol.asyncIterator]();
222
+ const outerIterator = yieldedStream && !init.done ? sourceIterator : void 0;
198
223
  let end = false;
224
+ let finalizing;
199
225
  const signal = request?.signal;
200
226
  let onAbort;
201
227
  const cleanupAbort = () => {
@@ -204,17 +230,21 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
204
230
  onAbort = void 0;
205
231
  }
206
232
  };
207
- const safeReturn = () => {
233
+ const safeReturn = async (target) => {
208
234
  try {
209
- const r = iterator.return?.();
210
- if (r && typeof r.then === "function") r.catch(() => {});
235
+ await target?.return?.();
211
236
  } catch {}
212
237
  };
213
- const closeSafely = (controller) => {
238
+ const finalize = (mode, controller, error) => {
239
+ if (end) return finalizing;
240
+ end = true;
241
+ cleanupAbort();
242
+ finalizing = safeReturn(iterator).then(() => safeReturn(outerIterator));
243
+ if (!controller) return finalizing;
214
244
  try {
215
- controller.close();
245
+ if (mode === "error") controller.error(error);
246
+ else controller.close();
216
247
  } catch {}
217
- cleanupAbort();
218
248
  };
219
249
  const enqueueValue = async (controller, value) => {
220
250
  if (value.toSSE) {
@@ -233,42 +263,33 @@ function createStreamHandler({ mapResponse, mapCompactResponse }) {
233
263
  return new Response(new ReadableStream({
234
264
  async start(controller) {
235
265
  if (signal) {
236
- onAbort = () => {
237
- cleanupAbort();
238
- end = true;
239
- safeReturn();
240
- try {
241
- controller.close();
242
- } catch {}
243
- };
266
+ onAbort = () => finalize("close", controller);
244
267
  if (signal.aborted) onAbort();
245
268
  else signal.addEventListener("abort", onAbort, { once: true });
246
269
  }
247
270
  if (!init || init.value instanceof ReadableStream || init.value === void 0 || init.value === null) return;
248
- await enqueueValue(controller, init.value);
271
+ try {
272
+ await enqueueValue(controller, init.value);
273
+ } catch (error) {
274
+ finalize("error", controller, error);
275
+ }
249
276
  },
250
277
  async pull(controller) {
251
- if (end) {
252
- closeSafely(controller);
253
- return;
254
- }
278
+ if (end) return;
255
279
  try {
256
280
  const { value: chunk, done } = await iterator.next();
257
281
  if (done || end) {
258
- closeSafely(controller);
282
+ finalize("close", controller);
259
283
  return;
260
284
  }
261
285
  if (chunk === void 0 || chunk === null) return;
262
286
  await enqueueValue(controller, chunk);
263
287
  } catch (error) {
264
- cleanupAbort();
265
- controller.error(error);
288
+ finalize("error", controller, error);
266
289
  }
267
290
  },
268
291
  cancel() {
269
- end = true;
270
- cleanupAbort();
271
- safeReturn();
292
+ finalize("close");
272
293
  }
273
294
  }, typedSSE ? { highWaterMark: 0 } : void 0), set);
274
295
  };
package/dist/base.d.ts CHANGED
@@ -39,6 +39,10 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
39
39
  hoc?: WrapFn<any>[];
40
40
  setup?: GracefulHandler<any>[];
41
41
  cleanup?: GracefulHandler<any>[];
42
+ cleanupEpoch?: (handler: GracefulHandler<any> | GracefulHandler<any>[]) => boolean;
43
+ stop?: (closeActiveConnections?: boolean, failure?: {
44
+ error: unknown;
45
+ }) => Promise<void>;
42
46
  capability?: {
43
47
  trace?: {
44
48
  provider: TraceCapability;
@@ -900,12 +904,19 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
900
904
  */
901
905
  cleanup(handler: MaybeArray<GracefulHandler<this>>): this;
902
906
  /**
903
- * Stop the underlying server (if any), running every `cleanup` handler once
904
- * it has stopped. Mirrors `Server.stop()`.
907
+ * Stop the underlying server (if any), then run every `cleanup` handler.
908
+ * Omitted and `false` are the same graceful stop: new requests are gated,
909
+ * tracked WebSockets are settled and active HTTP is drained, then cleanup
910
+ * runs and the epoch is released. `true` force-closes the transport
911
+ * instead of draining it, and escalates a graceful stop already in flight.
905
912
  *
906
- * @param closeActiveConnections Pass `true` to terminate in-flight
907
- * requests and WebSocket connections immediately. Defaults to
908
- * draining gracefully.
913
+ * Awaiting `stop()` from inside a `setup`, `cleanup` or WebSocket lifecycle
914
+ * callback is only supported while that callback is still synchronous; the
915
+ * teardown is waiting on the callback, so after an `await` it can only be
916
+ * issued as `void app.stop()`.
917
+ *
918
+ * @param closeActiveConnections Pass `true` to terminate active
919
+ * transports. Omit (or pass `false`) to drain safely.
909
920
  */
910
921
  stop(closeActiveConnections?: boolean): Promise<void> | void;
911
922
  }
package/dist/base.js CHANGED
@@ -9,6 +9,7 @@ const require_generation = require('./generation.js');
9
9
  const require_compile_handler_index = require('./compile/handler/index.js');
10
10
  const require_route_table = require('./route-table.js');
11
11
  const require_adapter_bun_index = require('./adapter/bun/index.js');
12
+ const require_context = require('./context.js');
12
13
  const require_handler_fetch = require('./handler/fetch.js');
13
14
  const require_compile_analysis_cache = require('./compile/analysis-cache.js');
14
15
  const require_schema_snapshot = require('./schema-snapshot.js');
@@ -18,6 +19,23 @@ memoirist = require_runtime.__toESM(memoirist);
18
19
  //#region src/base.ts
19
20
  const useNodesBuffer = [];
20
21
  const emptyHistory = Object.freeze([]);
22
+ const extCallbackIndexes = /* @__PURE__ */ new WeakMap();
23
+ const mergeExtCallbacks = (target, incoming) => {
24
+ let index = extCallbackIndexes.get(target);
25
+ if (!index) {
26
+ index = {
27
+ indexedLength: target.length,
28
+ seen: new Set(target)
29
+ };
30
+ extCallbackIndexes.set(target, index);
31
+ } else for (let i = index.indexedLength; i < target.length; i++) index.seen.add(target[i]);
32
+ for (const fn of incoming) {
33
+ if (index.seen.has(fn)) continue;
34
+ target.push(fn);
35
+ index.seen.add(fn);
36
+ }
37
+ index.indexedLength = target.length;
38
+ };
21
39
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
22
40
  /**
23
41
  * Every key a single route must answer to, in registration order.
@@ -755,27 +773,12 @@ var Elysia = class Elysia {
755
773
  }
756
774
  if (error) if (ext.error) for (const [code, handler] of error) ext.error.set(code, handler);
757
775
  else ext.error = new Map(error);
758
- if (hoc) if (ext.hoc) {
759
- const seen = new Set(ext.hoc);
760
- for (const fn of hoc) if (!seen.has(fn)) {
761
- seen.add(fn);
762
- ext.hoc.push(fn);
763
- }
764
- } else ext.hoc = hoc.slice();
765
- if (setup) if (ext.setup) {
766
- const seen = new Set(ext.setup);
767
- for (const fn of setup) if (!seen.has(fn)) {
768
- seen.add(fn);
769
- ext.setup.push(fn);
770
- }
771
- } else ext.setup = setup.slice();
772
- if (cleanup) if (ext.cleanup) {
773
- const seen = new Set(ext.cleanup);
774
- for (const fn of cleanup) if (!seen.has(fn)) {
775
- seen.add(fn);
776
- ext.cleanup.push(fn);
777
- }
778
- } else ext.cleanup = cleanup.slice();
776
+ if (hoc) if (ext.hoc) mergeExtCallbacks(ext.hoc, hoc);
777
+ else ext.hoc = hoc.slice();
778
+ if (setup) if (ext.setup) mergeExtCallbacks(ext.setup, setup);
779
+ else ext.setup = setup.slice();
780
+ if (cleanup) if (ext.cleanup) mergeExtCallbacks(ext.cleanup, cleanup);
781
+ else ext.cleanup = cleanup.slice();
779
782
  if (capability) {
780
783
  const target = ext.capability ??= require_utils.nullObject();
781
784
  if (capability.trace) {
@@ -954,11 +957,11 @@ var Elysia = class Elysia {
954
957
  get modules() {
955
958
  const ready = this.#ready;
956
959
  if (!ready) {
957
- if (this.#error !== void 0) return Promise.reject(this.#error);
960
+ if (this.#error !== void 0) return Promise.reject(this.#error.error);
958
961
  return Promise.resolve();
959
962
  }
960
963
  return ready.then(() => {
961
- if (this.#error !== void 0) throw this.#error;
964
+ if (this.#error !== void 0) throw this.#error.error;
962
965
  if (this.#ready && this.#ready !== ready) return this.modules;
963
966
  });
964
967
  }
@@ -973,13 +976,13 @@ var Elysia = class Elysia {
973
976
  if (plugin) try {
974
977
  this.use(plugin);
975
978
  } catch (err) {
976
- this.#error ??= err;
979
+ this.#error ??= { error: err };
977
980
  console.error(err);
978
981
  }
979
982
  }).finally(() => {
980
983
  this.#pending--;
981
984
  }).then(() => {}, (err) => {
982
- this.#error ??= err;
985
+ this.#error ??= { error: err };
983
986
  console.error(err);
984
987
  }).finally(() => this.#tryDrain(next));
985
988
  this.#ready = next;
@@ -988,10 +991,20 @@ var Elysia = class Elysia {
988
991
  #tryDrain(sentinel) {
989
992
  if (this.#pending > 0) return;
990
993
  if (this.#ready !== sentinel) return;
994
+ const previousCompiled = this.#compiled;
995
+ const previousJitColdRemaining = this.#jitColdRemaining;
991
996
  this.#ready = void 0;
997
+ this.#compiled = void 0;
992
998
  this.#fetchFn = void 0;
993
999
  this.#routerBuilt = false;
994
- this.#buildRouter(false);
1000
+ require_context.clearContextCache(this);
1001
+ try {
1002
+ this.#buildRouter(false);
1003
+ } catch (error) {
1004
+ this.#compiled = previousCompiled;
1005
+ this.#jitColdRemaining = previousJitColdRemaining;
1006
+ this.#error ??= { error };
1007
+ }
995
1008
  }
996
1009
  #add(method, path, hookOrFn, fn, hasHook = fn !== void 0) {
997
1010
  if (this["~Prefix"]) path = require_utils.joinPath(this["~Prefix"], path);
@@ -1211,20 +1224,22 @@ var Elysia = class Elysia {
1211
1224
  }
1212
1225
  handler(index, immediate, route, precomputedStatic, aliases, table) {
1213
1226
  if (this.#compiled?.[index]) return this.#compiled[index];
1214
- const compiled = this.#compiled ??= new Array(table?.length ?? this["~routes"].length);
1227
+ const indexedTable = table ?? (this.#routerBuilt ? this["~routeTable"] : void 0);
1228
+ const compiled = this.#compiled ??= new Array(indexedTable?.length ?? this["~routes"].length);
1215
1229
  if (immediate ?? this["~config"]?.precompile) {
1216
- const row = route ?? (table ? require_route_table.routeRow(table, index) : this["~routes"][index]);
1230
+ const row = route ?? (indexedTable ? require_route_table.routeRow(indexedTable, index) : this["~routes"][index]);
1231
+ const exactDuplicate = ((indexedTable?.flags[index] ?? 0) & require_route_table.RouteFlag.ExactDuplicate) !== 0;
1217
1232
  let handler;
1218
1233
  try {
1219
- handler = require_compile_handler_index.compileHandler(row, this, precomputedStatic);
1234
+ handler = require_compile_handler_index.compileHandler(row, this, precomputedStatic, exactDuplicate && require_compile_aot.Compiled.hasProgram(this["~programId"]));
1220
1235
  } catch (error) {
1221
1236
  throw new Error(`[Elysia] Failed to compile route ${row[0]} ${row[1]}: ${error?.message ?? error}`, { cause: error });
1222
1237
  }
1223
1238
  compiled[index] = handler;
1224
- this.#saveHandler(row[0], row[1], handler);
1239
+ if (indexedTable) this.#satisfyJit(indexedTable, index);
1225
1240
  return handler;
1226
1241
  }
1227
- return this.#jitHandler(index, route ?? (table ? void 0 : this["~routes"][index]), precomputedStatic, aliases, table);
1242
+ return this.#jitHandler(index, route ?? (indexedTable ? void 0 : this["~routes"][index]), precomputedStatic, aliases, indexedTable);
1228
1243
  }
1229
1244
  #jitHandler(index, route, precomputedStatic, aliases, table) {
1230
1245
  if (table !== void 0) this.#jitTable = table;
@@ -1233,12 +1248,58 @@ var Elysia = class Elysia {
1233
1248
  if (aliases !== void 0) (this.#jitAliases ??= [])[index] = aliases;
1234
1249
  return (context) => this.#jitDispatch(index, context);
1235
1250
  }
1251
+ #staticAliases(table, index) {
1252
+ const flags = table.flags[index];
1253
+ if ((flags & (require_route_table.RouteFlag.WS | require_route_table.RouteFlag.Dynamic)) !== 0) return;
1254
+ const path = table.path[index];
1255
+ const method = table.method[index];
1256
+ const needsEncode = (flags & require_route_table.RouteFlag.Encode) !== 0;
1257
+ const registerLoose = this["~config"]?.strictPath !== true && canRegisterLoose(path, false);
1258
+ if (!needsEncode && !registerLoose) return;
1259
+ let explicit;
1260
+ if (registerLoose) {
1261
+ explicit = /* @__PURE__ */ new Set();
1262
+ for (let i = 0; i < table.length; i++) {
1263
+ if (table.method[i] !== method) continue;
1264
+ const declared = table.path[i];
1265
+ explicit.add(declared);
1266
+ if ((table.flags[i] & require_route_table.RouteFlag.Encode) !== 0) explicit.add(encodeURI(declared));
1267
+ }
1268
+ }
1269
+ return {
1270
+ method,
1271
+ paths: expandPaths(path, needsEncode, registerLoose, explicit)
1272
+ };
1273
+ }
1274
+ #releaseJit() {
1275
+ require_compile_aot.Compiled.release(this["~programId"]);
1276
+ this.#jitColdRemaining = void 0;
1277
+ this.#jitRoute = void 0;
1278
+ this.#jitStatic = void 0;
1279
+ this.#jitAliases = void 0;
1280
+ this.#jitTable = void 0;
1281
+ }
1282
+ #satisfyJit(table, index) {
1283
+ if ((table.flags[index] & require_route_table.RouteFlag.ExactDuplicate) !== 0) return false;
1284
+ if (this.#jitColdRemaining === void 0) {
1285
+ if (this["~generation"] === void 0 && this["~config"]?.precompile !== true && require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv()) table.flags[index] |= require_route_table.RouteFlag.JITSatisfied;
1286
+ return false;
1287
+ }
1288
+ if ((table.flags[index] & require_route_table.RouteFlag.JITCold) === 0) return false;
1289
+ table.flags[index] &= ~require_route_table.RouteFlag.JITCold;
1290
+ if (--this.#jitColdRemaining > 0) return false;
1291
+ this.#releaseJit();
1292
+ return true;
1293
+ }
1236
1294
  #jitDispatch(index, context) {
1237
1295
  if (this.#compiled[index]) return this.#compiled[index](context);
1238
- const materialized = this.#jitRoute?.[index] ?? require_route_table.routeRow(this.#jitTable, index);
1296
+ const route = this.#jitRoute?.[index];
1297
+ const table = this.#jitTable ?? this["~routeTable"];
1298
+ const materialized = route ?? require_route_table.routeRow(table, index);
1299
+ const exactDuplicate = ((table?.flags[index] ?? 0) & require_route_table.RouteFlag.ExactDuplicate) !== 0;
1239
1300
  let handler;
1240
1301
  try {
1241
- handler = require_compile_handler_index.compileHandler(materialized, this, this.#jitStatic?.[index]);
1302
+ handler = require_compile_handler_index.compileHandler(materialized, this, this.#jitStatic?.[index], exactDuplicate && require_compile_aot.Compiled.hasProgram(this["~programId"]));
1242
1303
  } catch (error) {
1243
1304
  const routeError = new Error(`[Elysia] Failed to compile route ${materialized[0]} ${materialized[1]}: ${error?.message ?? error}`, { cause: error });
1244
1305
  const finalize = this["~finalizeError"];
@@ -1246,24 +1307,15 @@ var Elysia = class Elysia {
1246
1307
  throw routeError;
1247
1308
  }
1248
1309
  this.#compiled[index] = handler;
1249
- let releasedNow = false;
1250
- if (this.#jitColdRemaining !== void 0 && --this.#jitColdRemaining === 0) {
1251
- require_compile_aot.Compiled.release(this["~programId"]);
1252
- this.#jitColdRemaining = void 0;
1253
- releasedNow = true;
1310
+ if (!exactDuplicate) {
1311
+ const aliases = this.#jitAliases?.[index] ?? (table ? this.#staticAliases(table, index) : void 0);
1312
+ if (aliases) {
1313
+ this.#initMap();
1314
+ const map = this["~map"][aliases.method] ??= require_utils.nullObject();
1315
+ for (let p = 0; p < aliases.paths.length; p++) map[aliases.paths[p]] = handler;
1316
+ } else this.#saveHandler(materialized[0], materialized[1], handler);
1254
1317
  }
1255
- const aliases = this.#jitAliases?.[index];
1256
- if (aliases) {
1257
- this.#initMap();
1258
- const map = this["~map"][aliases.method] ??= require_utils.nullObject();
1259
- for (let p = 0; p < aliases.paths.length; p++) map[aliases.paths[p]] = handler;
1260
- } else this.#saveHandler(materialized[0], materialized[1], handler);
1261
- if (releasedNow) {
1262
- this.#jitRoute = void 0;
1263
- this.#jitStatic = void 0;
1264
- this.#jitAliases = void 0;
1265
- this.#jitTable = void 0;
1266
- } else {
1318
+ if (!(table ? this.#satisfyJit(table, index) : false)) {
1267
1319
  if (this.#jitRoute) this.#jitRoute[index] = void 0;
1268
1320
  if (this.#jitStatic) this.#jitStatic[index] = void 0;
1269
1321
  if (this.#jitAliases) this.#jitAliases[index] = void 0;
@@ -1404,19 +1456,30 @@ var Elysia = class Elysia {
1404
1456
  "~wsConfig": this["~wsConfig"]
1405
1457
  };
1406
1458
  if (require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv()) {
1407
- if (this["~config"]?.precompile) require_compile_aot.Compiled.release(this["~programId"]);
1459
+ if (this["~config"]?.precompile) this.#releaseJit();
1408
1460
  else {
1409
1461
  const table = this["~routeTable"];
1410
- const routeCount = table?.length ?? this["~routes"].length;
1411
- let cold = routeCount;
1462
+ const routeCount = table.length;
1412
1463
  const compiled = this.#compiled;
1413
- for (let i = 0; i < routeCount; i++) if (table !== void 0 && (table.flags[i] & require_route_table.RouteFlag.WS) !== 0 || compiled?.[i] !== void 0) cold--;
1414
- if (cold <= 0) require_compile_aot.Compiled.release(this["~programId"]);
1464
+ let cold = 0;
1465
+ for (let i = 0; i < routeCount; i++) {
1466
+ const satisfied = (table.flags[i] & require_route_table.RouteFlag.JITSatisfied) !== 0;
1467
+ table.flags[i] &= ~(require_route_table.RouteFlag.JITCold | require_route_table.RouteFlag.JITSatisfied);
1468
+ const flags = table.flags[i];
1469
+ if (satisfied || (flags & (require_route_table.RouteFlag.WS | require_route_table.RouteFlag.ExactDuplicate)) !== 0 || compiled?.[i] !== void 0) continue;
1470
+ table.flags[i] |= require_route_table.RouteFlag.JITCold;
1471
+ cold++;
1472
+ }
1473
+ if (cold === 0) this.#releaseJit();
1415
1474
  else this.#jitColdRemaining = cold;
1416
1475
  }
1417
1476
  require_compile_analysis_cache.clearAuthoringAnalysisCaches(this);
1418
1477
  if (!this["~ext"]?.macro && !this["~scopeChildren"]) this.#declaredRoutes = void 0;
1419
1478
  }
1479
+ const ext = this["~ext"];
1480
+ if (ext?.hoc) extCallbackIndexes.delete(ext.hoc);
1481
+ if (ext?.setup) extCallbackIndexes.delete(ext.setup);
1482
+ if (ext?.cleanup) extCallbackIndexes.delete(ext.cleanup);
1420
1483
  }
1421
1484
  ["~newGeneration"]() {
1422
1485
  this.#fetchFn = void 0;
@@ -1440,23 +1503,31 @@ var Elysia = class Elysia {
1440
1503
  const appChain = this["~hookChain"];
1441
1504
  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]);
1442
1505
  else for (let i = 0; i < length; i++) if (this.#routeMayHaveModelRef(table, i)) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1506
+ let markDuplicates = false;
1443
1507
  if (length) {
1444
1508
  const programId = this["~programId"];
1445
1509
  if (!require_compile_aot.Compiled.claim(programId, this["~aotFingerprint"] = require_compile_aot.createAotFingerprint())) require_compile_aot.Compiled.assertUncontested(programId);
1510
+ markDuplicates = require_compile_aot.Compiled.hasProgram(programId) || precompile !== true && require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv();
1446
1511
  }
1447
1512
  const isLoose = this["~config"]?.strictPath !== true;
1513
+ const collectExplicit = isLoose && table.hasLoose;
1448
1514
  let explicitPaths;
1449
- if (isLoose && table.hasLoose) {
1450
- explicitPaths = /* @__PURE__ */ new Map();
1515
+ if (markDuplicates || collectExplicit) {
1516
+ const seen = /* @__PURE__ */ new Map();
1517
+ if (collectExplicit) explicitPaths = seen;
1451
1518
  for (let i = 0; i < length; i++) {
1452
1519
  const m = method[i];
1453
1520
  const p = path[i];
1454
- let set = explicitPaths.get(m);
1455
- if (!set) explicitPaths.set(m, set = /* @__PURE__ */ new Set());
1456
- set.add(p);
1457
- if ((flags[i] & require_route_table.RouteFlag.Encode) !== 0) {
1521
+ let paths = seen.get(m);
1522
+ if (!paths) seen.set(m, paths = /* @__PURE__ */ new Map());
1523
+ if (markDuplicates && (flags[i] & require_route_table.RouteFlag.WS) === 0) {
1524
+ const displaced = paths.get(p);
1525
+ if (displaced !== void 0 && displaced >= 0) flags[displaced] |= require_route_table.RouteFlag.ExactDuplicate;
1526
+ paths.set(p, i);
1527
+ } else paths.set(p, -1);
1528
+ if (collectExplicit && (flags[i] & require_route_table.RouteFlag.Encode) !== 0) {
1458
1529
  const encoded = encodeURI(p);
1459
- if (encoded !== p) set.add(encoded);
1530
+ if (encoded !== p && !paths.has(encoded)) paths.set(encoded, -1);
1460
1531
  }
1461
1532
  }
1462
1533
  }
@@ -1563,6 +1634,7 @@ var Elysia = class Elysia {
1563
1634
  * ```
1564
1635
  */
1565
1636
  cleanup(handler) {
1637
+ if (this["~ext"]?.cleanupEpoch?.(handler)) return this;
1566
1638
  this.#assertMutable("cleanup");
1567
1639
  const arr = this.#ext.cleanup ??= [];
1568
1640
  if (Array.isArray(handler)) arr.push(...handler);
@@ -1570,24 +1642,49 @@ var Elysia = class Elysia {
1570
1642
  return this;
1571
1643
  }
1572
1644
  /**
1573
- * Stop the underlying server (if any), running every `cleanup` handler once
1574
- * it has stopped. Mirrors `Server.stop()`.
1645
+ * Stop the underlying server (if any), then run every `cleanup` handler.
1646
+ * Omitted and `false` are the same graceful stop: new requests are gated,
1647
+ * tracked WebSockets are settled and active HTTP is drained, then cleanup
1648
+ * runs and the epoch is released. `true` force-closes the transport
1649
+ * instead of draining it, and escalates a graceful stop already in flight.
1650
+ *
1651
+ * Awaiting `stop()` from inside a `setup`, `cleanup` or WebSocket lifecycle
1652
+ * callback is only supported while that callback is still synchronous; the
1653
+ * teardown is waiting on the callback, so after an `await` it can only be
1654
+ * issued as `void app.stop()`.
1575
1655
  *
1576
- * @param closeActiveConnections Pass `true` to terminate in-flight
1577
- * requests and WebSocket connections immediately. Defaults to
1578
- * draining gracefully.
1656
+ * @param closeActiveConnections Pass `true` to terminate active
1657
+ * transports. Omit (or pass `false`) to drain safely.
1579
1658
  */
1580
1659
  stop(closeActiveConnections) {
1660
+ const stop = this["~ext"]?.stop;
1661
+ if (stop) return stop(closeActiveConnections);
1581
1662
  const server = this.server;
1582
1663
  if (!server) return;
1583
- const r = server.stop?.(closeActiveConnections);
1584
- this.server = void 0;
1664
+ let result;
1665
+ const errors = [];
1666
+ try {
1667
+ result = server.stop?.(closeActiveConnections);
1668
+ } catch (error) {
1669
+ errors.push(error);
1670
+ } finally {
1671
+ this.server = void 0;
1672
+ }
1585
1673
  const handlers = this["~ext"]?.cleanup;
1586
- const fire = handlers ? async () => {
1587
- for (let i = 0; i < handlers.length; i++) await handlers[i](this);
1588
- } : void 0;
1589
- if (r && typeof r.then === "function") return fire ? r.then(fire) : r;
1590
- return fire?.();
1674
+ if (!errors.length && !handlers?.length) return result && typeof result.then === "function" ? result : void 0;
1675
+ return (async () => {
1676
+ try {
1677
+ await result;
1678
+ } catch (error) {
1679
+ errors.push(error);
1680
+ }
1681
+ if (handlers) for (let i = 0; i < handlers.length; i++) try {
1682
+ await handlers[i](this);
1683
+ } catch (error) {
1684
+ errors.push(error);
1685
+ }
1686
+ require_utils.throwLifecycleErrors(errors);
1687
+ })();
1591
1688
  }
1592
1689
  };
1593
1690