elysia 2.0.0-beta.2 → 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 (175) 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 +51 -30
  5. package/dist/adapter/utils.mjs +52 -31
  6. package/dist/adapter/web-standard/handler.js +5 -2
  7. package/dist/adapter/web-standard/handler.mjs +5 -2
  8. package/dist/base.d.ts +18 -7
  9. package/dist/base.js +213 -101
  10. package/dist/base.mjs +215 -103
  11. package/dist/compile/aot-capture.js +1 -1
  12. package/dist/compile/aot-capture.mjs +1 -1
  13. package/dist/compile/aot.d.ts +5 -4
  14. package/dist/compile/aot.js +17 -9
  15. package/dist/compile/aot.mjs +18 -9
  16. package/dist/compile/handler/descriptor.js +1 -1
  17. package/dist/compile/handler/descriptor.mjs +1 -1
  18. package/dist/compile/handler/frozen-validator.d.ts +4 -1
  19. package/dist/compile/handler/frozen-validator.js +8 -3
  20. package/dist/compile/handler/frozen-validator.mjs +5 -3
  21. package/dist/compile/handler/index.d.ts +2 -2
  22. package/dist/compile/handler/index.js +8 -6
  23. package/dist/compile/handler/index.mjs +8 -6
  24. package/dist/compile/handler/reconstruct.d.ts +1 -1
  25. package/dist/compile/handler/reconstruct.js +4 -3
  26. package/dist/compile/handler/reconstruct.mjs +4 -3
  27. package/dist/compile/handler/utils.js +2 -2
  28. package/dist/compile/handler/utils.mjs +3 -3
  29. package/dist/compile/jit-probe.d.ts +2 -2
  30. package/dist/context.d.ts +1 -1
  31. package/dist/context.js +8 -5
  32. package/dist/context.mjs +8 -5
  33. package/dist/cookie/config.d.ts +1 -0
  34. package/dist/cookie/config.js +3 -2
  35. package/dist/cookie/config.mjs +3 -2
  36. package/dist/cookie/cookie.d.ts +1 -0
  37. package/dist/cookie/cookie.js +6 -5
  38. package/dist/cookie/cookie.mjs +6 -5
  39. package/dist/cookie/crypto.d.ts +8 -7
  40. package/dist/cookie/crypto.js +38 -31
  41. package/dist/cookie/crypto.mjs +38 -32
  42. package/dist/cookie/error.js +1 -2
  43. package/dist/cookie/error.mjs +1 -2
  44. package/dist/cookie/lib.js +5 -1
  45. package/dist/cookie/lib.mjs +5 -1
  46. package/dist/cookie/serialize.js +1 -1
  47. package/dist/cookie/serialize.mjs +1 -1
  48. package/dist/cookie/types.d.ts +10 -0
  49. package/dist/cookie/utils.js +28 -14
  50. package/dist/cookie/utils.mjs +29 -15
  51. package/dist/handler/error.d.ts +23 -12
  52. package/dist/handler/error.js +24 -21
  53. package/dist/handler/error.mjs +20 -21
  54. package/dist/handler/fetch.js +6 -7
  55. package/dist/handler/fetch.mjs +7 -8
  56. package/dist/handler/utils.js +4 -2
  57. package/dist/handler/utils.mjs +5 -3
  58. package/dist/index.js +1 -8
  59. package/dist/package.js +1 -1
  60. package/dist/package.mjs +1 -1
  61. package/dist/plugin/aot/core.d.ts +33 -6
  62. package/dist/plugin/aot/core.js +120 -40
  63. package/dist/plugin/aot/core.mjs +121 -43
  64. package/dist/plugin/aot/esbuild.js +8 -2
  65. package/dist/plugin/aot/esbuild.mjs +9 -3
  66. package/dist/plugin/aot/hooks.d.ts +2 -1
  67. package/dist/plugin/aot/hooks.js +3 -3
  68. package/dist/plugin/aot/hooks.mjs +4 -4
  69. package/dist/plugin/aot/source.d.ts +4 -1
  70. package/dist/plugin/aot/source.js +21 -14
  71. package/dist/plugin/aot/source.mjs +21 -14
  72. package/dist/plugin/aot/worker.js +2 -2
  73. package/dist/plugin/aot/worker.mjs +2 -2
  74. package/dist/route-table.d.ts +3 -0
  75. package/dist/route-table.js +16 -11
  76. package/dist/route-table.mjs +16 -11
  77. package/dist/schema-snapshot.js +17 -22
  78. package/dist/schema-snapshot.mjs +17 -22
  79. package/dist/type/bridge-live.d.ts +1 -1
  80. package/dist/type/bridge-live.js +2 -2
  81. package/dist/type/bridge-live.mjs +2 -2
  82. package/dist/type/bridge.d.ts +1 -1
  83. package/dist/type/coerce-plan.js +3 -4
  84. package/dist/type/coerce-plan.mjs +1 -1
  85. package/dist/type/coerce.d.ts +1 -1
  86. package/dist/type/compat.d.ts +3 -0
  87. package/dist/type/compat.js +32 -14
  88. package/dist/type/compat.mjs +27 -8
  89. package/dist/type/constants.d.ts +1 -1
  90. package/dist/type/elysia/array-buffer.js +2 -3
  91. package/dist/type/elysia/array-buffer.mjs +1 -1
  92. package/dist/type/elysia/array-string.js +7 -4
  93. package/dist/type/elysia/array-string.mjs +9 -5
  94. package/dist/type/elysia/boolean-string.js +2 -3
  95. package/dist/type/elysia/boolean-string.mjs +1 -1
  96. package/dist/type/elysia/boolean.js +4 -3
  97. package/dist/type/elysia/boolean.mjs +4 -2
  98. package/dist/type/elysia/cookie.js +2 -1
  99. package/dist/type/elysia/cookie.mjs +2 -1
  100. package/dist/type/elysia/date.js +7 -7
  101. package/dist/type/elysia/date.mjs +3 -2
  102. package/dist/type/elysia/file-type.d.ts +9 -2
  103. package/dist/type/elysia/file-type.js +16 -1
  104. package/dist/type/elysia/file-type.mjs +16 -2
  105. package/dist/type/elysia/file.js +8 -4
  106. package/dist/type/elysia/file.mjs +8 -3
  107. package/dist/type/elysia/files.js +6 -4
  108. package/dist/type/elysia/files.mjs +5 -2
  109. package/dist/type/elysia/form.js +2 -3
  110. package/dist/type/elysia/form.mjs +3 -3
  111. package/dist/type/elysia/integer-string.js +4 -5
  112. package/dist/type/elysia/integer-string.mjs +3 -3
  113. package/dist/type/elysia/integer.js +4 -3
  114. package/dist/type/elysia/integer.mjs +4 -2
  115. package/dist/type/elysia/maybe-empty.js +3 -4
  116. package/dist/type/elysia/maybe-empty.mjs +1 -1
  117. package/dist/type/elysia/nullable.js +2 -3
  118. package/dist/type/elysia/nullable.mjs +1 -1
  119. package/dist/type/elysia/number.js +4 -3
  120. package/dist/type/elysia/number.mjs +4 -2
  121. package/dist/type/elysia/numeric-enum.js +2 -3
  122. package/dist/type/elysia/numeric-enum.mjs +1 -1
  123. package/dist/type/elysia/numeric.js +5 -5
  124. package/dist/type/elysia/numeric.mjs +3 -2
  125. package/dist/type/elysia/object-string.js +7 -4
  126. package/dist/type/elysia/object-string.mjs +9 -5
  127. package/dist/type/elysia/string.js +4 -3
  128. package/dist/type/elysia/string.mjs +4 -2
  129. package/dist/type/elysia/uint8-array.js +2 -3
  130. package/dist/type/elysia/uint8-array.mjs +1 -1
  131. package/dist/type/elysia/union-enum.js +2 -3
  132. package/dist/type/elysia/union-enum.mjs +1 -1
  133. package/dist/type/elysia/utils.js +2 -3
  134. package/dist/type/elysia/utils.mjs +1 -1
  135. package/dist/type/exports.js +10 -1
  136. package/dist/type/exports.mjs +7 -0
  137. package/dist/type/index.d.ts +9 -1
  138. package/dist/type/index.js +41 -13
  139. package/dist/type/index.mjs +44 -9
  140. package/dist/type/typebox-type-live.d.ts +9 -0
  141. package/dist/type/typebox-type-live.js +31 -0
  142. package/dist/type/typebox-type-live.mjs +16 -0
  143. package/dist/type/typebox-type.d.ts +24 -0
  144. package/dist/type/typebox-type.js +130 -0
  145. package/dist/type/typebox-type.mjs +66 -0
  146. package/dist/type/typebox-value-live.d.ts +9 -0
  147. package/dist/type/typebox-value-live.js +27 -0
  148. package/dist/type/typebox-value-live.mjs +11 -0
  149. package/dist/type/typebox-value.d.ts +30 -0
  150. package/dist/type/typebox-value.js +147 -0
  151. package/dist/type/typebox-value.mjs +62 -0
  152. package/dist/type/validator/custom-error.js +4 -5
  153. package/dist/type/validator/custom-error.mjs +3 -3
  154. package/dist/type/validator/default-precompute.js +4 -5
  155. package/dist/type/validator/default-precompute.mjs +1 -1
  156. package/dist/type/validator/index.d.ts +1 -1
  157. package/dist/type/validator/index.js +51 -42
  158. package/dist/type/validator/index.mjs +33 -23
  159. package/dist/type/validator/validator-cache.d.ts +1 -1
  160. package/dist/type/validator/validator-cache.js +27 -14
  161. package/dist/type/validator/validator-cache.mjs +27 -13
  162. package/dist/universal/constants.d.ts +2 -1
  163. package/dist/universal/constants.js +3 -1
  164. package/dist/universal/constants.mjs +2 -1
  165. package/dist/utils.d.ts +19 -1
  166. package/dist/utils.js +25 -3
  167. package/dist/utils.mjs +19 -3
  168. package/dist/validator/index.js +2 -1
  169. package/dist/validator/index.mjs +2 -1
  170. package/dist/ws/context.d.ts +8 -1
  171. package/dist/ws/context.js +65 -29
  172. package/dist/ws/context.mjs +64 -29
  173. package/dist/ws/route.js +176 -28
  174. package/dist/ws/route.mjs +179 -31
  175. package/package.json +5 -3
