elysia 2.0.0-exp.29 → 2.0.0-exp.30
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/base.d.ts +8 -0
- package/dist/base.js +9 -1
- package/dist/base.mjs +9 -1
- package/dist/compile/aot.d.ts +4 -1
- package/dist/compile/aot.js +12 -2
- package/dist/compile/aot.mjs +12 -2
- package/dist/compile/handler/frozen-validator.d.ts +2 -4
- package/dist/compile/handler/frozen-validator.js +10 -10
- package/dist/compile/handler/frozen-validator.mjs +10 -10
- package/dist/error.d.ts +1 -1
- package/dist/plugin/core.d.ts +7 -47
- package/dist/plugin/core.js +10 -61
- package/dist/plugin/core.mjs +10 -61
- package/dist/plugin/source.d.ts +8 -1
- package/dist/plugin/source.js +7 -1
- package/dist/plugin/source.mjs +7 -1
- package/dist/type/bridge.d.ts +6 -6
- package/dist/type/coerce-plan.d.ts +6 -2
- package/dist/type/coerce-plan.js +21 -10
- package/dist/type/coerce-plan.mjs +20 -10
- package/dist/type/coerce.js +32 -4
- package/dist/type/coerce.mjs +33 -5
- package/dist/type/elysia/array-string.js +1 -1
- package/dist/type/elysia/array-string.mjs +1 -1
- package/dist/type/elysia/files.js +1 -1
- package/dist/type/elysia/files.mjs +1 -1
- package/dist/type/elysia/integer-string.js +1 -1
- package/dist/type/elysia/integer-string.mjs +1 -1
- package/dist/type/elysia/numeric-enum.js +1 -1
- package/dist/type/elysia/numeric-enum.mjs +1 -1
- package/dist/type/elysia/numeric.js +1 -1
- package/dist/type/elysia/numeric.mjs +1 -1
- package/dist/type/exports.js +5 -5
- package/dist/type/exports.mjs +5 -5
- package/dist/type/index.js +5 -5
- package/dist/type/index.mjs +5 -5
- package/dist/type/validator/index.js +1 -1
- package/dist/type/validator/index.mjs +1 -1
- package/dist/types.d.ts +0 -10
- package/dist/utils.js +7 -0
- package/dist/utils.mjs +7 -0
- package/dist/ws/route.js +16 -7
- package/dist/ws/route.mjs +16 -7
- package/package.json +6 -1
package/dist/base.d.ts
CHANGED
|
@@ -56,6 +56,14 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
|
|
|
56
56
|
'~scopeChild'?: boolean;
|
|
57
57
|
'~scopeChildren'?: AnyElysia[];
|
|
58
58
|
constructor(config?: ElysiaConfig<BasePath, Scope>);
|
|
59
|
+
/**
|
|
60
|
+
* @deprecated use `app.history` instead
|
|
61
|
+
*
|
|
62
|
+
* Elysia changes it store route internally
|
|
63
|
+
* This API is provided as compatibility
|
|
64
|
+
*
|
|
65
|
+
* Will remove in 2.1
|
|
66
|
+
*/
|
|
59
67
|
get routes(): PublicRoute[];
|
|
60
68
|
/**
|
|
61
69
|
* ### decorate
|
package/dist/base.js
CHANGED
|
@@ -55,6 +55,14 @@ var Elysia = class Elysia {
|
|
|
55
55
|
if (this["~Prefix"] && !this["~Prefix"].startsWith("/")) this["~Prefix"] = `/${this["~Prefix"]}`;
|
|
56
56
|
if (config?.name) this.#hash = require_utils.fnv1a(config.seed ? `${config.name}_${typeof config.seed === "object" ? JSON.stringify(config.seed) : config.seed}` : config.name);
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated use `app.history` instead
|
|
60
|
+
*
|
|
61
|
+
* Elysia changes it store route internally
|
|
62
|
+
* This API is provided as compatibility
|
|
63
|
+
*
|
|
64
|
+
* Will remove in 2.1
|
|
65
|
+
*/
|
|
58
66
|
get routes() {
|
|
59
67
|
if (!this.#history) return [];
|
|
60
68
|
if (this.#cachedRoutes) return this.#cachedRoutes;
|
|
@@ -1106,7 +1114,7 @@ var Elysia = class Elysia {
|
|
|
1106
1114
|
if (seen.has(key)) {
|
|
1107
1115
|
hasDuplicate = true;
|
|
1108
1116
|
if (!require_error.isProduction()) {
|
|
1109
|
-
if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key}
|
|
1117
|
+
if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} at`, (/* @__PURE__ */ new Error()).stack);
|
|
1110
1118
|
}
|
|
1111
1119
|
}
|
|
1112
1120
|
seen.set(key, i);
|
package/dist/base.mjs
CHANGED
|
@@ -52,6 +52,14 @@ var Elysia = class Elysia {
|
|
|
52
52
|
if (this["~Prefix"] && !this["~Prefix"].startsWith("/")) this["~Prefix"] = `/${this["~Prefix"]}`;
|
|
53
53
|
if (config?.name) this.#hash = fnv1a(config.seed ? `${config.name}_${typeof config.seed === "object" ? JSON.stringify(config.seed) : config.seed}` : config.name);
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* @deprecated use `app.history` instead
|
|
57
|
+
*
|
|
58
|
+
* Elysia changes it store route internally
|
|
59
|
+
* This API is provided as compatibility
|
|
60
|
+
*
|
|
61
|
+
* Will remove in 2.1
|
|
62
|
+
*/
|
|
55
63
|
get routes() {
|
|
56
64
|
if (!this.#history) return [];
|
|
57
65
|
if (this.#cachedRoutes) return this.#cachedRoutes;
|
|
@@ -1103,7 +1111,7 @@ var Elysia = class Elysia {
|
|
|
1103
1111
|
if (seen.has(key)) {
|
|
1104
1112
|
hasDuplicate = true;
|
|
1105
1113
|
if (!isProduction()) {
|
|
1106
|
-
if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key}
|
|
1114
|
+
if (this.#history[seen.get(key)][2] !== route[2]) console.warn(`[Elysia] Duplicate route ${key} at`, (/* @__PURE__ */ new Error()).stack);
|
|
1107
1115
|
}
|
|
1108
1116
|
}
|
|
1109
1117
|
seen.set(key, i);
|
package/dist/compile/aot.d.ts
CHANGED
|
@@ -71,11 +71,14 @@ interface CompiledSnapshot {
|
|
|
71
71
|
lazyGroups: Array<() => ValidatorManifest> | undefined;
|
|
72
72
|
lazyGroupOf: Record<string, Record<string, number>> | undefined;
|
|
73
73
|
builtGroups: number[];
|
|
74
|
+
planRebuilder: ((original: unknown, plan: CoercePlan) => any) | undefined;
|
|
74
75
|
}
|
|
75
76
|
declare abstract class Compiled {
|
|
76
77
|
static get handlers(): HandlerManifest | undefined;
|
|
77
78
|
static set handlers(manifest: HandlerManifest);
|
|
78
79
|
static get validators(): ValidatorManifest | undefined;
|
|
80
|
+
static get planRebuilder(): ((original: unknown, plan: CoercePlan) => any) | undefined;
|
|
81
|
+
static set planRebuilder(rebuild: ((original: unknown, plan: CoercePlan) => any) | undefined);
|
|
79
82
|
static set validators(manifest: ValidatorManifest);
|
|
80
83
|
static registerLazyValidators(groups: Array<() => ValidatorManifest>, groupOf: Record<string, Record<string, number>>): void;
|
|
81
84
|
static getValidator(method: string, path: string, slot: ValidatorSlot): FrozenValidator | undefined;
|
|
@@ -226,7 +229,7 @@ declare const Capture: {
|
|
|
226
229
|
};
|
|
227
230
|
/**
|
|
228
231
|
* Reset module-level capture lifecycle state.
|
|
229
|
-
* FOR TESTS ONLY
|
|
232
|
+
* FOR TESTS ONLY
|
|
230
233
|
*/
|
|
231
234
|
declare const resetCaptureLifecycleForTests: () => void;
|
|
232
235
|
//#endregion
|
package/dist/compile/aot.js
CHANGED
|
@@ -6,6 +6,7 @@ const require_universal_env = require('../universal/env.js');
|
|
|
6
6
|
//#region src/compile/aot.ts
|
|
7
7
|
let validators;
|
|
8
8
|
let handlers;
|
|
9
|
+
let planRebuilder;
|
|
9
10
|
let lazyGroups;
|
|
10
11
|
let lazyGroupOf;
|
|
11
12
|
const builtGroups = /* @__PURE__ */ new Set();
|
|
@@ -19,6 +20,12 @@ var Compiled = class {
|
|
|
19
20
|
static get validators() {
|
|
20
21
|
return validators;
|
|
21
22
|
}
|
|
23
|
+
static get planRebuilder() {
|
|
24
|
+
return planRebuilder;
|
|
25
|
+
}
|
|
26
|
+
static set planRebuilder(rebuild) {
|
|
27
|
+
planRebuilder = rebuild;
|
|
28
|
+
}
|
|
22
29
|
static set validators(manifest) {
|
|
23
30
|
validators = manifest;
|
|
24
31
|
lazyGroups = void 0;
|
|
@@ -56,7 +63,8 @@ var Compiled = class {
|
|
|
56
63
|
handlers,
|
|
57
64
|
lazyGroups,
|
|
58
65
|
lazyGroupOf,
|
|
59
|
-
builtGroups: [...builtGroups]
|
|
66
|
+
builtGroups: [...builtGroups],
|
|
67
|
+
planRebuilder
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
70
|
/** @internal restore registry after in-process AOT analysis */
|
|
@@ -67,6 +75,7 @@ var Compiled = class {
|
|
|
67
75
|
lazyGroupOf = snapshot.lazyGroupOf;
|
|
68
76
|
builtGroups.clear();
|
|
69
77
|
for (const group of snapshot.builtGroups) builtGroups.add(group);
|
|
78
|
+
planRebuilder = snapshot.planRebuilder;
|
|
70
79
|
}
|
|
71
80
|
/** @internal test isolation */
|
|
72
81
|
static clear() {
|
|
@@ -75,6 +84,7 @@ var Compiled = class {
|
|
|
75
84
|
lazyGroups = void 0;
|
|
76
85
|
lazyGroupOf = void 0;
|
|
77
86
|
builtGroups.clear();
|
|
87
|
+
planRebuilder = void 0;
|
|
78
88
|
}
|
|
79
89
|
};
|
|
80
90
|
function reconstructCheck(build) {
|
|
@@ -336,7 +346,7 @@ const Capture = {
|
|
|
336
346
|
};
|
|
337
347
|
/**
|
|
338
348
|
* Reset module-level capture lifecycle state.
|
|
339
|
-
* FOR TESTS ONLY
|
|
349
|
+
* FOR TESTS ONLY
|
|
340
350
|
*/
|
|
341
351
|
const resetCaptureLifecycleForTests = () => void 0;
|
|
342
352
|
|
package/dist/compile/aot.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { env } from "../universal/env.mjs";
|
|
|
5
5
|
//#region src/compile/aot.ts
|
|
6
6
|
let validators;
|
|
7
7
|
let handlers;
|
|
8
|
+
let planRebuilder;
|
|
8
9
|
let lazyGroups;
|
|
9
10
|
let lazyGroupOf;
|
|
10
11
|
const builtGroups = /* @__PURE__ */ new Set();
|
|
@@ -18,6 +19,12 @@ var Compiled = class {
|
|
|
18
19
|
static get validators() {
|
|
19
20
|
return validators;
|
|
20
21
|
}
|
|
22
|
+
static get planRebuilder() {
|
|
23
|
+
return planRebuilder;
|
|
24
|
+
}
|
|
25
|
+
static set planRebuilder(rebuild) {
|
|
26
|
+
planRebuilder = rebuild;
|
|
27
|
+
}
|
|
21
28
|
static set validators(manifest) {
|
|
22
29
|
validators = manifest;
|
|
23
30
|
lazyGroups = void 0;
|
|
@@ -55,7 +62,8 @@ var Compiled = class {
|
|
|
55
62
|
handlers,
|
|
56
63
|
lazyGroups,
|
|
57
64
|
lazyGroupOf,
|
|
58
|
-
builtGroups: [...builtGroups]
|
|
65
|
+
builtGroups: [...builtGroups],
|
|
66
|
+
planRebuilder
|
|
59
67
|
};
|
|
60
68
|
}
|
|
61
69
|
/** @internal restore registry after in-process AOT analysis */
|
|
@@ -66,6 +74,7 @@ var Compiled = class {
|
|
|
66
74
|
lazyGroupOf = snapshot.lazyGroupOf;
|
|
67
75
|
builtGroups.clear();
|
|
68
76
|
for (const group of snapshot.builtGroups) builtGroups.add(group);
|
|
77
|
+
planRebuilder = snapshot.planRebuilder;
|
|
69
78
|
}
|
|
70
79
|
/** @internal test isolation */
|
|
71
80
|
static clear() {
|
|
@@ -74,6 +83,7 @@ var Compiled = class {
|
|
|
74
83
|
lazyGroups = void 0;
|
|
75
84
|
lazyGroupOf = void 0;
|
|
76
85
|
builtGroups.clear();
|
|
86
|
+
planRebuilder = void 0;
|
|
77
87
|
}
|
|
78
88
|
};
|
|
79
89
|
function reconstructCheck(build) {
|
|
@@ -335,7 +345,7 @@ const Capture = {
|
|
|
335
345
|
};
|
|
336
346
|
/**
|
|
337
347
|
* Reset module-level capture lifecycle state.
|
|
338
|
-
* FOR TESTS ONLY
|
|
348
|
+
* FOR TESTS ONLY
|
|
339
349
|
*/
|
|
340
350
|
const resetCaptureLifecycleForTests = () => void 0;
|
|
341
351
|
|
|
@@ -7,6 +7,7 @@ declare const isBridgeNotInitialized: (error: unknown) => boolean;
|
|
|
7
7
|
declare class FrozenSlotValidator {
|
|
8
8
|
#private;
|
|
9
9
|
isAsync: boolean;
|
|
10
|
+
hasCodec: boolean;
|
|
10
11
|
schema: unknown;
|
|
11
12
|
constructor(frozen: FrozenValidator, schema: unknown, raw: unknown, normalize: boolean | 'exactMirror' | 'typebox' | undefined);
|
|
12
13
|
Check(value: unknown): boolean;
|
|
@@ -23,9 +24,6 @@ interface FrozenRouteValidatorShape {
|
|
|
23
24
|
response?: Record<number, FrozenSlotValidator>;
|
|
24
25
|
}
|
|
25
26
|
declare function buildFrozenRouteValidator(hook: AnyLocalHook, root: AnyElysia, method: HTTPMethod, path: string): FrozenRouteValidatorShape | undefined;
|
|
26
|
-
|
|
27
|
-
* Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
|
|
28
|
-
*/
|
|
29
|
-
declare function isCapturedBridgeFree(c: CapturedValidator, schema: unknown): boolean;
|
|
27
|
+
declare function isCapturedBridgeFree(c: CapturedValidator, schema: unknown, coerced?: unknown): boolean;
|
|
30
28
|
//#endregion
|
|
31
29
|
export { buildFrozenRouteValidator, isBridgeNotInitialized, isCapturedBridgeFree };
|
|
@@ -3,7 +3,6 @@ const require_utils = require('../../utils.js');
|
|
|
3
3
|
const require_type_constants = require('../../type/constants.js');
|
|
4
4
|
const require_error = require('../../error.js');
|
|
5
5
|
const require_compile_aot = require('../aot.js');
|
|
6
|
-
const require_type_coerce_plan = require('../../type/coerce-plan.js');
|
|
7
6
|
|
|
8
7
|
//#region src/compile/handler/frozen-validator.ts
|
|
9
8
|
const isBridgeNotInitialized = (error) => error instanceof Error && error.message.startsWith("Typebox module isn't initialized");
|
|
@@ -69,8 +68,10 @@ var FrozenSlotValidator = class {
|
|
|
69
68
|
#noValidate;
|
|
70
69
|
constructor(frozen, schema, raw, normalize) {
|
|
71
70
|
this.isAsync = false;
|
|
71
|
+
this.hasCodec = false;
|
|
72
72
|
this.schema = schema;
|
|
73
73
|
if (frozen.ic) require_compile_aot.reconstructInnerCodecs(frozen.ic, schema);
|
|
74
|
+
this.hasCodec = frozen.k === 1;
|
|
74
75
|
if (frozen.k === 1 && frozen.dm) {
|
|
75
76
|
const both = require_compile_aot.instantiateFrozenBoth(frozen, schema, raw);
|
|
76
77
|
this.#check = both.check;
|
|
@@ -159,7 +160,12 @@ function buildFrozenRouteValidator(hook, root, method, path) {
|
|
|
159
160
|
if (!schema) return void 0;
|
|
160
161
|
const frozen = require_compile_aot.Compiled.getValidator(method, path, slot);
|
|
161
162
|
if (!frozen) return void 0;
|
|
162
|
-
|
|
163
|
+
let coerced = schema;
|
|
164
|
+
if (frozen.cp) {
|
|
165
|
+
const rebuild = require_compile_aot.Compiled.planRebuilder;
|
|
166
|
+
if (!rebuild) return void 0;
|
|
167
|
+
coerced = rebuild(schema, frozen.cp);
|
|
168
|
+
}
|
|
163
169
|
if (!isBridgeFreeComplete(frozen, coerced, schema)) return void 0;
|
|
164
170
|
out[slot] = new FrozenSlotValidator(frozen, coerced, schema, normalize);
|
|
165
171
|
}
|
|
@@ -181,10 +187,7 @@ function buildFrozenRouteValidator(hook, root, method, path) {
|
|
|
181
187
|
return out;
|
|
182
188
|
}
|
|
183
189
|
const isResponseMap = (schema) => !("~kind" in schema || "~elyAcl" in schema || "~standard" in schema);
|
|
184
|
-
|
|
185
|
-
* Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
|
|
186
|
-
*/
|
|
187
|
-
function isCapturedBridgeFree(c, schema) {
|
|
190
|
+
function isCapturedBridgeFree(c, schema, coerced = schema) {
|
|
188
191
|
if (typeof schema === "string") return false;
|
|
189
192
|
/**
|
|
190
193
|
* A captured `cm` entry is one that froze BOTH check and mirror
|
|
@@ -193,10 +196,7 @@ function isCapturedBridgeFree(c, schema) {
|
|
|
193
196
|
* @see plugin/source.ts `entryParts`
|
|
194
197
|
*/
|
|
195
198
|
if (!(c.checkValue && c.mirror)) return false;
|
|
196
|
-
if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe))
|
|
197
|
-
const coerced = c.coercePlan ? require_type_coerce_plan.buildCoercedFromPlan(schema, c.coercePlan) : schema;
|
|
198
|
-
return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
|
|
199
|
-
}
|
|
199
|
+
if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
|
|
200
200
|
if (c.external || c.mirror.u || c.decodeMirror || c.encodeMirror || c.hasCodec || c.customErrors?.length || c.innerCodecs?.length || c.async) return false;
|
|
201
201
|
if (c.hasDefault && !c.precomputeSafe) return false;
|
|
202
202
|
if (isFullyClosedObject(schema)) return false;
|
|
@@ -2,7 +2,6 @@ import { nullObject } from "../../utils.mjs";
|
|
|
2
2
|
import { ELYSIA_TYPES } from "../../type/constants.mjs";
|
|
3
3
|
import { ValidationError } from "../../error.mjs";
|
|
4
4
|
import { Compiled, EMPTY_EXTERNALS, collectMirrorUnions, collectStringCodecNodes, instantiateFrozenBoth, instantiateFrozenDecodeMirror, reconstructInnerCodecs } from "../aot.mjs";
|
|
5
|
-
import { buildCoercedFromPlan } from "../../type/coerce-plan.mjs";
|
|
6
5
|
|
|
7
6
|
//#region src/compile/handler/frozen-validator.ts
|
|
8
7
|
const isBridgeNotInitialized = (error) => error instanceof Error && error.message.startsWith("Typebox module isn't initialized");
|
|
@@ -68,8 +67,10 @@ var FrozenSlotValidator = class {
|
|
|
68
67
|
#noValidate;
|
|
69
68
|
constructor(frozen, schema, raw, normalize) {
|
|
70
69
|
this.isAsync = false;
|
|
70
|
+
this.hasCodec = false;
|
|
71
71
|
this.schema = schema;
|
|
72
72
|
if (frozen.ic) reconstructInnerCodecs(frozen.ic, schema);
|
|
73
|
+
this.hasCodec = frozen.k === 1;
|
|
73
74
|
if (frozen.k === 1 && frozen.dm) {
|
|
74
75
|
const both = instantiateFrozenBoth(frozen, schema, raw);
|
|
75
76
|
this.#check = both.check;
|
|
@@ -158,7 +159,12 @@ function buildFrozenRouteValidator(hook, root, method, path) {
|
|
|
158
159
|
if (!schema) return void 0;
|
|
159
160
|
const frozen = Compiled.getValidator(method, path, slot);
|
|
160
161
|
if (!frozen) return void 0;
|
|
161
|
-
|
|
162
|
+
let coerced = schema;
|
|
163
|
+
if (frozen.cp) {
|
|
164
|
+
const rebuild = Compiled.planRebuilder;
|
|
165
|
+
if (!rebuild) return void 0;
|
|
166
|
+
coerced = rebuild(schema, frozen.cp);
|
|
167
|
+
}
|
|
162
168
|
if (!isBridgeFreeComplete(frozen, coerced, schema)) return void 0;
|
|
163
169
|
out[slot] = new FrozenSlotValidator(frozen, coerced, schema, normalize);
|
|
164
170
|
}
|
|
@@ -180,10 +186,7 @@ function buildFrozenRouteValidator(hook, root, method, path) {
|
|
|
180
186
|
return out;
|
|
181
187
|
}
|
|
182
188
|
const isResponseMap = (schema) => !("~kind" in schema || "~elyAcl" in schema || "~standard" in schema);
|
|
183
|
-
|
|
184
|
-
* Build-time twin of `isBridgeFreeComplete`, operating on the captured entry & its schema
|
|
185
|
-
*/
|
|
186
|
-
function isCapturedBridgeFree(c, schema) {
|
|
189
|
+
function isCapturedBridgeFree(c, schema, coerced = schema) {
|
|
187
190
|
if (typeof schema === "string") return false;
|
|
188
191
|
/**
|
|
189
192
|
* A captured `cm` entry is one that froze BOTH check and mirror
|
|
@@ -192,10 +195,7 @@ function isCapturedBridgeFree(c, schema) {
|
|
|
192
195
|
* @see plugin/source.ts `entryParts`
|
|
193
196
|
*/
|
|
194
197
|
if (!(c.checkValue && c.mirror)) return false;
|
|
195
|
-
if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe))
|
|
196
|
-
const coerced = c.coercePlan ? buildCoercedFromPlan(schema, c.coercePlan) : schema;
|
|
197
|
-
return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
|
|
198
|
-
}
|
|
198
|
+
if (c.hasCodec && !!c.decodeMirror && !c.encodeMirror && !c.customErrors?.length && !c.async && !(c.hasDefault && !c.precomputeSafe)) return innerCodecsAligned(c.innerCodecs?.length, coerced) && mirrorUnionsAligned(c.mirror.u, schema) && mirrorUnionsAligned(c.decodeMirror.u, coerced);
|
|
199
199
|
if (c.external || c.mirror.u || c.decodeMirror || c.encodeMirror || c.hasCodec || c.customErrors?.length || c.innerCodecs?.length || c.async) return false;
|
|
200
200
|
if (c.hasDefault && !c.precomputeSafe) return false;
|
|
201
201
|
if (isFullyClosedObject(schema)) return false;
|
package/dist/error.d.ts
CHANGED
|
@@ -183,7 +183,7 @@ declare const PROBLEM_JSON = "application/problem+json";
|
|
|
183
183
|
*/
|
|
184
184
|
type Problem<Code extends number | keyof StatusMap = number | keyof StatusMap, Extension extends Record<string, unknown> = {}> = {
|
|
185
185
|
/** URI (or slug) identifying the problem type. @default 'about:blank' */type?: string; /** Short, human-readable summary of the problem type */
|
|
186
|
-
title?: string; /** HTTP status
|
|
186
|
+
title?: string; /** HTTP status. A number or a `StatusMap` name (e.g. `'Conflict'`) */
|
|
187
187
|
status?: Code; /** Human-readable explanation specific to this occurrence */
|
|
188
188
|
detail?: string; /** URI identifying the specific occurrence of the problem */
|
|
189
189
|
instance?: string;
|
package/dist/plugin/core.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ interface ElysiaAotOptions {
|
|
|
15
15
|
* Validator entries are registered as grouped thunks: a group's
|
|
16
16
|
* validators are constructed on the first request to any route in that
|
|
17
17
|
* group, trading first-request latency in unbuilt groups for lower
|
|
18
|
-
* startup cost. Handlers are always eager
|
|
18
|
+
* startup cost. Handlers are always eager. Only validator construction
|
|
19
19
|
* is deferred. Pass a number to set the group size explicitly.
|
|
20
20
|
*
|
|
21
21
|
* @default decided by Elysia based on route batch scale
|
|
@@ -84,7 +84,7 @@ interface StubPlan {
|
|
|
84
84
|
/**
|
|
85
85
|
* Stub the `memory` module's `clearSucroseCache` edge when handler JIT is
|
|
86
86
|
* stubbed. Sucrose never runs in a precompiled app, so its caches are always
|
|
87
|
-
* empty and the flush is a no-op
|
|
87
|
+
* empty and the flush is a no-op. Dropping the import lets the Sucrose
|
|
88
88
|
* analyzer tree-shake. `flushMemory`'s other clears are preserved, and the
|
|
89
89
|
* public `elysia/sucrose` module is left untouched
|
|
90
90
|
*/
|
|
@@ -92,7 +92,7 @@ interface StubPlan {
|
|
|
92
92
|
/**
|
|
93
93
|
* Stub `type/compat` so `setupTypebox` becomes a no-op, un-wiring the
|
|
94
94
|
* TypeBox bridge from the bundle. Set in BOTH sealed (mode A) and wired
|
|
95
|
-
* (mode B) builds. In mode A the bridge is severed entirely
|
|
95
|
+
* (mode B) builds. In mode A the bridge is severed entirely. Every captured
|
|
96
96
|
* validator is bridge-free so nothing reaches it. In mode B the mirror
|
|
97
97
|
* (`bridge` → `bridge-live` reroute) replaces the latch, so `setupTypebox`
|
|
98
98
|
* must still be a no-op to guarantee the bridge is wired exactly once
|
|
@@ -109,13 +109,13 @@ interface StubPlan {
|
|
|
109
109
|
/**
|
|
110
110
|
* Chosen TypeBox-collapse strategy for the build, logged in the dry-run line.
|
|
111
111
|
*
|
|
112
|
-
* - `'sealed'` (mode A): every captured validator is bridge-free and
|
|
113
|
-
* fully stripped (handler JIT unused
|
|
114
|
-
* bridge is severed
|
|
112
|
+
* - `'sealed'` (mode A): every captured validator is bridge-free (HTTP and WS
|
|
113
|
+
* alike) and the app is fully stripped (handler JIT unused). `compat` is
|
|
114
|
+
* stubbed and the bridge is severed, TypeBox collapses entirely
|
|
115
115
|
* - `'wired'` (mode B): at least one validator still needs the bridge. `compat`
|
|
116
116
|
* is stubbed AND the bridge is re-routed to the statically-wired mirror so it
|
|
117
117
|
* works without the DCE-fragile `setupTypebox()` anchor
|
|
118
|
-
* - `'off'`: no bridge action (strip disabled, or handler JIT still reachable
|
|
118
|
+
* - `'off'`: no bridge action (strip disabled, or handler JIT still reachable,
|
|
119
119
|
* the frozen path is not active so the bridge stays wired the normal way)
|
|
120
120
|
*/
|
|
121
121
|
type BridgeMode = 'sealed' | 'wired' | 'off';
|
|
@@ -123,51 +123,11 @@ declare const STUB_SOURCES: Record<keyof StubPlan, Array<{
|
|
|
123
123
|
filter: RegExp;
|
|
124
124
|
source: string;
|
|
125
125
|
}>>;
|
|
126
|
-
/**
|
|
127
|
-
* Rewrite a stub's extensionless relative imports to match the extension of the
|
|
128
|
-
* real module it replaces
|
|
129
|
-
*
|
|
130
|
-
* The sucrose stub replaces `memory.(mjs|js|ts)`. Its bare relative specifiers
|
|
131
|
-
* (`./context`, …) are correct against the `.ts` source, but against a `.mjs`
|
|
132
|
-
* dist module esbuild's node resolver defaults an extensionless specifier to the
|
|
133
|
-
* sibling CJS `.js` (elysia's package.json has no `"type": "module"`), pulling a
|
|
134
|
-
* whole duplicate CJS copy of the elysia subtree alongside the app's `.mjs` one.
|
|
135
|
-
* Anchoring the specifiers to the replaced file's extension keeps the stub on the
|
|
136
|
-
* same module instances the app already resolves. `./validator` is a directory,
|
|
137
|
-
* so it expands to `./validator/index`
|
|
138
|
-
*/
|
|
139
126
|
declare const alignStubExtensions: (stubSource: string, targetPath: string) => string;
|
|
140
|
-
/**
|
|
141
|
-
* Elysia's `t.*` overrides — the 27 names `elysia/type` replaces (or adds) over
|
|
142
|
-
* `typebox/type`. Each maps to its leaf module basename (under `type/elysia/`)
|
|
143
|
-
* and the name it is exported as from that leaf (some leaves rename to avoid
|
|
144
|
-
* shadowing a JS global, e.g. `Array` → `ArrayType`).
|
|
145
|
-
*
|
|
146
|
-
* SINGLE SOURCE OF TRUTH: mirrors `src/type/index.ts` (the runtime `t` object)
|
|
147
|
-
* and `src/type/exports.ts`. `test/aot/virtual-type-drift.test.ts` pins it
|
|
148
|
-
* against the live `t` object vs `typebox/type` (`Object.keys` diff) so a future
|
|
149
|
-
* `t.*` addition/removal that skips this table trips the drift test.
|
|
150
|
-
*/
|
|
151
127
|
declare const OVERRIDE_MAP: Record<string, {
|
|
152
128
|
leaf: string;
|
|
153
129
|
export: string;
|
|
154
130
|
}>;
|
|
155
|
-
/**
|
|
156
|
-
* Build the virtual `elysia/type` module source — a plain re-export surface with
|
|
157
|
-
* NO `setupTypebox` call (that is the whole point: consuming `t` this way must
|
|
158
|
-
* not drag the bridge latch).
|
|
159
|
-
*
|
|
160
|
-
* TypeBox's `t.*` names pass through to the user's own `typebox/type` (resolved
|
|
161
|
-
* to an absolute path — the same copy the bundle uses); the 27 Elysia overrides
|
|
162
|
-
* re-export from their leaf modules (absolute paths, because elysia's `exports`
|
|
163
|
-
* map blocks `./dist/*` subpaths). Emitting STATIC NAMED re-exports (not
|
|
164
|
-
* `export *`) is what lets `import * as t from 'elysia/type'` — the specifier the
|
|
165
|
-
* treeshake rewrite emits — tree-shake unused constructors per-member.
|
|
166
|
-
*
|
|
167
|
-
* `typeSpecifier` is the specifier the plugin intercepts (`elysia/type`); it is
|
|
168
|
-
* resolved to locate the dist `type/elysia/*` leaf modules and to derive their
|
|
169
|
-
* extension.
|
|
170
|
-
*/
|
|
171
131
|
declare function generateVirtualType(typeSpecifier?: string): Promise<string>;
|
|
172
132
|
declare function generateCompiledModule(file: string, options?: ElysiaAotOptions): Promise<string>;
|
|
173
133
|
interface CompiledArtifacts {
|
package/dist/plugin/core.js
CHANGED
|
@@ -38,21 +38,21 @@ const NO_STUB = {
|
|
|
38
38
|
* `true` throws when handler JIT is still reachable; `false` disables.
|
|
39
39
|
*
|
|
40
40
|
* Also decides the TypeBox-collapse `mode`:
|
|
41
|
-
* - `'sealed'
|
|
42
|
-
*
|
|
43
|
-
* and the bridge is
|
|
44
|
-
* - `'wired'
|
|
41
|
+
* - `'sealed'`: the frozen path is active (`jit`) and EVERY captured
|
|
42
|
+
* validator, HTTP and WS is bridge-free (`bridgeFree`). `setupTypebox` is
|
|
43
|
+
* stubbed and the bridge is severe, nothing reaches it, TypeBox collapses.
|
|
44
|
+
* - `'wired'`: the frozen path is active but at least one validator
|
|
45
45
|
* still needs the bridge. `setupTypebox` is stubbed AND the bridge is
|
|
46
46
|
* re-routed to the statically-wired mirror (works without the DCE-fragile
|
|
47
47
|
* `setupTypebox()` anchor).
|
|
48
48
|
* - `'off'`: handler JIT still reachable (or strip disabled). The frozen path
|
|
49
|
-
* is not active, so the bridge must stay wired the ordinary way
|
|
49
|
+
* is not active, so the bridge must stay wired the ordinary way. No compat
|
|
50
50
|
* stub, no reroute.
|
|
51
51
|
*/
|
|
52
|
-
function planFromReport(strip, report, hasWS, aliases, allBridgeFree) {
|
|
52
|
+
function planFromReport(strip, report, hasWS, aliases, allBridgeFree, zeroCapture) {
|
|
53
53
|
const jit = report.jit;
|
|
54
54
|
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
|
-
const mode = !
|
|
55
|
+
const mode = !jit ? "off" : allBridgeFree ? "sealed" : hasWS && zeroCapture ? "off" : "wired";
|
|
56
56
|
return {
|
|
57
57
|
plan: {
|
|
58
58
|
jit,
|
|
@@ -104,35 +104,11 @@ const STUB_SOURCES = {
|
|
|
104
104
|
source: `export * from './bridge-live'\n`
|
|
105
105
|
}]
|
|
106
106
|
};
|
|
107
|
-
/**
|
|
108
|
-
* Rewrite a stub's extensionless relative imports to match the extension of the
|
|
109
|
-
* real module it replaces
|
|
110
|
-
*
|
|
111
|
-
* The sucrose stub replaces `memory.(mjs|js|ts)`. Its bare relative specifiers
|
|
112
|
-
* (`./context`, …) are correct against the `.ts` source, but against a `.mjs`
|
|
113
|
-
* dist module esbuild's node resolver defaults an extensionless specifier to the
|
|
114
|
-
* sibling CJS `.js` (elysia's package.json has no `"type": "module"`), pulling a
|
|
115
|
-
* whole duplicate CJS copy of the elysia subtree alongside the app's `.mjs` one.
|
|
116
|
-
* Anchoring the specifiers to the replaced file's extension keeps the stub on the
|
|
117
|
-
* same module instances the app already resolves. `./validator` is a directory,
|
|
118
|
-
* so it expands to `./validator/index`
|
|
119
|
-
*/
|
|
120
107
|
const alignStubExtensions = (stubSource, targetPath) => {
|
|
121
108
|
const ext = targetPath.slice(targetPath.lastIndexOf("."));
|
|
122
109
|
if (ext !== ".mjs" && ext !== ".js" && ext !== ".cjs") return stubSource;
|
|
123
110
|
return stubSource.replace(/(from ')(\.[^']+)(')/g, (_m, open, spec, close) => open + (spec === "./validator" ? spec + "/index" : spec) + ext + close);
|
|
124
111
|
};
|
|
125
|
-
/**
|
|
126
|
-
* Elysia's `t.*` overrides — the 27 names `elysia/type` replaces (or adds) over
|
|
127
|
-
* `typebox/type`. Each maps to its leaf module basename (under `type/elysia/`)
|
|
128
|
-
* and the name it is exported as from that leaf (some leaves rename to avoid
|
|
129
|
-
* shadowing a JS global, e.g. `Array` → `ArrayType`).
|
|
130
|
-
*
|
|
131
|
-
* SINGLE SOURCE OF TRUTH: mirrors `src/type/index.ts` (the runtime `t` object)
|
|
132
|
-
* and `src/type/exports.ts`. `test/aot/virtual-type-drift.test.ts` pins it
|
|
133
|
-
* against the live `t` object vs `typebox/type` (`Object.keys` diff) so a future
|
|
134
|
-
* `t.*` addition/removal that skips this table trips the drift test.
|
|
135
|
-
*/
|
|
136
112
|
const OVERRIDE_MAP = {
|
|
137
113
|
Accelerate: {
|
|
138
114
|
leaf: "accelerate",
|
|
@@ -244,17 +220,6 @@ const OVERRIDE_MAP = {
|
|
|
244
220
|
}
|
|
245
221
|
};
|
|
246
222
|
const requireFromHere = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
247
|
-
/**
|
|
248
|
-
* Resolve a specifier to an absolute path using the SAME resolution the ESM
|
|
249
|
-
* bundle will use (the `import` condition, so `elysia/type` → `.mjs` — the
|
|
250
|
-
* extension every ESM bundle here resolves to, and the one the leaf modules must
|
|
251
|
-
* carry to avoid the CJS dual-copy the `alignStubExtensions` lesson warns of).
|
|
252
|
-
*
|
|
253
|
-
* `import.meta.resolve` honours the `import` condition and is present in the ESM
|
|
254
|
-
* plugin build (the one every bundler integration loads). In the CJS build
|
|
255
|
-
* Rolldown lowers `import.meta` to `{}`, so `import.meta.resolve` is `undefined`
|
|
256
|
-
* and we fall back to `require.resolve`.
|
|
257
|
-
*/
|
|
258
223
|
const resolveSpecifier = (specifier) => {
|
|
259
224
|
const meta = {};
|
|
260
225
|
if (typeof meta.resolve === "function") {
|
|
@@ -263,22 +228,6 @@ const resolveSpecifier = (specifier) => {
|
|
|
263
228
|
}
|
|
264
229
|
return requireFromHere.resolve(specifier);
|
|
265
230
|
};
|
|
266
|
-
/**
|
|
267
|
-
* Build the virtual `elysia/type` module source — a plain re-export surface with
|
|
268
|
-
* NO `setupTypebox` call (that is the whole point: consuming `t` this way must
|
|
269
|
-
* not drag the bridge latch).
|
|
270
|
-
*
|
|
271
|
-
* TypeBox's `t.*` names pass through to the user's own `typebox/type` (resolved
|
|
272
|
-
* to an absolute path — the same copy the bundle uses); the 27 Elysia overrides
|
|
273
|
-
* re-export from their leaf modules (absolute paths, because elysia's `exports`
|
|
274
|
-
* map blocks `./dist/*` subpaths). Emitting STATIC NAMED re-exports (not
|
|
275
|
-
* `export *`) is what lets `import * as t from 'elysia/type'` — the specifier the
|
|
276
|
-
* treeshake rewrite emits — tree-shake unused constructors per-member.
|
|
277
|
-
*
|
|
278
|
-
* `typeSpecifier` is the specifier the plugin intercepts (`elysia/type`); it is
|
|
279
|
-
* resolved to locate the dist `type/elysia/*` leaf modules and to derive their
|
|
280
|
-
* extension.
|
|
281
|
-
*/
|
|
282
231
|
async function generateVirtualType(typeSpecifier = "elysia/type") {
|
|
283
232
|
const typePath = resolveSpecifier(typeSpecifier);
|
|
284
233
|
const typeboxPath = resolveSpecifier("typebox/type");
|
|
@@ -288,7 +237,7 @@ async function generateVirtualType(typeSpecifier = "elysia/type") {
|
|
|
288
237
|
const overrideNames = new Set(Object.keys(OVERRIDE_MAP));
|
|
289
238
|
const passthrough = Object.keys(typebox).filter((name) => !overrideNames.has(name));
|
|
290
239
|
const toSpecifier = (p) => JSON.stringify(p.replace(/\\/g, "/"));
|
|
291
|
-
let source = `// Generated by Elysia build plugin
|
|
240
|
+
let source = `// Generated by Elysia build plugin. Virtual 'elysia/type' re-export surface.\n`;
|
|
292
241
|
source += `export { ${passthrough.join(", ")} } from ${toSpecifier(typeboxPath)}\n`;
|
|
293
242
|
for (const [name, { leaf, export: exported }] of Object.entries(OVERRIDE_MAP)) {
|
|
294
243
|
const spec = toSpecifier((0, node_path.join)(leafDir, leaf + ext));
|
|
@@ -308,7 +257,7 @@ async function generateCompiledArtifacts(file, options) {
|
|
|
308
257
|
const entryReal = realPath(entry);
|
|
309
258
|
let mod;
|
|
310
259
|
if (_importedEntries.has(entryReal)) {
|
|
311
|
-
console.warn("[elysia-aot] re-importing \"" + entry + "\" for rebuild
|
|
260
|
+
console.warn("[elysia-aot] re-importing \"" + entry + "\" for rebuild. top-level side effects will re-run. This is expected in watch/rebuild flows.");
|
|
312
261
|
const cacheBustSpecifier = entry + "?elysia-aot=" + Date.now();
|
|
313
262
|
try {
|
|
314
263
|
mod = await import(cacheBustSpecifier);
|
|
@@ -358,7 +307,7 @@ async function generateCompiledArtifacts(file, options) {
|
|
|
358
307
|
}
|
|
359
308
|
if (typedApp["~config"]?.normalize === "typebox") routesForbidSeal = true;
|
|
360
309
|
const frozenSlots = artifacts.validators.length;
|
|
361
|
-
const { plan: stub, mode } = planFromReport(strip, report, hasWS, aliases, artifacts.handlers.length > 0 && !routesForbidSeal && frozenSlots >= expectedSlots && artifacts.validators.every((v) => v.bridgeFree === true));
|
|
310
|
+
const { plan: stub, mode } = planFromReport(strip, report, hasWS, aliases, (artifacts.handlers.length > 0 || artifacts.validators.length > 0) && !routesForbidSeal && frozenSlots >= expectedSlots && artifacts.validators.every((v) => v.bridgeFree === true), artifacts.handlers.length === 0 && artifacts.validators.length === 0);
|
|
362
311
|
if (options?.target === "workerd") {
|
|
363
312
|
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.`);
|
|
364
313
|
if (frozenSlots < expectedSlots) console.warn(`[elysia-aot] target 'workerd': only ${frozenSlots}/${expectedSlots} validator slots were frozen unfrozen slots compile at runtime and will fail on workerd.`);
|