elysia 2.0.0-exp.29 → 2.0.0-exp.30

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 (44) hide show
  1. package/dist/base.d.ts +8 -0
  2. package/dist/base.js +9 -1
  3. package/dist/base.mjs +9 -1
  4. package/dist/compile/aot.d.ts +4 -1
  5. package/dist/compile/aot.js +12 -2
  6. package/dist/compile/aot.mjs +12 -2
  7. package/dist/compile/handler/frozen-validator.d.ts +2 -4
  8. package/dist/compile/handler/frozen-validator.js +10 -10
  9. package/dist/compile/handler/frozen-validator.mjs +10 -10
  10. package/dist/error.d.ts +1 -1
  11. package/dist/plugin/core.d.ts +7 -47
  12. package/dist/plugin/core.js +10 -61
  13. package/dist/plugin/core.mjs +10 -61
  14. package/dist/plugin/source.d.ts +8 -1
  15. package/dist/plugin/source.js +7 -1
  16. package/dist/plugin/source.mjs +7 -1
  17. package/dist/type/bridge.d.ts +6 -6
  18. package/dist/type/coerce-plan.d.ts +6 -2
  19. package/dist/type/coerce-plan.js +21 -10
  20. package/dist/type/coerce-plan.mjs +20 -10
  21. package/dist/type/coerce.js +32 -4
  22. package/dist/type/coerce.mjs +33 -5
  23. package/dist/type/elysia/array-string.js +1 -1
  24. package/dist/type/elysia/array-string.mjs +1 -1
  25. package/dist/type/elysia/files.js +1 -1
  26. package/dist/type/elysia/files.mjs +1 -1
  27. package/dist/type/elysia/integer-string.js +1 -1
  28. package/dist/type/elysia/integer-string.mjs +1 -1
  29. package/dist/type/elysia/numeric-enum.js +1 -1
  30. package/dist/type/elysia/numeric-enum.mjs +1 -1
  31. package/dist/type/elysia/numeric.js +1 -1
  32. package/dist/type/elysia/numeric.mjs +1 -1
  33. package/dist/type/exports.js +5 -5
  34. package/dist/type/exports.mjs +5 -5
  35. package/dist/type/index.js +5 -5
  36. package/dist/type/index.mjs +5 -5
  37. package/dist/type/validator/index.js +1 -1
  38. package/dist/type/validator/index.mjs +1 -1
  39. package/dist/types.d.ts +0 -10
  40. package/dist/utils.js +7 -0
  41. package/dist/utils.mjs +7 -0
  42. package/dist/ws/route.js +16 -7
  43. package/dist/ws/route.mjs +16 -7
  44. package/package.json +6 -1
package/dist/base.d.ts CHANGED
@@ -56,6 +56,14 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
56
56
  '~scopeChild'?: boolean;
57
57
  '~scopeChildren'?: AnyElysia[];
58
58
  constructor(config?: ElysiaConfig<BasePath, Scope>);
59
+ /**
60
+ * @deprecated use `app.history` instead
61
+ *
62
+ * Elysia changes it store route internally
63
+ * This API is provided as compatibility
64
+ *
65
+ * Will remove in 2.1
66
+ */
59
67
  get routes(): PublicRoute[];
