elysia 2.0.0-beta.2 → 2.0.0-beta.3

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 (145) hide show
  1. package/dist/adapter/utils.js +3 -3
  2. package/dist/adapter/utils.mjs +4 -4
  3. package/dist/adapter/web-standard/handler.js +5 -2
  4. package/dist/adapter/web-standard/handler.mjs +5 -2
  5. package/dist/base.d.ts +2 -2
  6. package/dist/base.js +42 -27
  7. package/dist/base.mjs +43 -28
  8. package/dist/compile/aot.d.ts +4 -4
  9. package/dist/compile/aot.js +14 -9
  10. package/dist/compile/aot.mjs +15 -9
  11. package/dist/compile/handler/descriptor.js +1 -1
  12. package/dist/compile/handler/descriptor.mjs +1 -1
  13. package/dist/compile/handler/index.d.ts +2 -2
  14. package/dist/compile/handler/index.js +6 -4
  15. package/dist/compile/handler/index.mjs +6 -4
  16. package/dist/compile/handler/utils.js +2 -2
  17. package/dist/compile/handler/utils.mjs +3 -3
  18. package/dist/context.js +3 -4
  19. package/dist/context.mjs +3 -4
  20. package/dist/cookie/config.d.ts +1 -0
  21. package/dist/cookie/config.js +3 -2
  22. package/dist/cookie/config.mjs +3 -2
  23. package/dist/cookie/cookie.d.ts +1 -0
  24. package/dist/cookie/cookie.js +6 -5
  25. package/dist/cookie/cookie.mjs +6 -5
  26. package/dist/cookie/crypto.d.ts +8 -7
  27. package/dist/cookie/crypto.js +38 -31
  28. package/dist/cookie/crypto.mjs +38 -32
  29. package/dist/cookie/error.js +1 -2
  30. package/dist/cookie/error.mjs +1 -2
  31. package/dist/cookie/lib.js +5 -1
  32. package/dist/cookie/lib.mjs +5 -1
  33. package/dist/cookie/serialize.js +1 -1
  34. package/dist/cookie/serialize.mjs +1 -1
  35. package/dist/cookie/types.d.ts +10 -0
  36. package/dist/cookie/utils.js +28 -14
  37. package/dist/cookie/utils.mjs +29 -15
  38. package/dist/handler/error.d.ts +23 -12
  39. package/dist/handler/error.js +24 -21
  40. package/dist/handler/error.mjs +20 -21
  41. package/dist/handler/fetch.js +6 -7
  42. package/dist/handler/fetch.mjs +7 -8
  43. package/dist/handler/utils.js +4 -2
  44. package/dist/handler/utils.mjs +5 -3
  45. package/dist/index.js +1 -8
  46. package/dist/package.js +1 -1
  47. package/dist/package.mjs +1 -1
  48. package/dist/plugin/aot/core.d.ts +22 -1
  49. package/dist/plugin/aot/core.js +35 -3
  50. package/dist/plugin/aot/core.mjs +35 -3
  51. package/dist/route-table.js +12 -10
  52. package/dist/route-table.mjs +12 -10
  53. package/dist/type/bridge-live.d.ts +1 -1
  54. package/dist/type/bridge-live.js +1 -1
  55. package/dist/type/bridge-live.mjs +1 -1
  56. package/dist/type/bridge.d.ts +1 -1
  57. package/dist/type/coerce-plan.js +3 -4
  58. package/dist/type/coerce-plan.mjs +1 -1
  59. package/dist/type/compat.d.ts +3 -0
  60. package/dist/type/compat.js +32 -14
  61. package/dist/type/compat.mjs +27 -8
  62. package/dist/type/elysia/array-buffer.js +2 -3
  63. package/dist/type/elysia/array-buffer.mjs +1 -1
  64. package/dist/type/elysia/array-string.js +7 -4
  65. package/dist/type/elysia/array-string.mjs +9 -5
  66. package/dist/type/elysia/boolean-string.js +2 -3
  67. package/dist/type/elysia/boolean-string.mjs +1 -1
  68. package/dist/type/elysia/boolean.js +4 -3
  69. package/dist/type/elysia/boolean.mjs +4 -2
  70. package/dist/type/elysia/cookie.js +2 -1
  71. package/dist/type/elysia/cookie.mjs +2 -1
  72. package/dist/type/elysia/date.js +7 -7
  73. package/dist/type/elysia/date.mjs +3 -2
  74. package/dist/type/elysia/file-type.d.ts +9 -2
  75. package/dist/type/elysia/file-type.js +16 -1
  76. package/dist/type/elysia/file-type.mjs +16 -2
  77. package/dist/type/elysia/file.js +8 -4
  78. package/dist/type/elysia/file.mjs +8 -3
  79. package/dist/type/elysia/files.js +6 -4
  80. package/dist/type/elysia/files.mjs +5 -2
  81. package/dist/type/elysia/form.js +2 -3
  82. package/dist/type/elysia/form.mjs +3 -3
  83. package/dist/type/elysia/integer-string.js +4 -5
  84. package/dist/type/elysia/integer-string.mjs +3 -3
  85. package/dist/type/elysia/integer.js +4 -3
  86. package/dist/type/elysia/integer.mjs +4 -2
  87. package/dist/type/elysia/maybe-empty.js +3 -4
  88. package/dist/type/elysia/maybe-empty.mjs +1 -1
  89. package/dist/type/elysia/nullable.js +2 -3
  90. package/dist/type/elysia/nullable.mjs +1 -1
  91. package/dist/type/elysia/number.js +4 -3
  92. package/dist/type/elysia/number.mjs +4 -2
  93. package/dist/type/elysia/numeric-enum.js +2 -3
  94. package/dist/type/elysia/numeric-enum.mjs +1 -1
  95. package/dist/type/elysia/numeric.js +5 -5
  96. package/dist/type/elysia/numeric.mjs +3 -2
  97. package/dist/type/elysia/object-string.js +7 -4
  98. package/dist/type/elysia/object-string.mjs +9 -5
  99. package/dist/type/elysia/string.js +4 -3
  100. package/dist/type/elysia/string.mjs +4 -2
  101. package/dist/type/elysia/uint8-array.js +2 -3
  102. package/dist/type/elysia/uint8-array.mjs +1 -1
  103. package/dist/type/elysia/union-enum.js +2 -3
  104. package/dist/type/elysia/union-enum.mjs +1 -1
  105. package/dist/type/elysia/utils.js +2 -3
  106. package/dist/type/elysia/utils.mjs +1 -1
  107. package/dist/type/exports.js +10 -1
  108. package/dist/type/exports.mjs +7 -0
  109. package/dist/type/index.d.ts +9 -1
  110. package/dist/type/index.js +41 -13
  111. package/dist/type/index.mjs +44 -9
  112. package/dist/type/typebox-type-live.d.ts +9 -0
  113. package/dist/type/typebox-type-live.js +31 -0
  114. package/dist/type/typebox-type-live.mjs +16 -0
  115. package/dist/type/typebox-type.d.ts +24 -0
  116. package/dist/type/typebox-type.js +130 -0
  117. package/dist/type/typebox-type.mjs +66 -0
  118. package/dist/type/typebox-value-live.d.ts +9 -0
  119. package/dist/type/typebox-value-live.js +27 -0
  120. package/dist/type/typebox-value-live.mjs +11 -0
  121. package/dist/type/typebox-value.d.ts +30 -0
  122. package/dist/type/typebox-value.js +147 -0
  123. package/dist/type/typebox-value.mjs +62 -0
  124. package/dist/type/validator/custom-error.js +4 -5
  125. package/dist/type/validator/custom-error.mjs +3 -3
  126. package/dist/type/validator/default-precompute.js +4 -5
  127. package/dist/type/validator/default-precompute.mjs +1 -1
  128. package/dist/type/validator/index.js +41 -35
  129. package/dist/type/validator/index.mjs +23 -16
  130. package/dist/type/validator/validator-cache.js +10 -5
  131. package/dist/type/validator/validator-cache.mjs +10 -4
  132. package/dist/universal/constants.d.ts +2 -1
  133. package/dist/universal/constants.js +3 -1
  134. package/dist/universal/constants.mjs +2 -1
  135. package/dist/utils.d.ts +10 -1
  136. package/dist/utils.js +18 -3
  137. package/dist/utils.mjs +14 -3
  138. package/dist/validator/index.js +2 -1
  139. package/dist/validator/index.mjs +2 -1
  140. package/dist/ws/context.d.ts +4 -0
  141. package/dist/ws/context.js +44 -26
  142. package/dist/ws/context.mjs +44 -26
  143. package/dist/ws/route.js +126 -23
  144. package/dist/ws/route.mjs +128 -25
  145. package/package.json +4 -2
