elysia 2.0.0-beta.31 → 2.0.0-beta.32

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 (40) hide show
  1. package/dist/base.d.ts +18 -18
  2. package/dist/base.js +2 -2
  3. package/dist/base.mjs +2 -2
  4. package/dist/compile/aot-capture.js +1 -1
  5. package/dist/compile/aot-capture.mjs +1 -1
  6. package/dist/compile/aot.d.ts +4 -5
  7. package/dist/compile/aot.js +11 -12
  8. package/dist/compile/aot.mjs +11 -12
  9. package/dist/compile/handler/jit.js +6 -4
  10. package/dist/compile/handler/jit.mjs +6 -4
  11. package/dist/compile/handler/utils.d.ts +1 -1
  12. package/dist/compile/handler/utils.js +2 -2
  13. package/dist/compile/handler/utils.mjs +2 -2
  14. package/dist/error.d.ts +55 -50
  15. package/dist/handler/error.d.ts +7 -8
  16. package/dist/handler/error.js +26 -10
  17. package/dist/handler/error.mjs +27 -12
  18. package/dist/plugin/aot/core.d.ts +15 -3
  19. package/dist/plugin/aot/core.js +147 -83
  20. package/dist/plugin/aot/core.mjs +148 -85
  21. package/dist/plugin/aot/source.d.ts +6 -1
  22. package/dist/plugin/aot/source.js +32 -10
  23. package/dist/plugin/aot/source.mjs +32 -12
  24. package/dist/type/bridge-live.d.ts +1 -1
  25. package/dist/type/bridge-live.js +1 -1
  26. package/dist/type/bridge-live.mjs +1 -1
  27. package/dist/type/bridge.d.ts +1 -1
  28. package/dist/type/coerce.d.ts +1 -1
  29. package/dist/type/constants.d.ts +1 -1
  30. package/dist/type/typebox-value-live.d.ts +2 -2
  31. package/dist/type/typebox-value-live.js +2 -2
  32. package/dist/type/typebox-value-live.mjs +2 -2
  33. package/dist/type/typebox-value-ops.d.ts +2 -2
  34. package/dist/type/typebox-value-ops.js +2 -2
  35. package/dist/type/typebox-value-ops.mjs +2 -2
  36. package/dist/type/typebox-value.d.ts +6 -6
  37. package/dist/types.d.ts +70 -64
  38. package/dist/ws/route.js +6 -4
  39. package/dist/ws/route.mjs +7 -5
  40. package/package.json +1 -1
@@ -60,19 +60,28 @@ function statusFallbackBody(error, status, mask = "Internal Server Error", asDet
60
60
  const message = error.message ?? "";
61
61
  return asDetail && message === "" && !Object.hasOwn(error, "message") ? void 0 : message;
62
62
  }