@@ -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,
@@ -29,6 +51,8 @@ const NO_STUB = {
29
51
  sucrose: false,
30
52
  compat: false,
31
53
  bridge: false,
54
+ typeboxValue: false,
55
+ typeboxType: false,
32
56
  adapter: false,
33
57
  isProduction: false
34
58
  };
@@ -49,7 +73,7 @@ const NO_STUB = {
49
73
  * is not active, so the bridge must stay wired the ordinary way. No compat
50
74
  * stub, no reroute.
51
75
  */
52
- function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true) {
76
+ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true, wsCookie = false) {
53
77
  const jit = report.jit;
54
78
  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
79
  const mode = !jit ? "off" : allBridgeFree ? "sealed" : hasWS && zeroCapture ? "off" : "wired";
@@ -58,11 +82,13 @@ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree,
58
82
  jit,
59
83
  ws: !hasWS,
60
84
  reconstruct: jit && !aliases.has("va") && !aliases.has("cc") && !aliases.has("tr"),
61
- cookie: jit && !aliases.has("cc"),
85
+ cookie: jit && !aliases.has("cc") && !wsCookie,
62
86
  trace: !aliases.has("tr") && (jit || !mayTrace),
63
87
  sucrose: jit,
64
88
  compat: mode !== "off",
65
89
  bridge: mode === "wired",
90
+ typeboxValue: mode !== "sealed",
91
+ typeboxType: true,
66
92
  adapter: adapterStub,
67
93
  isProduction: productionStub
68
94
  },
