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
package/dist/ws/route.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  import { isBun } from "../universal/constants.mjs";
2
- import { deriveEntryFn, isNotEmpty, nullObject } from "../utils.mjs";
2
+ import { assignOwn, deriveEntryFn, isNotEmpty, nullObject } from "../utils.mjs";
3
3
  import { isBridgeLive } from "../type/bridge.mjs";
4
- import { isProduction } from "../universal/is-production.mjs";
5
4
  import { ElysiaError, ElysiaStatus, ValidationError, internalServerErrorBodyString, problemBody, problemResponse } from "../error.mjs";
6
5
  import { mapResponse } from "../adapter/web-standard/handler.mjs";
7
6
  import { parseQueryFromURL } from "../parse-query.mjs";
8
7
  import { frozenRootOf } from "../generation.mjs";
9
- import { createErrorHandler } from "../handler/error.mjs";
8
+ import { compileCookieConfig } from "../cookie/config.mjs";
9
+ import { buildCookieJar, parseCookieRaw } from "../cookie/utils.mjs";
10
+ import { adoptErrorType, claimsProblemType, createErrorHandler, readAnnotation, resolveStatus, statusFallbackBody } from "../handler/error.mjs";
10
11
  import { deriveModes, getQueryParseChannels, replaceDeriveContext } from "../compile/handler/utils.mjs";
11
12
  import { StandardValidator } from "../validator/index.mjs";
12
13
  import { RouteValidator } from "../validator/route.mjs";
@@ -180,9 +181,45 @@ function wsErrorFrame(error) {
180
181
  }
181
182
  return wsErrorFrameFallback(error);
182
183
  }
