elysia 2.0.0-exp.50 → 2.0.0-exp.52

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 (73) hide show
  1. package/dist/adapter/bun/index.js +26 -2
  2. package/dist/adapter/bun/index.mjs +26 -2
  3. package/dist/adapter/origin.d.ts +63 -0
  4. package/dist/adapter/origin.js +64 -0
  5. package/dist/adapter/origin.mjs +62 -0
  6. package/dist/base.js +283 -273
  7. package/dist/base.mjs +284 -274
  8. package/dist/compile/analysis-cache.js +0 -2
  9. package/dist/compile/analysis-cache.mjs +0 -2
  10. package/dist/compile/aot.js +2 -1
  11. package/dist/compile/aot.mjs +2 -1
  12. package/dist/compile/handler/descriptor.d.ts +3 -6
  13. package/dist/compile/handler/descriptor.js +5 -7
  14. package/dist/compile/handler/descriptor.mjs +5 -7
  15. package/dist/compile/handler/index.js +16 -9
  16. package/dist/compile/handler/index.mjs +18 -11
  17. package/dist/compile/handler/jit.d.ts +3 -1
  18. package/dist/compile/handler/jit.js +48 -35
  19. package/dist/compile/handler/jit.mjs +49 -37
  20. package/dist/compile/handler/params.js +1 -0
  21. package/dist/compile/handler/params.mjs +2 -1
  22. package/dist/compile/handler/utils.d.ts +12 -3
  23. package/dist/compile/handler/utils.js +24 -5
  24. package/dist/compile/handler/utils.mjs +24 -6
  25. package/dist/context.d.ts +2 -2
  26. package/dist/cookie/crypto.js +15 -10
  27. package/dist/cookie/crypto.mjs +15 -10
  28. package/dist/handler/fetch.js +28 -6
  29. package/dist/handler/fetch.mjs +28 -6
  30. package/dist/package.js +1 -1
  31. package/dist/package.mjs +1 -1
  32. package/dist/plugin/aot/core.js +1 -1
  33. package/dist/plugin/aot/core.mjs +1 -1
  34. package/dist/route-table.d.ts +1 -1
  35. package/dist/route-table.js +3 -3
  36. package/dist/route-table.mjs +3 -3
  37. package/dist/schema-snapshot.d.ts +4 -0
  38. package/dist/schema-snapshot.js +193 -33
  39. package/dist/schema-snapshot.mjs +194 -33
  40. package/dist/sucrose.js +64 -44
  41. package/dist/sucrose.mjs +64 -44
  42. package/dist/type/bridge-live.d.ts +2 -2
  43. package/dist/type/bridge-live.js +1 -0
  44. package/dist/type/bridge-live.mjs +2 -2
  45. package/dist/type/bridge.d.ts +4 -2
  46. package/dist/type/bridge.js +8 -0
  47. package/dist/type/bridge.mjs +3 -1
  48. package/dist/type/compat.js +1 -0
  49. package/dist/type/compat.mjs +2 -1
  50. package/dist/type/elysia/boolean-string.js +2 -1
  51. package/dist/type/elysia/boolean-string.mjs +2 -1
  52. package/dist/type/elysia/integer-string.js +4 -2
  53. package/dist/type/elysia/integer-string.mjs +4 -2
  54. package/dist/type/elysia/numeric.js +29 -3
  55. package/dist/type/elysia/numeric.mjs +29 -3
  56. package/dist/type/shared.d.ts +10 -1
  57. package/dist/type/shared.js +18 -0
  58. package/dist/type/shared.mjs +17 -1
  59. package/dist/type/utils.js +10 -1
  60. package/dist/type/utils.mjs +10 -1
  61. package/dist/type/validator/index.d.ts +2 -2
  62. package/dist/type/validator/index.js +5 -4
  63. package/dist/type/validator/index.mjs +7 -7
  64. package/dist/type/validator/validator-cache.d.ts +11 -5
  65. package/dist/type/validator/validator-cache.js +153 -53
  66. package/dist/type/validator/validator-cache.mjs +150 -53
  67. package/dist/types.d.ts +10 -0
  68. package/dist/utils.d.ts +9 -5
  69. package/dist/utils.js +47 -43
  70. package/dist/utils.mjs +47 -43
  71. package/dist/validator/route.js +2 -1
  72. package/dist/validator/route.mjs +3 -2
  73. package/package.json +1 -1