@@ -172,6 +198,14 @@ const STUB_SOURCES = {
172
198
  bridge: [{
173
199
  filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]bridge\.(m?js|ts)$/,
174
200
  source: `export * from './bridge-live'\n`
201
+ }],
202
+ typeboxValue: [{
203
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]typebox-value\.(m?js|ts)$/,
204
+ source: `export * from './typebox-value-live'\n`
205
+ }],
206
+ typeboxType: [{
207
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]typebox-type\.(m?js|ts)$/,
208
+ source: `export * from './typebox-type-live'\n`
175
209
  }]
176
210
  };
177
211
  const alignStubExtensions = (stubSource, targetPath) => {
@@ -290,17 +324,20 @@ const OVERRIDE_MAP = {
290
324
  }
291
325
  };
292
326
  const requireFromHere = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
293
- const resolveSpecifier = (specifier) => {
294
- const meta = {};
295
- if (typeof meta.resolve === "function") {
296
- 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);
297
333
  return url.startsWith("file://") ? new URL(url).pathname : url;
298
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.");
299
336
  return requireFromHere.resolve(specifier);
300
337
  };
301
- async function generateVirtualType(typeSpecifier = "elysia/type") {
302
- const typePath = resolveSpecifier(typeSpecifier);
303
- 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);
304
341
  const ext = typePath.slice(typePath.lastIndexOf("."));