184
+ /**
185
+ * Frame served by an error that claims an RFC 9457 problem document, mirroring
186
+ * the HTTP tiers in `handler/error.ts`: `value` replaces the whole frame,
187
+ * `detail` fills the `detail` member, the message is the last resort.
188
+ *
189
+ * A frame carries no headers, so a `headers` annotation is ignored outright
190
+ * rather than half-applied
191
+ */
192
+ function wsProblemFrame(error) {
193
+ const status = resolveStatus(error.status);
194
+ const served = typeof status === "number" && status >= 100 ? status : 500;
195
+ const stringify = (value) => typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
196
+ const problemFrame = (detail) => JSON.stringify(problemBody({
197
+ type: error.type ?? "about:blank",
198
+ detail,
199
+ status: served
200
+ }));
201
+ const serveMessage = () => problemFrame(statusFallbackBody(error, served));
202
+ const serveDetail = () => {
203
+ const detail = readAnnotation(error, "detail", true);
204
+ if (detail === void 0) return serveMessage();
205
+ if (detail instanceof Promise) return detail.then((resolved) => resolved === void 0 ? serveMessage() : problemFrame(resolved)).catch(() => wsLegacyFrame(error));
206
+ return problemFrame(detail);
207
+ };
208
+ const value = readAnnotation(error, "value", true);
209
+ if (value === void 0) return serveDetail();
210
+ if (value instanceof Promise) return value.then((resolved) => resolved === void 0 ? serveDetail() : stringify(resolved)).catch(() => wsLegacyFrame(error));
211
+ return stringify(value);
212
+ }
183
213
  function wsErrorFrameFallback(error) {
214
+ if (claimsProblemType(error)) try {
215
+ return wsProblemFrame(error);
216
+ } catch {}
217
+ return wsLegacyFrame(error);
218
+ }
219
+ /** Frame an error that never self-described has always served */
220
+ function wsLegacyFrame(error) {
184
221
  if (error?.status) {
185
- const body = error.response !== void 0 ? error.response : isProduction() ? error.status >= 500 ? "Internal Server Error" : "" : error.message ?? "";
222
+ const body = statusFallbackBody(error, error.status);
186
223
  return typeof body === "object" ? JSON.stringify(body) : String(body);
187
224
  }
188
225
  return internalServerErrorBodyString(error);
@@ -217,13 +254,20 @@ const wsOptions = [
217
254
  ];
218
255
  function buildWSRoute(route, app) {
219
256
  const hook = resolveWSLocalHook(localMacroRoot(route[7] ?? route[3] ?? app, app), route[4], app) ?? nullObject();
257
+ const instance = route[3] ?? app;
258
+ const appHookChain = route[5];
259
+ const inheritedChain = route[6];
260
+ const composed = composeRouteHook(instance, route[4], appHookChain, inheritedChain, app, route[7]) ?? hook;
220
261
  let validators;
221
- const frozenEager = isBridgeLive() ? void 0 : buildFrozenRouteValidator(hook, app, "WS", route[1]);
262
+ const frozenEager = isBridgeLive() ? void 0 : buildFrozenRouteValidator(composed, app, "WS", route[1]);
222
263
  if (frozenEager) validators = frozenEager;
223
264
  else try {
224
- validators = new RouteValidator(hook, {
265
+ validators = new RouteValidator(composed, {
225
266
  models: frozenRootOf(app)["~ext"]?.models,
226
267
  app,
268
+ normalize: frozenRootOf(app)["~config"]?.normalize,
269
+ sanitize: frozenRootOf(app)["~config"]?.sanitize,
270
+ schemas: composed.schemas,
227
271
  aot: {
228
272
  method: "WS",
229
273
  path: route[1]
@@ -232,18 +276,17 @@ function buildWSRoute(route, app) {
232
276
  });
233
277
  } catch (error) {
234
278
  if (!isBridgeNotInitialized(error)) throw error;
235
- const frozen = buildFrozenRouteValidator(hook, app, "WS", route[1]);
279
+ const frozen = buildFrozenRouteValidator(composed, app, "WS", route[1]);
236
280
  if (!frozen) throw error;
237
281
  validators = frozen;
238
282
  }
239
283
  const responseValidator = validators.response;
240
284
  const defaultResponseValidator = responseValidator ? responseValidator[200] ?? responseValidator[Object.keys(responseValidator)[0]] : void 0;
285
+ const cookieConfig = validators.cookie ? compileCookieConfig(composed.cookie, frozenRootOf(app)["~config"]?.cookie) : void 0;
286
+ const cookieIsOptional = !!composed.cookie?.["~optional"];
241
287
  const queryChannels = getQueryParseChannels(validators.query?.schema);
242
288
  const queryArray = queryChannels?.array;
243
289
  const queryObject = queryChannels?.object;
244
- const instance = route[3] ?? app;
245
- const appHookChain = route[5];
246
- const inheritedChain = route[6];
247
290
  const flatAppHook = composeRouteHook(instance, void 0, appHookChain, inheritedChain, app, route[7]) ?? {};
248
291
  const parseHooks = hook.parse == null ? [] : Array.isArray(hook.parse) ? hook.parse : [hook.parse];
249
292
  const transforms = concatHooks(flatAppHook.transform, hook.transform);
@@ -271,6 +314,7 @@ function buildWSRoute(route, app) {
271
314
  break;
272
315
  }
273
316
  if (r !== void 0) {
317
+ r = adoptErrorType(r, error);
274
318
  try {
275
319
  await handleWSResponse(ws, r, mapResponses);
276
320
  } catch {}
@@ -418,6 +462,15 @@ function buildWSRoute(route, app) {
418
462
  if (r instanceof Promise) r = await r;
419
463
  context.headers = r;
420
464
  }
465
+ if (cookieConfig) {
466
+ const raw = await parseCookieRaw(request.headers.get("cookie"), cookieConfig);
467
+ let r = raw;
468
+ if (!cookieIsOptional || Object.keys(raw).length) {
469
+ r = validateUpgradeChannel(validators.cookie, raw, "cookie");
470
+ if (r instanceof Promise) r = await r;
471
+ }
472
+ context.cookie = buildCookieJar(context.set, r, cookieConfig);
473
+ }
421
474
  for (let i = 0; i < transforms.length; i++) {
422
475
  const r = transforms[i](context);
423
476
  if (r instanceof Promise) await r;
@@ -429,7 +482,7 @@ function buildWSRoute(route, app) {
429
482
  const deriveMode = upgradeDeriveModes?.[i];
430
483
  if (deriveMode !== void 0 && !(r instanceof ElysiaStatus)) {
431
484
  if (r && typeof r === "object") if (deriveMode) context = replaceDeriveContext(context, r);
432
- else Object.assign(context, r);
485
+ else assignOwn(context, r);
433
486
  } else if (r !== void 0) {
434
487
  if (r instanceof Response) return r;
435
488
  return mapResponse(r, context.set, context.request);
@@ -484,22 +537,37 @@ function resolveWSOptions(entries) {
484
537
  const incoming = entry.value[key];
485
538
  if (key in result && result[key] !== incoming && !warned?.has(key)) {
486
539
  (warned ??= /* @__PURE__ */ new Set()).add(key);
487
- console.warn(`[Elysia] Conflicting WebSocket option '${key}' across .use(websocket()) registrations; the shallowest (nearest-root) registration wins.`);
540
+ console.warn(`[Elysia] Conflicting WebSocket option '${key}' across .use(websocket()). Using nearest-root registration.`);
488
541
  }
489
542
  result[key] = incoming;
490
543
  }
491
544
  return result;
492
545
  }
546
+ const wsLimits = [
547
+ "maxPayloadLength",
548
+ "backpressureLimit",
549
+ "idleTimeout"
550
+ ];
551
+ const routeOwnedKeys = /* @__PURE__ */ new WeakMap();
493
552
  function accumulateWSOptions(target, routeOptions, _path) {
494
553
  if (!isNotEmpty(routeOptions)) return;
495
- if (isBun) {
496
- for (const key in routeOptions) if (key in target && target[key] !== routeOptions[key]) {
497
- console.warn(`[Elysia] Conflicting per-route WebSocket option '${key}'\nBun uses one global WebSocket config per server, per-route values are not enforced (the last-registered route wins).`);
498
- console.warn((/* @__PURE__ */ new Error()).stack);
554
+ let owned = routeOwnedKeys.get(target);
555
+ if (!owned) routeOwnedKeys.set(target, owned = /* @__PURE__ */ new Set());
556
+ for (const key in routeOptions) {
557
+ const incoming = routeOptions[key];
558
+ const current = target[key];
559
+ if (key in target && current !== incoming) {
560
+ if (isBun) {
561
+ console.warn(`[Elysia] Conflicting per-route WebSocket option '${key}'\nBun uses one global WebSocket config per server, per-route values are not enforced (for limits the strictest route wins, otherwise the last-registered route).`);
562
+ console.warn((/* @__PURE__ */ new Error()).stack);
563
+ }
564
+ if (owned.has(key) && typeof current === "number" && typeof incoming === "number" && current < incoming && wsLimits.includes(key)) continue;
499
565
  }
566
+ owned.add(key);
567
+ target[key] = incoming;
500
568
  }
501
- Object.assign(target, routeOptions);
502
569
  }
570
+ const MAX_INFLIGHT_MESSAGES = 256;
503
571
  function buildGlobalWSHandler() {
504
572
  function getElysia(ws) {
505
573
  let elysia = ws.data.elysia;
@@ -515,19 +583,52 @@ function buildGlobalWSHandler() {
515
583
  ws.send(typeof error?.message === "string" ? error.message : "Internal Server Error");
516
584
  } catch {}
517
585
  };
586
+ const release = (ws) => {
587
+ const inflight = ws.data.inflight;
588
+ if (inflight) ws.data.inflight = inflight - 1;
589
+ };
590
+ function dispatch(ws, message) {
591
+ let result;
592
+ try {
593
+ result = ws.data.message(getElysia(ws), message);
594
+ } catch (error) {
595
+ release(ws);
596
+ sendErrMsg(ws, error);
597
+ return;
598
+ }
599
+ if (result instanceof Promise) result.then(() => release(ws), (error) => {
600
+ release(ws);
601
+ sendErrMsg(ws, error);
602
+ });
603
+ else release(ws);
604
+ }
518
605
  return {
519
606
  message(ws, message) {
520
- let result;
521
- try {
522
- result = ws.data.message?.(getElysia(ws), message);
523
- } catch (error) {
524
- sendErrMsg(ws, error);
607
+ const data = ws.data;
608
+ if (!data.message) return;
609
+ const inflight = (data.inflight ?? 0) + 1;
610
+ if (inflight > MAX_INFLIGHT_MESSAGES) {
611
+ try {
612
+ ws.close(1013, "Too many in-flight messages");
613
+ } catch {}
525
614
  return;
526
615
  }
527
- if (result instanceof Promise) result.catch((error) => sendErrMsg(ws, error));
616
+ data.inflight = inflight;
617
+ const opening = data.opening;
618
+ if (opening) return void opening.then(() => {
619
+ if (ws.readyState > 1) return release(ws);
620
+ dispatch(ws, message);
621
+ });
622
+ dispatch(ws, message);
528
623
  },
529
624
  open(ws) {
530
- ws.data.open?.(getElysia(ws));
625
+ const result = ws.data.open?.(getElysia(ws));
626
+ if (result instanceof Promise) {
627
+ const clear = () => {
628
+ ws.data.opening = void 0;
629
+ };
630
+ ws.data.opening = result.then(clear, clear);
631
+ }
531
632
  },
532
633
  drain(ws) {
533
634
  const elyWs = getElysia(ws);
@@ -537,9 +638,11 @@ function buildGlobalWSHandler() {
537
638
  close(ws, code, reason) {
538
639
  const elyWs = getElysia(ws);
539
640
  drainWaiters(elyWs);
641
+ ws.data.opening = void 0;
540
642
  if (ws.data.closeHandlerInvoked) return;
541
643
  ws.data.closeHandlerInvoked = true;
542
- ws.data.close?.(elyWs, code, reason);
644
+ const result = ws.data.close?.(elyWs, code, reason);
645
+ if (result instanceof Promise) result.catch(() => {});
543
646
  },
544
647
  ping(ws, data) {
545
648
  ws.data.ping?.(getElysia(ws), data);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elysia",
3
3
  "description": "Ergonomic Framework for Human",
4
- "version": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.3",
5
5
  "author": {
6
6
  "name": "saltyAom",
7
7
  "url": "https://github.com/SaltyAom",
@@ -238,7 +238,9 @@
238
238
  "./dist/type/exports.mjs",
239
239
  "./dist/type/exports.js",
240
240
  "./dist/type/bridge-live.mjs",
241
- "./dist/type/bridge-live.js"
241
+ "./dist/type/bridge-live.js",
242
+ "./dist/type/typebox-value-live.mjs",
243
+ "./dist/type/typebox-value-live.js"
242
244
  ],
243
245
  "dependencies": {
244
246
  "deuri": "^3.0.0",