elysia 2.0.0-exp.2 → 2.0.0-exp.21

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 (135) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +2 -0
  2. package/dist/adapter/bun/index.d.ts +2 -25
  3. package/dist/adapter/bun/index.js +18 -12
  4. package/dist/adapter/bun/index.mjs +19 -13
  5. package/dist/adapter/bun/router.js +1 -1
  6. package/dist/adapter/bun/router.mjs +1 -1
  7. package/dist/adapter/constants.d.ts +3 -26
  8. package/dist/adapter/index.d.ts +1 -26
  9. package/dist/adapter/index.js +1 -17
  10. package/dist/adapter/index.mjs +1 -17
  11. package/dist/adapter/types.d.ts +1 -0
  12. package/dist/adapter/utils.d.ts +3 -4
  13. package/dist/adapter/utils.js +83 -81
  14. package/dist/adapter/utils.mjs +82 -80
  15. package/dist/adapter/web-standard/handler.js +6 -3
  16. package/dist/adapter/web-standard/handler.mjs +6 -3
  17. package/dist/adapter/web-standard/index.d.ts +2 -26
  18. package/dist/adapter/web-standard/index.js +12 -1
  19. package/dist/adapter/web-standard/index.mjs +12 -1
  20. package/dist/base.d.ts +38 -68
  21. package/dist/base.js +133 -97
  22. package/dist/base.mjs +133 -97
  23. package/dist/compile/aot.d.ts +22 -1
  24. package/dist/compile/aot.js +22 -0
  25. package/dist/compile/aot.mjs +22 -0
  26. package/dist/compile/handler/index.d.ts +2 -2
  27. package/dist/compile/handler/index.js +85 -456
  28. package/dist/compile/handler/index.mjs +86 -457
  29. package/dist/compile/handler/jit.d.ts +35 -0
  30. package/dist/compile/handler/jit.js +445 -0
  31. package/dist/compile/handler/jit.mjs +443 -0
  32. package/dist/compile/handler/params.js +3 -2
  33. package/dist/compile/handler/params.mjs +4 -3
  34. package/dist/compile/handler/reconstruct.d.ts +13 -0
  35. package/dist/compile/handler/reconstruct.js +29 -0
  36. package/dist/compile/handler/reconstruct.mjs +28 -0
  37. package/dist/compile/handler/utils.d.ts +2 -1
  38. package/dist/compile/handler/utils.js +9 -3
  39. package/dist/compile/handler/utils.mjs +9 -4
  40. package/dist/compile/jit-probe.d.ts +19 -0
  41. package/dist/compile/jit-probe.js +38 -0
  42. package/dist/compile/jit-probe.mjs +36 -0
  43. package/dist/cookie/index.d.ts +4 -3
  44. package/dist/cookie/index.js +2 -1
  45. package/dist/cookie/index.mjs +2 -1
  46. package/dist/cookie/serialize.d.ts +6 -0
  47. package/dist/cookie/serialize.js +28 -0
  48. package/dist/cookie/serialize.mjs +27 -0
  49. package/dist/cookie/utils.d.ts +1 -2
  50. package/dist/cookie/utils.js +2 -23
  51. package/dist/cookie/utils.mjs +5 -25
  52. package/dist/handler/error.js +2 -2
  53. package/dist/handler/error.mjs +3 -3
  54. package/dist/handler/fetch.js +49 -42
  55. package/dist/handler/fetch.mjs +50 -43
  56. package/dist/index.d.ts +13 -8
  57. package/dist/index.js +11 -4
  58. package/dist/index.mjs +7 -6
  59. package/dist/parse-query.js +1 -1
  60. package/dist/parse-query.mjs +1 -1
  61. package/dist/plugin/bun.d.ts +10 -0
  62. package/dist/plugin/bun.js +18 -1
  63. package/dist/plugin/bun.mjs +19 -2
  64. package/dist/plugin/core.d.ts +61 -1
  65. package/dist/plugin/core.js +90 -10
  66. package/dist/plugin/core.mjs +90 -12
  67. package/dist/plugin/esbuild.d.ts +10 -0
  68. package/dist/plugin/esbuild.js +18 -1
  69. package/dist/plugin/esbuild.mjs +19 -2
  70. package/dist/plugin/source.d.ts +38 -1
  71. package/dist/plugin/source.js +104 -13
  72. package/dist/plugin/source.mjs +102 -14
  73. package/dist/plugin/vite.d.ts +7 -0
  74. package/dist/plugin/vite.js +22 -1
  75. package/dist/plugin/vite.mjs +23 -2
  76. package/dist/sucrose.js +23 -10
  77. package/dist/sucrose.mjs +23 -10
  78. package/dist/trace.d.ts +1 -1
  79. package/dist/trace.js +4 -6
  80. package/dist/trace.mjs +4 -6
  81. package/dist/type/bridge.d.ts +9 -8
  82. package/dist/type/coerce.d.ts +20 -2
  83. package/dist/type/coerce.js +110 -0
  84. package/dist/type/coerce.mjs +109 -2
  85. package/dist/type/compat.js +4 -3
  86. package/dist/type/compat.mjs +2 -1
  87. package/dist/type/constants.d.ts +1 -1
  88. package/dist/type/elysia/boolean-string.js +1 -1
  89. package/dist/type/elysia/boolean-string.mjs +1 -1
  90. package/dist/type/elysia/file-type.js +1 -1
  91. package/dist/type/elysia/file-type.mjs +1 -1
  92. package/dist/type/elysia/integer-string.js +1 -1
  93. package/dist/type/elysia/integer-string.mjs +1 -1
  94. package/dist/type/exports.js +29 -2059
  95. package/dist/type/exports.mjs +7 -295
  96. package/dist/type/index.d.ts +6 -2
  97. package/dist/type/index.js +4 -0
  98. package/dist/type/index.mjs +3 -1
  99. package/dist/type/types.d.ts +1 -1
  100. package/dist/type/utils.js +4 -4
  101. package/dist/type/utils.mjs +4 -4
  102. package/dist/type/validator/custom-error.d.ts +10 -0
  103. package/dist/type/validator/custom-error.js +113 -0
  104. package/dist/type/validator/custom-error.mjs +110 -0
  105. package/dist/type/validator/default-precompute.d.ts +22 -0
  106. package/dist/type/validator/default-precompute.js +476 -0
  107. package/dist/type/validator/default-precompute.mjs +465 -0
  108. package/dist/type/validator/frozen-check.d.ts +11 -0
  109. package/dist/type/validator/frozen-check.js +19 -0
  110. package/dist/type/validator/frozen-check.mjs +18 -0
  111. package/dist/type/{validator.d.ts → validator/index.d.ts} +7 -20
  112. package/dist/type/validator/index.js +572 -0
  113. package/dist/type/validator/index.mjs +567 -0
  114. package/dist/type/validator/string-codec-aot.d.ts +9 -0
  115. package/dist/type/validator/string-codec-aot.js +106 -0
  116. package/dist/type/validator/string-codec-aot.mjs +102 -0
  117. package/dist/type/validator/validator-cache.d.ts +21 -0
  118. package/dist/type/validator/validator-cache.js +163 -0
  119. package/dist/type/validator/validator-cache.mjs +161 -0
  120. package/dist/types.d.ts +22 -14
  121. package/dist/universal/file.d.ts +1 -1
  122. package/dist/universal/file.js +35 -29
  123. package/dist/universal/file.mjs +35 -29
  124. package/dist/utils.d.ts +12 -1
  125. package/dist/utils.js +89 -45
  126. package/dist/utils.mjs +88 -46
  127. package/dist/validator/index.d.ts +1 -1
  128. package/dist/validator/index.js +15 -2
  129. package/dist/validator/index.mjs +15 -2
  130. package/dist/validator/route.d.ts +0 -1
  131. package/dist/ws/route.js +47 -24
  132. package/dist/ws/route.mjs +48 -25
  133. package/package.json +10 -4
  134. package/dist/type/validator.js +0 -1033
  135. package/dist/type/validator.mjs +0 -1029