305
342
  const leafDir = (0, node_path.join)((0, node_path.dirname)(typePath), "elysia");
306
343
  const typebox = await import((0, node_url.pathToFileURL)(typeboxPath).href);
@@ -336,12 +373,13 @@ const workerUrl = () => {
336
373
  return new URL("./worker" + siblingModuleExt(moduleUrl), moduleUrl);
337
374
  };
338
375
  /** @internal Re-evaluate an entry in a disposable worker for watch rebuilds. */
339
- async function generateCompiledArtifactsIsolated(file, options) {
376
+ async function generateCompiledArtifactsIsolated(file, options, moduleCondition = resolveEntryModuleKind(file)) {
340
377
  const entry = resolveEntry(file);
341
378
  console.warn("[elysia-aot] re-evaluating \"" + entry + "\" in an isolated worker for rebuild. Top-level side effects run only inside the worker.");
342
379
  const worker = new node_worker_threads.Worker(workerUrl(), { workerData: {
343
380
  file: entry,
344
- options
381
+ options,
382
+ moduleCondition
345
383
  } });
346
384
  activeGenerationWorkers++;
347
385
  const exit = new Promise((resolve) => worker.once("exit", resolve));
@@ -371,15 +409,32 @@ async function generateCompiledArtifactsIsolated(file, options) {
371
409
  }
372
410
  }
373
411
  }
374
- function isStandardResponse(response) {
375
- if (response == null || typeof response !== "object") return false;
376
- if (require_compile_handler_frozen_validator.isStandardSchema(response)) return true;
377
- if ("~kind" in response || "~elyAcl" in response) return false;
378
- const entries = Object.values(response);
379
- if (entries.length === 0) return false;
380
- 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;
381
420
  }