60
68
  /**
61
69
  * ### decorate
package/dist/base.js CHANGED
@@ -55,6 +55,14 @@ var Elysia = class Elysia {
55
55
  if (this["~Prefix"] && !this["~Prefix"].startsWith("/")) this["~Prefix"] = `/${this["~Prefix"]}`;
56
56
  if (config?.name) this.#hash = require_utils.fnv1a(config.seed ? `${config.name}_${typeof config.seed === "object" ? JSON.stringify(config.seed) : config.seed}` : config.name);
57
57
  }
58
+ /**
59
+ * @deprecated use `app.history` instead
60
+ *
61
+ * Elysia changes it store route internally
62
+ * This API is provided as compatibility
63
+ *
64
+ * Will remove in 2.1
65
+ */
58
66
  get routes() {
59
67
  if (!this.#history) return [];
60
68
  if (this.#cachedRoutes) return this.#cachedRoutes;
@@ -1106,7 +1114,7 @@ var Elysia = class Elysia {
1106
1114
  if (seen.has(key)) {
1107
1115
  hasDuplicate = true;
1108
1116
  if (!require_error.isProduction()) {
1109
- if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} the later definition overrides the earlier one (its schema/hooks are dropped).`);
1117
+ if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} at`, (/* @__PURE__ */ new Error()).stack);
1110
1118
  }
1111
1119
  }
1112
1120
  seen.set(key, i);
package/dist/base.mjs CHANGED
@@ -52,6 +52,14 @@ var Elysia = class Elysia {
52
52
  if (this["~Prefix"] && !this["~Prefix"].startsWith("/")) this["~Prefix"] = `/${this["~Prefix"]}`;
53
53
  if (config?.name) this.#hash = fnv1a(config.seed ? `${config.name}_${typeof config.seed === "object" ? JSON.stringify(config.seed) : config.seed}` : config.name);
54
54
  }
55
+ /**
56
+ * @deprecated use `app.history` instead
57
+ *
58
+ * Elysia changes it store route internally
59
+ * This API is provided as compatibility
60
+ *
61
+ * Will remove in 2.1
62
+ */
55
63
  get routes() {
56
64
  if (!this.#history) return [];
57
65
  if (this.#cachedRoutes) return this.#cachedRoutes;
@@ -1103,7 +1111,7 @@ var Elysia = class Elysia {
1103
1111
  if (seen.has(key)) {
1104
1112
  hasDuplicate = true;
1105
1113
  if (!isProduction()) {
1106
- if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} the later definition overrides the earlier one (its schema/hooks are dropped).`);
1114
+ if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} at`, (/* @__PURE__ */ new Error()).stack);
1107
1115
  }
1108
1116
  }
1109
1117
  seen.set(key, i);
@@ -71,11 +71,14 @@ interface CompiledSnapshot {
71
71
  lazyGroups: Array<() => ValidatorManifest> | undefined;
72
72
  lazyGroupOf: Record<string, Record<string, number>> | undefined;
73
73
  builtGroups: number[];
74
+ planRebuilder: ((original: unknown, plan: CoercePlan) => any) | undefined;
74
75
  }
75
76
  declare abstract class Compiled {
76
77
  static get handlers(): HandlerManifest | undefined;
77
78
  static set handlers(manifest: HandlerManifest);
78
79
  static get validators(): ValidatorManifest | undefined;
80
+ static get planRebuilder(): ((original: unknown, plan: CoercePlan) => any) | undefined;
81
+ static set planRebuilder(rebuild: ((original: unknown, plan: CoercePlan) => any) | undefined);
79
82
  static set validators(manifest: ValidatorManifest);
80
83
  static registerLazyValidators(groups: Array<() => ValidatorManifest>, groupOf: Record<string, Record<string, number>>): void;
81
84
  static getValidator(method: string, path: string, slot: ValidatorSlot): FrozenValidator | undefined;
@@ -226,7 +229,7 @@ declare const Capture: {
226
229
  };
227
230
  /**
228
231
  * Reset module-level capture lifecycle state.
229
- * FOR TESTS ONLY — not exported from the package index.
232
+ * FOR TESTS ONLY
230
233
  */
231
234
  declare const resetCaptureLifecycleForTests: () => void;
232
235
  //#endregion
@@ -6,6 +6,7 @@ const require_universal_env = require('../universal/env.js');
6
6
  //#region src/compile/aot.ts
7
7
  let validators;
8
8
  let handlers;
9
+ let planRebuilder;
9
10
  let lazyGroups;
10
11
  let lazyGroupOf;
11
12
  const builtGroups = /* @__PURE__ */ new Set();
@@ -19,6 +20,12 @@ var Compiled = class {
19
20
  static get validators() {
20
21
  return validators;
21
22
  }
23
+ static get planRebuilder() {
24
+ return planRebuilder;
25
+ }
26
+ static set planRebuilder(rebuild) {
27
+ planRebuilder = rebuild;
28
+ }
22
29
  static set validators(manifest) {
23
30
  validators = manifest;
24
31
  lazyGroups = void 0;
@@ -56,7 +63,8 @@ var Compiled = class {
56
63
  handlers,
57
64
  lazyGroups,
58
65
  lazyGroupOf,
59
- builtGroups: [...builtGroups]
66
+ builtGroups: [...builtGroups],
67
+ planRebuilder
60
68
  };
61
69
  }
62
70
  /** @internal restore registry after in-process AOT analysis */
@@ -67,6 +75,7 @@ var Compiled = class {
67
75
  lazyGroupOf = snapshot.lazyGroupOf;
68
76
  builtGroups.clear();
69
77
  for (const group of snapshot.builtGroups) builtGroups.add(group);
78
+ planRebuilder = snapshot.planRebuilder;
70
79
  }
71
80
  /** @internal test isolation */
72
81
  static clear() {
@@ -75,6 +84,7 @@ var Compiled = class {
75
84
  lazyGroups = void 0;
76
85
  lazyGroupOf = void 0;
77
86
  builtGroups.clear();
87
+ planRebuilder = void 0;
78
88
  }
79
89
  };
80
90
  function reconstructCheck(build) {
@@ -336,7 +346,7 @@ const Capture = {
336
346
  };
337
347
  /**
338
348
  * Reset module-level capture lifecycle state.
339
- * FOR TESTS ONLY — not exported from the package index.
349
+ * FOR TESTS ONLY
340
350
  */
341
351
  const resetCaptureLifecycleForTests = () => void 0;
342
352
 
@@ -5,6 +5,7 @@ import { env } from "../universal/env.mjs";
5
5
  //#region src/compile/aot.ts
6
6
  let validators;
7
7
  let handlers;
8
+ let planRebuilder;
8
9
  let lazyGroups;
9
10
  let lazyGroupOf;
10
11
  const builtGroups = /* @__PURE__ */ new Set();
@@ -18,6 +19,12 @@ var Compiled = class {
18
19
  static get validators() {
19
20
  return validators;
20
21
  }
22
+ static get planRebuilder() {
23
+ return planRebuilder;
24
+ }
25
+ static set planRebuilder(rebuild) {
26
+ planRebuilder = rebuild;
27
+ }
21
28
  static set validators(manifest) {
22
29
  validators = manifest;
23
30
  lazyGroups = void 0;
@@ -55,7 +62,8 @@ var Compiled = class {
55
62
  handlers,
56
63
  lazyGroups,
57
64
  lazyGroupOf,
58
- builtGroups: [...builtGroups]
65
+ builtGroups: [...builtGroups],
66
+ planRebuilder
59
67
  };
60
68
  }
61
69
  /** @internal restore registry after in-process AOT analysis */
@@ -66,6 +74,7 @@ var Compiled = class {
66
74
  lazyGroupOf = snapshot.lazyGroupOf;
67
75
  builtGroups.clear();
68
76
  for (const group of snapshot.builtGroups) builtGroups.add(group);
77
+ planRebuilder = snapshot.planRebuilder;
69
78
  }
70
79
  /** @internal test isolation */
71
80
  static clear() {
@@ -74,6 +83,7 @@ var Compiled = class {
74
83
  lazyGroups = void 0;
75
84
  lazyGroupOf = void 0;
76
85
  builtGroups.clear();
86
+ planRebuilder = void 0;
77
87
  }
78
88
  };
79
89
  function reconstructCheck(build) {
@@ -335,7 +345,7 @@ const Capture = {
335
345
  };
336
346
  /**
337
347
  * Reset module-level capture lifecycle state.
338
- * FOR TESTS ONLY — not exported from the package index.
348
+ * FOR TESTS ONLY
339
349
  */
340
350
  const resetCaptureLifecycleForTests = () => void 0;
341
351
 
@@ -7,6 +7,7 @@ declare const isBridgeNotInitialized: (error: unknown) => boolean;
7
7
  declare class FrozenSlotValidator {
8
8
  #private;
9
9
  isAsync: boolean;
10
+ hasCodec: boolean;
10
11
  schema: unknown;
11
12
  constructor(frozen: FrozenValidator, schema: unknown, raw: unknown, normalize: boolean | 'exactMirror' | 'typebox' | undefined);
12
13
  Check(value: unknown): boolean;
@@ -23,9 +24,6 @@ interface FrozenRouteValidatorShape {
23
24
  response?: Record<number, FrozenSlotValidator>;
24
25
  }
25
26
  declare function buildFrozenRouteValidator(hook: AnyLocalHook, root: AnyElysia, method: HTTPMethod, path: string): FrozenRouteValidatorShape | undefined;
26
- /**
27
- * Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
28
- */
29
- declare function isCapturedBridgeFree(c: CapturedValidator, schema: unknown): boolean;
27
+ declare function isCapturedBridgeFree(c: CapturedValidator, schema: unknown, coerced?: unknown): boolean;
30
28
  //#endregion
31
29
  export { buildFrozenRouteValidator, isBridgeNotInitialized, isCapturedBridgeFree };
@@ -3,7 +3,6 @@ const require_utils = require('../../utils.js');
3
3
  const require_type_constants = require('../../type/constants.js');
4
4
  const require_error = require('../../error.js');
5
5
  const require_compile_aot = require('../aot.js');
6
- const require_type_coerce_plan = require('../../type/coerce-plan.js');
7
6
 
8
7
  //#region src/compile/handler/frozen-validator.ts
9
8
  const isBridgeNotInitialized = (error) => error instanceof Error && error.message.startsWith("Typebox module isn't initialized");
@@ -69,8 +68,10 @@ var FrozenSlotValidator = class {
69
68
  #noValidate;
70
69
  constructor(frozen, schema, raw, normalize) {
71
70
  this.isAsync = false;
71
+ this.hasCodec = false;
72
72
  this.schema = schema;
73
73
  if (frozen.ic) require_compile_aot.reconstructInnerCodecs(frozen.ic, schema);
74
+ this.hasCodec = frozen.k === 1;
74
75
  if (frozen.k === 1 && frozen.dm) {
75
76
  const both = require_compile_aot.instantiateFrozenBoth(frozen, schema, raw);
76
77
  this.#check = both.check;
@@ -159,7 +160,12 @@ function buildFrozenRouteValidator(hook, root, method, path) {
159
160
  if (!schema) return void 0;
160
161
  const frozen = require_compile_aot.Compiled.getValidator(method, path, slot);
161
162
  if (!frozen) return void 0;
162
- const coerced = frozen.cp ? require_type_coerce_plan.buildCoercedFromPlan(schema, frozen.cp) : schema;
163
+ let coerced = schema;
164
+ if (frozen.cp) {
165
+ const rebuild = require_compile_aot.Compiled.planRebuilder;
166
+ if (!rebuild) return void 0;
167
+ coerced = rebuild(schema, frozen.cp);
168
+ }
163
169
  if (!isBridgeFreeComplete(frozen, coerced, schema)) return void 0;
164
170
  out[slot] = new FrozenSlotValidator(frozen, coerced, schema, normalize);
165
171
  }
@@ -181,10 +187,7 @@ function buildFrozenRouteValidator(hook, root, method, path) {
181
187
  return out;
182
188
  }
183
189
  const isResponseMap = (schema) => !("~kind" in schema || "~elyAcl" in schema || "~standard" in schema);
184
- /**
185
- * Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
186
- */
187
- function isCapturedBridgeFree(c, schema) {
190
+ function isCapturedBridgeFree(c, schema, coerced = schema) {
188
191
  if (typeof schema === "string") return false;
189
192
  /**
190
193
  * A captured `cm` entry is one that froze BOTH check and mirror
@@ -193,10 +196,7 @@ function isCapturedBridgeFree(c, schema) {
193
196
  * @see plugin/source.ts `entryParts`
194
197
  */
195
198
  if (!(c.checkValue && c.mirror)) return false;
196
- if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) {
197
- const coerced = c.coercePlan ? require_type_coerce_plan.buildCoercedFromPlan(schema, c.coercePlan) : schema;
198
- return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
199
- }
199
+ if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
200
200
  if (c.external || c.mirror.u || c.decodeMirror || c.encodeMirror || c.hasCodec || c.customErrors?.length || c.innerCodecs?.length || c.async) return false;
201
201
  if (c.hasDefault && !c.precomputeSafe) return false;
202
202
  if (isFullyClosedObject(schema)) return false;
@@ -2,7 +2,6 @@ import { nullObject } from "../../utils.mjs";
2
2
  import { ELYSIA_TYPES } from "../../type/constants.mjs";
3
3
  import { ValidationError } from "../../error.mjs";
4
4
  import { Compiled, EMPTY_EXTERNALS, collectMirrorUnions, collectStringCodecNodes, instantiateFrozenBoth, instantiateFrozenDecodeMirror, reconstructInnerCodecs } from "../aot.mjs";
5
- import { buildCoercedFromPlan } from "../../type/coerce-plan.mjs";
6
5
 
7
6
  //#region src/compile/handler/frozen-validator.ts
8
7
  const isBridgeNotInitialized = (error) => error instanceof Error && error.message.startsWith("Typebox module isn't initialized");
@@ -68,8 +67,10 @@ var FrozenSlotValidator = class {
68
67
  #noValidate;
69
68
  constructor(frozen, schema, raw, normalize) {
70
69
  this.isAsync = false;
70
+ this.hasCodec = false;
71
71
  this.schema = schema;
72
72
  if (frozen.ic) reconstructInnerCodecs(frozen.ic, schema);
73
+ this.hasCodec = frozen.k === 1;
73
74
  if (frozen.k === 1 && frozen.dm) {
74
75
  const both = instantiateFrozenBoth(frozen, schema, raw);
75
76
  this.#check = both.check;
@@ -158,7 +159,12 @@ function buildFrozenRouteValidator(hook, root, method, path) {
158
159
  if (!schema) return void 0;
159
160
  const frozen = Compiled.getValidator(method, path, slot);
160
161
  if (!frozen) return void 0;
161
- const coerced = frozen.cp ? buildCoercedFromPlan(schema, frozen.cp) : schema;
162
+ let coerced = schema;
163
+ if (frozen.cp) {
164
+ const rebuild = Compiled.planRebuilder;
165
+ if (!rebuild) return void 0;
166
+ coerced = rebuild(schema, frozen.cp);
167
+ }
162
168
  if (!isBridgeFreeComplete(frozen, coerced, schema)) return void 0;
163
169
  out[slot] = new FrozenSlotValidator(frozen, coerced, schema, normalize);
164
170
  }
@@ -180,10 +186,7 @@ function buildFrozenRouteValidator(hook, root, method, path) {
180
186
  return out;
181
187
  }
182
188
  const isResponseMap = (schema) => !("~kind" in schema || "~elyAcl" in schema || "~standard" in schema);
183
- /**
184
- * Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
185
- */
186
- function isCapturedBridgeFree(c, schema) {
189
+ function isCapturedBridgeFree(c, schema, coerced = schema) {
187
190
  if (typeof schema === "string") return false;
188
191
  /**
189
192
  * A captured `cm` entry is one that froze BOTH check and mirror
@@ -192,10 +195,7 @@ function isCapturedBridgeFree(c, schema) {
192
195
  * @see plugin/source.ts `entryParts`
193
196
  */
194
197
  if (!(c.checkValue && c.mirror)) return false;
195
- if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) {
196
- const coerced = c.coercePlan ? buildCoercedFromPlan(schema, c.coercePlan) : schema;
197
- return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
198
- }
198
+ if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
199
199
  if (c.external || c.mirror.u || c.decodeMirror || c.encodeMirror || c.hasCodec || c.customErrors?.length || c.innerCodecs?.length || c.async) return false;
200
200
  if (c.hasDefault && !c.precomputeSafe) return false;
201
201
  if (isFullyClosedObject(schema)) return false;
package/dist/error.d.ts CHANGED
@@ -183,7 +183,7 @@ declare const PROBLEM_JSON = "application/problem+json";
183
183
  */
184
184
  type Problem<Code extends number | keyof StatusMap = number | keyof StatusMap, Extension extends Record<string, unknown> = {}> = {
185
185
  /** URI (or slug) identifying the problem type. @default 'about:blank' */type?: string; /** Short, human-readable summary of the problem type */
186
- title?: string; /** HTTP status a number or a `StatusMap` name (e.g. `'Conflict'`) */
186
+ title?: string; /** HTTP status. A number or a `StatusMap` name (e.g. `'Conflict'`) */
187
187
  status?: Code; /** Human-readable explanation specific to this occurrence */
188
188
  detail?: string; /** URI identifying the specific occurrence of the problem */
189
189
  instance?: string;
@@ -15,7 +15,7 @@ interface ElysiaAotOptions {
15
15
  * Validator entries are registered as grouped thunks: a group's
16
16
  * validators are constructed on the first request to any route in that
17
17
  * group, trading first-request latency in unbuilt groups for lower
18
- * startup cost. Handlers are always eager only validator construction
18
+ * startup cost. Handlers are always eager. Only validator construction
19
19
  * is deferred. Pass a number to set the group size explicitly.
20
20
  *
21
21
  * @default decided by Elysia based on route batch scale
@@ -84,7 +84,7 @@ interface StubPlan {
84
84
  /**
85
85
  * Stub the `memory` module's `clearSucroseCache` edge when handler JIT is
86
86
  * stubbed. Sucrose never runs in a precompiled app, so its caches are always
87
- * empty and the flush is a no-op dropping the import lets the Sucrose
87
+ * empty and the flush is a no-op. Dropping the import lets the Sucrose
88
88
  * analyzer tree-shake. `flushMemory`'s other clears are preserved, and the
89
89
  * public `elysia/sucrose` module is left untouched
90
90
  */
@@ -92,7 +92,7 @@ interface StubPlan {
92
92
  /**
93
93
  * Stub `type/compat` so `setupTypebox` becomes a no-op, un-wiring the
94
94
  * TypeBox bridge from the bundle. Set in BOTH sealed (mode A) and wired
95
- * (mode B) builds. In mode A the bridge is severed entirely every captured
95
+ * (mode B) builds. In mode A the bridge is severed entirely. Every captured
96
96
  * validator is bridge-free so nothing reaches it. In mode B the mirror
97
97
  * (`bridge` → `bridge-live` reroute) replaces the latch, so `setupTypebox`
98
98
  * must still be a no-op to guarantee the bridge is wired exactly once
@@ -109,13 +109,13 @@ interface StubPlan {
109
109
  /**
110
110
  * Chosen TypeBox-collapse strategy for the build, logged in the dry-run line.
111
111
  *
112
- * - `'sealed'` (mode A): every captured validator is bridge-free and the app is
113
- * fully stripped (handler JIT unused, no WS). `compat` is stubbed and the
114
- * bridge is severed TypeBox collapses entirely
112
+ * - `'sealed'` (mode A): every captured validator is bridge-free (HTTP and WS
113
+ * alike) and the app is fully stripped (handler JIT unused). `compat` is
114
+ * stubbed and the bridge is severed, TypeBox collapses entirely
115
115
  * - `'wired'` (mode B): at least one validator still needs the bridge. `compat`
116
116
  * is stubbed AND the bridge is re-routed to the statically-wired mirror so it
117
117
  * works without the DCE-fragile `setupTypebox()` anchor
118
- * - `'off'`: no bridge action (strip disabled, or handler JIT still reachable
118
+ * - `'off'`: no bridge action (strip disabled, or handler JIT still reachable,
119
119
  * the frozen path is not active so the bridge stays wired the normal way)
120
120
  */
121
121
  type BridgeMode = 'sealed' | 'wired' | 'off';
@@ -123,51 +123,11 @@ declare const STUB_SOURCES: Record<keyof StubPlan, Array<{
123
123
  filter: RegExp;
124
124
  source: string;
125
125
  }>>;
126
- /**
127
- * Rewrite a stub's extensionless relative imports to match the extension of the
128
- * real module it replaces
129
- *
130
- * The sucrose stub replaces `memory.(mjs|js|ts)`. Its bare relative specifiers
131
- * (`./context`, …) are correct against the `.ts` source, but against a `.mjs`
132
- * dist module esbuild's node resolver defaults an extensionless specifier to the
133
- * sibling CJS `.js` (elysia's package.json has no `"type": "module"`), pulling a
134
- * whole duplicate CJS copy of the elysia subtree alongside the app's `.mjs` one.
135
- * Anchoring the specifiers to the replaced file's extension keeps the stub on the
136
- * same module instances the app already resolves. `./validator` is a directory,
137
- * so it expands to `./validator/index`
138
- */
139
126
  declare const alignStubExtensions: (stubSource: string, targetPath: string) => string;
140
- /**
141
- * Elysia's `t.*` overrides — the 27 names `elysia/type` replaces (or adds) over
142
- * `typebox/type`. Each maps to its leaf module basename (under `type/elysia/`)
143
- * and the name it is exported as from that leaf (some leaves rename to avoid
144
- * shadowing a JS global, e.g. `Array` → `ArrayType`).
145
- *
146
- * SINGLE SOURCE OF TRUTH: mirrors `src/type/index.ts` (the runtime `t` object)
147
- * and `src/type/exports.ts`. `test/aot/virtual-type-drift.test.ts` pins it
148
- * against the live `t` object vs `typebox/type` (`Object.keys` diff) so a future
149
- * `t.*` addition/removal that skips this table trips the drift test.
150
- */
151
127
  declare const OVERRIDE_MAP: Record<string, {
152
128
  leaf: string;
153
129
  export: string;
154
130
  }>;
155
- /**
156
- * Build the virtual `elysia/type` module source — a plain re-export surface with
157
- * NO `setupTypebox` call (that is the whole point: consuming `t` this way must
158
- * not drag the bridge latch).
159
- *
160
- * TypeBox's `t.*` names pass through to the user's own `typebox/type` (resolved
161
- * to an absolute path — the same copy the bundle uses); the 27 Elysia overrides
162
- * re-export from their leaf modules (absolute paths, because elysia's `exports`
163
- * map blocks `./dist/*` subpaths). Emitting STATIC NAMED re-exports (not
164
- * `export *`) is what lets `import * as t from 'elysia/type'` — the specifier the
165
- * treeshake rewrite emits — tree-shake unused constructors per-member.
166
- *
167
- * `typeSpecifier` is the specifier the plugin intercepts (`elysia/type`); it is
168
- * resolved to locate the dist `type/elysia/*` leaf modules and to derive their
169
- * extension.
170
- */
171
131
  declare function generateVirtualType(typeSpecifier?: string): Promise<string>;
172
132
  declare function generateCompiledModule(file: string, options?: ElysiaAotOptions): Promise<string>;
173
133
  interface CompiledArtifacts {
@@ -38,21 +38,21 @@ const NO_STUB = {
38
38
  * `true` throws when handler JIT is still reachable; `false` disables.
39
39
  *
40
40
  * Also decides the TypeBox-collapse `mode`:
41
- * - `'sealed'` (mode A): the frozen path is active (`jit`), no WS, and EVERY
42
- * captured validator is bridge-free (`bridgeFree`). `setupTypebox` is stubbed
43
- * and the bridge is severed nothing reaches it, TypeBox collapses.
44
- * - `'wired'` (mode B): the frozen path is active but at least one validator
41
+ * - `'sealed'`: the frozen path is active (`jit`) and EVERY captured
42
+ * validator, HTTP and WS is bridge-free (`bridgeFree`). `setupTypebox` is
43
+ * stubbed and the bridge is severe, nothing reaches it, TypeBox collapses.
44
+ * - `'wired'`: the frozen path is active but at least one validator
45
45
  * still needs the bridge. `setupTypebox` is stubbed AND the bridge is
46
46
  * re-routed to the statically-wired mirror (works without the DCE-fragile
47
47
  * `setupTypebox()` anchor).
48
48
  * - `'off'`: handler JIT still reachable (or strip disabled). The frozen path
49
- * is not active, so the bridge must stay wired the ordinary way no compat
49
+ * is not active, so the bridge must stay wired the ordinary way. No compat
50
50
  * stub, no reroute.
51
51
  */
52
- function planFromReport(strip, report, hasWS, aliases, allBridgeFree) {
52
+ function planFromReport(strip, report, hasWS, aliases, allBridgeFree, zeroCapture) {
53
53
  const jit = report.jit;
54
54
  if (strip === true && !jit) throw new Error(`[elysia-aot] strip: true requires every route to be covered by the AOT handler manifest, but handler JIT is still reachable (${report.reasons.join(", ") || "unknown"}). Use strip: 'auto' to skip stubbing when the app is not fully precompiled.`);
55
- const mode = !(jit && !hasWS) ? "off" : allBridgeFree ? "sealed" : "wired";
55
+ const mode = !jit ? "off" : allBridgeFree ? "sealed" : hasWS && zeroCapture ? "off" : "wired";
56
56
  return {
57
57
  plan: {
58
58
  jit,
@@ -104,35 +104,11 @@ const STUB_SOURCES = {
104
104
  source: `export * from './bridge-live'\n`
105
105
  }]
106
106
  };
107
- /**
108
- * Rewrite a stub's extensionless relative imports to match the extension of the
109
- * real module it replaces
110
- *
111
- * The sucrose stub replaces `memory.(mjs|js|ts)`. Its bare relative specifiers
112
- * (`./context`, …) are correct against the `.ts` source, but against a `.mjs`
113
- * dist module esbuild's node resolver defaults an extensionless specifier to the
114
- * sibling CJS `.js` (elysia's package.json has no `"type": "module"`), pulling a
115
- * whole duplicate CJS copy of the elysia subtree alongside the app's `.mjs` one.
116
- * Anchoring the specifiers to the replaced file's extension keeps the stub on the
117
- * same module instances the app already resolves. `./validator` is a directory,
118
- * so it expands to `./validator/index`
119
- */
120
107
  const alignStubExtensions = (stubSource, targetPath) => {
121
108
  const ext = targetPath.slice(targetPath.lastIndexOf("."));
122
109
  if (ext !== ".mjs" && ext !== ".js" && ext !== ".cjs") return stubSource;
123
110
  return stubSource.replace(/(from ')(\.[^']+)(')/g, (_m, open, spec, close) => open + (spec === "./validator" ? spec + "/index" : spec) + ext + close);
124
111
  };
125
- /**
126
- * Elysia's `t.*` overrides — the 27 names `elysia/type` replaces (or adds) over
127
- * `typebox/type`. Each maps to its leaf module basename (under `type/elysia/`)
128
- * and the name it is exported as from that leaf (some leaves rename to avoid
129
- * shadowing a JS global, e.g. `Array` → `ArrayType`).
130
- *
131
- * SINGLE SOURCE OF TRUTH: mirrors `src/type/index.ts` (the runtime `t` object)
132
- * and `src/type/exports.ts`. `test/aot/virtual-type-drift.test.ts` pins it
133
- * against the live `t` object vs `typebox/type` (`Object.keys` diff) so a future
134
- * `t.*` addition/removal that skips this table trips the drift test.
135
- */
136
112
  const OVERRIDE_MAP = {
137
113
  Accelerate: {
138
114
  leaf: "accelerate",
@@ -244,17 +220,6 @@ const OVERRIDE_MAP = {
244
220
  }
245
221
  };
246
222
  const requireFromHere = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
247
- /**
248
- * Resolve a specifier to an absolute path using the SAME resolution the ESM
249
- * bundle will use (the `import` condition, so `elysia/type` → `.mjs` — the
250
- * extension every ESM bundle here resolves to, and the one the leaf modules must
251
- * carry to avoid the CJS dual-copy the `alignStubExtensions` lesson warns of).
252
- *
253
- * `import.meta.resolve` honours the `import` condition and is present in the ESM
254
- * plugin build (the one every bundler integration loads). In the CJS build
255
- * Rolldown lowers `import.meta` to `{}`, so `import.meta.resolve` is `undefined`
256
- * and we fall back to `require.resolve`.
257
- */
258
223
  const resolveSpecifier = (specifier) => {
259
224
  const meta = {};
260
225
  if (typeof meta.resolve === "function") {
@@ -263,22 +228,6 @@ const resolveSpecifier = (specifier) => {
263
228
  }
264
229
  return requireFromHere.resolve(specifier);
265
230
  };
266
- /**
267
- * Build the virtual `elysia/type` module source — a plain re-export surface with
268
- * NO `setupTypebox` call (that is the whole point: consuming `t` this way must
269
- * not drag the bridge latch).
270
- *
271
- * TypeBox's `t.*` names pass through to the user's own `typebox/type` (resolved
272
- * to an absolute path — the same copy the bundle uses); the 27 Elysia overrides
273
- * re-export from their leaf modules (absolute paths, because elysia's `exports`
274
- * map blocks `./dist/*` subpaths). Emitting STATIC NAMED re-exports (not
275
- * `export *`) is what lets `import * as t from 'elysia/type'` — the specifier the
276
- * treeshake rewrite emits — tree-shake unused constructors per-member.
277
- *
278
- * `typeSpecifier` is the specifier the plugin intercepts (`elysia/type`); it is
279
- * resolved to locate the dist `type/elysia/*` leaf modules and to derive their
280
- * extension.
281
- */
282
231
  async function generateVirtualType(typeSpecifier = "elysia/type") {
283
232
  const typePath = resolveSpecifier(typeSpecifier);
284
233
  const typeboxPath = resolveSpecifier("typebox/type");
@@ -288,7 +237,7 @@ async function generateVirtualType(typeSpecifier = "elysia/type") {
288
237
  const overrideNames = new Set(Object.keys(OVERRIDE_MAP));
289
238
  const passthrough = Object.keys(typebox).filter((name) => !overrideNames.has(name));
290
239
  const toSpecifier = (p) => JSON.stringify(p.replace(/\\/g, "/"));
291
- let source = `// Generated by Elysia build plugin virtual 'elysia/type' re-export surface.\n`;
240
+ let source = `// Generated by Elysia build plugin. Virtual 'elysia/type' re-export surface.\n`;
292
241
  source += `export { ${passthrough.join(", ")} } from ${toSpecifier(typeboxPath)}\n`;
293
242
  for (const [name, { leaf, export: exported }] of Object.entries(OVERRIDE_MAP)) {
294
243
  const spec = toSpecifier((0, node_path.join)(leafDir, leaf + ext));
@@ -308,7 +257,7 @@ async function generateCompiledArtifacts(file, options) {
308
257
  const entryReal = realPath(entry);
309
258
  let mod;
310
259
  if (_importedEntries.has(entryReal)) {
311
- console.warn("[elysia-aot] re-importing \"" + entry + "\" for rebuild top-level side effects will re-run. This is expected in watch/rebuild flows.");
260
+ console.warn("[elysia-aot] re-importing \"" + entry + "\" for rebuild. top-level side effects will re-run. This is expected in watch/rebuild flows.");
312
261
  const cacheBustSpecifier = entry + "?elysia-aot=" + Date.now();
313
262
  try {
314
263
  mod = await import(cacheBustSpecifier);
@@ -358,7 +307,7 @@ async function generateCompiledArtifacts(file, options) {
358
307
  }
359
308
  if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
360
309
  const frozenSlots = artifacts.validators.length;
361
- const { plan: stub, mode } = planFromReport(strip, report, hasWS, aliases, artifacts.handlers.length > 0 && !routesForbidSeal && frozenSlots >= expectedSlots && artifacts.validators.every((v) => v.bridgeFree === true));
310
+ const { plan: stub, mode } = planFromReport(strip, report, hasWS, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal && frozenSlots >= expectedSlots && artifacts.validators.every((v) => v.bridgeFree === true), artifacts.handlers.length === 0 && artifacts.validators.length === 0);
362
311
  if (options?.target === "workerd") {
363
312
  if (!report.jit) throw new Error(`[elysia-aot] target 'workerd' but handler JIT is still reachable (${report.reasons.join(", ") || "unknown"}). Every route must be captured into the AOT manifest.`);
364
313
  if (frozenSlots < expectedSlots) console.warn(`[elysia-aot] target 'workerd': only ${frozenSlots}/${expectedSlots} validator slots were frozen unfrozen slots compile at runtime and will fail on workerd.`);