@@ -1,4 +1,4 @@
1
- import { compileToSource } from "./source.mjs";
1
+ import { captureArtifacts, compileToSource, replayStubbability } from "./source.mjs";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
 
@@ -18,19 +18,97 @@ function resolveLoader(entryPath) {
18
18
  return ext === ".js" || ext === ".mjs" || ext === ".cjs" ? "js" : ext === ".jsx" ? "jsx" : ext === ".tsx" ? "tsx" : "ts";
19
19
  }
20
20
  const entryFilter = (entryPath) => new RegExp("^" + entryPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "$");
21
+ const NO_STUB = {
22
+ jit: false,
23
+ ws: false,
24
+ reconstruct: false,
25
+ cookie: false,
26
+ trace: false,
27
+ sucrose: false
28
+ };
29
+ /**
30
+ * Resolve whether handler JIT is safe to stub for `entry`, honouring the
31
+ * `strip` option. `'auto'` stubs only when detection proves handler JIT unused;
32
+ * `true` throws when handler JIT is still reachable; `false` disables.
33
+ */
34
+ function planFromReport(strip, report, hasWS, aliases) {
35
+ const jit = report.jit;
36
+ 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.`);
37
+ return {
38
+ jit,
39
+ ws: !hasWS,
40
+ reconstruct: jit && !aliases.has("va") && !aliases.has("cc") && !aliases.has("tr"),
41
+ cookie: jit && !aliases.has("cc"),
42
+ trace: jit && !aliases.has("tr"),
43
+ sucrose: jit
44
+ };
45
+ }
46
+ const STUB_SOURCES = {
47
+ jit: [{
48
+ filter: /[\\/]compile[\\/]handler[\\/]jit\.(m?js|ts)$/,
49
+ source: "const e=()=>{throw new Error(\"[elysia-aot] handler compiler JIT was stripped (strip mode) but a route needed runtime compilation. Rebuild with strip:false.\")}\nexport function compileHandlerJit(){return e()}\nexport function setCaptureHeaderShorthand(){}\n"
50
+ }],
51
+ ws: [{
52
+ filter: /[\\/]ws[\\/]route\.(m?js|ts)$/,
53
+ source: "const e=()=>{throw new Error(\"[elysia-aot] WebSocket route builder was stripped (strip mode) but a WS route was used. Rebuild with strip:false.\")}\nexport function buildWSRoute(){return e()}\nexport function buildGlobalWSHandler(){return e()}\n"
54
+ }],
55
+ reconstruct: [{
56
+ filter: /[\\/]compile[\\/]handler[\\/]reconstruct\.(m?js|ts)$/,
57
+ source: "const e=()=>{throw new Error(\"[elysia-aot] handler reconstruction was stripped (strip mode) but a route needed it. Rebuild with strip:false.\")}\nexport class Reconstrct {\n static validator(){return e()}\n static cookie(){return e()}\n static trace(){return e()}\n}\n"
58
+ }],
59
+ cookie: [{
60
+ filter: /[\\/]cookie[\\/]utils\.(m?js|ts)$/,
61
+ source: "const e=()=>{throw new Error(\"[elysia-aot] cookie support was stripped (strip mode) but a route used cookies. Rebuild with strip:false.\")}\nexport function createCookieJar(){return e()}\nexport function parseCookie(){return e()}\nexport function parseCookieRaw(){return e()}\nexport function parseCookieRawSync(){return e()}\nexport function buildCookieJar(){return e()}\nexport function signCookieValues(){return e()}\nexport function signCookie(){return e()}\nexport function unsignCookie(){return e()}\n"
62
+ }, {
63
+ filter: /[\\/]cookie[\\/]config\.(m?js|ts)$/,
64
+ source: "const e=()=>{throw new Error(\"[elysia-aot] cookie support was stripped (strip mode) but a route used cookies. Rebuild with strip:false.\")}\nexport function compileCookieConfig(){return e()}\nexport function isCookieSigned(){return e()}\n"
65
+ }],
66
+ trace: [{
67
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]trace\.(m?js|ts)$/,
68
+ source: "const e=()=>{throw new Error(\"[elysia-aot] trace support was stripped (strip mode) but a route used trace. Rebuild with strip:false.\")}\nexport function createTracer(){return e()}\n"
69
+ }],
70
+ sucrose: [{
71
+ filter: /[\\/]elysia[\\/](dist|src)[\\/]memory\.(m?js|ts)$/,
72
+ source: "import { clearContextCache } from './context'\nimport { isBun } from './universal/constants'\nimport { Validator } from './validator'\nexport function flushMemory() {\n clearContextCache()\n Validator.clear()\n if (isBun) Bun.gc()\n else if (typeof global?.gc === 'function') global.gc()\n}\n"
73
+ }]
74
+ };
21
75
  async function generateCompiledModule(file, options) {
76
+ return (await generateCompiledArtifacts(file, options)).source;
77
+ }
78
+ async function generateCompiledArtifacts(file, options) {
79
+ const previousAotBuild = process.env.ELYSIA_AOT_BUILD;
22
80
  process.env.ELYSIA_AOT_BUILD = "1";
23
- const entry = resolveEntry(file);
24
- const mod = await import(entry);
25
- const app = mod.app ?? mod.default;
26
- if (!app || typeof app.compile !== "function") throw new Error(`[elysia-aot] "${entry}" must export an Elysia app`);
27
- return compileToSource(app, {
28
- register: true,
29
- registerFrom: options?.registerFrom,
30
- lazy: options?.lazy,
31
- target: options?.target
32
- });
81
+ try {
82
+ const entry = resolveEntry(file);
83
+ const mod = await import(entry);
84
+ const app = mod.app ?? mod.default;
85
+ if (!app || typeof app.compile !== "function") throw new Error(`[elysia-aot] "${entry}" must export an Elysia app`);
86
+ const typedApp = app;
87
+ const sourceOptions = {
88
+ register: true,
89
+ registerFrom: options?.registerFrom,
90
+ lazy: options?.lazy,
91
+ target: options?.target
92
+ };
93
+ const strip = options?.strip ?? "auto";
94
+ if (strip === false) return {
95
+ source: await compileToSource(typedApp, sourceOptions),
96
+ stub: NO_STUB
97
+ };
98
+ const artifacts = await captureArtifacts(typedApp, sourceOptions);
99
+ const report = replayStubbability(typedApp, artifacts.handlers);
100
+ const aliases = /* @__PURE__ */ new Set();
101
+ for (const handler of artifacts.handlers) if (handler.alias) for (const name of handler.alias.split(",")) aliases.add(name);
102
+ const hasWS = !!typedApp["~hasWS"] || !!typedApp.history?.some((route) => route?.[0] === "WS");
103
+ return {
104
+ source: artifacts.source,
105
+ stub: planFromReport(strip, report, hasWS, aliases)
106
+ };
107
+ } finally {
108
+ if (previousAotBuild === void 0) delete process.env.ELYSIA_AOT_BUILD;
109
+ else process.env.ELYSIA_AOT_BUILD = previousAotBuild;
110
+ }
33
111
  }
34
112
 
35
113
  //#endregion
36
- export { entryFilter, generateCompiledModule, resolveEntry, resolveLoader };
114
+ export { STUB_SOURCES, entryFilter, generateCompiledArtifacts, generateCompiledModule, resolveEntry, resolveLoader };
@@ -16,7 +16,17 @@ import { ElysiaAotOptions } from "./core.js";
16
16
  * outfile: 'dist/index.js',
17
17
  * plugins: [elysiaAot('src/index.ts')]
18
18
  * })
19
+ *
20
+ * process.exit(0)
19
21
  * ```
22
+ *
23
+ * The plugin imports your entry to capture the compiled app, running its
24
+ * top-level code. `.listen()` is auto-skipped during build (gated on
25
+ * `ELYSIA_AOT_BUILD`), but any other import-time handle — a DB pool,
26
+ * `setInterval`, a queue consumer — keeps the process alive after the bundle
27
+ * is written. End the build script with `process.exit(0)` (the bundle is
28
+ * already on disk), or gate the side effect with
29
+ * `if (!process.env.ELYSIA_AOT_BUILD)`.
20
30
  */
21
31
  declare const aot: (entry: string, options?: ElysiaAotOptions) => {
22
32
  name: string;
@@ -30,12 +30,22 @@ const realPath = (path) => {
30
30
  * outfile: 'dist/index.js',
31
31
  * plugins: [elysiaAot('src/index.ts')]
32
32
  * })
33
+ *
34
+ * process.exit(0)
33
35
  * ```
36
+ *
37
+ * The plugin imports your entry to capture the compiled app, running its
38
+ * top-level code. `.listen()` is auto-skipped during build (gated on
39
+ * `ELYSIA_AOT_BUILD`), but any other import-time handle — a DB pool,
40
+ * `setInterval`, a queue consumer — keeps the process alive after the bundle
41
+ * is written. End the build script with `process.exit(0)` (the bundle is
42
+ * already on disk), or gate the side effect with
43
+ * `if (!process.env.ELYSIA_AOT_BUILD)`.
34
44
  */
35
45
  const aot = (entry, options) => ({
36
46
  name: "elysia-aot",
37
47
  async setup(build) {
38
- const source = await require_plugin_core.generateCompiledModule(entry, options);
48
+ const { source, stub } = await require_plugin_core.generateCompiledArtifacts(entry, options);
39
49
  const entryPath = require_plugin_core.resolveEntry(entry);
40
50
  const entryReal = realPath(entryPath);
41
51
  const treeShake = options?.treeShake ?? true;
@@ -52,6 +62,13 @@ const aot = (entry, options) => ({
52
62
  loader: "js",
53
63
  resolveDir: (0, node_path.dirname)(entryPath)
54
64
  }));
65
+ for (const key of Object.keys(require_plugin_core.STUB_SOURCES)) {
66
+ if (!stub[key]) continue;
67
+ for (const { filter, source: stubSource } of require_plugin_core.STUB_SOURCES[key]) build.onLoad({ filter }, () => ({
68
+ contents: stubSource,
69
+ loader: "js"
70
+ }));
71
+ }
55
72
  if (treeShake) build.onLoad({ filter: SOURCE }, async (args) => {
56
73
  const isEntryFile = isEntry(args.path);
57
74
  const inModules = args.path.includes("/node_modules/");
@@ -1,4 +1,4 @@
1
- import { entryFilter, generateCompiledModule, resolveEntry, resolveLoader } from "./core.mjs";
1
+ import { STUB_SOURCES, entryFilter, generateCompiledArtifacts, resolveEntry, resolveLoader } from "./core.mjs";
2
2
  import { rewriteTypeImport } from "./treeshake.mjs";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { dirname } from "node:path";
@@ -28,12 +28,22 @@ const realPath = (path) => {
28
28
  * outfile: 'dist/index.js',
29
29
  * plugins: [elysiaAot('src/index.ts')]
30
30
  * })
31
+ *
32
+ * process.exit(0)
31
33
  * ```
34
+ *
35
+ * The plugin imports your entry to capture the compiled app, running its
36
+ * top-level code. `.listen()` is auto-skipped during build (gated on
37
+ * `ELYSIA_AOT_BUILD`), but any other import-time handle — a DB pool,
38
+ * `setInterval`, a queue consumer — keeps the process alive after the bundle
39
+ * is written. End the build script with `process.exit(0)` (the bundle is
40
+ * already on disk), or gate the side effect with
41
+ * `if (!process.env.ELYSIA_AOT_BUILD)`.
32
42
  */
33
43
  const aot = (entry, options) => ({
34
44
  name: "elysia-aot",
35
45
  async setup(build) {
36
- const source = await generateCompiledModule(entry, options);
46
+ const { source, stub } = await generateCompiledArtifacts(entry, options);
37
47
  const entryPath = resolveEntry(entry);
38
48
  const entryReal = realPath(entryPath);
39
49
  const treeShake = options?.treeShake ?? true;
@@ -50,6 +60,13 @@ const aot = (entry, options) => ({
50
60
  loader: "js",
51
61
  resolveDir: dirname(entryPath)
52
62
  }));
63
+ for (const key of Object.keys(STUB_SOURCES)) {
64
+ if (!stub[key]) continue;
65
+ for (const { filter, source: stubSource } of STUB_SOURCES[key]) build.onLoad({ filter }, () => ({
66
+ contents: stubSource,
67
+ loader: "js"
68
+ }));
69
+ }
53
70
  if (treeShake) build.onLoad({ filter: SOURCE }, async (args) => {
54
71
  const isEntryFile = isEntry(args.path);
55
72
  const inModules = args.path.includes("/node_modules/");
@@ -1,4 +1,6 @@
1
+ import { CapturedHandler, CapturedValidator } from "../compile/aot.js";
1
2
  import { AnyElysia } from "../base.js";
3
+ import { JITProbeReason } from "../compile/jit-probe.js";
2
4
 
3
5
  //#region src/plugin/source.d.ts
4
6
  type AotTarget = 'bun' | 'node' | 'workerd';
@@ -34,5 +36,40 @@ interface CompileToSourceOptions {
34
36
  }
35
37
  declare const autoGroupSize: (routes: number) => number;
36
38
  declare function compileToSource(app: AnyElysia, options?: CompileToSourceOptions): Promise<string>;
39
+ interface CapturedArtifacts {
40
+ source: string;
41
+ validators: CapturedValidator[];
42
+ handlers: CapturedHandler[];
43
+ }
44
+ declare function captureArtifacts(app: AnyElysia, options?: CompileToSourceOptions): Promise<CapturedArtifacts>;
45
+ interface StubbabilityReport {
46
+ /** Handler JIT is provably unused → handler-only strip is safe. */
47
+ stubbable: boolean;
48
+ /** `sucrose` + the handler `new Function` codegen is unused. */
49
+ jit: boolean;
50
+ /** Why handler JIT cannot be stripped, if any. */
51
+ reasons: JITProbeReason[];
52
+ }
53
+ /**
54
+ * Decide whether the frozen build can run with handler JIT replaced by a
55
+ * throwing stub
56
+ *
57
+ * Static prediction is unsound
58
+ *
59
+ * eg. an inline-eligible handler (`() => 'ok'`) is never captured into
60
+ * handler manifest and falls through to `sucrose` at runtime
61
+ *
62
+ * So instead of guessing, this captures the manifest, registers it in-process
63
+ * and replays every route through the real `compileHandler` with a tripwire armed
64
+ *
65
+ * Handler JIT is only reported stubbable when no handler-JIT entry point was reached
66
+ */
67
+ declare function analyzeStubbability(app: AnyElysia, options?: CompileToSourceOptions): Promise<StubbabilityReport>;
68
+ /**
69
+ * Replay a frozen build from already-captured artifacts (no second capture) and
70
+ * report whether handler JIT was reached. Shared by `analyzeStubbability` and
71
+ * the single-capture build path.
72
+ */
73
+ declare function replayStubbability(app: AnyElysia, handlers: CapturedHandler[]): StubbabilityReport;
37
74
  //#endregion
38
- export { AotTarget, CompileToSourceOptions, autoGroupSize, compileToSource };
75
+ export { AotTarget, CapturedArtifacts, CompileToSourceOptions, StubbabilityReport, analyzeStubbability, autoGroupSize, captureArtifacts, compileToSource, replayStubbability };
@@ -2,22 +2,102 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_utils = require('../utils.js');
3
3
  const require_universal_env = require('../universal/env.js');
4
4
  const require_compile_aot = require('../compile/aot.js');
5
+ const require_validator_index = require('../validator/index.js');
6
+ const require_compile_jit_probe = require('../compile/jit-probe.js');
7
+ const require_compile_handler_jit = require('../compile/handler/jit.js');
5
8
  const require_compile_handler_index = require('../compile/handler/index.js');
6
9
 
7
10
  //#region src/plugin/source.ts
8
11
  const autoGroupSize = (routes) => routes < 64 ? 1 : routes < 256 ? 2 : routes < 2048 ? 4 : routes < 8192 ? 16 : 64;
9
12
  async function compileToSource(app, options) {
13
+ return (await captureArtifacts(app, options)).source;
14
+ }
15
+ async function captureArtifacts(app, options) {
16
+ const previousAotBuild = require_universal_env.env.ELYSIA_AOT_BUILD;
10
17
  require_universal_env.env.ELYSIA_AOT_BUILD = "1";
11
- if (!require_compile_aot.Capture.isCapturing()) throw new Error("[elysia-aot]: ELYSIA_AOT_BUILD=1 must be set to enable AOT capture mode");
12
- require_compile_aot.beginValidatorCapture();
13
- const modules = app.modules;
14
- if (modules) await modules;
15
- if (options?.target !== void 0) require_compile_handler_index.setCaptureHeaderShorthand(options.target === "bun");
16
18
  try {
19
+ if (!require_compile_aot.Capture.isCapturing()) throw new Error("[elysia-aot]: ELYSIA_AOT_BUILD=1 must be set to enable AOT capture mode");
20
+ require_compile_aot.beginValidatorCapture();
21
+ const modules = app.modules;
22
+ if (modules) await modules;
23
+ if (options?.target !== void 0) require_compile_handler_jit.setCaptureHeaderShorthand(options.target === "bun");
17
24
  app.compile();
18
- return emitModule(require_compile_aot.endValidatorCapture(), require_compile_aot.endHandlerCapture(), options);
25
+ const captured = require_compile_aot.endValidatorCapture();
26
+ const handlers = require_compile_aot.endHandlerCapture();
27
+ return {
28
+ source: emitModule(captured, handlers, options),
29
+ validators: captured,
30
+ handlers
31
+ };
19
32
  } finally {
20
- require_compile_handler_index.setCaptureHeaderShorthand(void 0);
33
+ require_compile_handler_jit.setCaptureHeaderShorthand(void 0);
34
+ if (previousAotBuild === void 0) delete require_universal_env.env.ELYSIA_AOT_BUILD;
35
+ else require_universal_env.env.ELYSIA_AOT_BUILD = previousAotBuild;
36
+ }
37
+ }
38
+ /**
39
+ * Decide whether the frozen build can run with handler JIT replaced by a
40
+ * throwing stub
41
+ *
42
+ * Static prediction is unsound
43
+ *
44
+ * eg. an inline-eligible handler (`() => 'ok'`) is never captured into
45
+ * handler manifest and falls through to `sucrose` at runtime
46
+ *
47
+ * So instead of guessing, this captures the manifest, registers it in-process
48
+ * and replays every route through the real `compileHandler` with a tripwire armed
49
+ *
50
+ * Handler JIT is only reported stubbable when no handler-JIT entry point was reached
51
+ */
52
+ async function analyzeStubbability(app, options) {
53
+ const { handlers } = await captureArtifacts(app, options);
54
+ return replayStubbability(app, handlers);
55
+ }
56
+ const materialiseHandlersForReplay = (captured) => {
57
+ const manifest = {};
58
+ for (const h of captured) (manifest[h.method] ??= {})[h.path] = {
59
+ a: h.alias ? h.alias.split(",") : [],
60
+ f: new Function(`return ${require_compile_aot.Source.handlerFactory(h.alias, h.code)}`)()
61
+ };
62
+ return manifest;
63
+ };
64
+ /**
65
+ * Replay a frozen build from already-captured artifacts (no second capture) and
66
+ * report whether handler JIT was reached. Shared by `analyzeStubbability` and
67
+ * the single-capture build path.
68
+ */
69
+ function replayStubbability(app, handlers) {
70
+ const previousCompiled = require_compile_aot.Compiled.snapshot();
71
+ const previousAotBuild = require_universal_env.env.ELYSIA_AOT_BUILD;
72
+ if (previousAotBuild !== void 0) delete require_universal_env.env.ELYSIA_AOT_BUILD;
73
+ try {
74
+ require_compile_aot.Compiled.clear();
75
+ require_compile_aot.Compiled.handlers = materialiseHandlersForReplay(handlers);
76
+ require_validator_index.Validator.clear();
77
+ const history = app.history ?? [];
78
+ require_compile_jit_probe.JITProbe.begin();
79
+ for (const route of history) try {
80
+ if (route[0] === "WS") {
81
+ require_compile_jit_probe.JITProbe.end();
82
+ return {
83
+ stubbable: false,
84
+ jit: false,
85
+ reasons: ["sucrose"]
86
+ };
87
+ } else require_compile_handler_index.compileHandler(route, app);
88
+ } catch {
89
+ require_compile_jit_probe.JITProbe.end();
90
+ return {
91
+ stubbable: false,
92
+ jit: false,
93
+ reasons: ["handler:new-function"]
94
+ };
95
+ }
96
+ return require_compile_jit_probe.JITProbe.end();
97
+ } finally {
98
+ require_compile_aot.Compiled.restore(previousCompiled);
99
+ require_validator_index.Validator.clear();
100
+ if (previousAotBuild !== void 0) require_universal_env.env.ELYSIA_AOT_BUILD = previousAotBuild;
21
101
  }
22
102
  }
23
103
  function emitModule(captured, handlers, options) {
@@ -81,7 +161,10 @@ function emitModule(captured, handlers, options) {
81
161
  if (c.precomputeSafe) {
82
162
  parts.push("ps: 1");
83
163
  if (c.precomputedDefault !== void 0) parts.push(`pd: ${JSON.stringify(c.precomputedDefault)}`);
164
+ if (c.precomputeNull) parts.push("pn: 1");
84
165
  if (c.precomputedObjectDefault !== void 0) parts.push(`pod: ${JSON.stringify(c.precomputedObjectDefault)}`);
166
+ if (c.defaultCloner) parts.push(`dc: ${c.defaultCloner}`);
167
+ if (c.objectDefaultMerger) parts.push(`pm: ${c.objectDefaultMerger}`);
85
168
  }
86
169
  if (c.customErrors?.length) {
87
170
  const ce = c.customErrors.map((e) => `{ p: ${JSON.stringify(e.path)}, c: ${require_compile_aot.Source.checkFactory(e.identifier, require_compile_aot.Source.checkCode(e.checkDefs, e.checkValue))}${e.external ? ", e: 1" : ""} }`).join(", ");
@@ -99,6 +182,7 @@ function emitModule(captured, handlers, options) {
99
182
  }).join(", ");
100
183
  parts.push(`ic: [${ic}]`);
101
184
  }
185
+ if (c.coercePlan) parts.push(`cp: ${JSON.stringify(c.coercePlan)}`);
102
186
  return parts;
103
187
  };
104
188
  const treeToSource = (tree, slotRef, sink) => {
@@ -248,16 +332,23 @@ function emitModule(captured, handlers, options) {
248
332
  handlerExport += "}\n";
249
333
  if (options?.register) handlerExport += "Compiled.handlers = handlers\n";
250
334
  let body = "// Generated by Elysia build plugin. Do not edit.\n";
251
- if (options?.register) body += `import { Compiled } from ${JSON.stringify(options.registerFrom ?? "elysia")}\nimport { CheckContext } from 'typebox/schema'
252
- import { Guard } from 'typebox/guard'
253
- import { Format } from 'typebox/format'
254
- import { Hashing } from 'typebox/system'
255
- `;
335
+ if (options?.register) {
336
+ body += `import { Compiled } from ${JSON.stringify(options.registerFrom ?? "elysia")}\n`;
337
+ const generated = branchDecls + unionDecls + validatorDecls + handlerDecls;
338
+ const needs = (symbol) => new RegExp(`\\b${symbol}\\b`).test(generated);
339
+ if (needs("CheckContext")) body += "import { CheckContext } from 'typebox/schema'\n";
340
+ if (needs("Guard")) body += "import { Guard } from 'typebox/guard'\n";
341
+ if (needs("Format")) body += "import { Format } from 'typebox/format'\n";
342
+ if (needs("Hashing")) body += "import { Hashing } from 'typebox/system'\n";
343
+ }
256
344
  body += "\n" + branchDecls + (branchDecls && "\n") + unionDecls + (unionDecls && "\n") + validatorDecls + handlerDecls + "\n" + validatorExport + "\n" + handlerExport;
257
345
  if (!lazy) body += "\nexport default validators\n";
258
346
  return body;
259
347
  }
260
348
 
261
349
  //#endregion
350
+ exports.analyzeStubbability = analyzeStubbability;
262
351
  exports.autoGroupSize = autoGroupSize;
263
- exports.compileToSource = compileToSource;
352
+ exports.captureArtifacts = captureArtifacts;
353
+ exports.compileToSource = compileToSource;
354
+ exports.replayStubbability = replayStubbability;
@@ -1,22 +1,102 @@
1
1
  import { nullObject } from "../utils.mjs";
2
2
  import { env } from "../universal/env.mjs";
3
- import { Capture, Source, beginValidatorCapture, endHandlerCapture, endValidatorCapture } from "../compile/aot.mjs";
4
- import { setCaptureHeaderShorthand } from "../compile/handler/index.mjs";
3
+ import { Capture, Compiled, Source, beginValidatorCapture, endHandlerCapture, endValidatorCapture } from "../compile/aot.mjs";
4
+ import { Validator } from "../validator/index.mjs";
5
+ import { JITProbe } from "../compile/jit-probe.mjs";
6
+ import { setCaptureHeaderShorthand } from "../compile/handler/jit.mjs";
7
+ import { compileHandler } from "../compile/handler/index.mjs";
5
8
 
6
9
  //#region src/plugin/source.ts
7
10
  const autoGroupSize = (routes) => routes < 64 ? 1 : routes < 256 ? 2 : routes < 2048 ? 4 : routes < 8192 ? 16 : 64;
8
11
  async function compileToSource(app, options) {
12
+ return (await captureArtifacts(app, options)).source;
13
+ }
14
+ async function captureArtifacts(app, options) {
15
+ const previousAotBuild = env.ELYSIA_AOT_BUILD;
9
16
  env.ELYSIA_AOT_BUILD = "1";
10
- if (!Capture.isCapturing()) throw new Error("[elysia-aot]: ELYSIA_AOT_BUILD=1 must be set to enable AOT capture mode");
11
- beginValidatorCapture();
12
- const modules = app.modules;
13
- if (modules) await modules;
14
- if (options?.target !== void 0) setCaptureHeaderShorthand(options.target === "bun");
15
17
  try {
18
+ if (!Capture.isCapturing()) throw new Error("[elysia-aot]: ELYSIA_AOT_BUILD=1 must be set to enable AOT capture mode");
19
+ beginValidatorCapture();
20
+ const modules = app.modules;
21
+ if (modules) await modules;
22
+ if (options?.target !== void 0) setCaptureHeaderShorthand(options.target === "bun");
16
23
  app.compile();
17
- return emitModule(endValidatorCapture(), endHandlerCapture(), options);
24
+ const captured = endValidatorCapture();
25
+ const handlers = endHandlerCapture();
26
+ return {
27
+ source: emitModule(captured, handlers, options),
28
+ validators: captured,
29
+ handlers
30
+ };
18
31
  } finally {
19
32
  setCaptureHeaderShorthand(void 0);
33
+ if (previousAotBuild === void 0) delete env.ELYSIA_AOT_BUILD;
34
+ else env.ELYSIA_AOT_BUILD = previousAotBuild;
35
+ }
36
+ }
37
+ /**
38
+ * Decide whether the frozen build can run with handler JIT replaced by a
39
+ * throwing stub
40
+ *
41
+ * Static prediction is unsound
42
+ *
43
+ * eg. an inline-eligible handler (`() => 'ok'`) is never captured into
44
+ * handler manifest and falls through to `sucrose` at runtime
45
+ *
46
+ * So instead of guessing, this captures the manifest, registers it in-process
47
+ * and replays every route through the real `compileHandler` with a tripwire armed
48
+ *
49
+ * Handler JIT is only reported stubbable when no handler-JIT entry point was reached
50
+ */
51
+ async function analyzeStubbability(app, options) {
52
+ const { handlers } = await captureArtifacts(app, options);
53
+ return replayStubbability(app, handlers);
54
+ }
55
+ const materialiseHandlersForReplay = (captured) => {
56
+ const manifest = {};
57
+ for (const h of captured) (manifest[h.method] ??= {})[h.path] = {
58
+ a: h.alias ? h.alias.split(",") : [],
59
+ f: new Function(`return ${Source.handlerFactory(h.alias, h.code)}`)()
60
+ };
61
+ return manifest;
62
+ };
63
+ /**
64
+ * Replay a frozen build from already-captured artifacts (no second capture) and
65
+ * report whether handler JIT was reached. Shared by `analyzeStubbability` and
66
+ * the single-capture build path.
67
+ */
68
+ function replayStubbability(app, handlers) {
69
+ const previousCompiled = Compiled.snapshot();
70
+ const previousAotBuild = env.ELYSIA_AOT_BUILD;
71
+ if (previousAotBuild !== void 0) delete env.ELYSIA_AOT_BUILD;
72
+ try {
73
+ Compiled.clear();
74
+ Compiled.handlers = materialiseHandlersForReplay(handlers);
75
+ Validator.clear();
76
+ const history = app.history ?? [];
77
+ JITProbe.begin();
78
+ for (const route of history) try {
79
+ if (route[0] === "WS") {
80
+ JITProbe.end();
81
+ return {
82
+ stubbable: false,
83
+ jit: false,
84
+ reasons: ["sucrose"]
85
+ };
86
+ } else compileHandler(route, app);
87
+ } catch {
88
+ JITProbe.end();
89
+ return {
90
+ stubbable: false,
91
+ jit: false,
92
+ reasons: ["handler:new-function"]
93
+ };
94
+ }
95
+ return JITProbe.end();
96
+ } finally {
97
+ Compiled.restore(previousCompiled);
98
+ Validator.clear();
99
+ if (previousAotBuild !== void 0) env.ELYSIA_AOT_BUILD = previousAotBuild;
20
100
  }
21
101
  }
22
102
  function emitModule(captured, handlers, options) {
@@ -80,7 +160,10 @@ function emitModule(captured, handlers, options) {
80
160
  if (c.precomputeSafe) {
81
161
  parts.push("ps: 1");
82
162
  if (c.precomputedDefault !== void 0) parts.push(`pd: ${JSON.stringify(c.precomputedDefault)}`);
163
+ if (c.precomputeNull) parts.push("pn: 1");
83
164
  if (c.precomputedObjectDefault !== void 0) parts.push(`pod: ${JSON.stringify(c.precomputedObjectDefault)}`);
165
+ if (c.defaultCloner) parts.push(`dc: ${c.defaultCloner}`);
166
+ if (c.objectDefaultMerger) parts.push(`pm: ${c.objectDefaultMerger}`);
84
167
  }
85
168
  if (c.customErrors?.length) {
86
169
  const ce = c.customErrors.map((e) => `{ p: ${JSON.stringify(e.path)}, c: ${Source.checkFactory(e.identifier, Source.checkCode(e.checkDefs, e.checkValue))}${e.external ? ", e: 1" : ""} }`).join(", ");
@@ -98,6 +181,7 @@ function emitModule(captured, handlers, options) {
98
181
  }).join(", ");
99
182
  parts.push(`ic: [${ic}]`);
100
183
  }
184
+ if (c.coercePlan) parts.push(`cp: ${JSON.stringify(c.coercePlan)}`);
101
185
  return parts;
102
186
  };
103
187
  const treeToSource = (tree, slotRef, sink) => {
@@ -247,15 +331,19 @@ function emitModule(captured, handlers, options) {
247
331
  handlerExport += "}\n";
248
332
  if (options?.register) handlerExport += "Compiled.handlers = handlers\n";
249
333
  let body = "// Generated by Elysia build plugin. Do not edit.\n";
250
- if (options?.register) body += `import { Compiled } from ${JSON.stringify(options.registerFrom ?? "elysia")}\nimport { CheckContext } from 'typebox/schema'
251
- import { Guard } from 'typebox/guard'
252
- import { Format } from 'typebox/format'
253
- import { Hashing } from 'typebox/system'
254
- `;
334
+ if (options?.register) {
335
+ body += `import { Compiled } from ${JSON.stringify(options.registerFrom ?? "elysia")}\n`;
336
+ const generated = branchDecls + unionDecls + validatorDecls + handlerDecls;
337
+ const needs = (symbol) => new RegExp(`\\b${symbol}\\b`).test(generated);
338
+ if (needs("CheckContext")) body += "import { CheckContext } from 'typebox/schema'\n";
339
+ if (needs("Guard")) body += "import { Guard } from 'typebox/guard'\n";
340
+ if (needs("Format")) body += "import { Format } from 'typebox/format'\n";
341
+ if (needs("Hashing")) body += "import { Hashing } from 'typebox/system'\n";
342
+ }
255
343
  body += "\n" + branchDecls + (branchDecls && "\n") + unionDecls + (unionDecls && "\n") + validatorDecls + handlerDecls + "\n" + validatorExport + "\n" + handlerExport;
256
344
  if (!lazy) body += "\nexport default validators\n";
257
345
  return body;
258
346
  }
259
347
 
260
348
  //#endregion
261
- export { autoGroupSize, compileToSource };
349
+ export { analyzeStubbability, autoGroupSize, captureArtifacts, compileToSource, replayStubbability };
@@ -23,6 +23,13 @@ interface ElysiaAotVitePlugin {
23
23
  * plugins: [aot('src/index.ts')]
24
24
  * })
25
25
  * ```
26
+ *
27
+ * The plugin imports your entry to capture the compiled app, running its
28
+ * top-level code. `.listen()` is auto-skipped during build (gated on
29
+ * `ELYSIA_AOT_BUILD`), but any other import-time handle — a DB pool,
30
+ * `setInterval`, a queue consumer — can keep the build process alive after
31
+ * the bundle is written. Gate the side effect with
32
+ * `if (!process.env.ELYSIA_AOT_BUILD)`.
26
33
  */
27
34
  declare const aot: (entry: string, options?: ElysiaAotOptions) => ElysiaAotVitePlugin;
28
35
  //#endregion
@@ -18,17 +18,34 @@ const VIRTUAL = "\0elysia/compiled";
18
18
  * plugins: [aot('src/index.ts')]
19
19
  * })
20
20
  * ```
21
+ *
22
+ * The plugin imports your entry to capture the compiled app, running its
23
+ * top-level code. `.listen()` is auto-skipped during build (gated on
24
+ * `ELYSIA_AOT_BUILD`), but any other import-time handle — a DB pool,
25
+ * `setInterval`, a queue consumer — can keep the build process alive after
26
+ * the bundle is written. Gate the side effect with
27
+ * `if (!process.env.ELYSIA_AOT_BUILD)`.
21
28
  */
22
29
  const aot = (entry, options) => {
23
30
  const entryPath = require_plugin_core.resolveEntry(entry);
24
31
  const treeShake = options?.treeShake ?? true;
25
32
  let source = "";
33
+ let stub = {
34
+ jit: false,
35
+ ws: false,
36
+ reconstruct: false,
37
+ cookie: false,
38
+ trace: false,
39
+ sucrose: false
40
+ };
26
41
  return {
27
42
  name: "elysia-aot",
28
43
  enforce: "pre",
29
44
  apply: "build",
30
45
  async buildStart() {
31
- source = await require_plugin_core.generateCompiledModule(entry, options);
46
+ const generated = await require_plugin_core.generateCompiledArtifacts(entry, options);
47
+ source = generated.source;
48
+ stub = generated.stub;
32
49
  },
33
50
  resolveId(id) {
34
51
  if (id === "elysia/compiled") return VIRTUAL;
@@ -37,6 +54,10 @@ const aot = (entry, options) => {
37
54
  if (id === VIRTUAL) return source;
38
55
  },
39
56
  transform(code, id) {
57
+ for (const key of Object.keys(require_plugin_core.STUB_SOURCES)) {
58
+ if (!stub[key]) continue;
59
+ for (const { filter, source: stubSource } of require_plugin_core.STUB_SOURCES[key]) if (filter.test(id)) return stubSource;
60
+ }
40
61
  let out = code;
41
62
  if (treeShake && SOURCE.test(id) && !id.includes("node_modules")) out = require_plugin_treeshake.rewriteTypeImport(out);
42
63
  if (id === entryPath) out = `import 'elysia/compiled'\n${out}`;