elysia 2.0.0-exp.47 → 2.0.0-exp.49

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 (50) hide show
  1. package/dist/adapter/bun/index.js +16 -5
  2. package/dist/adapter/bun/index.mjs +17 -6
  3. package/dist/base.d.ts +27 -2
  4. package/dist/base.js +101 -41
  5. package/dist/base.mjs +101 -41
  6. package/dist/compile/aot-capture.js +11 -1
  7. package/dist/compile/aot-capture.mjs +11 -1
  8. package/dist/compile/aot.js +1 -0
  9. package/dist/compile/aot.mjs +1 -0
  10. package/dist/compile/handler/descriptor.js +4 -3
  11. package/dist/compile/handler/descriptor.mjs +5 -4
  12. package/dist/compile/handler/index.js +2 -1
  13. package/dist/compile/handler/index.mjs +2 -1
  14. package/dist/compile/handler/jit.js +4 -2
  15. package/dist/compile/handler/jit.mjs +4 -2
  16. package/dist/compile/handler/reconstruct.d.ts +1 -1
  17. package/dist/compile/handler/reconstruct.js +12 -3
  18. package/dist/compile/handler/reconstruct.mjs +13 -4
  19. package/dist/compile/lexer.d.ts +1 -1
  20. package/dist/generation.d.ts +11 -1
  21. package/dist/generation.js +17 -1
  22. package/dist/generation.mjs +13 -1
  23. package/dist/handler/fetch.js +4 -4
  24. package/dist/handler/fetch.mjs +4 -4
  25. package/dist/package.js +1 -1
  26. package/dist/package.mjs +1 -1
  27. package/dist/plugin/aot/core.d.ts +13 -0
  28. package/dist/plugin/aot/source.js +28 -10
  29. package/dist/plugin/aot/source.mjs +28 -10
  30. package/dist/plugin/trace.d.ts +10 -0
  31. package/dist/plugin/trace.js +39 -0
  32. package/dist/plugin/trace.mjs +36 -0
  33. package/dist/plugin/websocket.d.ts +6 -0
  34. package/dist/plugin/websocket.js +61 -0
  35. package/dist/plugin/websocket.mjs +60 -0
  36. package/dist/trace.d.ts +6 -1
  37. package/dist/type/bridge-live.d.ts +2 -1
  38. package/dist/type/bridge-live.js +2 -0
  39. package/dist/type/bridge-live.mjs +2 -1
  40. package/dist/type/bridge.d.ts +2 -1
  41. package/dist/type/bridge.js +2 -0
  42. package/dist/type/bridge.mjs +2 -1
  43. package/dist/type/constants.d.ts +1 -1
  44. package/dist/types.d.ts +1 -14
  45. package/dist/ws/context.d.ts +1 -1
  46. package/dist/ws/route.d.ts +4 -2
  47. package/dist/ws/route.js +37 -3
  48. package/dist/ws/route.mjs +36 -4
  49. package/dist/ws/types.d.ts +46 -2
  50. package/package.json +9 -4
@@ -6,7 +6,6 @@ const require_adapter_web_standard_index = require('../web-standard/index.js');
6
6
  const require_generation = require('../../generation.js');
7
7
  const require_compile_handler_index = require('../../compile/handler/index.js');
8
8
  const require_route_table = require('../../route-table.js');
9
- const require_ws_route = require('../../ws/route.js');
10
9
 
11
10
  //#region src/adapter/bun/index.ts
