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.
- package/dist/adapter/bun/index.d.ts +1 -1
- package/dist/adapter/bun/index.js +320 -56
- package/dist/adapter/bun/index.mjs +321 -57
- package/dist/adapter/utils.js +51 -30
- package/dist/adapter/utils.mjs +52 -31
- package/dist/adapter/web-standard/handler.js +5 -2
- package/dist/adapter/web-standard/handler.mjs +5 -2
- package/dist/base.d.ts +18 -7
- package/dist/base.js +213 -101
- package/dist/base.mjs +215 -103
- package/dist/compile/aot-capture.js +1 -1
- package/dist/compile/aot-capture.mjs +1 -1
- package/dist/compile/aot.d.ts +5 -4
- package/dist/compile/aot.js +17 -9
- package/dist/compile/aot.mjs +18 -9
- package/dist/compile/handler/descriptor.js +1 -1
- package/dist/compile/handler/descriptor.mjs +1 -1
- package/dist/compile/handler/frozen-validator.d.ts +4 -1
- package/dist/compile/handler/frozen-validator.js +8 -3
- package/dist/compile/handler/frozen-validator.mjs +5 -3
- package/dist/compile/handler/index.d.ts +2 -2
- package/dist/compile/handler/index.js +8 -6
- package/dist/compile/handler/index.mjs +8 -6
- package/dist/compile/handler/reconstruct.d.ts +1 -1
- package/dist/compile/handler/reconstruct.js +4 -3
- package/dist/compile/handler/reconstruct.mjs +4 -3
- package/dist/compile/handler/utils.js +2 -2
- package/dist/compile/handler/utils.mjs +3 -3
- package/dist/compile/jit-probe.d.ts +2 -2
- package/dist/context.d.ts +1 -1
- package/dist/context.js +8 -5
- package/dist/context.mjs +8 -5
- package/dist/cookie/config.d.ts +1 -0
- package/dist/cookie/config.js +3 -2
- package/dist/cookie/config.mjs +3 -2
- package/dist/cookie/cookie.d.ts +1 -0
- package/dist/cookie/cookie.js +6 -5
- package/dist/cookie/cookie.mjs +6 -5
- package/dist/cookie/crypto.d.ts +8 -7
- package/dist/cookie/crypto.js +38 -31
- package/dist/cookie/crypto.mjs +38 -32
- package/dist/cookie/error.js +1 -2
- package/dist/cookie/error.mjs +1 -2
- package/dist/cookie/lib.js +5 -1
- package/dist/cookie/lib.mjs +5 -1
- package/dist/cookie/serialize.js +1 -1
- package/dist/cookie/serialize.mjs +1 -1
- package/dist/cookie/types.d.ts +10 -0
- package/dist/cookie/utils.js +28 -14
- package/dist/cookie/utils.mjs +29 -15
- package/dist/handler/error.d.ts +23 -12
- package/dist/handler/error.js +24 -21
- package/dist/handler/error.mjs +20 -21
- package/dist/handler/fetch.js +6 -7
- package/dist/handler/fetch.mjs +7 -8
- package/dist/handler/utils.js +4 -2
- package/dist/handler/utils.mjs +5 -3
- package/dist/index.js +1 -8
- package/dist/package.js +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugin/aot/core.d.ts +33 -6
- package/dist/plugin/aot/core.js +120 -40
- package/dist/plugin/aot/core.mjs +121 -43
- package/dist/plugin/aot/esbuild.js +8 -2
- package/dist/plugin/aot/esbuild.mjs +9 -3
- package/dist/plugin/aot/hooks.d.ts +2 -1
- package/dist/plugin/aot/hooks.js +3 -3
- package/dist/plugin/aot/hooks.mjs +4 -4
- package/dist/plugin/aot/source.d.ts +4 -1
- package/dist/plugin/aot/source.js +21 -14
- package/dist/plugin/aot/source.mjs +21 -14
- package/dist/plugin/aot/worker.js +2 -2
- package/dist/plugin/aot/worker.mjs +2 -2
- package/dist/route-table.d.ts +3 -0
- package/dist/route-table.js +16 -11
- package/dist/route-table.mjs +16 -11
- package/dist/schema-snapshot.js +17 -22
- package/dist/schema-snapshot.mjs +17 -22
- package/dist/type/bridge-live.d.ts +1 -1
- package/dist/type/bridge-live.js +2 -2
- package/dist/type/bridge-live.mjs +2 -2
- package/dist/type/bridge.d.ts +1 -1
- package/dist/type/coerce-plan.js +3 -4
- package/dist/type/coerce-plan.mjs +1 -1
- package/dist/type/coerce.d.ts +1 -1
- package/dist/type/compat.d.ts +3 -0
- package/dist/type/compat.js +32 -14
- package/dist/type/compat.mjs +27 -8
- package/dist/type/constants.d.ts +1 -1
- package/dist/type/elysia/array-buffer.js +2 -3
- package/dist/type/elysia/array-buffer.mjs +1 -1
- package/dist/type/elysia/array-string.js +7 -4
- package/dist/type/elysia/array-string.mjs +9 -5
- package/dist/type/elysia/boolean-string.js +2 -3
- package/dist/type/elysia/boolean-string.mjs +1 -1
- package/dist/type/elysia/boolean.js +4 -3
- package/dist/type/elysia/boolean.mjs +4 -2
- package/dist/type/elysia/cookie.js +2 -1
- package/dist/type/elysia/cookie.mjs +2 -1
- package/dist/type/elysia/date.js +7 -7
- package/dist/type/elysia/date.mjs +3 -2
- package/dist/type/elysia/file-type.d.ts +9 -2
- package/dist/type/elysia/file-type.js +16 -1
- package/dist/type/elysia/file-type.mjs +16 -2
- package/dist/type/elysia/file.js +8 -4
- package/dist/type/elysia/file.mjs +8 -3
- package/dist/type/elysia/files.js +6 -4
- package/dist/type/elysia/files.mjs +5 -2
- package/dist/type/elysia/form.js +2 -3
- package/dist/type/elysia/form.mjs +3 -3
- package/dist/type/elysia/integer-string.js +4 -5
- package/dist/type/elysia/integer-string.mjs +3 -3
- package/dist/type/elysia/integer.js +4 -3
- package/dist/type/elysia/integer.mjs +4 -2
- package/dist/type/elysia/maybe-empty.js +3 -4
- package/dist/type/elysia/maybe-empty.mjs +1 -1
- package/dist/type/elysia/nullable.js +2 -3
- package/dist/type/elysia/nullable.mjs +1 -1
- package/dist/type/elysia/number.js +4 -3
- package/dist/type/elysia/number.mjs +4 -2
- package/dist/type/elysia/numeric-enum.js +2 -3
- package/dist/type/elysia/numeric-enum.mjs +1 -1
- package/dist/type/elysia/numeric.js +5 -5
- package/dist/type/elysia/numeric.mjs +3 -2
- package/dist/type/elysia/object-string.js +7 -4
- package/dist/type/elysia/object-string.mjs +9 -5
- package/dist/type/elysia/string.js +4 -3
- package/dist/type/elysia/string.mjs +4 -2
- package/dist/type/elysia/uint8-array.js +2 -3
- package/dist/type/elysia/uint8-array.mjs +1 -1
- package/dist/type/elysia/union-enum.js +2 -3
- package/dist/type/elysia/union-enum.mjs +1 -1
- package/dist/type/elysia/utils.js +2 -3
- package/dist/type/elysia/utils.mjs +1 -1
- package/dist/type/exports.js +10 -1
- package/dist/type/exports.mjs +7 -0
- package/dist/type/index.d.ts +9 -1
- package/dist/type/index.js +41 -13
- package/dist/type/index.mjs +44 -9
- package/dist/type/typebox-type-live.d.ts +9 -0
- package/dist/type/typebox-type-live.js +31 -0
- package/dist/type/typebox-type-live.mjs +16 -0
- package/dist/type/typebox-type.d.ts +24 -0
- package/dist/type/typebox-type.js +130 -0
- package/dist/type/typebox-type.mjs +66 -0
- package/dist/type/typebox-value-live.d.ts +9 -0
- package/dist/type/typebox-value-live.js +27 -0
- package/dist/type/typebox-value-live.mjs +11 -0
- package/dist/type/typebox-value.d.ts +30 -0
- package/dist/type/typebox-value.js +147 -0
- package/dist/type/typebox-value.mjs +62 -0
- package/dist/type/validator/custom-error.js +4 -5
- package/dist/type/validator/custom-error.mjs +3 -3
- package/dist/type/validator/default-precompute.js +4 -5
- package/dist/type/validator/default-precompute.mjs +1 -1
- package/dist/type/validator/index.d.ts +1 -1
- package/dist/type/validator/index.js +51 -42
- package/dist/type/validator/index.mjs +33 -23
- package/dist/type/validator/validator-cache.d.ts +1 -1
- package/dist/type/validator/validator-cache.js +27 -14
- package/dist/type/validator/validator-cache.mjs +27 -13
- package/dist/universal/constants.d.ts +2 -1
- package/dist/universal/constants.js +3 -1
- package/dist/universal/constants.mjs +2 -1
- package/dist/utils.d.ts +19 -1
- package/dist/utils.js +25 -3
- package/dist/utils.mjs +19 -3
- package/dist/validator/index.js +2 -1
- package/dist/validator/index.mjs +2 -1
- package/dist/ws/context.d.ts +8 -1
- package/dist/ws/context.js +65 -29
- package/dist/ws/context.mjs +64 -29
- package/dist/ws/route.js +176 -28
- package/dist/ws/route.mjs +179 -31
- package/package.json +5 -3
package/dist/plugin/aot/core.js
CHANGED
|
@@ -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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
454
|
-
const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal &&
|
|
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 (
|
|
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;
|
package/dist/plugin/aot/core.mjs
CHANGED
|
@@ -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
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
if (
|
|
378
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
452
|
-
const { plan: stub, mode } = planFromReport(strip, report, hasWS, mayTrace, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal &&
|
|
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 (
|
|
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
|
|
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)(
|
|
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
|
|
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(
|
|
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>;
|