382
- async function generateCompiledArtifacts(file, options) {
421
+ /**
422
+ * The manifest is one flat `method -> path` table with no per-app namespace,
423
+ * bound to whichever app builds a router first, so a mounted sub-app (a second
424
+ * Elysia building on its own schedule) is not representable
425
+ *
426
+ * refuse at build time rather than ship a coin flip
427
+ */
428
+ const assertNoMount = (app, entry) => {
429
+ const routes = app["~routes"];
430
+ if (!routes) return;
431
+ const mounted = [];
432
+ for (const route of routes) if (route[2]?.["~mount"] && !mounted.includes(route[1])) mounted.push(route[1]);
433
+ if (!mounted.length) return;
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`);
435
+ };
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.`);
383
438
  const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
384
439
  process.env.ELYSIA_AOT_BUILD = "1";
385
440
  const previousAotVerbose = process.env.ELYSIA_AOT_VERBOSE;
@@ -388,14 +443,16 @@ async function generateCompiledArtifacts(file, options) {
388
443
  try {
389
444
  const entry = resolveEntry(file);
390
445
  const entryReal = realPath(entry);
391
- if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options);
446
+ if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options, moduleCondition);
392
447
  _importedEntries.add(entryReal);
393
448
  const mod = await import(entry);
394
449
  const app = mod.app ?? mod.default;
395
450
  if (!app || typeof app.compile !== "function") throw new Error(`[elysia-aot] "${entry}" must export an Elysia app`);
396
451
  const typedApp = app;
452
+ assertNoMount(typedApp, entry);
397
453
  const sourceOptions = {
398
454
  register: true,
455
+ moduleCondition,
399
456
  registerFrom: options?.registerFrom,
400
457
  reconstructFrom: options?.reconstructFrom,
401
458
  lazy: options?.lazy,
@@ -408,6 +465,8 @@ async function generateCompiledArtifacts(file, options) {
408
465
  source: await require_plugin_aot_source.compileToSource(typedApp, sourceOptions),
409
466
  stub: {
410
467
  ...NO_STUB,
468
+ typeboxValue: true,
469
+ typeboxType: true,
411
470
  adapter: adapterStub,
412
471
  isProduction: productionStub
413
472
  },
@@ -420,29 +479,48 @@ async function generateCompiledArtifacts(file, options) {
420
479
  const hasWS = !!typedApp["~hasWS"] || !!typedApp["~routes"]?.some((route) => route?.[0] === "WS");
421
480
  const history = typedApp["~routes"] ?? [];
422
481
  const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
423
- let expectedSlots = 0;
482
+ const expectedSlots = [];
483
+ const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
424
484
  let routesForbidSeal = false;
485
+ let wsCookie = false;
425
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
+ };
426
502
  const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
427
503
  const hooks = require_compile_handler_index.composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
504
+ if (route[0] === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
428
505
  if (!hooks) continue;
429
506
  let routeHasTypeBoxDirectSlot = false;
430
- for (const slot of [
431
- "body",
432
- "query",
433
- "params",
434
- "headers",
435
- "cookie"
436
- ]) {
437
- const value = hooks[slot];
438
- if (value === void 0) continue;
439
- 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;
440
513
  routeHasTypeBoxDirectSlot = true;
441
- 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);
442
519
  }
443
- if (hooks.response !== void 0 && !isStandardResponse(hooks.response)) {
444
- routeHasTypeBoxDirectSlot = true;
445
- 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");
446
524
  }
447
525
  const mergeSchemas = hooks.schemas;
448
526
  if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
@@ -450,13 +528,13 @@ async function generateCompiledArtifacts(file, options) {
450
528
  }
451
529
  }
452
530
  if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
453
- const frozenSlots = artifacts.validators.length;
454
- 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);
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);
455
533
  if (options?.target === "workerd") {
456
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.`);
457
- 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.");
458
536
  }
459
- const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type");
537
+ const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type", moduleCondition);
460
538
  return {
461
539
  source: artifacts.source,
462
540
  stub,
@@ -501,5 +579,7 @@ exports.planFromReport = planFromReport;
501
579
  exports.realPath = realPath;
502
580
  exports.resolveElysiaRoot = resolveElysiaRoot;
503
581
  exports.resolveEntry = resolveEntry;
582
+ exports.resolveEntryModuleKind = resolveEntryModuleKind;
504
583
  exports.rewriteIsProductionCalls = rewriteIsProductionCalls;
505
- 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,
@@ -27,6 +49,8 @@ const NO_STUB = {
27
49
  sucrose: false,
28
50
  compat: false,
29
51
  bridge: false,
52
+ typeboxValue: false,
53
+ typeboxType: false,
30
54
  adapter: false,
31
55
  isProduction: false
32
56
  };
@@ -47,7 +71,7 @@ const NO_STUB = {
47
71
  * is not active, so the bridge must stay wired the ordinary way. No compat
48
72
  * stub, no reroute.
49
73
  */
50
- function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true) {
74
+ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true, wsCookie = false) {
51
75
  const jit = report.jit;
52
76
  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.`);