@@ -4,10 +4,34 @@ const require_constants = require('../../constants.js');
4
4
  const require_utils = require('../../utils.js');
5
5
  const require_adapter_web_standard_index = require('../web-standard/index.js');
6
6
  const require_generation = require('../../generation.js');
7
+ const require_adapter_origin = require('../origin.js');
7
8
  const require_compile_handler_index = require('../../compile/handler/index.js');
8
9
  const require_route_table = require('../../route-table.js');
9
10
 
10
11
  //#region src/adapter/bun/index.ts
12
+ /**
13
+ * ! This may looks like it would cause race condition, but it is not
14
+ * Bun is single-threaded and synchronous, so the `finally` block will
15
+ * always run before the next request comes in
16
+ * @see ../origin.ts
17
+ *
18
+ * Publish the request `Bun.serve` handed us so the pipeline can prove, before
19
+ * its first suspension, that it holds the untouched original and may defer
20
+ * materializing `request.signal`. The proof happens in `createFetchHandler`'s
21
+ * prologue when there is a request hook, and at the compiled route's entry
22
+ * probe otherwise — both inside the same synchronous frame.
23
+ *
24
+ * `finally` runs on the synchronous return of `fetch` (the returned promise is
25
+ * not awaited), so the slot is live only for the handler's synchronous prologue.
26
+ */
27
+ const withOrigin = (fetch) => (request, server) => {
28
+ require_adapter_origin.origin.request = request;
29
+ try {
30
+ return fetch(request, server);
31
+ } finally {
32
+ require_adapter_origin.origin.request = void 0;
33
+ }
34
+ };
11
35
  const nativeStaticMethods = new Set([
12
36
  "GET",
13
37
  "POST",
@@ -124,7 +148,7 @@ const BunAdapter = require_adapter_index.createAdapter({
124
148
  let built;
125
149
  if (!needsGate) built = build();
126
150
  if (needsGate) serve.fetch = (request, server) => ready.then(() => app.fetch(request, server));
127
- else serve.fetch = built.fetch;
151
+ else serve.fetch = withOrigin(built.fetch);
128
152
  const server = app.server = Bun.serve(serve);
129
153
  const reload = () => {
130
154
  try {
@@ -172,7 +196,7 @@ const BunAdapter = require_adapter_index.createAdapter({
172
196
  const publish = () => {
173
197
  if (app.server !== server) return;
174
198
  built ??= build();
175
- serve.fetch = built.fetch;
199
+ serve.fetch = withOrigin(built.fetch);
176
200
  if (built.websocket) serve.websocket = built.websocket;
177
201
  if (built.routes) serve.routes = built.routes[0];
178
202
  if (needsGate || built.websocket || built.routes) reload();
@@ -3,10 +3,34 @@ import { isDynamicRegex, needEncodeRegex } from "../../constants.mjs";
3
3
  import { flattenChain, getLoosePath, nullObject } from "../../utils.mjs";
4
4
  import { WebStandardAdapter } from "../web-standard/index.mjs";
5
5
  import { frozenRootOf, resolvedWsOf } from "../../generation.mjs";
6
+ import { origin } from "../origin.mjs";
6
7
  import { buildNativeStaticResponse } from "../../compile/handler/index.mjs";
7
8
  import { routeRow } from "../../route-table.mjs";
8
9
 
9
10
  //#region src/adapter/bun/index.ts
11
+ /**
12
+ * ! This may looks like it would cause race condition, but it is not
13
+ * Bun is single-threaded and synchronous, so the `finally` block will
14
+ * always run before the next request comes in
15
+ * @see ../origin.ts
16
+ *
17
+ * Publish the request `Bun.serve` handed us so the pipeline can prove, before
18
+ * its first suspension, that it holds the untouched original and may defer
19
+ * materializing `request.signal`. The proof happens in `createFetchHandler`'s
20
+ * prologue when there is a request hook, and at the compiled route's entry
21
+ * probe otherwise — both inside the same synchronous frame.
22
+ *
23
+ * `finally` runs on the synchronous return of `fetch` (the returned promise is
24
+ * not awaited), so the slot is live only for the handler's synchronous prologue.
25
+ */
26
+ const withOrigin = (fetch) => (request, server) => {
27
+ origin.request = request;
28
+ try {
29
+ return fetch(request, server);
30
+ } finally {
31
+ origin.request = void 0;
32
+ }
33
+ };
10
34
  const nativeStaticMethods = new Set([
11
35
  "GET",
12
36
  "POST",
@@ -123,7 +147,7 @@ const BunAdapter = createAdapter({
123
147
  let built;
124
148
  if (!needsGate) built = build();
125
149
  if (needsGate) serve.fetch = (request, server) => ready.then(() => app.fetch(request, server));
126
- else serve.fetch = built.fetch;
150
+ else serve.fetch = withOrigin(built.fetch);
127
151
  const server = app.server = Bun.serve(serve);
128
152
  const reload = () => {
129
153
  try {
@@ -171,7 +195,7 @@ const BunAdapter = createAdapter({
171
195
  const publish = () => {
172
196
  if (app.server !== server) return;
173
197
  built ??= build();
174
- serve.fetch = built.fetch;
198
+ serve.fetch = withOrigin(built.fetch);
175
199
  if (built.websocket) serve.websocket = built.websocket;
176
200
  if (built.routes) serve.routes = built.routes[0];
177
201
  if (needsGate || built.websocket || built.routes) reload();
@@ -0,0 +1,63 @@
1
+ //#region src/adapter/origin.d.ts
2
+ /**
3
+ * Provenance channel for deferred abort-signal arming.
4
+ *
5
+ * Bun materializes `Request.signal` lazily (~214ns on the first read), so
6
+ * Elysia only pays for it once the pipeline can actually observe an abort,
7
+ * that is, after it suspends. Deferring that read is only sound for the exact
8
+ * `Request` instance `Bun.serve` handed to `fetch`: any other request
9
+ * (`app.handle`, a non-Bun adapter, or a `.wrap()` HOC that substitutes the
10
+ * request) may carry a user-controlled signal that is already aborted at entry
11
+ * and therefore must be observed synchronously.
12
+ *
13
+ * The Bun adapter publishes the untouched original here for the *synchronous*
14
+ * prologue of `app.fetch` and, on the lane where `fetch` has no request hook
15
+ * to run, for the compiled route's own entry probe, which is still inside that
16
+ * same synchronous frame, then clears it in `finally`, so the window closes
17
+ * before the first suspension and cannot leak across concurrent requests.
18
+ * A `.wrap()` HOC that replaces the request or defers calling `next()` past a
19
+ * microtask simply misses the window, fetch handler falls back to eager check
20
+ *
21
+ * ## "A single shared slot that every request writes to, so a race condition?"
22
+ *
23
+ * It looks like one, but it can't be. Explained like you're five:
24
+ *
25
+ * JavaScript only ever does ONE thing at a time. When a request comes in, the
26
+ * adapter writes its name on the whiteboard (`origin.request = request`),
27
+ * walks it down the hall (`fetch(request)` the synchronous part, where the
28
+ * one and only identity check happens, in `fetch`'s prologue, or at the
29
+ * compiled route's entry when `fetch` had no hook to run first), and erases
30
+ * the whiteboard `finally`
31
+ * all in one uninterruptible turn. The next request's turn CANNOT start
32
+ * until this turn is completely finished, because that is how the event loop
33
+ * works: run-to-completion, no preemption. So request B can never see request
34
+ * A's name on the board, by the time B's turn starts, the board was already
35
+ * erased at the end of A's turn.
36
+ *
37
+ * "But the handler is async!" — the `await`s inside it run LATER, in separate
38
+ * turns. That is fine, because the check already happened: the verdict was
39
+ * copied onto that request's own context (`'~sig'`) during the synchronous
40
+ * part, and the code that runs after an `await` only ever reads the context,
41
+ * never this slot.
42
+ *
43
+ * And if any assumption here ever breaks, a runtime that re-enters, a wrap
44
+ * that delays, a future edit that sneaks an `await` in before the check the
45
+ * comparison simply MISSES and the request falls back to eager arming, which
46
+ * is the exact pre-optimization behavior. Every failure mode makes a request
47
+ * slightly slower, never incorrect
48
+ *
49
+ * Adversarial proof, not just argument: `test/core/abort-race.test.ts` storms
50
+ * concurrent servers, in-process `handle()` calls, request-substituting and
51
+ * `next()`-delaying wraps at this slot and asserts zero misclassification.
52
+ *
53
+ * Never infer the Bun runtime from `fetch`'s second argument: Cloudflare
54
+ * Workers pass `env` there.
55
+ *
56
+ * It's insanely stupid, but it works. As Bun is single-threaded and synchronous
57
+ * so the `finally` block will always run before the next request comes in.
58
+ */
59
+ declare const origin: {
60
+ request: Request | undefined;
61
+ };
62
+ //#endregion
63
+ export { origin };
@@ -0,0 +1,64 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/adapter/origin.ts
4
+ /**
5
+ * Provenance channel for deferred abort-signal arming.
6
+ *
7
+ * Bun materializes `Request.signal` lazily (~214ns on the first read), so
8
+ * Elysia only pays for it once the pipeline can actually observe an abort,
9
+ * that is, after it suspends. Deferring that read is only sound for the exact
10
+ * `Request` instance `Bun.serve` handed to `fetch`: any other request
11
+ * (`app.handle`, a non-Bun adapter, or a `.wrap()` HOC that substitutes the
12
+ * request) may carry a user-controlled signal that is already aborted at entry
13
+ * and therefore must be observed synchronously.
14
+ *
15
+ * The Bun adapter publishes the untouched original here for the *synchronous*
16
+ * prologue of `app.fetch` and, on the lane where `fetch` has no request hook
17
+ * to run, for the compiled route's own entry probe, which is still inside that
18
+ * same synchronous frame, then clears it in `finally`, so the window closes
19
+ * before the first suspension and cannot leak across concurrent requests.
20
+ * A `.wrap()` HOC that replaces the request or defers calling `next()` past a
21
+ * microtask simply misses the window, fetch handler falls back to eager check
22
+ *
23
+ * ## "A single shared slot that every request writes to, so a race condition?"
24
+ *
25
+ * It looks like one, but it can't be. Explained like you're five:
26
+ *
27
+ * JavaScript only ever does ONE thing at a time. When a request comes in, the
28
+ * adapter writes its name on the whiteboard (`origin.request = request`),
29
+ * walks it down the hall (`fetch(request)` the synchronous part, where the
30
+ * one and only identity check happens, in `fetch`'s prologue, or at the
31
+ * compiled route's entry when `fetch` had no hook to run first), and erases
32
+ * the whiteboard `finally`
33
+ * all in one uninterruptible turn. The next request's turn CANNOT start
34
+ * until this turn is completely finished, because that is how the event loop
35
+ * works: run-to-completion, no preemption. So request B can never see request
36
+ * A's name on the board, by the time B's turn starts, the board was already
37
+ * erased at the end of A's turn.
38
+ *
39
+ * "But the handler is async!" — the `await`s inside it run LATER, in separate
40
+ * turns. That is fine, because the check already happened: the verdict was
41
+ * copied onto that request's own context (`'~sig'`) during the synchronous
42
+ * part, and the code that runs after an `await` only ever reads the context,
43
+ * never this slot.
44
+ *
45
+ * And if any assumption here ever breaks, a runtime that re-enters, a wrap
46
+ * that delays, a future edit that sneaks an `await` in before the check the
47
+ * comparison simply MISSES and the request falls back to eager arming, which
48
+ * is the exact pre-optimization behavior. Every failure mode makes a request
49
+ * slightly slower, never incorrect
50
+ *
51
+ * Adversarial proof, not just argument: `test/core/abort-race.test.ts` storms
52
+ * concurrent servers, in-process `handle()` calls, request-substituting and
53
+ * `next()`-delaying wraps at this slot and asserts zero misclassification.
54
+ *
55
+ * Never infer the Bun runtime from `fetch`'s second argument: Cloudflare
56
+ * Workers pass `env` there.
57
+ *
58
+ * It's insanely stupid, but it works. As Bun is single-threaded and synchronous
59
+ * so the `finally` block will always run before the next request comes in.
60
+ */
61
+ const origin = { request: void 0 };
62
+
63
+ //#endregion
64
+ exports.origin = origin;
@@ -0,0 +1,62 @@
1
+ //#region src/adapter/origin.ts
2
+ /**
3
+ * Provenance channel for deferred abort-signal arming.
4
+ *
5
+ * Bun materializes `Request.signal` lazily (~214ns on the first read), so
6
+ * Elysia only pays for it once the pipeline can actually observe an abort,
7
+ * that is, after it suspends. Deferring that read is only sound for the exact
8
+ * `Request` instance `Bun.serve` handed to `fetch`: any other request
9
+ * (`app.handle`, a non-Bun adapter, or a `.wrap()` HOC that substitutes the
10
+ * request) may carry a user-controlled signal that is already aborted at entry
11
+ * and therefore must be observed synchronously.
12
+ *
13
+ * The Bun adapter publishes the untouched original here for the *synchronous*
14
+ * prologue of `app.fetch` and, on the lane where `fetch` has no request hook
15
+ * to run, for the compiled route's own entry probe, which is still inside that
16
+ * same synchronous frame, then clears it in `finally`, so the window closes
17
+ * before the first suspension and cannot leak across concurrent requests.
18
+ * A `.wrap()` HOC that replaces the request or defers calling `next()` past a
19
+ * microtask simply misses the window, fetch handler falls back to eager check
20
+ *
21
+ * ## "A single shared slot that every request writes to, so a race condition?"
22
+ *
23
+ * It looks like one, but it can't be. Explained like you're five:
24
+ *
25
+ * JavaScript only ever does ONE thing at a time. When a request comes in, the
26
+ * adapter writes its name on the whiteboard (`origin.request = request`),
27
+ * walks it down the hall (`fetch(request)` the synchronous part, where the
28
+ * one and only identity check happens, in `fetch`'s prologue, or at the
29
+ * compiled route's entry when `fetch` had no hook to run first), and erases
30
+ * the whiteboard `finally`
31
+ * all in one uninterruptible turn. The next request's turn CANNOT start
32
+ * until this turn is completely finished, because that is how the event loop
33
+ * works: run-to-completion, no preemption. So request B can never see request
34
+ * A's name on the board, by the time B's turn starts, the board was already
35
+ * erased at the end of A's turn.
36
+ *
37
+ * "But the handler is async!" — the `await`s inside it run LATER, in separate
38
+ * turns. That is fine, because the check already happened: the verdict was
39
+ * copied onto that request's own context (`'~sig'`) during the synchronous
40
+ * part, and the code that runs after an `await` only ever reads the context,
41
+ * never this slot.
42
+ *
43
+ * And if any assumption here ever breaks, a runtime that re-enters, a wrap
44
+ * that delays, a future edit that sneaks an `await` in before the check the
45
+ * comparison simply MISSES and the request falls back to eager arming, which
46
+ * is the exact pre-optimization behavior. Every failure mode makes a request
47
+ * slightly slower, never incorrect
48
+ *
49
+ * Adversarial proof, not just argument: `test/core/abort-race.test.ts` storms
50
+ * concurrent servers, in-process `handle()` calls, request-substituting and
51
+ * `next()`-delaying wraps at this slot and asserts zero misclassification.
52
+ *
53
+ * Never infer the Bun runtime from `fetch`'s second argument: Cloudflare
54
+ * Workers pass `env` there.
55
+ *
56
+ * It's insanely stupid, but it works. As Bun is single-threaded and synchronous
57
+ * so the `finally` block will always run before the next request comes in.
58
+ */
59
+ const origin = { request: void 0 };
60
+
61
+ //#endregion
62
+ export { origin };