12
11
  const nativeStaticMethods = new Set([
@@ -29,6 +28,16 @@ function collectStaticRoutes(app) {
29
28
  if (!table || !length) return;
30
29
  const methods = table.method;
31
30
  const paths = table.path;
31
+ const handlers = table.handler;
32
+ let hasCandidate = false;
33
+ for (let i = 0; i < length; i++) {
34
+ const h = handlers[i];
35
+ if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
36
+ if (!nativeStaticMethods.has(methods[i])) continue;
37
+ hasCandidate = true;
38
+ break;
39
+ }
40
+ if (!hasCandidate) return;
32
41
  const ready = require_utils.nullObject();
33
42
  const strictPath = frozenRoot["~config"]?.strictPath === true;
34
43
  const seen = /* @__PURE__ */ new Map();
@@ -58,6 +67,8 @@ function collectStaticRoutes(app) {
58
67
  const path = paths[i];
59
68
  if (seen.get(method + " " + path) !== i) continue;
60
69
  if (!nativeStaticMethods.has(method)) continue;
70
+ const h = handlers[i];
71
+ if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
61
72
  const value = require_compile_handler_index.buildNativeStaticResponse(require_route_table.routeRow(table, i), app);
62
73
  if (!value) continue;
63
74
  add(method, path, value);
@@ -98,11 +109,11 @@ const BunAdapter = require_adapter_index.createAdapter({
98
109
  } catch (error) {
99
110
  console.warn("[Elysia] Native static promotion was skipped:", error);
100
111
  }
101
- const hasWs = app["~hasWS"];
102
112
  let websocket;
103
- if (hasWs) {
104
- const defaultConfig = require_generation.frozenRootOf(app)["~config"]?.websocket;
105
- websocket = defaultConfig ? Object.assign(require_ws_route.buildGlobalWSHandler(), defaultConfig) : require_ws_route.buildGlobalWSHandler();
113
+ if (app["~hasWS"]) {
114
+ const resolved = require_generation.resolvedWsOf(app);
115
+ if (!resolved) throw new Error("[Elysia] internal: WebSocket routes are present but no capability provider was resolved.");
116
+ websocket = resolved.config ? Object.assign(resolved.provider.buildGlobalWSHandler(), resolved.config) : resolved.provider.buildGlobalWSHandler();
106
117
  }
107
118
  return {
108
119
  fetch,
@@ -2,10 +2,9 @@ import { createAdapter } from "../index.mjs";
2
2
  import { isDynamicRegex, needEncodeRegex } from "../../constants.mjs";
3
3
  import { flattenChain, getLoosePath, nullObject } from "../../utils.mjs";
4
4
  import { WebStandardAdapter } from "../web-standard/index.mjs";
5
- import { frozenRootOf } from "../../generation.mjs";
5
+ import { frozenRootOf, resolvedWsOf } from "../../generation.mjs";
6
6
  import { buildNativeStaticResponse } from "../../compile/handler/index.mjs";
7
7
  import { routeRow } from "../../route-table.mjs";
8
- import { buildGlobalWSHandler } from "../../ws/route.mjs";
9
8
 
10
9
  //#region src/adapter/bun/index.ts
11
10
  const nativeStaticMethods = new Set([
@@ -28,6 +27,16 @@ function collectStaticRoutes(app) {
28
27
  if (!table || !length) return;
29
28
  const methods = table.method;
30
29
  const paths = table.path;
30
+ const handlers = table.handler;
31
+ let hasCandidate = false;
32
+ for (let i = 0; i < length; i++) {
33
+ const h = handlers[i];
34
+ if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
35
+ if (!nativeStaticMethods.has(methods[i])) continue;
36
+ hasCandidate = true;
37
+ break;
38
+ }
39
+ if (!hasCandidate) return;
31
40
  const ready = nullObject();
32
41
  const strictPath = frozenRoot["~config"]?.strictPath === true;
33
42
  const seen = /* @__PURE__ */ new Map();
@@ -57,6 +66,8 @@ function collectStaticRoutes(app) {
57
66
  const path = paths[i];
58
67
  if (seen.get(method + " " + path) !== i) continue;
59
68
  if (!nativeStaticMethods.has(method)) continue;
69
+ const h = handlers[i];
70
+ if (typeof h === "function" || h instanceof Error || h instanceof Promise) continue;
60
71
  const value = buildNativeStaticResponse(routeRow(table, i), app);
61
72
  if (!value) continue;
62
73
  add(method, path, value);
@@ -97,11 +108,11 @@ const BunAdapter = createAdapter({
97
108
  } catch (error) {
98
109
  console.warn("[Elysia] Native static promotion was skipped:", error);
99
110
  }
100
- const hasWs = app["~hasWS"];
101
111
  let websocket;
102
- if (hasWs) {
103
- const defaultConfig = frozenRootOf(app)["~config"]?.websocket;
104
- websocket = defaultConfig ? Object.assign(buildGlobalWSHandler(), defaultConfig) : buildGlobalWSHandler();
112
+ if (app["~hasWS"]) {
113
+ const resolved = resolvedWsOf(app);
114
+ if (!resolved) throw new Error("[Elysia] internal: WebSocket routes are present but no capability provider was resolved.");
115
+ websocket = resolved.config ? Object.assign(resolved.provider.buildGlobalWSHandler(), resolved.config) : resolved.provider.buildGlobalWSHandler();
105
116
  }
106
117
  return {
107
118
  fetch,
package/dist/base.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { ElysiaStatus } from "./error.js";
2
- import { WSHandlerResponse, WSLocalHook, WSMessageHandler } from "./ws/types.js";
3
- import { TraceHandler } from "./trace.js";
2
+ import { TraceCapability, TraceHandler } from "./trace.js";
4
3
  import { ListenCallback, Serve, Server } from "./universal/server.js";
5
4
  import { AddRoute, AddWSRoute, AfterHandler, AfterResponseHandler, AnyErrorConstructor, AnyLocalHook, BodyHandler, CompiledHandler, CreateEden, DefaultEphemeral, DefaultMetadata, DefaultSingleton, DefinitionBase, DocumentDecoration, ElysiaConfig, ElysiaHandlerToResponseSchemaAmbiguous, EphemeralType, ErrorDefinitionEntry, ErrorHandler, EventScope, ExcludeElysiaResponse, ExtractErrorFromHandle, GlobalHookReturn, GracefulHandler, GuardHookSingleton, GuardLocalHook, HTTPMethod, HistoryEntry, HookContextSchema, HookContextSingleton, InlineHandler, InlineHandlerNonMacro, InputSchema, InputSchemaKey, InternalRoute, IntersectIfObjectSchema, JoinPath, LocalHook, LocalHookReturn, Macro, MacroSchemaChannel, MacroToContext, MacroToProperty, MapResponse, MaybeArray, MaybePromise, MergeElysiaInstances, MergeSchema, MergeScopedSchemas, MetadataBase, NonResolvableMacroKey, ObjectMacroDefs, OptionalHandler, PluginHookReturn, PreHandler, Prettify, PublicRoute, ResolveRouteErrors, RouteBase, ScopedHookReturn, ScopedMapDeriveReturn, SingletonBase, StaticMapAliases, TransformHandler, UnionResponseStatus, UnwrapRoute, WrapFn } from "./types.js";
6
5
  import { ChainNode } from "./utils.js";
7
6
  import { Context, ErrorContext, LifecycleContext } from "./context.js";
8
7
  import { AnySchema } from "./type/types.js";
9
8
  import { AotFingerprint, CompilerSession, ProgramId } from "./compile/aot.js";
9
+ import { WSCapability, WSHandlerResponse, WSLocalHook, WSMessageHandler, WSOptions, WSOptionsEntry } from "./ws/types.js";
10
10
  import { RouteTable } from "./route-table.js";
11
11
  import { Generation } from "./generation.js";
12
12
  import Memoirist from "memoirist";
@@ -39,8 +39,18 @@ 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
+ capability?: {
43
+ trace?: {
44
+ provider: TraceCapability;
45
+ };
46
+ ws?: {
47
+ provider: WSCapability;
48
+ options?: WSOptionsEntry[];
49
+ };
50
+ };
42
51
  };
43
52
  '~hookChain'?: ChainNode;
53
+ '~wsConfig'?: WSOptions;
44
54
  server?: Server;
45
55
  get history(): readonly HistoryEntry[];
46
56
  get ['~routes'](): readonly InternalRoute[];
@@ -514,6 +524,21 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
514
524
  parser: Metadata['parser'];
515
525
  response: Metadata['response'] & MacroContext['response'] & ElysiaHandlerToResponseSchemaAmbiguous<BeforeHandle> & ElysiaHandlerToResponseSchemaAmbiguous<AfterHandle> & ElysiaHandlerToResponseSchemaAmbiguous<ErrorHandle>;
516
526
  }, {}, Ephemeral, Volatile>) => NewElysia): Elysia<BasePath, Scope, Singleton, Definitions, Metadata, Routes & NewElysia['~Routes'], Ephemeral, Volatile>;
527
+ /**
528
+ * Single idempotent capability resolver-accessor (README rev 2.1). Reads
529
+ * the merged `~ext.capability` channel — never mutated during a build — and
530
+ * throws when a capability is used but its provider was never registered.
531
+ * For `trace` the resolved view IS the nearest-root provider (merge already
532
+ * keeps it); for `ws` it additionally resolves the depth-precedence base
533
+ * options. Called at the start of every router build and by `compileHandler`
534
+ * before it reads the frozen root, so direct-compiler paths resolve without
535
+ * a prior seal.
536
+ */
537
+ ['~resolvedCapability'](kind: 'trace'): TraceCapability | undefined;
538
+ ['~resolvedCapability'](kind: 'ws'): {
539
+ provider: WSCapability;
540
+ options: WSOptions | undefined;
541
+ } | undefined;
517
542
  /**
518
543
  * ### macro
519
544
  * Declare a custom route property: applying it on a route or guard folds
package/dist/base.js CHANGED
@@ -6,9 +6,9 @@ const require_utils = require('./utils.js');
6
6
  const require_type_bridge = require('./type/bridge.js');
7
7
  const require_universal_is_production = require('./universal/is-production.js');
8
8
  const require_compile_aot = require('./compile/aot.js');
9
+ const require_generation = require('./generation.js');
9
10
  const require_compile_handler_index = require('./compile/handler/index.js');
10
11
  const require_route_table = require('./route-table.js');
11
- const require_ws_route = require('./ws/route.js');
12
12
  const require_adapter_bun_index = require('./adapter/bun/index.js');
13
13
  const require_handler_fetch = require('./handler/fetch.js');
14
14
  const require_compile_analysis_cache = require('./compile/analysis-cache.js');
@@ -57,20 +57,20 @@ var Elysia = class Elysia {
57
57
  if (!this.#declaredRoutes?.length) return [];
58
58
  const routes = this.#declaredRoutes;
59
59
  if (!this["~ext"]?.macro && !this["~scopeChildren"]) return routes;
60
- return routes.map((route) => {
61
- const localHook = route[4];
62
- if (!localHook) return route;
63
- const resolved = require_compile_handler_index.resolveLocalHook(require_compile_handler_index.localMacroRoot(route[7] ?? route[3] ?? this, this), localHook, this);
64
- if (resolved === localHook) return route;
60
+ return routes.map((r) => {
61
+ const localHook = r[4];
62
+ if (!localHook) return r;
63
+ const resolved = require_compile_handler_index.resolveLocalHook(require_compile_handler_index.localMacroRoot(r[7] ?? r[3] ?? this, this), localHook, this);
64
+ if (resolved === localHook) return r;
65
65
  return [
66
- route[0],
67
- route[1],
68
- route[2],
69
- route[3],
66
+ r[0],
67
+ r[1],
68
+ r[2],
69
+ r[3],
70
70
  resolved,
71
- route[5],
72
- route[6],
73
- route[7]
71
+ r[5],
72
+ r[6],
73
+ r[7]
74
74
  ];
75
75
  });
76
76
  }
@@ -385,6 +385,11 @@ var Elysia = class Elysia {
385
385
  if (src.models) ext.models = Object.assign(require_utils.nullObject(), src.models);
386
386
  if (src.macro) ext.macro = Object.create(src.macro);
387
387
  if (src.parser) ext.parser = Object.assign(require_utils.nullObject(), src.parser);
388
+ if (src.capability) {
389
+ const cap = ext.capability = require_utils.nullObject();
390
+ if (src.capability.trace) cap.trace = src.capability.trace;
391
+ if (src.capability.ws) cap.ws = src.capability.ws;
392
+ }
388
393
  }
389
394
  if (isSchema) child.guard({ ...schemaOrRun });
390
395
  callback(child);
@@ -395,6 +400,21 @@ var Elysia = class Elysia {
395
400
  get #ext() {
396
401
  return this["~ext"] ??= require_utils.nullObject();
397
402
  }
403
+ ["~resolvedCapability"](kind) {
404
+ if (kind === "ws") {
405
+ const ws = this["~ext"]?.capability?.ws;
406
+ const provider = ws?.provider;
407
+ if (this["~hasWS"] && !provider) throw new Error(require_generation.wsCapabilityRequired);
408
+ if (!provider) return;
409
+ return {
410
+ provider,
411
+ options: this["~hasWS"] && ws.options?.length ? provider.resolveOptions(ws.options) : void 0
412
+ };
413
+ }
414
+ const provider = this["~ext"]?.capability?.trace?.provider;
415
+ if (this["~hasTrace"] && !provider) throw new Error(require_generation.traceCapabilityRequired);
416
+ return provider;
417
+ }
398
418
  #ensureMacroTable() {
399
419
  const ext = this.#ext;
400
420
  if (ext.macro) return ext.macro;
@@ -620,7 +640,7 @@ var Elysia = class Elysia {
620
640
  }
621
641
  const hookChain = app["~hookChain"];
622
642
  if (app["~ext"]) {
623
- const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup } = app["~ext"];
643
+ const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, capability } = app["~ext"];
624
644
  const ext = this["~ext"] ??= require_utils.nullObject();
625
645
  if (decorator) {
626
646
  const cloned = require_utils.clonePlainDecorators(decorator);
@@ -678,6 +698,48 @@ var Elysia = class Elysia {
678
698
  ext.cleanup.push(fn);
679
699
  }
680
700
  } else ext.cleanup = cleanup.slice();
701
+ if (capability) {
702
+ const target = ext.capability ??= require_utils.nullObject();
703
+ if (capability.trace) {
704
+ const incoming = capability.trace.provider;
705
+ if (!target.trace) target.trace = { provider: incoming };
706
+ else if (target.trace.provider !== incoming) console.warn(`[Elysia] Duplicate trace capability providers detected:\n ${target.trace.provider.id}\n ${incoming.id}\nUsing the first; ensure a single copy of 'elysia/trace' is installed.`);
707
+ }
708
+ if (capability.ws) {
709
+ const incoming = capability.ws.provider;
710
+ const existing = target.ws;
711
+ const incomingOptions = capability.ws.options;
712
+ if (!existing) target.ws = {
713
+ provider: incoming,
714
+ options: incomingOptions?.length ? incomingOptions.map((entry) => ({
715
+ depth: entry.depth + 1,
716
+ value: entry.value,
717
+ origin: entry.origin
718
+ })) : void 0
719
+ };
720
+ else {
721
+ if (existing.provider !== incoming) console.warn(`[Elysia] Duplicate WebSocket capability providers detected:\n ${existing.provider.id}\n ${incoming.id}\nUsing the first; ensure a single copy of 'elysia/websocket' is installed.`);
722
+ if (incomingOptions?.length) {
723
+ const base = existing.options ?? [];
724
+ const seen = new Set(base.map((e) => e.origin));
725
+ let next;
726
+ for (const entry of incomingOptions) {
727
+ if (seen.has(entry.origin)) continue;
728
+ seen.add(entry.origin);
729
+ (next ??= base.slice()).push({
730
+ depth: entry.depth + 1,
731
+ value: entry.value,
732
+ origin: entry.origin
733
+ });
734
+ }
735
+ if (next) target.ws = {
736
+ provider: existing.provider,
737
+ options: next
738
+ };
739
+ }
740
+ }
741
+ }
742
+ }
681
743
  }
682
744
  if (app.#hasPlugin || app.#hasGlobal || hookChain) {
683
745
  let pluginEvents;
@@ -1088,9 +1150,11 @@ var Elysia = class Elysia {
1088
1150
  throw routeError;
1089
1151
  }
1090
1152
  this.#compiled[index] = handler;
1153
+ let releasedNow = false;
1091
1154
  if (this.#jitColdRemaining !== void 0 && --this.#jitColdRemaining === 0) {
1092
1155
  require_compile_aot.Compiled.release(this["~programId"]);
1093
1156
  this.#jitColdRemaining = void 0;
1157
+ releasedNow = true;
1094
1158
  }
1095
1159
  const aliases = this.#jitAliases?.[index];
1096
1160
  if (aliases) {
@@ -1098,9 +1162,16 @@ var Elysia = class Elysia {
1098
1162
  const map = this["~map"][aliases.method] ??= require_utils.nullObject();
1099
1163
  for (let p = 0; p < aliases.paths.length; p++) map[aliases.paths[p]] = handler;
1100
1164
  } else this.#saveHandler(materialized[0], materialized[1], handler);
1101
- if (this.#jitRoute) this.#jitRoute[index] = void 0;
1102
- if (this.#jitStatic) this.#jitStatic[index] = void 0;
1103
- if (this.#jitAliases) this.#jitAliases[index] = void 0;
1165
+ if (releasedNow) {
1166
+ this.#jitRoute = void 0;
1167
+ this.#jitStatic = void 0;
1168
+ this.#jitAliases = void 0;
1169
+ this.#jitTable = void 0;
1170
+ } else {
1171
+ if (this.#jitRoute) this.#jitRoute[index] = void 0;
1172
+ if (this.#jitStatic) this.#jitStatic[index] = void 0;
1173
+ if (this.#jitAliases) this.#jitAliases[index] = void 0;
1174
+ }
1104
1175
  return handler(context);
1105
1176
  }
1106
1177
  #saveHandler(method, path, handler) {
@@ -1216,10 +1287,6 @@ var Elysia = class Elysia {
1216
1287
  const previousJitStatic = this.#jitStatic;
1217
1288
  const previousJitAliases = this.#jitAliases;
1218
1289
  const previousHasDynamicWS = this["~hasDynamicWS"];
1219
- const config = this["~config"];
1220
- const hadWebsocket = !!config && Object.hasOwn(config, "websocket");
1221
- const previousWebsocket = config?.websocket;
1222
- const websocketSnapshot = previousWebsocket && typeof previousWebsocket === "object" ? { ...previousWebsocket } : previousWebsocket;
1223
1290
  const previousGeneration = this["~generation"];
1224
1291
  this["~map"] = void 0;
1225
1292
  this["~router"] = void 0;
@@ -1258,11 +1325,6 @@ var Elysia = class Elysia {
1258
1325
  this.#jitAliases = previousJitAliases;
1259
1326
  this["~hasDynamicWS"] = previousHasDynamicWS;
1260
1327
  this["~generation"] = previousGeneration;
1261
- if (config) {
1262
- this["~config"] = config;
1263
- if (hadWebsocket) config.websocket = websocketSnapshot;
1264
- else delete config.websocket;
1265
- } else this["~config"] = void 0;
1266
1328
  throw error;
1267
1329
  } finally {
1268
1330
  require_compile_aot.endCompilerSession(this, compilerSession, !buildSucceeded);
@@ -1278,17 +1340,17 @@ var Elysia = class Elysia {
1278
1340
  "~hookChain": this["~hookChain"],
1279
1341
  "~scopeChildren": this["~scopeChildren"],
1280
1342
  "~applyMacro": this["~applyMacro"].bind(this),
1281
- "~programId": this["~programId"]
1343
+ "~programId": this["~programId"],
1344
+ "~wsConfig": this["~wsConfig"]
1282
1345
  };
1283
1346
  if (require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv()) {
1284
1347
  if (this["~config"]?.precompile) require_compile_aot.Compiled.release(this["~programId"]);
1285
1348
  else {
1286
- const routeCount = this["~routeTable"]?.length ?? this["~routes"].length;
1349
+ const table = this["~routeTable"];
1350
+ const routeCount = table?.length ?? this["~routes"].length;
1287
1351
  let cold = routeCount;
1288
1352
  const compiled = this.#compiled;
1289
- if (compiled) {
1290
- for (let i = 0; i < routeCount; i++) if (compiled[i] !== void 0) cold--;
1291
- }
1353
+ 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--;
1292
1354
  if (cold <= 0) require_compile_aot.Compiled.release(this["~programId"]);
1293
1355
  else this.#jitColdRemaining = cold;
1294
1356
  }
@@ -1305,6 +1367,9 @@ var Elysia = class Elysia {
1305
1367
  }
1306
1368
  #buildRouterUnsafe() {
1307
1369
  const precompile = this["~config"]?.precompile;
1370
+ this["~resolvedCapability"]("trace");
1371
+ const wsCap = this["~resolvedCapability"]("ws");
1372
+ let wsConfig;
1308
1373
  this.#initMap();
1309
1374
  const methods = this["~map"];
1310
1375
  const table = this["~routeTable"] = require_route_table.buildRouteTable(this["~routes"]);
@@ -1341,9 +1406,10 @@ var Elysia = class Elysia {
1341
1406
  const routePath = path[i];
1342
1407
  const routeFlags = flags[i];
1343
1408
  if ((routeFlags & require_route_table.RouteFlag.WS) !== 0) {
1344
- const ws = require_ws_route.buildWSRoute(require_route_table.routeRow(table, i), this);
1409
+ const ws = wsCap.provider.buildWSRoute(require_route_table.routeRow(table, i), this);
1345
1410
  const handler = ws[0];
1346
1411
  const options = ws[1];
1412
+ if (wsConfig === void 0 && wsCap.options) wsConfig = wsCap.options;
1347
1413
  if ((routeFlags & require_route_table.RouteFlag.Dynamic) !== 0) {
1348
1414
  (this["~router"] ??= new memoirist.default({ loosePath: isLoose })).add("WS", routePath, handler);
1349
1415
  this["~hasDynamicWS"] = true;
@@ -1357,15 +1423,8 @@ var Elysia = class Elysia {
1357
1423
  }
1358
1424
  }
1359
1425
  if (options && require_utils.isNotEmpty(options)) {
1360
- this["~config"] ??= require_utils.nullObject();
1361
- const existing = this["~config"].websocket;
1362
- if (existing && require_universal_constants.isBun) {
1363
- for (const key in options) if (key in existing && existing[key] !== options[key]) {
1364
- console.warn(`[Elysia] Conflicting per-route WebSocket option '${key}'\nBun uses one global WebSocket config per server, per-route values are not enforced (the last-registered route wins).`);
1365
- console.warn((/* @__PURE__ */ new Error()).stack);
1366
- }
1367
- Object.assign(existing, options);
1368
- } else this["~config"].websocket = options;
1426
+ wsConfig ??= require_utils.nullObject();
1427
+ wsCap.provider.accumulateOptions(wsConfig, options, routePath);
1369
1428
  }
1370
1429
  continue;
1371
1430
  }
@@ -1405,6 +1464,7 @@ var Elysia = class Elysia {
1405
1464
  for (let p = 0; p < paths.length; p++) map[paths[p]] = handler;
1406
1465
  }
1407
1466
  }
1467
+ this["~wsConfig"] = wsConfig;
1408
1468
  }
1409
1469
  #fetchFn;
1410
1470
  get fetch() {
package/dist/base.mjs CHANGED
@@ -4,9 +4,9 @@ import { clonePlainDecorators, clonePlainDeep, coalesceStandaloneSchemas, create
4
4
  import { Ref } from "./type/bridge.mjs";
5
5
  import { isProduction } from "./universal/is-production.mjs";
6
6
  import { Capture, Compiled, beginCompilerSession, createAotFingerprint, createProgramId, endCompilerSession } from "./compile/aot.mjs";
7
+ import { traceCapabilityRequired, wsCapabilityRequired } from "./generation.mjs";
7
8
  import { compileHandler, composeRouteHook, localMacroRoot, resolveLocalHook } from "./compile/handler/index.mjs";
8
9
  import { RouteFlag, buildRouteTable, routeRow } from "./route-table.mjs";
9
- import { buildWSRoute } from "./ws/route.mjs";
10
10
  import { BunAdapter } from "./adapter/bun/index.mjs";
11
11
  import { applyHoc, createFetchHandler } from "./handler/fetch.mjs";
12
12
  import { clearAuthoringAnalysisCaches } from "./compile/analysis-cache.mjs";
@@ -54,20 +54,20 @@ var Elysia = class Elysia {
54
54
  if (!this.#declaredRoutes?.length) return [];
55
55
  const routes = this.#declaredRoutes;
56
56
  if (!this["~ext"]?.macro && !this["~scopeChildren"]) return routes;
57
- return routes.map((route) => {
58
- const localHook = route[4];
59
- if (!localHook) return route;
60
- const resolved = resolveLocalHook(localMacroRoot(route[7] ?? route[3] ?? this, this), localHook, this);
61
- if (resolved === localHook) return route;
57
+ return routes.map((r) => {
58
+ const localHook = r[4];
59
+ if (!localHook) return r;
60
+ const resolved = resolveLocalHook(localMacroRoot(r[7] ?? r[3] ?? this, this), localHook, this);
61
+ if (resolved === localHook) return r;
62
62
  return [
63
- route[0],
64
- route[1],
65
- route[2],
66
- route[3],
63
+ r[0],
64
+ r[1],
65
+ r[2],
66
+ r[3],
67
67
  resolved,
68
- route[5],
69
- route[6],
70
- route[7]
68
+ r[5],
69
+ r[6],
70
+ r[7]
71
71
  ];
72
72
  });
73
73
  }
@@ -382,6 +382,11 @@ var Elysia = class Elysia {
382
382
  if (src.models) ext.models = Object.assign(nullObject(), src.models);
383
383
  if (src.macro) ext.macro = Object.create(src.macro);
384
384
  if (src.parser) ext.parser = Object.assign(nullObject(), src.parser);
385
+ if (src.capability) {
386
+ const cap = ext.capability = nullObject();
387
+ if (src.capability.trace) cap.trace = src.capability.trace;
388
+ if (src.capability.ws) cap.ws = src.capability.ws;
389
+ }
385
390
  }
386
391
  if (isSchema) child.guard({ ...schemaOrRun });
387
392
  callback(child);
@@ -392,6 +397,21 @@ var Elysia = class Elysia {
392
397
  get #ext() {
393
398
  return this["~ext"] ??= nullObject();
394
399
  }
400
+ ["~resolvedCapability"](kind) {
401
+ if (kind === "ws") {
402
+ const ws = this["~ext"]?.capability?.ws;
403
+ const provider = ws?.provider;
404
+ if (this["~hasWS"] && !provider) throw new Error(wsCapabilityRequired);
405
+ if (!provider) return;
406
+ return {
407
+ provider,
408
+ options: this["~hasWS"] && ws.options?.length ? provider.resolveOptions(ws.options) : void 0
409
+ };
410
+ }
411
+ const provider = this["~ext"]?.capability?.trace?.provider;
412
+ if (this["~hasTrace"] && !provider) throw new Error(traceCapabilityRequired);
413
+ return provider;
414
+ }
395
415
  #ensureMacroTable() {
396
416
  const ext = this.#ext;
397
417
  if (ext.macro) return ext.macro;
@@ -617,7 +637,7 @@ var Elysia = class Elysia {
617
637
  }
618
638
  const hookChain = app["~hookChain"];
619
639
  if (app["~ext"]) {
620
- const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup } = app["~ext"];
640
+ const { decorator, store, headers, models, parser, macro, error, hoc, setup, cleanup, capability } = app["~ext"];
621
641
  const ext = this["~ext"] ??= nullObject();
622
642
  if (decorator) {
623
643
  const cloned = clonePlainDecorators(decorator);
@@ -675,6 +695,48 @@ var Elysia = class Elysia {
675
695
  ext.cleanup.push(fn);
676
696
  }
677
697
  } else ext.cleanup = cleanup.slice();
698
+ if (capability) {
699
+ const target = ext.capability ??= nullObject();
700
+ if (capability.trace) {
701
+ const incoming = capability.trace.provider;
702
+ if (!target.trace) target.trace = { provider: incoming };
703
+ else if (target.trace.provider !== incoming) console.warn(`[Elysia] Duplicate trace capability providers detected:\n ${target.trace.provider.id}\n ${incoming.id}\nUsing the first; ensure a single copy of 'elysia/trace' is installed.`);
704
+ }
705
+ if (capability.ws) {
706
+ const incoming = capability.ws.provider;
707
+ const existing = target.ws;
708
+ const incomingOptions = capability.ws.options;
709
+ if (!existing) target.ws = {
710
+ provider: incoming,
711
+ options: incomingOptions?.length ? incomingOptions.map((entry) => ({
712
+ depth: entry.depth + 1,
713
+ value: entry.value,
714
+ origin: entry.origin
715
+ })) : void 0
716
+ };
717
+ else {
718
+ if (existing.provider !== incoming) console.warn(`[Elysia] Duplicate WebSocket capability providers detected:\n ${existing.provider.id}\n ${incoming.id}\nUsing the first; ensure a single copy of 'elysia/websocket' is installed.`);
719
+ if (incomingOptions?.length) {
720
+ const base = existing.options ?? [];
721
+ const seen = new Set(base.map((e) => e.origin));
722
+ let next;
723
+ for (const entry of incomingOptions) {
724
+ if (seen.has(entry.origin)) continue;
725
+ seen.add(entry.origin);
726
+ (next ??= base.slice()).push({
727
+ depth: entry.depth + 1,
728
+ value: entry.value,
729
+ origin: entry.origin
730
+ });
731
+ }
732
+ if (next) target.ws = {
733
+ provider: existing.provider,
734
+ options: next
735
+ };
736
+ }
737
+ }
738
+ }
739
+ }
678
740
  }
679
741
  if (app.#hasPlugin || app.#hasGlobal || hookChain) {
680
742
  let pluginEvents;
@@ -1085,9 +1147,11 @@ var Elysia = class Elysia {
1085
1147
  throw routeError;
1086
1148
  }
1087
1149
  this.#compiled[index] = handler;
1150
+ let releasedNow = false;
1088
1151
  if (this.#jitColdRemaining !== void 0 && --this.#jitColdRemaining === 0) {
1089
1152
  Compiled.release(this["~programId"]);
1090
1153
  this.#jitColdRemaining = void 0;
1154
+ releasedNow = true;
1091
1155
  }
1092
1156
  const aliases = this.#jitAliases?.[index];
1093
1157
  if (aliases) {
@@ -1095,9 +1159,16 @@ var Elysia = class Elysia {
1095
1159
  const map = this["~map"][aliases.method] ??= nullObject();
1096
1160
  for (let p = 0; p < aliases.paths.length; p++) map[aliases.paths[p]] = handler;
1097
1161
  } else this.#saveHandler(materialized[0], materialized[1], handler);
1098
- if (this.#jitRoute) this.#jitRoute[index] = void 0;
1099
- if (this.#jitStatic) this.#jitStatic[index] = void 0;
1100
- if (this.#jitAliases) this.#jitAliases[index] = void 0;
1162
+ if (releasedNow) {
1163
+ this.#jitRoute = void 0;
1164
+ this.#jitStatic = void 0;
1165
+ this.#jitAliases = void 0;
1166
+ this.#jitTable = void 0;
1167
+ } else {
1168
+ if (this.#jitRoute) this.#jitRoute[index] = void 0;
1169
+ if (this.#jitStatic) this.#jitStatic[index] = void 0;
1170
+ if (this.#jitAliases) this.#jitAliases[index] = void 0;
1171
+ }
1101
1172
  return handler(context);
1102
1173
  }
1103
1174
  #saveHandler(method, path, handler) {
@@ -1213,10 +1284,6 @@ var Elysia = class Elysia {
1213
1284
  const previousJitStatic = this.#jitStatic;
1214
1285
  const previousJitAliases = this.#jitAliases;
1215
1286
  const previousHasDynamicWS = this["~hasDynamicWS"];
1216
- const config = this["~config"];
1217
- const hadWebsocket = !!config && Object.hasOwn(config, "websocket");
1218
- const previousWebsocket = config?.websocket;
1219
- const websocketSnapshot = previousWebsocket && typeof previousWebsocket === "object" ? { ...previousWebsocket } : previousWebsocket;
1220
1287
  const previousGeneration = this["~generation"];
1221
1288
  this["~map"] = void 0;
1222
1289
  this["~router"] = void 0;
@@ -1255,11 +1322,6 @@ var Elysia = class Elysia {
1255
1322
  this.#jitAliases = previousJitAliases;
1256
1323
  this["~hasDynamicWS"] = previousHasDynamicWS;
1257
1324
  this["~generation"] = previousGeneration;
1258
- if (config) {
1259
- this["~config"] = config;
1260
- if (hadWebsocket) config.websocket = websocketSnapshot;
1261
- else delete config.websocket;
1262
- } else this["~config"] = void 0;
1263
1325
  throw error;
1264
1326
  } finally {
1265
1327
  endCompilerSession(this, compilerSession, !buildSucceeded);
@@ -1275,17 +1337,17 @@ var Elysia = class Elysia {
1275
1337
  "~hookChain": this["~hookChain"],
1276
1338
  "~scopeChildren": this["~scopeChildren"],
1277
1339
  "~applyMacro": this["~applyMacro"].bind(this),
1278
- "~programId": this["~programId"]
1340
+ "~programId": this["~programId"],
1341
+ "~wsConfig": this["~wsConfig"]
1279
1342
  };
1280
1343
  if (isProduction() && !Capture.isAotBuildEnv()) {
1281
1344
  if (this["~config"]?.precompile) Compiled.release(this["~programId"]);
1282
1345
  else {
1283
- const routeCount = this["~routeTable"]?.length ?? this["~routes"].length;
1346
+ const table = this["~routeTable"];
1347
+ const routeCount = table?.length ?? this["~routes"].length;
1284
1348
  let cold = routeCount;
1285
1349
  const compiled = this.#compiled;
1286
- if (compiled) {
1287
- for (let i = 0; i < routeCount; i++) if (compiled[i] !== void 0) cold--;
1288
- }
1350
+ for (let i = 0; i < routeCount; i++) if (table !== void 0 && (table.flags[i] & RouteFlag.WS) !== 0 || compiled?.[i] !== void 0) cold--;
1289
1351
  if (cold <= 0) Compiled.release(this["~programId"]);
1290
1352
  else this.#jitColdRemaining = cold;
1291
1353
  }
@@ -1302,6 +1364,9 @@ var Elysia = class Elysia {
1302
1364
  }
1303
1365
  #buildRouterUnsafe() {
1304
1366
  const precompile = this["~config"]?.precompile;
1367
+ this["~resolvedCapability"]("trace");
1368
+ const wsCap = this["~resolvedCapability"]("ws");
1369
+ let wsConfig;
1305
1370
  this.#initMap();
1306
1371
  const methods = this["~map"];
1307
1372
  const table = this["~routeTable"] = buildRouteTable(this["~routes"]);
@@ -1338,9 +1403,10 @@ var Elysia = class Elysia {
1338
1403
  const routePath = path[i];
1339
1404
  const routeFlags = flags[i];
1340
1405
  if ((routeFlags & RouteFlag.WS) !== 0) {
1341
- const ws = buildWSRoute(routeRow(table, i), this);
1406
+ const ws = wsCap.provider.buildWSRoute(routeRow(table, i), this);
1342
1407
  const handler = ws[0];
1343
1408
  const options = ws[1];
1409
+ if (wsConfig === void 0 && wsCap.options) wsConfig = wsCap.options;
1344
1410
  if ((routeFlags & RouteFlag.Dynamic) !== 0) {
1345
1411
  (this["~router"] ??= new Memoirist({ loosePath: isLoose })).add("WS", routePath, handler);
1346
1412
  this["~hasDynamicWS"] = true;
@@ -1354,15 +1420,8 @@ var Elysia = class Elysia {
1354
1420
  }
1355
1421
  }
1356
1422
  if (options && isNotEmpty(options)) {
1357
- this["~config"] ??= nullObject();
1358
- const existing = this["~config"].websocket;
1359
- if (existing && isBun) {
1360
- for (const key in options) if (key in existing && existing[key] !== options[key]) {
1361
- console.warn(`[Elysia] Conflicting per-route WebSocket option '${key}'\nBun uses one global WebSocket config per server, per-route values are not enforced (the last-registered route wins).`);
1362
- console.warn((/* @__PURE__ */ new Error()).stack);
1363
- }
1364
- Object.assign(existing, options);
1365
- } else this["~config"].websocket = options;
1423
+ wsConfig ??= nullObject();
1424
+ wsCap.provider.accumulateOptions(wsConfig, options, routePath);
1366
1425
  }
1367
1426
  continue;
1368
1427
  }
@@ -1402,6 +1461,7 @@ var Elysia = class Elysia {
1402
1461
  for (let p = 0; p < paths.length; p++) map[paths[p]] = handler;
1403
1462
  }
1404
1463
  }
1464
+ this["~wsConfig"] = wsConfig;
1405
1465
  }
1406
1466
  #fetchFn;
1407
1467
  get fetch() {