53
77
  const mode = !jit ? "off" : allBridgeFree ? "sealed" : hasWS && zeroCapture ? "off" : "wired";
@@ -56,11 +80,13 @@ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree,
56
80
  jit,
57
81
  ws: !hasWS,
58
82
  reconstruct: jit && !aliases.has("va") && !aliases.has("cc") && !aliases.has("tr"),
59
- cookie: jit && !aliases.has("cc"),
83
+ cookie: jit && !aliases.has("cc") && !wsCookie,
60
84
  trace: !aliases.has("tr") && (jit || !mayTrace),
61
85
  sucrose: jit,
62
86
  compat: mode !== "off",
63
87
  bridge: mode === "wired",
88
+ typeboxValue: mode !== "sealed",
89
+ typeboxType: true,
64
90
  adapter: adapterStub,
65
91
  isProduction: productionStub
66
92
  },
@@ -170,6 +196,14 @@ const STUB_SOURCES = {
170
196
  bridge: [{
171
197
  filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]bridge\.(m?js|ts)$/,
172
198
  source: `export * from './bridge-live'\n`
199
+ }],
200
+ typeboxValue: [{
201
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]typebox-value\.(m?js|ts)$/,
202
+ source: `export * from './typebox-value-live'\n`
203
+ }],
204
+ typeboxType: [{
205
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]type[\\/]typebox-type\.(m?js|ts)$/,
206
+ source: `export * from './typebox-type-live'\n`
173
207
  }]
174
208
  };
175
209
  const alignStubExtensions = (stubSource, targetPath) => {
@@ -288,17 +322,20 @@ const OVERRIDE_MAP = {
288
322
  }
289
323
  };
290
324
  const requireFromHere = createRequire(import.meta.url);
291
- const resolveSpecifier = (specifier) => {
292
- const meta = import.meta;
293
- if (typeof meta.resolve === "function") {
294
- 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);
295
331
  return url.startsWith("file://") ? new URL(url).pathname : url;
296
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.");
297
334
  return requireFromHere.resolve(specifier);
298
335
  };
299
- async function generateVirtualType(typeSpecifier = "elysia/type") {
300
- const typePath = resolveSpecifier(typeSpecifier);
301
- 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);
302
339
  const ext = typePath.slice(typePath.lastIndexOf("."));
303
340
  const leafDir = join(dirname(typePath), "elysia");
304
341
  const typebox = await import(pathToFileURL(typeboxPath).href);
@@ -334,12 +371,13 @@ const workerUrl = () => {
334
371
  return new URL("./worker" + siblingModuleExt(moduleUrl), moduleUrl);
335
372
  };
336
373
  /** @internal Re-evaluate an entry in a disposable worker for watch rebuilds. */
337
- async function generateCompiledArtifactsIsolated(file, options) {
374
+ async function generateCompiledArtifactsIsolated(file, options, moduleCondition = resolveEntryModuleKind(file)) {
338
375
  const entry = resolveEntry(file);
339
376
  console.warn("[elysia-aot] re-evaluating \"" + entry + "\" in an isolated worker for rebuild. Top-level side effects run only inside the worker.");
340
377
  const worker = new Worker(workerUrl(), { workerData: {
341
378
  file: entry,
342
- options
379
+ options,
380
+ moduleCondition
343
381
  } });
344
382
  activeGenerationWorkers++;
345
383
  const exit = new Promise((resolve) => worker.once("exit", resolve));
@@ -369,15 +407,32 @@ async function generateCompiledArtifactsIsolated(file, options) {
369
407
  }
370
408
  }
371
409
  }
372
- function isStandardResponse(response) {
373
- if (response == null || typeof response !== "object") return false;
374
- if (isStandardSchema(response)) return true;
375
- if ("~kind" in response || "~elyAcl" in response) return false;
376
- const entries = Object.values(response);
377
- if (entries.length === 0) return false;
378
- 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;
379
418
  }