63
- const problemOf = (self, type, detail, status, claimsProblem) => {
63
+ function problemOf(self, type, detail, status, claimsProblem, members) {
64
64
  const code = claimsProblem ? self.code : void 0;
65
65
  return new require_error.ElysiaStatus(status, require_error.problemBody({
66
66
  type: typeof type === "string" ? type : "about:blank",
67
67
  ...typeof code === "string" ? { code } : {},
68
+ ...members,
68
69
  ...detail === void 0 ? {} : { detail },
69
70
  status
70
71
  }), { "content-type": require_error.PROBLEM_JSON });
71
- };
72
- const readAnnotation = (self, key, claimsProblem) => {
72
+ }
73
+ function extensionOf(self) {
74
+ const extension = self.extension;
75
+ const members = typeof extension === "function" && extension.call(self);
76
+ if (!require_utils.isPlainObject(members)) return;
77
+ const served = require_utils.nullObject();
78
+ for (const [key, value] of Object.entries(members)) if (typeof value !== "function" && key !== "type" && key !== "code" && key !== "status" && (key !== "detail" || typeof value === "string")) served[key] = value;
79
+ return served;
80
+ }
81
+ function readAnnotation(self, key, claimsProblem) {
73
82
  const annotation = self[key];
74
- return typeof annotation === "function" ? claimsProblem ? annotation.call(self) : void 0 : annotation;
75
- };
83
+ return typeof annotation === "function" ? claimsProblem && key === "value" ? annotation.call(self) : void 0 : annotation;
84
+ }
76
85
  function fallbackErrorResponse(context, error, mapResponse) {
77
86
  if (error instanceof require_error.ElysiaStatus || error instanceof Response) return mapResponse(error, context.set, context);
78
87
  const self = error ?? {};
@@ -93,26 +102,28 @@ function fallbackErrorResponse(context, error, mapResponse) {
93
102
  if (error?.message != null && (context.set.status === void 0 || context.set.status === 200)) context.set.status = 500;
94
103
  return mapResponse(require_error.internalServerErrorResponse(error), context.set, context);
95
104
  };
105
+ let members;
96
106
  const serveMessage = () => {
97
107
  if (!claimsProblem) return legacy();
98
108
  mergeHeaders();
99
- return mapResponse(problemOf(self, type, statusFallbackBody(error, served, require_error.titleOf(served), true), served, claimsProblem), context.set, context);
109
+ return mapResponse(problemOf(self, type, members?.detail ?? statusFallbackBody(error, served, require_error.titleOf(served), true), served, claimsProblem, members), context.set, context);
100
110
  };
101
111
  const serveAnnotation = (key) => {
102
112
  let annotation;
103
113
  try {
104
114
  annotation = readAnnotation(self, key, claimsProblem);
115
+ if (key === "detail" && claimsProblem) members = extensionOf(self);
105
116
  } catch (cause) {
106
117
  return failed(cause);
107
118
  }
108
119
  if (annotation === void 0) return key === "value" ? serveAnnotation("detail") : serveMessage();
109
- if (annotation instanceof Promise) return annotation.then((resolved) => {
110
- if (resolved === void 0) return key === "value" ? serveAnnotation("detail") : serveMessage();
120
+ if (key === "value" && annotation instanceof Promise) return annotation.then((resolved) => {
121
+ if (resolved === void 0) return serveAnnotation("detail");
111
122
  mergeHeaders();
112
- return mapResponse(key === "value" ? resolved : problemOf(self, type, resolved, served, claimsProblem), context.set, context);
123
+ return mapResponse(resolved, context.set, context);
113
124
  }, failed);
114
125
  mergeHeaders();
115
- return mapResponse(key === "value" ? annotation : problemOf(self, type, annotation, served, claimsProblem), context.set, context);
126
+ return mapResponse(key === "value" ? annotation : problemOf(self, type, annotation, served, claimsProblem, members), context.set, context);
116
127
  };
117
128
  if (error instanceof require_error.ValidationError) return mapResponse(error.type === "response" ? require_error.internalServerErrorResponse(void 0) : new require_error.ElysiaStatus(422, require_error.problemBody({
118
129
  type: "validation",
@@ -141,6 +152,10 @@ function createErrorHandler(onErrors, mapResponse, allowUnsafe = false) {
141
152
  return fallbackResponse(context, error, mapWith(sign));
142
153
  };
143
154
  const respond = (context, error, result, sign) => {
155
+ if (result instanceof Error) {
156
+ context.set.status = result.status || 500;
157
+ return serveUnhandled(context, result, sign);
158
+ }
144
159
  if (result instanceof require_error.ElysiaStatus || result instanceof Response) context.set.status = result.status;
145
160
  else if (context.set.status === void 0 || context.set.status === 200) context.set.status = 500;
146
161
  return mapWith(sign)(adoptErrorType(result, error), context.set, context);
@@ -171,6 +186,7 @@ function createErrorHandler(onErrors, mapResponse, allowUnsafe = false) {
171
186
  exports.adoptErrorType = adoptErrorType;
172
187
  exports.claimsProblemType = claimsProblemType;
173
188
  exports.createErrorHandler = createErrorHandler;
189
+ exports.extensionOf = extensionOf;
174
190
  exports.fallbackResponse = fallbackResponse;
175
191
  exports.readAnnotation = readAnnotation;
176
192
  exports.resolveStatus = resolveStatus;
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  import { StatusMap } from "../constants.mjs";
3
- import { isNotEmpty } from "../utils.mjs";
3
+ import { isNotEmpty, isPlainObject, nullObject } from "../utils.mjs";
4
4
  import { isProduction } from "../universal/is-production.mjs";
5
5
  import { ElysiaStatus, HTTPError, NotFound, PROBLEM_JSON, ValidationError, internalServerErrorResponse, problemBody, titleOf } from "../error.mjs";
6
6
  import { materializeSetHeaders } from "../adapter/utils.mjs";
@@ -60,19 +60,28 @@ function statusFallbackBody(error, status, mask = "Internal Server Error", asDet
60
60
  const message = error.message ?? "";
61
61
  return asDetail && message === "" && !Object.hasOwn(error, "message") ? void 0 : message;
62
62
  }
63
- const problemOf = (self, type, detail, status, claimsProblem) => {
63
+ function problemOf(self, type, detail, status, claimsProblem, members) {
64
64
  const code = claimsProblem ? self.code : void 0;
65
65
  return new ElysiaStatus(status, problemBody({
66
66
  type: typeof type === "string" ? type : "about:blank",
67
67
  ...typeof code === "string" ? { code } : {},
68
+ ...members,
68
69
  ...detail === void 0 ? {} : { detail },
69
70
  status
70
71
  }), { "content-type": PROBLEM_JSON });
71
- };
72
- const readAnnotation = (self, key, claimsProblem) => {
72
+ }
73
+ function extensionOf(self) {
74
+ const extension = self.extension;
75
+ const members = typeof extension === "function" && extension.call(self);
76
+ if (!isPlainObject(members)) return;
77
+ const served = nullObject();
78
+ for (const [key, value] of Object.entries(members)) if (typeof value !== "function" && key !== "type" && key !== "code" && key !== "status" && (key !== "detail" || typeof value === "string")) served[key] = value;
79
+ return served;
80
+ }
81
+ function readAnnotation(self, key, claimsProblem) {
73
82
  const annotation = self[key];
74
- return typeof annotation === "function" ? claimsProblem ? annotation.call(self) : void 0 : annotation;
75
- };
83
+ return typeof annotation === "function" ? claimsProblem && key === "value" ? annotation.call(self) : void 0 : annotation;
84
+ }
76
85
  function fallbackErrorResponse(context, error, mapResponse) {
77
86
  if (error instanceof ElysiaStatus || error instanceof Response) return mapResponse(error, context.set, context);
78
87
  const self = error ?? {};
@@ -93,26 +102,28 @@ function fallbackErrorResponse(context, error, mapResponse) {
93
102
  if (error?.message != null && (context.set.status === void 0 || context.set.status === 200)) context.set.status = 500;
94
103
  return mapResponse(internalServerErrorResponse(error), context.set, context);
95
104
  };
105
+ let members;
96
106
  const serveMessage = () => {
97
107
  if (!claimsProblem) return legacy();
98
108
  mergeHeaders();
99
- return mapResponse(problemOf(self, type, statusFallbackBody(error, served, titleOf(served), true), served, claimsProblem), context.set, context);
109
+ return mapResponse(problemOf(self, type, members?.detail ?? statusFallbackBody(error, served, titleOf(served), true), served, claimsProblem, members), context.set, context);
100
110
  };
101
111
  const serveAnnotation = (key) => {
102
112
  let annotation;
103
113
  try {
104
114
  annotation = readAnnotation(self, key, claimsProblem);
115
+ if (key === "detail" && claimsProblem) members = extensionOf(self);
105
116
  } catch (cause) {
106
117
  return failed(cause);
107
118
  }
108
119
  if (annotation === void 0) return key === "value" ? serveAnnotation("detail") : serveMessage();
109
- if (annotation instanceof Promise) return annotation.then((resolved) => {
110
- if (resolved === void 0) return key === "value" ? serveAnnotation("detail") : serveMessage();
120
+ if (key === "value" && annotation instanceof Promise) return annotation.then((resolved) => {
121
+ if (resolved === void 0) return serveAnnotation("detail");
111
122
  mergeHeaders();
112
- return mapResponse(key === "value" ? resolved : problemOf(self, type, resolved, served, claimsProblem), context.set, context);
123
+ return mapResponse(resolved, context.set, context);
113
124
  }, failed);
114
125
  mergeHeaders();
115
- return mapResponse(key === "value" ? annotation : problemOf(self, type, annotation, served, claimsProblem), context.set, context);
126
+ return mapResponse(key === "value" ? annotation : problemOf(self, type, annotation, served, claimsProblem, members), context.set, context);
116
127
  };
117
128
  if (error instanceof ValidationError) return mapResponse(error.type === "response" ? internalServerErrorResponse(void 0) : new ElysiaStatus(422, problemBody({
118
129
  type: "validation",
@@ -141,6 +152,10 @@ function createErrorHandler(onErrors, mapResponse, allowUnsafe = false) {
141
152
  return fallbackResponse(context, error, mapWith(sign));
142
153
  };
143
154
  const respond = (context, error, result, sign) => {
155
+ if (result instanceof Error) {
156
+ context.set.status = result.status || 500;
157
+ return serveUnhandled(context, result, sign);
158
+ }
144
159
  if (result instanceof ElysiaStatus || result instanceof Response) context.set.status = result.status;
145
160
  else if (context.set.status === void 0 || context.set.status === 200) context.set.status = 500;
146
161
  return mapWith(sign)(adoptErrorType(result, error), context.set, context);
@@ -168,4 +183,4 @@ function createErrorHandler(onErrors, mapResponse, allowUnsafe = false) {
168
183
  }
169
184
 
170
185
  //#endregion
171
- export { adoptErrorType, claimsProblemType, createErrorHandler, fallbackResponse, readAnnotation, resolveStatus, statusFallbackBody };
186
+ export { adoptErrorType, claimsProblemType, createErrorHandler, extensionOf, fallbackResponse, readAnnotation, resolveStatus, statusFallbackBody };
@@ -100,7 +100,7 @@ interface ElysiaAotOptions {
100
100
  }
101
101
  declare const resolveEntry: (entry: string) => string;
102
102
  /** @internal Resolve the package condition used to evaluate an AOT entry. */
103
- declare const resolveEntryModuleKind: (entry: string) => "cjs" | "esm";
103
+ declare const resolveEntryModuleKind: (entry: string) => "esm" | "cjs";
104
104
  /** Runtime handler-JIT module a strip build can replace with a throwing stub. */
105
105
  interface StubPlan {
106
106
  /** Stub the internal handler codegen module. */
@@ -225,7 +225,19 @@ declare function planFromReport(strip: boolean | 'auto', report: JITProbeResult,
225
225
  * outside the handler JIT, so it emits no `cc` alias — stubbing on that
226
226
  * alias alone would hand `src/ws/route.ts` a throwing `parseCookieRaw`
227
227
  */
228
- wsCookie?: boolean, /** `exact-mirror` resolves from the elysia package at build time. */exactMirror?: boolean): {
228
+ wsCookie?: boolean, /** `exact-mirror` resolves from the elysia package at build time. */exactMirror?: boolean, /** Manifest key, names the app in a multi-app build's errors */key?: string): {
229
+ plan: StubPlan;
230
+ mode: BridgeMode;
231
+ };
232
+ /**
233
+ * Fold the per-app plans of a multi-app build into the one shared bundle: a
234
+ * module is stubbed only when every app can drop it, and the TypeBox wiring
235
+ * follows the least sealed app
236
+ */
237
+ declare function mergePlans(plans: {
238
+ plan: StubPlan;
239
+ mode: BridgeMode;
240
+ }[], exactMirror: boolean): {
229
241
  plan: StubPlan;
230
242
  mode: BridgeMode;
231
243
  };
@@ -281,4 +293,4 @@ declare function generateCompiledArtifacts(file: string, options?: ElysiaAotOpti
281
293
  declare const SOURCE_REGEX: RegExp;
282
294
  declare const realPath: (path: string) => string;
283
295
  //#endregion
284
- export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, BridgeMode, CompiledArtifacts, ELYSIA_MODULE_FILTER, EXACT_MIRROR_REQUIRE_FILTER, ElysiaAotOptions, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, StubPlan, TYPEBOX_REQUIRE_FILTER, TYPEBOX_SYSTEM_LITE_FILTER, TYPEBOX_TYPE_FILTER, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, makeIsElysiaModule, planFromReport, realPath, resolveElysiaRoot, resolveEntry, resolveEntryModuleKind, resolveExactMirror, rewriteIsProductionCalls, siblingModuleExt, validatorSlotSetsMatch };
296
+ export { ADAPTER_BUN_FILTER, ADAPTER_CONSTANTS_FILTER, BridgeMode, CompiledArtifacts, ELYSIA_MODULE_FILTER, EXACT_MIRROR_REQUIRE_FILTER, ElysiaAotOptions, IS_PRODUCTION_FILTER, NO_STUB, OVERRIDE_MAP, SOURCE_REGEX, STUB_SOURCES, StubPlan, TYPEBOX_REQUIRE_FILTER, TYPEBOX_SYSTEM_LITE_FILTER, TYPEBOX_TYPE_FILTER, adapterConstantsSource, alignStubExtensions, bunAdapterStubSource, generateCompiledArtifacts, generateCompiledArtifactsIsolated, generateVirtualType, makeIsElysiaModule, mergePlans, planFromReport, realPath, resolveElysiaRoot, resolveEntry, resolveEntryModuleKind, resolveExactMirror, rewriteIsProductionCalls, siblingModuleExt, validatorSlotSetsMatch };
@@ -3,6 +3,7 @@ const require_runtime = require('../../_virtual/_rolldown/runtime.js');
3
3
  const require_compile_handler_params = require('../../compile/handler/params.js');
4
4
  const require_compile_handler_frozen_validator = require('../../compile/handler/frozen-validator.js');
5
5
  const require_compile_handler_index = require('../../compile/handler/index.js');
6
+ const require_compile_aot_capture = require('../../compile/aot-capture.js');
6
7
  const require_plugin_aot_source = require('./source.js');
7
8
  let node_fs = require("node:fs");
8
9
  let node_module = require("node:module");
@@ -57,9 +58,9 @@ const NO_STUB = {
57
58
  adapter: false,
58
59
  isProduction: false
59
60
  };
60
- function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true, wsCookie = false, exactMirror = false) {
61
+ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree, zeroCapture, adapterStub = false, productionStub = true, wsCookie = false, exactMirror = false, key = "") {
61
62
  const jit = report.jit;
62
- 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.`);
63
+ if (strip === true && !jit) throw new Error(`[elysia-aot] strip: true requires every route${key && ` of app "${key}"`} 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.`);
63
64
  const mode = !jit ? "off" : allBridgeFree ? "sealed" : hasWS && zeroCapture ? "off" : "wired";
64
65
  return {
65
66
  plan: {
@@ -70,16 +71,51 @@ function planFromReport(strip, report, hasWS, mayTrace, aliases, allBridgeFree,
70
71
  cookie: jit && !aliases.has("cc") && !wsCookie,
71
72
  trace: !aliases.has("tr") && (jit || !mayTrace),
72
73
  sucrose: jit,
73
- compat: mode !== "off",
74
- bridge: mode === "wired",
75
- typeboxValue: mode !== "sealed",
76
- exactMirror: exactMirror && mode !== "sealed",
74
+ ...bridgePlan(mode, exactMirror),
77
75
  adapter: adapterStub,
78
76
  isProduction: productionStub
79
77
  },
80
78
  mode
81
79
  };
82
80
  }
81
+ const bridgePlan = (mode, exactMirror) => ({
82
+ compat: mode !== "off",
83
+ bridge: mode === "wired",
84
+ typeboxValue: mode !== "sealed",
85
+ exactMirror: exactMirror && mode !== "sealed"
86
+ });
87
+ const MODE_RANK = {
88
+ off: 0,
89
+ wired: 1,
90
+ sealed: 2
91
+ };
92
+ const REMOVAL_FLAGS = {
93
+ jit: 0,
94
+ staticClone: 0,
95
+ ws: 0,
96
+ reconstruct: 0,
97
+ cookie: 0,
98
+ trace: 0,
99
+ sucrose: 0
100
+ };
101
+ function mergePlans(plans, exactMirror) {
102
+ const plan = { ...plans[0].plan };
103
+ let mode = plans[0].mode;
104
+ for (const each of plans) {
105
+ if (MODE_RANK[each.mode] < MODE_RANK[mode]) mode = each.mode;
106
+ for (const key in REMOVAL_FLAGS) {
107
+ const flag = key;
108
+ plan[flag] &&= each.plan[flag];
109
+ }
110
+ }
111
+ return {
112
+ plan: {
113
+ ...plan,
114
+ ...bridgePlan(mode, exactMirror)
115
+ },
116
+ mode
117
+ };
118
+ }
83
119
  function adapterConstantsSource(target) {
84
120
  if (target === "bun") return "import { BunAdapter } from './bun/index'\nexport const defaultAdapter = BunAdapter\n";
85
121
  return "import { WebStandardAdapter } from './web-standard/index'\nexport const defaultAdapter = WebStandardAdapter\n";
@@ -411,13 +447,6 @@ function validatorSlotSetsMatch(expected, captured) {
411
447
  for (const key of expectedKeys) if (!capturedKeys.has(key)) return false;
412
448
  return true;
413
449
  }
414
- const assertNoMount = (app, entry) => {
415
- const routes = app["~routes"];
416
- const mounted = [];
417
- for (const route of routes) if (route[2]?.["~mount"] && !mounted.includes(route[1])) mounted.push(route[1]);
418
- if (!mounted.length) return;
419
- 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`);
420
- };
421
450
  async function generateCompiledArtifacts(file, options, moduleCondition = resolveEntryModuleKind(file)) {
422
451
  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.`);
423
452
  const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
@@ -432,6 +461,8 @@ async function generateCompiledArtifacts(file, options, moduleCondition = resolv
432
461
  _importedEntries.add(entryReal);
433
462
  const mod = await import(entry);
434
463
  const isApp = (value) => typeof value?.compile === "function";
464
+ await Promise.allSettled(Object.values(mod).filter(isApp).map((app) => app.modules));
465
+ require_compile_aot_capture.abortCapture();
435
466
  let app = isApp(mod.app) ? mod.app : mod.default;
436
467
  if (!isApp(app)) {
437
468
  const names = Object.keys(mod).filter((name) => isApp(mod[name]));
@@ -439,8 +470,22 @@ async function generateCompiledArtifacts(file, options, moduleCondition = resolv
439
470
  app = names[0] ? mod[names[0]] : void 0;
440
471
  }
441
472
  if (!isApp(app)) throw new Error(`[elysia-aot] "${entry}" must export an Elysia app`);
442
- const typedApp = app;
443
- assertNoMount(typedApp, entry);
473
+ const primary = app;
474
+ const apps = [primary];
475
+ const exportOf = new Map([[require_plugin_aot_source.manifestKey(primary), Object.keys(mod).find((name) => mod[name] === app)]]);
476
+ for (const name of Object.keys(mod)) {
477
+ const other = mod[name];
478
+ const key = isApp(other) && !apps.includes(other) && require_plugin_aot_source.manifestKey(other);
479
+ if (!key || !other["~routes"].length) continue;
480
+ if (exportOf.has(key)) throw new Error(`[elysia-aot] "${entry}" exports two Elysia apps named "${key}" (${exportOf.get(key)}, ${name}). Each AOT manifest is keyed by app name, rename one`);
481
+ exportOf.set(key, name);
482
+ apps.push(other);
483
+ }
484
+ if (apps.length > 1 && !require_plugin_aot_source.manifestKey(primary)) {
485
+ const unnamed = exportOf.get("");
486
+ const named = [...exportOf.values()].slice(1).map((name) => `\`${name}\``).join(", ");
487
+ throw new Error(`[elysia-aot] "${entry}" exports \`${unnamed}\` with no name next to named Elysia apps (${named}): name it (new Elysia({ name: '${unnamed}' })), or stop exporting ${named}`);
488
+ }
444
489
  const sourceOptions = {
445
490
  moduleCondition,
446
491
  registerFrom: options?.registerFrom,
@@ -452,8 +497,90 @@ async function generateCompiledArtifacts(file, options, moduleCondition = resolv
452
497
  const adapterStub = options?.target === "bun" ? "bun" : options?.target === "node" || options?.target === "workerd" ? "web-standard" : false;
453
498
  const productionStub = options?.production !== false;
454
499
  const exactMirror = resolveExactMirror(entry);
500
+ const analyze = (typedApp, artifacts) => {
501
+ const report = require_plugin_aot_source.replayStubbability(typedApp, artifacts.handlers, options?.target);
502
+ const aliases = /* @__PURE__ */ new Set();
503
+ for (const handler of artifacts.handlers) if (handler.alias) for (const name of handler.alias.split(",")) aliases.add(require_compile_handler_params.isResponseParam(name) ? "va" : name);
504
+ const history = typedApp["~routes"];
505
+ const hasWS = !!typedApp["~hasWS"] || history.some((route) => route[0] === "WS");
506
+ const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
507
+ const expectedSlots = [];
508
+ const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
509
+ let routesForbidSeal = false;
510
+ let wsCookie = false;
511
+ for (const route of history) {
512
+ const method = route[0];
513
+ const path = route[1];
514
+ const expectTypeBoxSlot = (slot) => {
515
+ const identity = {
516
+ method,
517
+ path,
518
+ slot
519
+ };
520
+ const key = validatorSlotKey(identity);
521
+ if (declaredTypeBoxSlots.has(key)) routesForbidSeal = true;
522
+ else {
523
+ declaredTypeBoxSlots.add(key);
524
+ expectedSlots.push(identity);
525
+ }
526
+ };
527
+ const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
528
+ const hooks = require_compile_handler_index.composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
529
+ require_compile_handler_index.runIntrospect(hooks);
530
+ if (method === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
531
+ if (!hooks) continue;
532
+ let routeHasTypeBoxDirectSlot = false;
533
+ const classify = (schema, slot) => {
534
+ if (schema === void 0) {
535
+ routesForbidSeal = true;
536
+ return;
537
+ }
538
+ if (require_compile_handler_frozen_validator.isStandardSchema(schema)) return;
539
+ routeHasTypeBoxDirectSlot = true;
540
+ expectTypeBoxSlot(slot);
541
+ };
542
+ for (const slot of require_compile_handler_frozen_validator.REQUEST_SLOTS) {
543
+ const value = hooks[slot];
544
+ if (value !== void 0) classify(require_compile_handler_frozen_validator.resolveModelRef(value, typedApp), slot);
545
+ }
546
+ if (hooks.response !== void 0) {
547
+ const response = require_compile_handler_frozen_validator.resolveModelRef(hooks.response, typedApp);
548
+ 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}`);
549
+ else classify(response, "response:200");
550
+ }
551
+ const mergeSchemas = hooks.schemas;
552
+ if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
553
+ if (routeHasTypeBoxDirectSlot || !require_compile_handler_frozen_validator.mergeSchemasAllStandard(mergeSchemas)) routesForbidSeal = true;
554
+ }
555
+ }
556
+ if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
557
+ const validatorSlotsMatch = validatorSlotSetsMatch(expectedSlots, artifacts.validators);
558
+ const hasReplayableCapture = artifacts.handlers.some((h) => h.code) || artifacts.validators.length > 0;
559
+ const plan = planFromReport(strip, report, hasWS, mayTrace, aliases, hasReplayableCapture && !routesForbidSeal && validatorSlotsMatch && artifacts.validators.every((v) => v.bridgeFree === true), !hasReplayableCapture, adapterStub, productionStub, wsCookie, exactMirror, artifacts.key);
560
+ if (options?.target === "workerd") {
561
+ if (!report.jit) throw new Error(`[elysia-aot] target 'workerd' but handler JIT is still reachable${artifacts.key && ` in app "${artifacts.key}"`} (${report.reasons.join(", ") || "unknown"}). Every route must be captured into the AOT manifest.`);
562
+ 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.");
563
+ }
564
+ return plan;
565
+ };
566
+ const captured = [];
567
+ const plans = [];
568
+ for (const each of apps) {
569
+ let artifacts;
570
+ try {
571
+ artifacts = await require_plugin_aot_source.captureArtifacts(each, sourceOptions);
572
+ } catch (error) {
573
+ if (each === primary) throw error;
574
+ const key = require_plugin_aot_source.manifestKey(each);
575
+ console.warn(`[elysia-aot] export \`${exportOf.get(key)}\` (app "${key}") gets no AOT manifest, its capture failed: ${error.message}`);
576
+ continue;
577
+ }
578
+ captured.push(artifacts);
579
+ if (strip !== false) plans.push(analyze(each, artifacts));
580
+ }
581
+ const source = captured.length > 1 ? require_plugin_aot_source.emitModule(captured, captured[0].fingerprint, sourceOptions) : captured[0].source;
455
582
  if (strip === false) return {
456
- source: await require_plugin_aot_source.compileToSource(typedApp, sourceOptions),
583
+ source,
457
584
  stub: {
458
585
  ...NO_STUB,
459
586
  typeboxValue: true,
@@ -463,76 +590,12 @@ async function generateCompiledArtifacts(file, options, moduleCondition = resolv
463
590
  },
464
591
  mode: "off"
465
592
  };
466
- const artifacts = await require_plugin_aot_source.captureArtifacts(typedApp, sourceOptions);
467
- const report = require_plugin_aot_source.replayStubbability(typedApp, artifacts.handlers, options?.target);
468
- const aliases = /* @__PURE__ */ new Set();
469
- for (const handler of artifacts.handlers) if (handler.alias) for (const name of handler.alias.split(",")) aliases.add(require_compile_handler_params.isResponseParam(name) ? "va" : name);
470
- const history = typedApp["~routes"];
471
- const hasWS = !!typedApp["~hasWS"] || history.some((route) => route[0] === "WS");
472
- const mayTrace = !!typedApp["~hasTrace"] || history.some((route) => (route?.[4])?.trace?.length || route?.[2]?.["~mount"]);
473
- const expectedSlots = [];
474
- const declaredTypeBoxSlots = /* @__PURE__ */ new Set();
475
- let routesForbidSeal = false;
476
- let wsCookie = false;
477
- for (const route of history) {
478
- const method = route[0];
479
- const path = route[1];
480
- const expectTypeBoxSlot = (slot) => {
481
- const identity = {
482
- method,
483
- path,
484
- slot
485
- };
486
- const key = validatorSlotKey(identity);
487
- if (declaredTypeBoxSlots.has(key)) routesForbidSeal = true;
488
- else {
489
- declaredTypeBoxSlots.add(key);
490
- expectedSlots.push(identity);
491
- }
492
- };
493
- const [, , , instance, hook, appHook, inheritedChain, macroScope] = route;
494
- const hooks = require_compile_handler_index.composeRouteHook(instance, hook, appHook, inheritedChain, typedApp, macroScope);
495
- require_compile_handler_index.runIntrospect(hooks);
496
- if (method === "WS" && (hook?.cookie !== void 0 || hooks?.cookie !== void 0)) wsCookie = true;
497
- if (!hooks) continue;
498
- let routeHasTypeBoxDirectSlot = false;
499
- const classify = (schema, slot) => {
500
- if (schema === void 0) {
501
- routesForbidSeal = true;
502
- return;
503
- }
504
- if (require_compile_handler_frozen_validator.isStandardSchema(schema)) return;
505
- routeHasTypeBoxDirectSlot = true;
506
- expectTypeBoxSlot(slot);
507
- };
508
- for (const slot of require_compile_handler_frozen_validator.REQUEST_SLOTS) {
509
- const value = hooks[slot];
510
- if (value !== void 0) classify(require_compile_handler_frozen_validator.resolveModelRef(value, typedApp), slot);
511
- }
512
- if (hooks.response !== void 0) {
513
- const response = require_compile_handler_frozen_validator.resolveModelRef(hooks.response, typedApp);
514
- 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}`);
515
- else classify(response, "response:200");
516
- }
517
- const mergeSchemas = hooks.schemas;
518
- if (Array.isArray(mergeSchemas) && mergeSchemas.length > 0) {
519
- if (routeHasTypeBoxDirectSlot || !require_compile_handler_frozen_validator.mergeSchemasAllStandard(mergeSchemas)) routesForbidSeal = true;
520
- }
521
- }
522
- if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
523
- const validatorSlotsMatch = validatorSlotSetsMatch(expectedSlots, artifacts.validators);
524
- const hasReplayableCapture = artifacts.handlers.some((h) => h.code) || artifacts.validators.length > 0;
525
- const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, aliases, hasReplayableCapture && !routesForbidSeal && validatorSlotsMatch && artifacts.validators.every((v) => v.bridgeFree === true), !hasReplayableCapture, adapterStub, productionStub, wsCookie, exactMirror);
526
- if (options?.target === "workerd") {
527
- 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.`);
528
- 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.");
529
- }
530
- const virtualType = mode === "off" ? void 0 : await generateVirtualType(moduleCondition);
593
+ const { plan: stub, mode } = mergePlans(plans, exactMirror);
531
594
  return {
532
- source: artifacts.source,
595
+ source,
533
596
  stub,
534
597
  mode,
535
- virtualType
598
+ virtualType: mode === "off" ? void 0 : await generateVirtualType(moduleCondition)
536
599
  };
537
600
  } finally {
538
601
  if (previousAotBuild === void 0) delete process.env.ELYSIA_AOT_BUILD;
@@ -570,6 +633,7 @@ exports.generateCompiledArtifacts = generateCompiledArtifacts;
570
633
  exports.generateCompiledArtifactsIsolated = generateCompiledArtifactsIsolated;
571
634
  exports.generateVirtualType = generateVirtualType;
572
635
  exports.makeIsElysiaModule = makeIsElysiaModule;
636
+ exports.mergePlans = mergePlans;
573
637
  exports.planFromReport = planFromReport;
574
638
  exports.realPath = realPath;
575
639
  exports.resolveElysiaRoot = resolveElysiaRoot;