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
@@ -1,8 +1,8 @@
1
1
  //#region src/compile/jit-probe.d.ts
2
2
  /** A handler-JIT entry point the frozen replay can trip over. */
3
- type JITProbeReason = 'sucrose' | 'handler:new-function';
3
+ type JITProbeReason = 'sucrose' | 'handler:new-function' | 'handler:indexed-duplicate';
4
4
  interface JITProbeResult {
5
- /** `sucrose` + the handler `new Function` codegen is unused. */
5
+ /** No route needs sucrose, handler codegen, or indexed duplicate replay. */
6
6
  jit: boolean;
7
7
  reasons: JITProbeReason[];
8
8
  }
package/dist/context.d.ts CHANGED
@@ -9,7 +9,7 @@ import { AnyElysia } from "./base.js";
9
9
 
10
10
  //#region src/context.d.ts
11
11
  declare function createBaseContext(app: AnyElysia): any;
12
- declare function clearContextCache(): void;
12
+ declare function clearContextCache(app?: AnyElysia): void;
13
13
  declare function createContext(app: AnyElysia): new (request: Request) => Context;
14
14
  interface ContextBase<in out Route extends RouteSchema, in out Singleton extends SingletonBase> {
15
15
  server: Server | null;
package/dist/context.js CHANGED
@@ -41,7 +41,11 @@ function createBaseContext(app) {
41
41
  Object.defineProperty(Decorator.prototype, "defer", defer);
42
42
  return Decorator;
43
43
  }
44
- function clearContextCache() {
44
+ function clearContextCache(app) {
45
+ if (app !== void 0) {
46
+ contextCache.delete(app);
47
+ return;
48
+ }
45
49
  contextCache = /* @__PURE__ */ new WeakMap();
46
50
  sharedEmptyDecorator = null;
47
51
  sharedEmptyContext = null;
package/dist/context.mjs CHANGED
@@ -40,7 +40,11 @@ function createBaseContext(app) {
40
40
  Object.defineProperty(Decorator.prototype, "defer", defer);
41
41
  return Decorator;
42
42
  }
43
- function clearContextCache() {
43
+ function clearContextCache(app) {
44
+ if (app !== void 0) {
45
+ contextCache.delete(app);
46
+ return;
47
+ }
44
48
  contextCache = /* @__PURE__ */ new WeakMap();
45
49
  sharedEmptyDecorator = null;
46
50
  sharedEmptyContext = null;
package/dist/package.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  //#region package.json
3
- var version = "2.0.0-beta.3";
3
+ var version = "2.0.0-beta.4";
4
4
 
5
5
  //#endregion
6
6
  Object.defineProperty(exports, 'version', {
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "2.0.0-beta.3";
2
+ var version = "2.0.0-beta.4";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -1,5 +1,6 @@
1
+ import { CapturedValidator } from "../../compile/aot.js";
1
2
  import { JITProbeResult } from "../../compile/jit-probe.js";
2
- import { AotTarget } from "./source.js";
3
+ import { AotModuleCondition, AotTarget } from "./source.js";
3
4
 
4
5
  //#region src/plugin/aot/core.d.ts
5
6
  interface ElysiaAotOptions {
@@ -93,6 +94,8 @@ interface ElysiaAotOptions {
93
94
  verbose?: boolean;
94
95
  }
95
96
  declare const resolveEntry: (entry: string) => string;
97
+ /** @internal Resolve the package condition used to evaluate an AOT entry. */
98
+ declare const resolveEntryModuleKind: (entry: string) => "cjs" | "esm";
96
99
  /** Runtime handler-JIT module a strip build can replace with a throwing stub. */
97
100
  interface StubPlan {
98
101
  /** Stub the internal handler codegen module. */
@@ -279,7 +282,7 @@ declare const OVERRIDE_MAP: Record<string, {
279
282
  leaf: string;
280
283
  export: string;
281
284
  }>;
282
- declare function generateVirtualType(typeSpecifier?: string): Promise<string>;
285
+ declare function generateVirtualType(typeSpecifier?: string, moduleCondition?: AotModuleCondition): Promise<string>;
283
286
  interface CompiledArtifacts {
284
287
  source: string;
285
288
  /** Handler-JIT modules detection proved safe to stub for this app. */
@@ -305,9 +308,12 @@ declare const getAotWorkerDiagnostics: () => {
305
308
  */
306
309
  declare function siblingModuleExt(moduleUrl: string): '.mjs' | '.js' | '.ts';
307
310
  /** @internal Re-evaluate an entry in a disposable worker for watch rebuilds. */
308
- declare function generateCompiledArtifactsIsolated(file: string, options?: ElysiaAotOptions): Promise<CompiledArtifacts>;
309
- declare function generateCompiledArtifacts(file: string, options?: ElysiaAotOptions): Promise<CompiledArtifacts>;
311
+ declare function generateCompiledArtifactsIsolated(file: string, options?: ElysiaAotOptions, moduleCondition?: string): Promise<CompiledArtifacts>;
312
+ type ValidatorSlotIdentity = Pick<CapturedValidator, 'method' | 'path' | 'slot'>;
313
+ /** @internal Exact logical-slot gate used by AOT mode selection. */
314
+ declare function validatorSlotSetsMatch(expected: readonly ValidatorSlotIdentity[], captured: readonly ValidatorSlotIdentity[]): boolean;
315
+ declare function generateCompiledArtifacts(file: string, options?: ElysiaAotOptions, moduleCondition?: string): Promise<CompiledArtifacts>;
310
316
  declare const SOURCE_REGEX: RegExp;
311
317
  declare const realPath: (path: string) => string;
312
318
  //#endregion
313
- export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, BridgeMode, CompiledArtifacts, ELYSIA_MODULE_FILTER, ElysiaAotOptions, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, StubPlan, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, getAotWorkerDiagnostics, makeElysiaModuleFilterRegex, makeIsElysiaModule, planFromReport, realPath, resolveElysiaRoot, resolveEntry, rewriteIsProductionCalls, siblingModuleExt };
319
+ export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, BridgeMode, CompiledArtifacts, ELYSIA_MODULE_FILTER, ElysiaAotOptions, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, StubPlan, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, getAotWorkerDiagnostics, makeElysiaModuleFilterRegex, makeIsElysiaModule, planFromReport, realPath, resolveElysiaRoot, resolveEntry, resolveEntryModuleKind, rewriteIsProductionCalls, siblingModuleExt, validatorSlotSetsMatch };
@@ -20,6 +20,28 @@ function findPackageRoot(from = process.cwd()) {
20
20
  return dir;
21
21
  }
22
22
  const resolveEntry = (entry) => (0, node_path.resolve)(findPackageRoot(), entry);
23
+ /** @internal Resolve the package condition used to evaluate an AOT entry. */
24
+ const resolveEntryModuleKind = (entry) => {
25
+ const entryPath = resolveEntry(entry);
26
+ switch ((0, node_path.extname)(entryPath)) {
27
+ case ".cjs":
28
+ case ".cts": return "cjs";
29
+ case ".mjs":
30
+ case ".mts":
31
+ case ".ts":
32
+ case ".tsx": return "esm";
33
+ case ".js":
34
+ case ".jsx": {
35
+ const packagePath = (0, node_path.join)(findPackageRoot((0, node_path.dirname)(entryPath)), "package.json");
36
+ if (!(0, node_fs.existsSync)(packagePath)) return "cjs";
37
+ const pkg = JSON.parse((0, node_fs.readFileSync)(packagePath, "utf8"));
38
+ if (pkg.type === "module") return "esm";
39
+ if (pkg.type === void 0 || pkg.type === "commonjs") return "cjs";
40
+ throw new Error(`[elysia-aot] package.json has unsupported "type" ${JSON.stringify(pkg.type)} for JavaScript entry ${JSON.stringify(entryPath)}.`);
41
+ }
42
+ default: throw new Error(`[elysia-aot] entry ${JSON.stringify(entryPath)} has an ambiguous module condition. Use a .cjs/.cts CommonJS entry or a .mjs/.mts/.ts/.tsx ESM entry; .js/.jsx follows the nearest package.json "type".`);
43
+ }
44
+ };
23
45
  const NO_STUB = {
24
46
  jit: false,
25
47
  ws: false,
@@ -302,17 +324,20 @@ const OVERRIDE_MAP = {
302
324
  }
303
325
  };
304
326
  const requireFromHere = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
305
- const resolveSpecifier = (specifier) => {
306
- const meta = {};
307
- if (typeof meta.resolve === "function") {
308
- const url = meta.resolve(specifier);
327
+ const moduleMeta = {};
328
+ const loadedModuleCondition = require("url").pathToFileURL(__filename).href.endsWith(".js") ? "cjs" : "esm";
329
+ const resolveSpecifier = (specifier, moduleCondition) => {
330
+ if (moduleCondition === "cjs") return requireFromHere.resolve(specifier);
331
+ if (moduleMeta.resolve) {
332
+ const url = moduleMeta.resolve(specifier);
309
333
  return url.startsWith("file://") ? new URL(url).pathname : url;
310
334
  }
335
+ if (moduleCondition === "esm") throw new Error("[elysia-aot] CommonJS-loaded build plugin cannot resolve the ESM package condition. Load the build plugin through import for an ESM entry.");
311
336
  return requireFromHere.resolve(specifier);
312
337
  };
313
- async function generateVirtualType(typeSpecifier = "elysia/type") {
314
- const typePath = resolveSpecifier(typeSpecifier);
315
- const typeboxPath = resolveSpecifier("typebox/type");
338
+ async function generateVirtualType(typeSpecifier = "elysia/type", moduleCondition) {
339
+ const typePath = resolveSpecifier(typeSpecifier, moduleCondition);
340
+ const typeboxPath = resolveSpecifier("typebox/type", moduleCondition);
316
341
  const ext = typePath.slice(typePath.lastIndexOf("."));
317
342
  const leafDir = (0, node_path.join)((0, node_path.dirname)(typePath), "elysia");
318
343
  const typebox = await import((0, node_url.pathToFileURL)(typeboxPath).href);
@@ -348,12 +373,13 @@ const workerUrl = () => {
348
373
  return new URL("./worker" + siblingModuleExt(moduleUrl), moduleUrl);
349
374
  };
350
375
  /** @internal Re-evaluate an entry in a disposable worker for watch rebuilds. */
351
- async function generateCompiledArtifactsIsolated(file, options) {
376
+ async function generateCompiledArtifactsIsolated(file, options, moduleCondition = resolveEntryModuleKind(file)) {
352
377
  const entry = resolveEntry(file);
353
378
  console.warn("[elysia-aot] re-evaluating \"" + entry + "\" in an isolated worker for rebuild. Top-level side effects run only inside the worker.");
354
379
  const worker = new node_worker_threads.Worker(workerUrl(), { workerData: {
355
380
  file: entry,
356
- options
381
+ options,
382
+ moduleCondition
357
383
  } });
358
384
  activeGenerationWorkers++;
359
385
  const exit = new Promise((resolve) => worker.once("exit", resolve));
@@ -383,13 +409,14 @@ async function generateCompiledArtifactsIsolated(file, options) {
383
409
  }
384
410
  }
385
411
  }
386
- function isStandardResponse(response) {
387
- if (response == null || typeof response !== "object") return false;
388
- if (require_compile_handler_frozen_validator.isStandardSchema(response)) return true;
389
- if ("~kind" in response || "~elyAcl" in response) return false;
390
- const entries = Object.values(response);
391
- if (entries.length === 0) return false;
392
- return entries.every((v) => require_compile_handler_frozen_validator.isStandardSchema(v));
412
+ const validatorSlotKey = ({ method, path, slot }) => `${method}\0${path}\0${slot}`;
413
+ /** @internal Exact logical-slot gate used by AOT mode selection. */
414
+ function validatorSlotSetsMatch(expected, captured) {
415
+ const expectedKeys = new Set(expected.map(validatorSlotKey));
416
+ const capturedKeys = new Set(captured.map(validatorSlotKey));
417
+ if (expectedKeys.size !== capturedKeys.size) return false;
418
+ for (const key of expectedKeys) if (!capturedKeys.has(key)) return false;
419
+ return true;
393
420
  }
394
421
  /**
395
422
  * The manifest is one flat `method -> path` table with no per-app namespace,
@@ -406,7 +433,8 @@ const assertNoMount = (app, entry) => {
406
433
  if (!mounted.length) return;
407
434
  throw new Error(`[elysia-aot] "${entry}" mounts a sub-app (${mounted.join(", ")}). AOT cannot compile a mounted sub-app. Compose with .use() instead of .mount(), or disable AOT plugin`);
408
435
  };
409
- async function generateCompiledArtifacts(file, options) {
436
+ async function generateCompiledArtifacts(file, options, moduleCondition = resolveEntryModuleKind(file)) {
437
+ if (moduleCondition !== loadedModuleCondition) throw new Error(`[elysia-aot] entry uses the ${moduleCondition === "cjs" ? "CommonJS \"require\"" : "ESM \"import\""} package condition, but the AOT build plugin was loaded through ${loadedModuleCondition === "cjs" ? "CommonJS \"require\"" : "ESM \"import\""}; entry: ${JSON.stringify(resolveEntry(file))}. Load the plugin through ${moduleCondition === "cjs" ? "require()" : "import"} so capture and generated registration share one Elysia module instance.`);
410
438
  const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
411
439
  process.env.ELYSIA_AOT_BUILD = "1";
412
440
  const previousAotVerbose = process.env.ELYSIA_AOT_VERBOSE;
@@ -415,7 +443,7 @@ async function generateCompiledArtifacts(file, options) {
415
443
  try {
416
444
  const entry = resolveEntry(file);
417
445
  const entryReal = realPath(entry);
418
- if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options);
446
+ if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options, moduleCondition);
419
447
  _importedEntries.add(entryReal);
420
448
  const mod = await import(entry);
421
449
  const app = mod.app ?? mod.default;
@@ -424,6 +452,7 @@ async function generateCompiledArtifacts(file, options) {
424
452
  assertNoMount(typedApp, entry);
425
453
  const sourceOptions = {
426
454
  register: true,
455
+ moduleCondition,
427
456
  registerFrom: options?.registerFrom,
428
457
  reconstructFrom: options?.reconstructFrom,
429
458
  lazy: options?.lazy,
@@ -450,31 +479,48 @@ async function generateCompiledArtifacts(file, options) {
450
479
  const hasWS = !!typedApp["~hasWS"] || !!typedApp["~routes"]?.some((route) => route?.[0] === "WS");
451
480
  const history = typedApp["~routes"] ?? [];
452
481
  const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
453
- let expectedSlots = 0;
482
+ const expectedSlots = [];
483
+ const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
454
484
  let routesForbidSeal = false;
455
485
  let wsCookie = false;
456
486
  for (const route of history) {
487
+ const method = route[0];
488
+ const path = route[1];
489
+ const expectTypeBoxSlot = (slot) => {
490
+ const identity = {
491
+ method,
492
+ path,
493
+ slot
494
+ };
495
+ const key = validatorSlotKey(identity);
496
+ if (declaredTypeBoxSlots.has(key)) routesForbidSeal = true;
497
+ else {
498
+ declaredTypeBoxSlots.add(key);
499
+ expectedSlots.push(identity);
500
+ }
501
+ };
457
502
  const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
458
503
  const hooks = require_compile_handler_index.composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
459
504
  if (route[0] === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
460
505
  if (!hooks) continue;
461
506
  let routeHasTypeBoxDirectSlot = false;
462
- for (const slot of [
463
- "body",
464
- "query",
465
- "params",
466
- "headers",
467
- "cookie"
468
- ]) {
469
- const value = hooks[slot];
470
- if (value === void 0) continue;
471
- if (require_compile_handler_frozen_validator.isStandardSchema(value)) continue;
507
+ const classify = (schema, slot) => {
508
+ if (schema === void 0) {
509
+ routesForbidSeal = true;
510
+ return;
511
+ }
512
+ if (require_compile_handler_frozen_validator.isStandardSchema(schema)) return;
472
513
  routeHasTypeBoxDirectSlot = true;
473
- expectedSlots++;
514
+ expectTypeBoxSlot(slot);
515
+ };
516
+ for (const slot of require_compile_handler_frozen_validator.REQUEST_SLOTS) {
517
+ const value = hooks[slot];
518
+ if (value !== void 0) classify(require_compile_handler_frozen_validator.resolveModelRef(value, typedApp), slot);
474
519
  }
475
- if (hooks.response !== void 0 && !isStandardResponse(hooks.response)) {
476
- routeHasTypeBoxDirectSlot = true;
477
- expectedSlots++;
520
+ if (hooks.response !== void 0) {
521
+ const response = require_compile_handler_frozen_validator.resolveModelRef(hooks.response, typedApp);
522
+ if (require_compile_handler_frozen_validator.isResponseMap(response)) for (const status in response) classify(require_compile_handler_frozen_validator.resolveModelRef(response[status], typedApp), `response:${status}`);
523
+ else classify(response, "response:200");
478
524
  }
479
525
  const mergeSchemas = hooks.schemas;
480
526
  if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
@@ -482,13 +528,13 @@ async function generateCompiledArtifacts(file, options) {
482
528
  }
483
529
  }
484
530
  if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
485
- const frozenSlots = artifacts.validators.length;
486
- const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, 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, adapterStub, productionStub, wsCookie);
531
+ const validatorSlotsMatch = validatorSlotSetsMatch(expectedSlots, artifacts.validators);
532
+ const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal && validatorSlotsMatch && artifacts.validators.every((v) => v.bridgeFree === true), artifacts.handlers.length === 0 && artifacts.validators.length === 0, adapterStub, productionStub, wsCookie);
487
533
  if (options?.target === "workerd") {
488
534
  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.`);
489
- 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.`);
535
+ if (!validatorSlotsMatch) console.warn("[elysia-aot] target 'workerd': captured validator slots do not exactly match declared slots; unfrozen slots compile at runtime and will fail on workerd.");
490
536
  }
491
- const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type");
537
+ const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type", moduleCondition);
492
538
  return {
493
539
  source: artifacts.source,
494
540
  stub,
@@ -533,5 +579,7 @@ exports.planFromReport = planFromReport;
533
579
  exports.realPath = realPath;
534
580
  exports.resolveElysiaRoot = resolveElysiaRoot;
535
581
  exports.resolveEntry = resolveEntry;
582
+ exports.resolveEntryModuleKind = resolveEntryModuleKind;
536
583
  exports.rewriteIsProductionCalls = rewriteIsProductionCalls;
537
- exports.siblingModuleExt = siblingModuleExt;
584
+ exports.siblingModuleExt = siblingModuleExt;
585
+ exports.validatorSlotSetsMatch = validatorSlotSetsMatch;
@@ -1,9 +1,9 @@
1
- import { isStandardSchema, mergeSchemasAllStandard } from "../../compile/handler/frozen-validator.mjs";
1
+ import { REQUEST_SLOTS, isResponseMap, isStandardSchema, mergeSchemasAllStandard, resolveModelRef } from "../../compile/handler/frozen-validator.mjs";
2
2
  import { composeRouteHook } from "../../compile/handler/index.mjs";
3
3
  import { captureArtifacts, compileToSource, replayStubbability } from "./source.mjs";
4
4
  import { createRequire } from "node:module";
5
- import { existsSync, realpathSync } from "node:fs";
6
- import { dirname, join, resolve } from "node:path";
5
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
6
+ import { dirname, extname, join, resolve } from "node:path";
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { Worker } from "node:worker_threads";
9
9
 
@@ -18,6 +18,28 @@ function findPackageRoot(from = process.cwd()) {
18
18
  return dir;
19
19
  }
20
20
  const resolveEntry = (entry) => resolve(findPackageRoot(), entry);
21
+ /** @internal Resolve the package condition used to evaluate an AOT entry. */
22
+ const resolveEntryModuleKind = (entry) => {
23
+ const entryPath = resolveEntry(entry);
24
+ switch (extname(entryPath)) {
25
+ case ".cjs":
26
+ case ".cts": return "cjs";
27
+ case ".mjs":
28
+ case ".mts":
29
+ case ".ts":
30
+ case ".tsx": return "esm";
31
+ case ".js":
32
+ case ".jsx": {
33
+ const packagePath = join(findPackageRoot(dirname(entryPath)), "package.json");
34
+ if (!existsSync(packagePath)) return "cjs";
35
+ const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
36
+ if (pkg.type === "module") return "esm";
37
+ if (pkg.type === void 0 || pkg.type === "commonjs") return "cjs";
38
+ throw new Error(`[elysia-aot] package.json has unsupported "type" ${JSON.stringify(pkg.type)} for JavaScript entry ${JSON.stringify(entryPath)}.`);
39
+ }
40
+ default: throw new Error(`[elysia-aot] entry ${JSON.stringify(entryPath)} has an ambiguous module condition. Use a .cjs/.cts CommonJS entry or a .mjs/.mts/.ts/.tsx ESM entry; .js/.jsx follows the nearest package.json "type".`);
41
+ }
42
+ };
21
43
  const NO_STUB = {
22
44
  jit: false,
23
45
  ws: false,
@@ -300,17 +322,20 @@ const OVERRIDE_MAP = {
300
322
  }
301
323
  };
302
324
  const requireFromHere = createRequire(import.meta.url);
303
- const resolveSpecifier = (specifier) => {
304
- const meta = import.meta;
305
- if (typeof meta.resolve === "function") {
306
- const url = meta.resolve(specifier);
325
+ const moduleMeta = import.meta;
326
+ const loadedModuleCondition = import.meta.url.endsWith(".js") ? "cjs" : "esm";
327
+ const resolveSpecifier = (specifier, moduleCondition) => {
328
+ if (moduleCondition === "cjs") return requireFromHere.resolve(specifier);
329
+ if (moduleMeta.resolve) {
330
+ const url = moduleMeta.resolve(specifier);
307
331
  return url.startsWith("file://") ? new URL(url).pathname : url;
308
332
  }
333
+ if (moduleCondition === "esm") throw new Error("[elysia-aot] CommonJS-loaded build plugin cannot resolve the ESM package condition. Load the build plugin through import for an ESM entry.");
309
334
  return requireFromHere.resolve(specifier);
310
335
  };
311
- async function generateVirtualType(typeSpecifier = "elysia/type") {
312
- const typePath = resolveSpecifier(typeSpecifier);
313
- const typeboxPath = resolveSpecifier("typebox/type");
336
+ async function generateVirtualType(typeSpecifier = "elysia/type", moduleCondition) {
337
+ const typePath = resolveSpecifier(typeSpecifier, moduleCondition);
338
+ const typeboxPath = resolveSpecifier("typebox/type", moduleCondition);
314
339
  const ext = typePath.slice(typePath.lastIndexOf("."));
315
340
  const leafDir = join(dirname(typePath), "elysia");
316
341
  const typebox = await import(pathToFileURL(typeboxPath).href);
@@ -346,12 +371,13 @@ const workerUrl = () => {
346
371
  return new URL("./worker" + siblingModuleExt(moduleUrl), moduleUrl);
347
372
  };
348
373
  /** @internal Re-evaluate an entry in a disposable worker for watch rebuilds. */
349
- async function generateCompiledArtifactsIsolated(file, options) {
374
+ async function generateCompiledArtifactsIsolated(file, options, moduleCondition = resolveEntryModuleKind(file)) {
350
375
  const entry = resolveEntry(file);
351
376
  console.warn("[elysia-aot] re-evaluating \"" + entry + "\" in an isolated worker for rebuild. Top-level side effects run only inside the worker.");
352
377
  const worker = new Worker(workerUrl(), { workerData: {
353
378
  file: entry,
354
- options
379
+ options,
380
+ moduleCondition
355
381
  } });
356
382
  activeGenerationWorkers++;
357
383
  const exit = new Promise((resolve) => worker.once("exit", resolve));
@@ -381,13 +407,14 @@ async function generateCompiledArtifactsIsolated(file, options) {
381
407
  }
382
408
  }
383
409
  }
384
- function isStandardResponse(response) {
385
- if (response == null || typeof response !== "object") return false;
386
- if (isStandardSchema(response)) return true;
387
- if ("~kind" in response || "~elyAcl" in response) return false;
388
- const entries = Object.values(response);
389
- if (entries.length === 0) return false;
390
- return entries.every((v) => isStandardSchema(v));
410
+ const validatorSlotKey = ({ method, path, slot }) => `${method}\0${path}\0${slot}`;
411
+ /** @internal Exact logical-slot gate used by AOT mode selection. */
412
+ function validatorSlotSetsMatch(expected, captured) {
413
+ const expectedKeys = new Set(expected.map(validatorSlotKey));
414
+ const capturedKeys = new Set(captured.map(validatorSlotKey));
415
+ if (expectedKeys.size !== capturedKeys.size) return false;
416
+ for (const key of expectedKeys) if (!capturedKeys.has(key)) return false;
417
+ return true;
391
418
  }
392
419
  /**
393
420
  * The manifest is one flat `method -> path` table with no per-app namespace,
@@ -404,7 +431,8 @@ const assertNoMount = (app, entry) => {
404
431
  if (!mounted.length) return;
405
432
  throw new Error(`[elysia-aot] "${entry}" mounts a sub-app (${mounted.join(", ")}). AOT cannot compile a mounted sub-app. Compose with .use() instead of .mount(), or disable AOT plugin`);
406
433
  };
407
- async function generateCompiledArtifacts(file, options) {
434
+ async function generateCompiledArtifacts(file, options, moduleCondition = resolveEntryModuleKind(file)) {
435
+ if (moduleCondition !== loadedModuleCondition) throw new Error(`[elysia-aot] entry uses the ${moduleCondition === "cjs" ? "CommonJS \"require\"" : "ESM \"import\""} package condition, but the AOT build plugin was loaded through ${loadedModuleCondition === "cjs" ? "CommonJS \"require\"" : "ESM \"import\""}; entry: ${JSON.stringify(resolveEntry(file))}. Load the plugin through ${moduleCondition === "cjs" ? "require()" : "import"} so capture and generated registration share one Elysia module instance.`);
408
436
  const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
409
437
  process.env.ELYSIA_AOT_BUILD = "1";
410
438
  const previousAotVerbose = process.env.ELYSIA_AOT_VERBOSE;
@@ -413,7 +441,7 @@ async function generateCompiledArtifacts(file, options) {
413
441
  try {
414
442
  const entry = resolveEntry(file);
415
443
  const entryReal = realPath(entry);
416
- if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options);
444
+ if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options, moduleCondition);
417
445
  _importedEntries.add(entryReal);
418
446
  const mod = await import(entry);
419
447
  const app = mod.app ?? mod.default;
@@ -422,6 +450,7 @@ async function generateCompiledArtifacts(file, options) {
422
450
  assertNoMount(typedApp, entry);
423
451
  const sourceOptions = {
424
452
  register: true,
453
+ moduleCondition,
425
454
  registerFrom: options?.registerFrom,
426
455
  reconstructFrom: options?.reconstructFrom,
427
456
  lazy: options?.lazy,
@@ -448,31 +477,48 @@ async function generateCompiledArtifacts(file, options) {
448
477
  const hasWS = !!typedApp["~hasWS"] || !!typedApp["~routes"]?.some((route) => route?.[0] === "WS");
449
478
  const history = typedApp["~routes"] ?? [];
450
479
  const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
451
- let expectedSlots = 0;
480
+ const expectedSlots = [];
481
+ const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
452
482
  let routesForbidSeal = false;
453
483
  let wsCookie = false;
454
484
  for (const route of history) {
485
+ const method = route[0];
486
+ const path = route[1];
487
+ const expectTypeBoxSlot = (slot) => {
488
+ const identity = {
489
+ method,
490
+ path,
491
+ slot
492
+ };
493
+ const key = validatorSlotKey(identity);
494
+ if (declaredTypeBoxSlots.has(key)) routesForbidSeal = true;
495
+ else {
496
+ declaredTypeBoxSlots.add(key);
497
+ expectedSlots.push(identity);
498
+ }
499
+ };
455
500
  const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
456
501
  const hooks = composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
457
502
  if (route[0] === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
458
503
  if (!hooks) continue;
459
504
  let routeHasTypeBoxDirectSlot = false;
460
- for (const slot of [
461
- "body",
462
- "query",
463
- "params",
464
- "headers",
465
- "cookie"
466
- ]) {
467
- const value = hooks[slot];
468
- if (value === void 0) continue;
469
- if (isStandardSchema(value)) continue;
505
+ const classify = (schema, slot) => {
506
+ if (schema === void 0) {
507
+ routesForbidSeal = true;
508
+ return;
509
+ }
510
+ if (isStandardSchema(schema)) return;
470
511
  routeHasTypeBoxDirectSlot = true;
471
- expectedSlots++;
512
+ expectTypeBoxSlot(slot);
513
+ };
514
+ for (const slot of REQUEST_SLOTS) {
515
+ const value = hooks[slot];
516
+ if (value !== void 0) classify(resolveModelRef(value, typedApp), slot);
472
517
  }
473
- if (hooks.response !== void 0 && !isStandardResponse(hooks.response)) {
474
- routeHasTypeBoxDirectSlot = true;
475
- expectedSlots++;
518
+ if (hooks.response !== void 0) {
519
+ const response = resolveModelRef(hooks.response, typedApp);
520
+ if (isResponseMap(response)) for (const status in response) classify(resolveModelRef(response[status], typedApp), `response:${status}`);
521
+ else classify(response, "response:200");
476
522
  }
477
523
  const mergeSchemas = hooks.schemas;
478
524
  if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
@@ -480,13 +526,13 @@ async function generateCompiledArtifacts(file, options) {
480
526
  }
481
527
  }
482
528
  if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
483
- const frozenSlots = artifacts.validators.length;
484
- const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, 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, adapterStub, productionStub, wsCookie);
529
+ const validatorSlotsMatch = validatorSlotSetsMatch(expectedSlots, artifacts.validators);
530
+ const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal && validatorSlotsMatch && artifacts.validators.every((v) => v.bridgeFree === true), artifacts.handlers.length === 0 && artifacts.validators.length === 0, adapterStub, productionStub, wsCookie);
485
531
  if (options?.target === "workerd") {
486
532
  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.`);
487
- 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.`);
533
+ if (!validatorSlotsMatch) console.warn("[elysia-aot] target 'workerd': captured validator slots do not exactly match declared slots; unfrozen slots compile at runtime and will fail on workerd.");
488
534
  }
489
- const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type");
535
+ const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type", moduleCondition);
490
536
  return {
491
537
  source: artifacts.source,
492
538
  stub,
@@ -510,4 +556,4 @@ const realPath = (path) => {
510
556
  };
511
557
 
512
558
  //#endregion
513
- export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, ELYSIA_MODULE_FILTER, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, getAotWorkerDiagnostics, makeElysiaModuleFilterRegex, makeIsElysiaModule, planFromReport, realPath, resolveElysiaRoot, resolveEntry, rewriteIsProductionCalls, siblingModuleExt };
559
+ export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, ELYSIA_MODULE_FILTER, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, getAotWorkerDiagnostics, makeElysiaModuleFilterRegex, makeIsElysiaModule, planFromReport, realPath, resolveElysiaRoot, resolveEntry, resolveEntryModuleKind, rewriteIsProductionCalls, siblingModuleExt, validatorSlotSetsMatch };
@@ -28,7 +28,11 @@ let node_fs_promises = require("node:fs/promises");
28
28
  const aot = (entry, options) => ({
29
29
  name: "elysia-aot",
30
30
  async setup(build) {
31
- const hooks = require_plugin_aot_hooks.createAotPluginHooks(entry, options);
31
+ const entryPath = require_plugin_aot_core.resolveEntry(entry);
32
+ const moduleCondition = require_plugin_aot_core.resolveEntryModuleKind(entryPath);
33
+ if (build.initialOptions?.conditions?.includes("types")) throw new Error("[elysia-aot] esbuild conditions includes 'types'; this selects declaration files as runtime inputs. Remove the conflicting condition.");
34
+ if (moduleCondition === "cjs" && build.initialOptions?.conditions?.includes("import")) throw new Error("[elysia-aot] esbuild conditions includes 'import' for a CommonJS entry; this would load two Elysia module instances. Remove the conflicting condition.");
35
+ const hooks = require_plugin_aot_hooks.createAotPluginHooks(entry, options, moduleCondition);
32
36
  let initial = true;
33
37
  build.onStart(async () => {
34
38
  if (initial) {
@@ -39,8 +43,10 @@ const aot = (entry, options) => ({
39
43
  });
40
44
  await require_plugin_aot_hooks.setupAotOnLoad(build, hooks, {
41
45
  readText: (path) => (0, node_fs_promises.readFile)(path, "utf8"),
42
- resolveDir: (0, node_path.dirname)(require_plugin_aot_core.resolveEntry(entry))
46
+ resolveDir: (0, node_path.dirname)(entryPath)
43
47
  });
48
+ const allowedKinds = moduleCondition === "cjs" ? new Set(["require-call", "require-resolve"]) : new Set(["import-statement", "dynamic-import"]);
49
+ build.onResolve({ filter: /^elysia(?:\/.*)?$/ }, (args) => allowedKinds.has(args.kind) ? void 0 : { errors: [{ text: `[elysia-aot] ${args.path} uses esbuild edge kind ${JSON.stringify(args.kind)}, which conflicts with the ${JSON.stringify(moduleCondition)} entry module condition. Use one consistent import/require condition for Elysia.` }] });
44
50
  }
45
51
  });
46
52
 
@@ -1,4 +1,4 @@
1
- import { resolveEntry } from "./core.mjs";
1
+ import { resolveEntry, resolveEntryModuleKind } from "./core.mjs";
2
2
  import { createAotPluginHooks, setupAotOnLoad } from "./hooks.mjs";
3
3
  import { dirname } from "node:path";
4
4
  import { readFile } from "node:fs/promises";
@@ -26,7 +26,11 @@ import { readFile } from "node:fs/promises";
26
26
  const aot = (entry, options) => ({
27
27
  name: "elysia-aot",
28
28
  async setup(build) {
29
- const hooks = createAotPluginHooks(entry, options);
29
+ const entryPath = resolveEntry(entry);
30
+ const moduleCondition = resolveEntryModuleKind(entryPath);
31
+ if (build.initialOptions?.conditions?.includes("types")) throw new Error("[elysia-aot] esbuild conditions includes 'types'; this selects declaration files as runtime inputs. Remove the conflicting condition.");
32
+ if (moduleCondition === "cjs" && build.initialOptions?.conditions?.includes("import")) throw new Error("[elysia-aot] esbuild conditions includes 'import' for a CommonJS entry; this would load two Elysia module instances. Remove the conflicting condition.");
33
+ const hooks = createAotPluginHooks(entry, options, moduleCondition);
30
34
  let initial = true;
31
35
  build.onStart(async () => {
32
36
  if (initial) {
@@ -37,8 +41,10 @@ const aot = (entry, options) => ({
37
41
  });
38
42
  await setupAotOnLoad(build, hooks, {
39
43
  readText: (path) => readFile(path, "utf8"),
40
- resolveDir: dirname(resolveEntry(entry))
44
+ resolveDir: dirname(entryPath)
41
45
  });
46
+ const allowedKinds = moduleCondition === "cjs" ? new Set(["require-call", "require-resolve"]) : new Set(["import-statement", "dynamic-import"]);
47
+ build.onResolve({ filter: /^elysia(?:\/.*)?$/ }, (args) => allowedKinds.has(args.kind) ? void 0 : { errors: [{ text: `[elysia-aot] ${args.path} uses esbuild edge kind ${JSON.stringify(args.kind)}, which conflicts with the ${JSON.stringify(moduleCondition)} entry module condition. Use one consistent import/require condition for Elysia.` }] });
42
48
  }
43
49
  });
44
50
 
@@ -1,3 +1,4 @@
1
+ import { AotModuleCondition } from "./source.js";
1
2
  import { ElysiaAotOptions } from "./core.js";
2
3
 
3
4
  //#region src/plugin/aot/hooks.d.ts
@@ -9,7 +10,7 @@ interface AotPluginHooks {
9
10
  transform(code: string, id: string): string | undefined | Promise<string | undefined>;
10
11
  isTransformCandidate(id: string): boolean;
11
12
  }
12
- declare const createAotPluginHooks: (entry: string, options?: ElysiaAotOptions) => AotPluginHooks;
13
+ declare const createAotPluginHooks: (entry: string, options?: ElysiaAotOptions, moduleCondition?: AotModuleCondition) => AotPluginHooks;
13
14
  interface AotOnLoadAdapterOptions {
14
15
  /** Read a file's UTF-8 text (abstracted so Bun and esbuild can supply their own reader). */
15
16
  readText: (path: string) => Promise<string>;