380
- async function generateCompiledArtifacts(file, options) {
419
+ /**
420
+ * The manifest is one flat `method -> path` table with no per-app namespace,
421
+ * bound to whichever app builds a router first, so a mounted sub-app (a second
422
+ * Elysia building on its own schedule) is not representable
423
+ *
424
+ * refuse at build time rather than ship a coin flip
425
+ */
426
+ const assertNoMount = (app, entry) => {
427
+ const routes = app["~routes"];
428
+ if (!routes) return;
429
+ const mounted = [];
430
+ for (const route of routes) if (route[2]?.["~mount"] && !mounted.includes(route[1])) mounted.push(route[1]);
431
+ if (!mounted.length) return;
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`);
433
+ };
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.`);
381
436
  const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
382
437
  process.env.ELYSIA_AOT_BUILD = "1";
383
438
  const previousAotVerbose = process.env.ELYSIA_AOT_VERBOSE;
@@ -386,14 +441,16 @@ async function generateCompiledArtifacts(file, options) {
386
441
  try {
387
442
  const entry = resolveEntry(file);
388
443
  const entryReal = realPath(entry);
389
- if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options);
444
+ if (_importedEntries.has(entryReal)) return generateCompiledArtifactsIsolated(file, options, moduleCondition);
390
445
  _importedEntries.add(entryReal);
391
446
  const mod = await import(entry);
392
447
  const app = mod.app ?? mod.default;
393
448
  if (!app || typeof app.compile !== "function") throw new Error(`[elysia-aot] "${entry}" must export an Elysia app`);
394
449
  const typedApp = app;
450
+ assertNoMount(typedApp, entry);
395
451
  const sourceOptions = {
396
452
  register: true,
453
+ moduleCondition,
397
454
  registerFrom: options?.registerFrom,
398
455
  reconstructFrom: options?.reconstructFrom,
399
456
  lazy: options?.lazy,
@@ -406,6 +463,8 @@ async function generateCompiledArtifacts(file, options) {
406
463
  source: await compileToSource(typedApp, sourceOptions),
407
464
  stub: {
408
465
  ...NO_STUB,
466
+ typeboxValue: true,
467
+ typeboxType: true,
409
468
  adapter: adapterStub,
410
469
  isProduction: productionStub
411
470
  },
@@ -418,29 +477,48 @@ async function generateCompiledArtifacts(file, options) {
418
477
  const hasWS = !!typedApp["~hasWS"] || !!typedApp["~routes"]?.some((route) => route?.[0] === "WS");
419
478
  const history = typedApp["~routes"] ?? [];
420
479
  const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
421
- let expectedSlots = 0;
480
+ const expectedSlots = [];
481
+ const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
422
482
  let routesForbidSeal = false;
483
+ let wsCookie = false;
423
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
+ };
424
500
  const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
425
501
  const hooks = composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
502
+ if (route[0] === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
426
503
  if (!hooks) continue;
427
504
  let routeHasTypeBoxDirectSlot = false;
428
- for (const slot of [
429
- "body",
430
- "query",
431
- "params",
432
- "headers",
433
- "cookie"
434
- ]) {
435
- const value = hooks[slot];
436
- if (value === void 0) continue;
437
- 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;
438
511
  routeHasTypeBoxDirectSlot = true;
439
- 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);
440
517
  }
441
- if (hooks.response !== void 0 && !isStandardResponse(hooks.response)) {
442
- routeHasTypeBoxDirectSlot = true;
443
- 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");
444
522
  }
445
523
  const mergeSchemas = hooks.schemas;
446
524
  if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
@@ -448,13 +526,13 @@ async function generateCompiledArtifacts(file, options) {
448
526
  }
449
527
  }
450
528
  if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
451
- const frozenSlots = artifacts.validators.length;
452
- 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);
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);
453
531
  if (options?.target === "workerd") {
454
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.`);
455
- 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.");
456
534
  }
457
- const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type");
535
+ const virtualType = mode === "off" ? void 0 : await generateVirtualType("elysia/type", moduleCondition);
458
536
  return {
459
537
  source: artifacts.source,
460
538
  stub,
@@ -478,4 +556,4 @@ const realPath = (path) => {
478
556
  };
479
557
 
480
558
  //#endregion
481
- 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>;