@@ -14,7 +14,7 @@ function materializeSetHeaders(set) {
14
14
  if (headers["\0"] !== headers) return headers;
15
15
  return set.headers = Object.assign(require_utils.nullObject(), headers);
16
16
  }
17
- const sseFormat = (data) => `data: ${data}\n\n`;
17
+ const sseFormat = (data) => require_utils.sseData(data) + "\n";
18
18
  const identityFormat = (data) => data;
19
19
  const textEncoder = new TextEncoder();
20
20
  const encodeChunk = (s) => textEncoder.encode(s);
@@ -284,7 +284,7 @@ function handleSet(set) {
284
284
  const proto = Object.getPrototypeOf(set.headers);
285
285
  if (proto !== null && proto !== Object.prototype) {
286
286
  const flat = Object.create(null);
287
- for (const key in set.headers) flat[key] = set.headers[key];
287
+ for (const key of Object.keys(set.headers)) flat[key] = set.headers[key];
288
288
  set.headers = flat;
289
289
  }
290
290
  if (set.cookie && require_utils.isNotEmpty(set.cookie)) {
@@ -311,7 +311,7 @@ function applySetHeaders(target, setHeaders, present) {
311
311
  for (const key of setHeaders.keys()) if (key !== setCookie && !present.has(key)) target.set(key, setHeaders.get(key) ?? "");
312
312
  } else {
313
313
  let cookies;
314
- for (const key in setHeaders) if (key === setCookie) {
314
+ for (const key of Object.keys(setHeaders)) if (key === setCookie) {
315
315
  const cookie = setHeaders[key];
316
316
  cookies ??= target.getSetCookie();
317
317
  if (!cookies.includes(cookie)) {
@@ -1,6 +1,6 @@
1
1
  import { hasHeaderShorthand, isBun } from "../universal/constants.mjs";
2
2
  import { StatusMap } from "../constants.mjs";
3
- import { isByteStream, isNotEmpty, nullObject } from "../utils.mjs";
3
+ import { isByteStream, isNotEmpty, nullObject, sseData } from "../utils.mjs";
4
4
  import { skipClone } from "./skip-clone.mjs";
5
5
  import { serializeCookie } from "../cookie/serialize.mjs";
6
6
  import { isBorrowedResponse } from "./response-ownership.mjs";
@@ -13,7 +13,7 @@ function materializeSetHeaders(set) {
13
13
  if (headers["\0"] !== headers) return headers;
14
14
  return set.headers = Object.assign(nullObject(), headers);
15
15
  }
16
- const sseFormat = (data) => `data: ${data}\n\n`;
16
+ const sseFormat = (data) => sseData(data) + "\n";
17
17
  const identityFormat = (data) => data;
18
18
  const textEncoder = new TextEncoder();
19
19
  const encodeChunk = (s) => textEncoder.encode(s);
@@ -283,7 +283,7 @@ function handleSet(set) {
283
283
  const proto = Object.getPrototypeOf(set.headers);
284
284
  if (proto !== null && proto !== Object.prototype) {
285
285
  const flat = Object.create(null);
286
- for (const key in set.headers) flat[key] = set.headers[key];
286
+ for (const key of Object.keys(set.headers)) flat[key] = set.headers[key];
287
287
  set.headers = flat;
288
288
  }
289
289
  if (set.cookie && isNotEmpty(set.cookie)) {
@@ -310,7 +310,7 @@ function applySetHeaders(target, setHeaders, present) {
310
310
  for (const key of setHeaders.keys()) if (key !== setCookie && !present.has(key)) target.set(key, setHeaders.get(key) ?? "");
311
311
  } else {
312
312
  let cookies;
313
- for (const key in setHeaders) if (key === setCookie) {
313
+ for (const key of Object.keys(setHeaders)) if (key === setCookie) {
314
314
  const cookie = setHeaders[key];
315
315
  cookies ??= target.getSetCookie();
316
316
  if (!cookies.includes(cookie)) {
@@ -5,6 +5,7 @@ const require_utils = require('../../utils.js');
5
5
  const require_error = require('../../error.js');
6
6
  const require_adapter_default_headers = require('../default-headers.js');
7
7
  const require_adapter_utils = require('../utils.js');
8
+ const require_cookie_cookie = require('../../cookie/cookie.js');
8
9
 
9
10
  //#region src/adapter/web-standard/handler.ts
10
11
  const textPlainInit = { headers: { "content-type": "text/plain" } };
@@ -23,7 +24,9 @@ function handleElysiaFile(file, set = { headers: require_utils.nullObject() }, r
23
24
  }
24
25
  function responseTag(response) {
25
26
  if (response == null) return;
26
- return Object.getPrototypeOf(response)?.constructor?.name;
27
+ const constructor = Object.getPrototypeOf(response)?.constructor;
28
+ if (typeof constructor !== "function") return typeof response.next === "function" ? "" : void 0;
29
+ return constructor.name;
27
30
  }
28
31
  function mapResponseWithSet(response, set, request, owned) {
29
32
  require_adapter_utils.handleSet(set);
@@ -134,7 +137,7 @@ function mapFallback(response, set, request, owned = false) {
134
137
  if (typeof response?.then === "function") return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
135
138
  if (Array.isArray(response)) return Response.json(response);
136
139
  if (typeof response?.toResponse === "function") return set ? mapResponse(response.toResponse(), set, request, owned) : mapCompactResponse(response.toResponse(), request, owned);
137
- if (responseTag(response) === "Cookie") return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
140
+ if (responseTag(response) === "Cookie" && require_cookie_cookie.Cookie.isCookie(response)) return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
138
141
  return new Response(response, set);
139
142
  }
140
143
  const handleResponse = require_adapter_utils.createResponseHandler({
@@ -4,6 +4,7 @@ import { formToFormData, isElysiaForm, isNotEmpty, nullObject } from "../../util
4
4
  import { ElysiaStatus, PROBLEM_JSON, internalServerErrorBody } from "../../error.mjs";
5
5
  import { defaultHeaders } from "../default-headers.mjs";
6
6
  import { createResponseHandler, createStreamHandler, handleFile, handleSet, materializeSetHeaders } from "../utils.mjs";
7
+ import { Cookie } from "../../cookie/cookie.mjs";
7
8
 
8
9
  //#region src/adapter/web-standard/handler.ts
9
10
  const textPlainInit = { headers: { "content-type": "text/plain" } };
@@ -22,7 +23,9 @@ function handleElysiaFile(file, set = { headers: nullObject() }, request) {
22
23
  }
23
24
  function responseTag(response) {
24
25
  if (response == null) return;
25
- return Object.getPrototypeOf(response)?.constructor?.name;
26
+ const constructor = Object.getPrototypeOf(response)?.constructor;
27
+ if (typeof constructor !== "function") return typeof response.next === "function" ? "" : void 0;
28
+ return constructor.name;
26
29
  }
27
30
  function mapResponseWithSet(response, set, request, owned) {
28
31
  handleSet(set);
@@ -133,7 +136,7 @@ function mapFallback(response, set, request, owned = false) {
133
136
  if (typeof response?.then === "function") return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
134
137
  if (Array.isArray(response)) return Response.json(response);
135
138
  if (typeof response?.toResponse === "function") return set ? mapResponse(response.toResponse(), set, request, owned) : mapCompactResponse(response.toResponse(), request, owned);
136
- if (responseTag(response) === "Cookie") return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
139
+ if (responseTag(response) === "Cookie" && Cookie.isCookie(response)) return set ? mapResponse(response.value, set, request, owned) : mapCompactResponse(response.value, request, owned);
137
140
  return new Response(response, set);
138
141
  }
139
142
  const handleResponse = createResponseHandler({
package/dist/base.d.ts CHANGED
@@ -66,7 +66,7 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
66
66
  '~hasDynamicWS'?: boolean;
67
67
  '~hasTrace'?: boolean;
68
68
  '~finalizeError'?: (context: Context, error: Error) => MaybePromise<Response>;
69
- '~programId': ProgramId;
69
+ get ['~programId'](): ProgramId;
70
70
  '~aotFingerprint'?: AotFingerprint;
71
71
  '~compilerSession'?: CompilerSession;
72
72
  '~generation'?: Generation;
@@ -864,7 +864,7 @@ declare class Elysia<const in out BasePath extends string = '', const in out Sco
864
864
  * on the config and triggers a fresh build of the fetch handler).
865
865
  */
866
866
  compile(): this;
867
- handler(index: number, immediate?: boolean | undefined, route?: InternalRoute, precomputedStatic?: Response, aliases?: StaticMapAliases, table?: RouteTable): CompiledHandler;
867
+ handler(index: number, immediate?: boolean, route?: InternalRoute, precomputedStatic?: Response, aliases?: StaticMapAliases, table?: RouteTable): CompiledHandler;
868
868
  ['~newGeneration'](): this;
869
869
  get fetch(): (request: Request, ...rest: any[]) => MaybePromise<Response>;
870
870
  get handle(): (url: string | Request, options?: RequestInit) => Promise<Response>;
package/dist/base.js CHANGED
@@ -19,6 +19,29 @@ memoirist = require_runtime.__toESM(memoirist);
19
19
  const useNodesBuffer = [];
20
20
  const emptyHistory = Object.freeze([]);
21
21
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
22
+ /**
23
+ * Every key a single route must answer to, in registration order.
24
+ *
25
+ * `registerLoose` is the caller's call, not this helper's: HTTP gates it on
26
+ * `canRegisterLoose` because its lookup retries the trailing slash, while WS
27
+ * has no such retry and must own both directions outright
28
+ */
29
+ const expandPaths = (path, needsEncode, registerLoose, explicit) => {
30
+ const variants = [path];
31
+ if (needsEncode) {
32
+ const encoded = encodeURI(path);
33
+ if (encoded !== path) variants.push(encoded);
34
+ }
35
+ if (!registerLoose) return variants;
36
+ const paths = [];
37
+ for (let v = 0; v < variants.length; v++) {
38
+ const p = variants[v];
39
+ paths.push(p);
40
+ const loose = require_utils.getLoosePath(p);
41
+ if (loose !== p && !explicit?.has(loose)) paths.push(loose);
42
+ }
43
+ return paths;
44
+ };
22
45
  var Elysia = class Elysia {
23
46
  #hasPlugin;
24
47
  #hasGlobal;
@@ -80,11 +103,14 @@ var Elysia = class Elysia {
80
103
  #jitRoute;
81
104
  #jitStatic;
82
105
  #jitAliases;
106
+ get ["~programId"]() {
107
+ return this;
108
+ }
83
109
  constructor(config) {
84
110
  /**
85
111
  * ! Don't tune
86
112
  *
87
- * Interesting JSC internal: These `= undefined` are load-bearing
113
+ * Interesting JSC internal: These `= undefined` shouldn't be removed
88
114
  *
89
115
  * Inline slots live inside the object cell, so JSC must pick their
90
116
  * count at allocation before any initializer runs
@@ -102,7 +128,8 @@ var Elysia = class Elysia {
102
128
  * Don't omitted field either for monomorphic access
103
129
  *
104
130
  * @see test/memory/instance-footprint.test.ts
105
- * Est. Overflow 37 -> 21 = butterfly 64 -> 32 slots = 738B -> 450B/instance
131
+ * Est. overflow 37 -> 21 = butterfly 64 -> 32 slots = 738B -> 450B;
132
+ * reusing the app as its program identity brings the current cell to ~386B.
106
133
  **/
107
134
  this["~config"] = config;
108
135
  this["~ext"] = void 0;
@@ -122,7 +149,6 @@ var Elysia = class Elysia {
122
149
  this["~introspect"] = void 0;
123
150
  this["~scopeChild"] = void 0;
124
151
  this["~scopeChildren"] = void 0;
125
- this["~programId"] = require_compile_aot.createProgramId();
126
152
  if (config) {
127
153
  const { prefix, name, seed, adapter } = config;
128
154
  this["~Prefix"] = prefix ? prefix.charCodeAt(0) === 47 ? prefix : `/${prefix}` : void 0;
@@ -1183,10 +1209,10 @@ var Elysia = class Elysia {
1183
1209
  this.fetch;
1184
1210
  return this;
1185
1211
  }
1186
- handler(index, immediate = this["~config"]?.precompile, route, precomputedStatic, aliases, table) {
1212
+ handler(index, immediate, route, precomputedStatic, aliases, table) {
1187
1213
  if (this.#compiled?.[index]) return this.#compiled[index];
1188
1214
  const compiled = this.#compiled ??= new Array(table?.length ?? this["~routes"].length);
1189
- if (immediate) {
1215
+ if (immediate ?? this["~config"]?.precompile) {
1190
1216
  const row = route ?? (table ? require_route_table.routeRow(table, index) : this["~routes"][index]);
1191
1217
  let handler;
1192
1218
  try {
@@ -1414,7 +1440,10 @@ var Elysia = class Elysia {
1414
1440
  const appChain = this["~hookChain"];
1415
1441
  if (this["~ext"]?.macro || this["~scopeChildren"] || appChain !== void 0 && appChain.refs) for (let i = 0; i < length; i++) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1416
1442
  else for (let i = 0; i < length; i++) if (this.#routeMayHaveModelRef(table, i)) this.#assertRouteModelRefs(require_route_table.routeRow(table, i), method[i]);
1417
- if (length) require_compile_aot.Compiled.claim(this["~programId"], this["~aotFingerprint"] = require_compile_aot.createAotFingerprint());
1443
+ if (length) {
1444
+ const programId = this["~programId"];
1445
+ if (!require_compile_aot.Compiled.claim(programId, this["~aotFingerprint"] = require_compile_aot.createAotFingerprint())) require_compile_aot.Compiled.assertUncontested(programId);
1446
+ }
1418
1447
  const isLoose = this["~config"]?.strictPath !== true;
1419
1448
  let explicitPaths;
1420
1449
  if (isLoose && table.hasLoose) {
@@ -1440,17 +1469,17 @@ var Elysia = class Elysia {
1440
1469
  const handler = ws[0];
1441
1470
  const options = ws[1];
1442
1471
  if (wsConfig === void 0 && wsCap.options) wsConfig = wsCap.options;
1472
+ const wsNeedsEncode = (routeFlags & require_route_table.RouteFlag.Encode) !== 0;
1443
1473
  if ((routeFlags & require_route_table.RouteFlag.Dynamic) !== 0) {
1444
- (this["~router"] ??= new memoirist.default({ loosePath: isLoose })).add("WS", routePath, handler);
1474
+ const wsRouter = this["~router"] ??= new memoirist.default({ loosePath: isLoose });
1475
+ const wsPaths = expandPaths(routePath, wsNeedsEncode, false, void 0);
1476
+ for (let v = 0; v < wsPaths.length; v++) wsRouter.add("WS", wsPaths[v], handler);
1445
1477
  this["~hasDynamicWS"] = true;
1446
1478
  } else {
1447
1479
  this.#initMap();
1448
1480
  const wsMap = this["~map"]["WS"] ??= require_utils.nullObject();
1449
- wsMap[routePath] = handler;
1450
- if (isLoose) {
1451
- const loose = require_utils.getLoosePath(routePath);
1452
- if (loose !== routePath && !explicitPaths?.get("WS")?.has(loose)) wsMap[loose] = handler;
1453
- }
1481
+ const wsPaths = expandPaths(routePath, wsNeedsEncode, isLoose, isLoose ? explicitPaths?.get("WS") : void 0);
1482
+ for (let v = 0; v < wsPaths.length; v++) wsMap[wsPaths[v]] = handler;
1454
1483
  }
1455
1484
  if (options && require_utils.isNotEmpty(options)) {
1456
1485
  wsConfig ??= require_utils.nullObject();
@@ -1461,26 +1490,12 @@ var Elysia = class Elysia {
1461
1490
  const isDynamic = (routeFlags & require_route_table.RouteFlag.Dynamic) !== 0;
1462
1491
  const needsEncode = (routeFlags & require_route_table.RouteFlag.Encode) !== 0;
1463
1492
  const registerLoose = isLoose && canRegisterLoose(routePath, isDynamic);
1464
- const explicitMain = registerLoose ? explicitPaths?.get(routeMethod) : void 0;
1465
1493
  if (!isDynamic && !needsEncode && !registerLoose) {
1466
1494
  const map = methods[routeMethod] ??= require_utils.nullObject();
1467
1495
  map[routePath] = this.handler(i, precompile, void 0, void 0, void 0, table);
1468
1496
  continue;
1469
1497
  }
1470
- const variants = [routePath];
1471
- if (needsEncode) {
1472
- const encoded = encodeURI(routePath);
1473
- if (encoded !== routePath) variants.push(encoded);
1474
- }
1475
- const paths = [];
1476
- for (let v = 0; v < variants.length; v++) {
1477
- const p = variants[v];
1478
- paths.push(p);
1479
- if (registerLoose) {
1480
- const loose = require_utils.getLoosePath(p);
1481
- if (loose !== p && !explicitMain?.has(loose)) paths.push(loose);
1482
- }
1483
- }
1498
+ const paths = expandPaths(routePath, needsEncode, registerLoose, registerLoose ? explicitPaths?.get(routeMethod) : void 0);
1484
1499
  if (isDynamic) {
1485
1500
  const router = this["~router"] ??= new memoirist.default({ loosePath: isLoose });
1486
1501
  const handler = this.handler(i, precompile, void 0, void 0, void 0, table);
package/dist/base.mjs CHANGED
@@ -2,7 +2,7 @@ import { isBun } from "./universal/constants.mjs";
2
2
  import { clonePlainDecorators, clonePlainDeep, coalesceSchemas, createErrorEventHandler, eventProperties, fnOrigin, fnv1a, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isEmpty, isNotEmpty, isRecordNumber, joinPath, macroOrigin, mapDeriveEntry, mergeDeep, mergeResponse, nullObject, pushField, schemaProperties, serializeMacroSeed } from "./utils.mjs";
3
3
  import { Ref } from "./type/bridge.mjs";
4
4
  import { isProduction } from "./universal/is-production.mjs";
5
- import { Capture, Compiled, beginCompilerSession, createAotFingerprint, createProgramId, endCompilerSession } from "./compile/aot.mjs";
5
+ import { Capture, Compiled, beginCompilerSession, createAotFingerprint, endCompilerSession } from "./compile/aot.mjs";
6
6
  import { traceCapabilityRequired, wsCapabilityRequired } from "./generation.mjs";
7
7
  import { compileHandler, composeRouteHook, localMacroRoot, resolveLocalHook } from "./compile/handler/index.mjs";
8
8
  import { RouteFlag, buildRouteTable, routeRow } from "./route-table.mjs";
@@ -16,6 +16,29 @@ import Memoirist from "memoirist";
16
16
  const useNodesBuffer = [];
17
17
  const emptyHistory = Object.freeze([]);
18
18
  const canRegisterLoose = (path, isDynamic) => !isDynamic && (path.length === 0 || path.charCodeAt(path.length - 1) === 47);
19
+ /**
20
+ * Every key a single route must answer to, in registration order.
21
+ *
22
+ * `registerLoose` is the caller's call, not this helper's: HTTP gates it on
23
+ * `canRegisterLoose` because its lookup retries the trailing slash, while WS
24
+ * has no such retry and must own both directions outright
25
+ */
26
+ const expandPaths = (path, needsEncode, registerLoose, explicit) => {
27
+ const variants = [path];
28
+ if (needsEncode) {
29
+ const encoded = encodeURI(path);
30
+ if (encoded !== path) variants.push(encoded);
31
+ }
32
+ if (!registerLoose) return variants;
33
+ const paths = [];
34
+ for (let v = 0; v < variants.length; v++) {
35
+ const p = variants[v];
36
+ paths.push(p);
37
+ const loose = getLoosePath(p);
38
+ if (loose !== p && !explicit?.has(loose)) paths.push(loose);
39
+ }
40
+ return paths;
41
+ };
19
42
  var Elysia = class Elysia {
20
43
  #hasPlugin;
21
44
  #hasGlobal;
@@ -77,11 +100,14 @@ var Elysia = class Elysia {
77
100
  #jitRoute;
78
101
  #jitStatic;
79
102
  #jitAliases;
103
+ get ["~programId"]() {
104
+ return this;
105
+ }
80
106
  constructor(config) {
81
107
  /**
82
108
  * ! Don't tune
83
109
  *
84
- * Interesting JSC internal: These `= undefined` are load-bearing
110
+ * Interesting JSC internal: These `= undefined` shouldn't be removed
85
111
  *
86
112
  * Inline slots live inside the object cell, so JSC must pick their
87
113
  * count at allocation before any initializer runs
@@ -99,7 +125,8 @@ var Elysia = class Elysia {
99
125
  * Don't omitted field either for monomorphic access
100
126
  *
101
127
  * @see test/memory/instance-footprint.test.ts
102
- * Est. Overflow 37 -> 21 = butterfly 64 -> 32 slots = 738B -> 450B/instance
128
+ * Est. overflow 37 -> 21 = butterfly 64 -> 32 slots = 738B -> 450B;
129
+ * reusing the app as its program identity brings the current cell to ~386B.
103
130
  **/
104
131
  this["~config"] = config;
105
132
  this["~ext"] = void 0;
@@ -119,7 +146,6 @@ var Elysia = class Elysia {
119
146
  this["~introspect"] = void 0;
120
147
  this["~scopeChild"] = void 0;
121
148
  this["~scopeChildren"] = void 0;
122
- this["~programId"] = createProgramId();
123
149
  if (config) {
124
150
  const { prefix, name, seed, adapter } = config;
125
151
  this["~Prefix"] = prefix ? prefix.charCodeAt(0) === 47 ? prefix : `/${prefix}` : void 0;
@@ -1180,10 +1206,10 @@ var Elysia = class Elysia {
1180
1206
  this.fetch;
1181
1207
  return this;
1182
1208
  }
1183
- handler(index, immediate = this["~config"]?.precompile, route, precomputedStatic, aliases, table) {
1209
+ handler(index, immediate, route, precomputedStatic, aliases, table) {
1184
1210
  if (this.#compiled?.[index]) return this.#compiled[index];
1185
1211
  const compiled = this.#compiled ??= new Array(table?.length ?? this["~routes"].length);
1186
- if (immediate) {
1212
+ if (immediate ?? this["~config"]?.precompile) {
1187
1213
  const row = route ?? (table ? routeRow(table, index) : this["~routes"][index]);
1188
1214
  let handler;
1189
1215
  try {
@@ -1411,7 +1437,10 @@ var Elysia = class Elysia {
1411
1437
  const appChain = this["~hookChain"];
1412
1438
  if (this["~ext"]?.macro || this["~scopeChildren"] || appChain !== void 0 && appChain.refs) for (let i = 0; i < length; i++) this.#assertRouteModelRefs(routeRow(table, i), method[i]);
1413
1439
  else for (let i = 0; i < length; i++) if (this.#routeMayHaveModelRef(table, i)) this.#assertRouteModelRefs(routeRow(table, i), method[i]);
1414
- if (length) Compiled.claim(this["~programId"], this["~aotFingerprint"] = createAotFingerprint());
1440
+ if (length) {
1441
+ const programId = this["~programId"];
1442
+ if (!Compiled.claim(programId, this["~aotFingerprint"] = createAotFingerprint())) Compiled.assertUncontested(programId);
1443
+ }
1415
1444
  const isLoose = this["~config"]?.strictPath !== true;
1416
1445
  let explicitPaths;
1417
1446
  if (isLoose && table.hasLoose) {
@@ -1437,17 +1466,17 @@ var Elysia = class Elysia {
1437
1466
  const handler = ws[0];
1438
1467
  const options = ws[1];
1439
1468
  if (wsConfig === void 0 && wsCap.options) wsConfig = wsCap.options;
1469
+ const wsNeedsEncode = (routeFlags & RouteFlag.Encode) !== 0;
1440
1470
  if ((routeFlags & RouteFlag.Dynamic) !== 0) {
1441
- (this["~router"] ??= new Memoirist({ loosePath: isLoose })).add("WS", routePath, handler);
1471
+ const wsRouter = this["~router"] ??= new Memoirist({ loosePath: isLoose });
1472
+ const wsPaths = expandPaths(routePath, wsNeedsEncode, false, void 0);
1473
+ for (let v = 0; v < wsPaths.length; v++) wsRouter.add("WS", wsPaths[v], handler);
1442
1474
  this["~hasDynamicWS"] = true;
1443
1475
  } else {
1444
1476
  this.#initMap();
1445
1477
  const wsMap = this["~map"]["WS"] ??= nullObject();
1446
- wsMap[routePath] = handler;
1447
- if (isLoose) {
1448
- const loose = getLoosePath(routePath);
1449
- if (loose !== routePath && !explicitPaths?.get("WS")?.has(loose)) wsMap[loose] = handler;
1450
- }
1478
+ const wsPaths = expandPaths(routePath, wsNeedsEncode, isLoose, isLoose ? explicitPaths?.get("WS") : void 0);
1479
+ for (let v = 0; v < wsPaths.length; v++) wsMap[wsPaths[v]] = handler;
1451
1480
  }
1452
1481
  if (options && isNotEmpty(options)) {
1453
1482
  wsConfig ??= nullObject();
@@ -1458,26 +1487,12 @@ var Elysia = class Elysia {
1458
1487
  const isDynamic = (routeFlags & RouteFlag.Dynamic) !== 0;
1459
1488
  const needsEncode = (routeFlags & RouteFlag.Encode) !== 0;
1460
1489
  const registerLoose = isLoose && canRegisterLoose(routePath, isDynamic);
1461
- const explicitMain = registerLoose ? explicitPaths?.get(routeMethod) : void 0;
1462
1490
  if (!isDynamic && !needsEncode && !registerLoose) {
1463
1491
  const map = methods[routeMethod] ??= nullObject();
1464
1492
  map[routePath] = this.handler(i, precompile, void 0, void 0, void 0, table);
1465
1493
  continue;
1466
1494
  }
1467
- const variants = [routePath];
1468
- if (needsEncode) {
1469
- const encoded = encodeURI(routePath);
1470
- if (encoded !== routePath) variants.push(encoded);
1471
- }
1472
- const paths = [];
1473
- for (let v = 0; v < variants.length; v++) {
1474
- const p = variants[v];
1475
- paths.push(p);
1476
- if (registerLoose) {
1477
- const loose = getLoosePath(p);
1478
- if (loose !== p && !explicitMain?.has(loose)) paths.push(loose);
1479
- }
1480
- }
1495
+ const paths = expandPaths(routePath, needsEncode, registerLoose, registerLoose ? explicitPaths?.get(routeMethod) : void 0);
1481
1496
  if (isDynamic) {
1482
1497
  const router = this["~router"] ??= new Memoirist({ loosePath: isLoose });
1483
1498
  const handler = this.handler(i, precompile, void 0, void 0, void 0, table);
@@ -8,8 +8,7 @@ interface AotFingerprint {
8
8
  interface ProgramId {
9
9
  readonly _: unique symbol;
10
10
  }
11
- declare const createProgramId: () => ProgramId;
12
- declare function createAotFingerprint(): AotFingerprint;
11
+ declare const createAotFingerprint: () => AotFingerprint;
13
12
  interface CompilerSession {
14
13
  readonly sucroseCache: Map<number, {
15
14
  content: string;
@@ -91,7 +90,7 @@ interface HandlerManifest {
91
90
  }
92
91
  interface CompiledSnapshot {
93
92
  registered: CompiledProgramRegistration | undefined;
94
- claimed: boolean;
93
+ claimed: WeakRef<ProgramId> | undefined;
95
94
  programs: WeakMap<ProgramId, CompiledProgram>;
96
95
  }
97
96
  interface CompiledProgramRegistration {
@@ -131,6 +130,7 @@ declare function reconstruct(): ReconstructImpl;
131
130
  declare abstract class Compiled {
132
131
  static register(manifest: CompiledProgramRegistration): void;
133
132
  static claim(id: ProgramId, fingerprint: AotFingerprint): boolean;
133
+ static assertUncontested(id: ProgramId): void;
134
134
  static get reconstruct(): ReconstructImpl | undefined;
135
135
  static set reconstruct(impl: ReconstructImpl | undefined);
136
136
  static getHandler(id: ProgramId | undefined, method: string, path: string): FrozenHandler | undefined;
@@ -284,4 +284,4 @@ interface CaptureImpl {
284
284
  declare let captureImpl: CaptureImpl | undefined;
285
285
  declare function setCaptureImpl(impl: CaptureImpl | undefined): void;
286
286
  //#endregion
287
- export { AOT_ABI, AOT_MANIFEST_FORMAT, AotFingerprint, Capture, CaptureImpl, CapturedHandler, CapturedMirror, CapturedValidator, CheckBuildResult, Compiled, CompiledProgramRegistration, CompiledSnapshot, CompilerSession, CompilerState, EMPTY_EXTERNALS, FrozenBothFactory, FrozenCheckFactory, FrozenHandler, FrozenMirror, FrozenMirrorFactory, FrozenValidator, HandlerManifest, ProgramId, ReconstructImpl, StringCodecNode, ValidatorManifest, ValidatorSlot, aotActivationError, beginCompilerSession, captureImpl, createAotFingerprint, createProgramId, endCompilerSession, getCompilerSession, reconstruct, setCaptureImpl };
287
+ export { AOT_ABI, AOT_MANIFEST_FORMAT, AotFingerprint, Capture, CaptureImpl, CapturedHandler, CapturedMirror, CapturedValidator, CheckBuildResult, Compiled, CompiledProgramRegistration, CompiledSnapshot, CompilerSession, CompilerState, EMPTY_EXTERNALS, FrozenBothFactory, FrozenCheckFactory, FrozenHandler, FrozenMirror, FrozenMirrorFactory, FrozenValidator, HandlerManifest, ProgramId, ReconstructImpl, StringCodecNode, ValidatorManifest, ValidatorSlot, aotActivationError, beginCompilerSession, captureImpl, createAotFingerprint, endCompilerSession, getCompilerSession, reconstruct, setCaptureImpl };
@@ -6,11 +6,8 @@ const require_package = require('../package.js');
6
6
  //#region src/compile/aot.ts
7
7
  const AOT_MANIFEST_FORMAT = 3;
8
8
  const AOT_ABI = `${require_package.version}:${3}`;
9
- const createProgramId = () => ({});
10
9
  const sharedAotFingerprint = Object.freeze({ abi: AOT_ABI });
11
- function createAotFingerprint() {
12
- return sharedAotFingerprint;
13
- }
10
+ const createAotFingerprint = () => sharedAotFingerprint;
14
11
  let activeSession;
15
12
  const newCompilerSession = () => ({ sucroseCache: /* @__PURE__ */ new Map() });
16
13
  function beginCompilerSession(app) {
@@ -41,7 +38,12 @@ function reconstruct() {
41
38
  return reconstructImpl;
42
39
  }
43
40
  let registered;
44
- let claimed = false;
41
+ /**
42
+ * App that consumed this process's manifest, held weakly: it only has to
43
+ * recognise a re-claim by the *same* app, and `assertUncontested` always
44
+ * compares against a live id, so a cleared ref answers as a strong one would
45
+ */
46
+ let claimed;
45
47
  let programs = /* @__PURE__ */ new WeakMap();
46
48
  const programFor = (id) => id ? programs.get(id) : void 0;
47
49
  const fingerprintMismatch = (manifest, actual) => {
@@ -54,14 +56,14 @@ const fingerprintMismatch = (manifest, actual) => {
54
56
  var Compiled = class {
55
57
  static register(manifest) {
56
58
  registered = manifest;
57
- claimed = false;
59
+ claimed = void 0;
58
60
  }
59
61
  static claim(id, fingerprint) {
60
62
  if (!registered || claimed) return false;
61
63
  const manifest = registered;
62
64
  const differences = fingerprintMismatch(manifest, fingerprint);
63
65
  if (differences.length) throw new Error(`[elysia-aot] Registered manifest fingerprint mismatch: ${differences.join("; ")}.`);
64
- claimed = true;
66
+ claimed = new WeakRef(id);
65
67
  registered = void 0;
66
68
  programs.set(id, {
67
69
  ...manifest,
@@ -69,6 +71,10 @@ var Compiled = class {
69
71
  });
70
72
  return true;
71
73
  }
74
+ static assertUncontested(id) {
75
+ if (claimed === void 0 || claimed.deref() === id || isAotBuildEnv()) return;
76
+ throw new Error("[elysia-aot] A second Elysia app built a router in this process. An AOT build seals one app per process.");
77
+ }
72
78
  static get reconstruct() {
73
79
  return reconstructImpl;
74
80
  }
@@ -116,7 +122,7 @@ var Compiled = class {
116
122
  /** @internal test isolation */
117
123
  static clear() {
118
124
  registered = void 0;
119
- claimed = false;
125
+ claimed = void 0;
120
126
  programs = /* @__PURE__ */ new WeakMap();
121
127
  }
122
128
  };
@@ -213,7 +219,6 @@ Object.defineProperty(exports, 'captureImpl', {
213
219
  }
214
220
  });
215
221
  exports.createAotFingerprint = createAotFingerprint;
216
- exports.createProgramId = createProgramId;
217
222
  exports.endCompilerSession = endCompilerSession;
218
223
  exports.getCompilerSession = getCompilerSession;
219
224
  exports.reconstruct = reconstruct;
@@ -5,11 +5,8 @@ import { version } from "../package.mjs";
5
5
  //#region src/compile/aot.ts
6
6
  const AOT_MANIFEST_FORMAT = 3;
7
7
  const AOT_ABI = `${version}:${3}`;
8
- const createProgramId = () => ({});
9
8
  const sharedAotFingerprint = Object.freeze({ abi: AOT_ABI });
10
- function createAotFingerprint() {
11
- return sharedAotFingerprint;
12
- }
9
+ const createAotFingerprint = () => sharedAotFingerprint;
13
10
  let activeSession;
14
11
  const newCompilerSession = () => ({ sucroseCache: /* @__PURE__ */ new Map() });
15
12
  function beginCompilerSession(app) {
@@ -40,7 +37,12 @@ function reconstruct() {
40
37
  return reconstructImpl;
41
38
  }
42
39
  let registered;
43
- let claimed = false;
40
+ /**
41
+ * App that consumed this process's manifest, held weakly: it only has to
42
+ * recognise a re-claim by the *same* app, and `assertUncontested` always
43
+ * compares against a live id, so a cleared ref answers as a strong one would
44
+ */
45
+ let claimed;
44
46
  let programs = /* @__PURE__ */ new WeakMap();
45
47
  const programFor = (id) => id ? programs.get(id) : void 0;
46
48
  const fingerprintMismatch = (manifest, actual) => {
@@ -53,14 +55,14 @@ const fingerprintMismatch = (manifest, actual) => {
53
55
  var Compiled = class {
54
56
  static register(manifest) {
55
57
  registered = manifest;
56
- claimed = false;
58
+ claimed = void 0;
57
59
  }
58
60
  static claim(id, fingerprint) {
59
61
  if (!registered || claimed) return false;
60
62
  const manifest = registered;
61
63
  const differences = fingerprintMismatch(manifest, fingerprint);
62
64
  if (differences.length) throw new Error(`[elysia-aot] Registered manifest fingerprint mismatch: ${differences.join("; ")}.`);
63
- claimed = true;
65
+ claimed = new WeakRef(id);
64
66
  registered = void 0;
65
67
  programs.set(id, {
66
68
  ...manifest,
@@ -68,6 +70,10 @@ var Compiled = class {
68
70
  });
69
71
  return true;
70
72
  }
73
+ static assertUncontested(id) {
74
+ if (claimed === void 0 || claimed.deref() === id || isAotBuildEnv()) return;
75
+ throw new Error("[elysia-aot] A second Elysia app built a router in this process. An AOT build seals one app per process.");
76
+ }
71
77
  static get reconstruct() {
72
78
  return reconstructImpl;
73
79
  }
@@ -115,7 +121,7 @@ var Compiled = class {
115
121
  /** @internal test isolation */
116
122
  static clear() {
117
123
  registered = void 0;
118
- claimed = false;
124
+ claimed = void 0;
119
125
  programs = /* @__PURE__ */ new WeakMap();
120
126
  }
121
127
  };
@@ -197,4 +203,4 @@ function setCaptureImpl(impl) {
197
203
  }
198
204
 
199
205
  //#endregion
200
- export { AOT_ABI, AOT_MANIFEST_FORMAT, Capture, Compiled, CompilerState, EMPTY_EXTERNALS, aotActivationError, beginCompilerSession, captureImpl, createAotFingerprint, createProgramId, endCompilerSession, getCompilerSession, reconstruct, setCaptureImpl };
206
+ export { AOT_ABI, AOT_MANIFEST_FORMAT, Capture, Compiled, CompilerState, EMPTY_EXTERNALS, aotActivationError, beginCompilerSession, captureImpl, createAotFingerprint, endCompilerSession, getCompilerSession, reconstruct, setCaptureImpl };
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_utils = require('../../utils.js');
3
+ const require_cookie_crypto = require('../../cookie/crypto.js');
3
4
  const require_compile_aot = require('../aot.js');
4
5
  const require_generation = require('../../generation.js');
5
- const require_cookie_crypto = require('../../cookie/crypto.js');
6
6
  const require_cookie_config = require('../../cookie/config.js');
7
7
  const require_compile_utils = require('../utils.js');
8
8
  const require_compile_jit_probe = require('../jit-probe.js');
@@ -1,7 +1,7 @@
1
1
  import { isNotEmpty } from "../../utils.mjs";
2
+ import { hasSyncHmac } from "../../cookie/crypto.mjs";
2
3
  import { Capture } from "../aot.mjs";
3
4
  import { frozenRootOf, resolvedTraceOf, traceCapabilityRequired } from "../../generation.mjs";
4
- import { hasSyncHmac } from "../../cookie/crypto.mjs";
5
5
  import { compileCookieConfig } from "../../cookie/config.mjs";
6
6
  import { isAsyncFunction, isAsyncLifecycle, mayReturnPromise } from "../utils.mjs";
7
7
  import { JITProbe } from "../jit-probe.mjs";
@@ -12,8 +12,8 @@ declare function resolveWSLocalHook(scope: AnyElysia, hook: Partial<AnyLocalHook
12
12
  */
13
13
  declare function clearHandlerAnalysisCaches(root: AnyElysia): void;
14
14
  declare const localMacroRoot: (instance: AnyElysia, root: AnyElysia) => AnyElysia;
15
- declare function buildNativeStaticResponse([,, handler, instance, localHook, appHook, inheritedChain, macroScope]: InternalRoute, root: AnyElysia): Response | undefined;
15
+ declare function buildNativeStaticResponse(route: InternalRoute, root: AnyElysia): Response | undefined;
16
16
  declare function composeRouteHook(instance: AnyElysia, localHook: Partial<AnyLocalHook> | undefined, appHook: ChainNode | undefined, inheritedChain: ChainNode | undefined, root: AnyElysia, macroScope?: AnyElysia): AnyLocalHook | undefined;
17
- declare function compileHandler([_method, path, handler, instance, localHook, appHook, inheritedChain, macroScope]: InternalRoute, root: AnyElysia, precomputedStatic?: Response): CompiledHandler;
17
+ declare function compileHandler(route: InternalRoute, root: AnyElysia, precomputedStatic?: Response): CompiledHandler;
18
18
  //#endregion
19
19
  export { buildNativeStaticResponse, clearHandlerAnalysisCaches, compileHandler, composeRouteHook, localMacroRoot, resolveLocalHook, resolveWSLocalHook, setCaptureHeaderShorthand };