castrum 0.7.0 → 0.9.1

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -368
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +2763 -0
  5. package/dist/index.js +8707 -0
  6. package/docs/API.md +337 -0
  7. package/docs/ARCHITECTURE.md +507 -0
  8. package/docs/BENCHMARKS.md +343 -0
  9. package/docs/CASE_STUDY.md +240 -0
  10. package/docs/ENVIRONMENT.md +81 -0
  11. package/docs/FFI_BUN_GUIDE.md +327 -0
  12. package/docs/GETTING_STARTED.md +192 -0
  13. package/docs/INGRESS-ROUTER.md +81 -0
  14. package/docs/INGRESS.md +746 -0
  15. package/docs/NATIVE-ROUTE.md +53 -0
  16. package/docs/REPO_MAP.md +279 -0
  17. package/docs/adr/0001-two-wire-formats.md +39 -0
  18. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  19. package/docs/adr/0003-bun-builtins-delegation.md +43 -0
  20. package/docs/bun-builtins-decision-matrix.md +115 -0
  21. package/index.ts +44 -8
  22. package/package.json +98 -33
  23. package/src/ingress/body.ts +289 -0
  24. package/src/ingress/constants.ts +220 -48
  25. package/src/ingress/context.ts +136 -0
  26. package/src/ingress/decode/baked-result.ts +121 -0
  27. package/src/ingress/decode/fast-result.ts +160 -0
  28. package/src/ingress/decode/packed-sections.ts +56 -0
  29. package/src/ingress/decode/result-base.ts +172 -0
  30. package/src/ingress/errors.ts +72 -0
  31. package/src/ingress/fast.ts +152 -1021
  32. package/src/ingress/handlers.ts +639 -1001
  33. package/src/ingress/headers/baked-templates.ts +102 -0
  34. package/src/ingress/headers/cors.ts +44 -0
  35. package/src/ingress/headers/fast-templates.ts +238 -0
  36. package/src/ingress/headers/hsts.ts +83 -0
  37. package/src/ingress/health.ts +68 -0
  38. package/src/ingress/index.ts +142 -405
  39. package/src/ingress/metrics.ts +132 -0
  40. package/src/ingress/options.ts +197 -0
  41. package/src/ingress/packing/gather-raw-headers.ts +139 -0
  42. package/src/ingress/packing/header-packing.ts +75 -0
  43. package/src/ingress/packing/input-packer.ts +164 -0
  44. package/src/ingress/packing/scratch.ts +50 -0
  45. package/src/ingress/packing/select-headers.ts +102 -0
  46. package/src/ingress/response/error-bodies.ts +75 -0
  47. package/src/ingress/response/terminal.ts +59 -0
  48. package/src/ingress/router.ts +237 -0
  49. package/src/ingress/routes/common.ts +79 -0
  50. package/src/ingress/routes/delete.ts +14 -0
  51. package/src/ingress/routes/echo.ts +107 -0
  52. package/src/ingress/routes/fallback.ts +23 -0
  53. package/src/ingress/routes/head.ts +27 -0
  54. package/src/ingress/routes/index.ts +11 -0
  55. package/src/ingress/routes/json-write.ts +129 -0
  56. package/src/ingress/routes/options.ts +26 -0
  57. package/src/ingress/routes/read.ts +35 -0
  58. package/src/ingress/routes/responder.ts +159 -0
  59. package/src/ingress/server-node.ts +376 -0
  60. package/src/ingress/server.ts +475 -0
  61. package/src/ingress/shared.ts +132 -0
  62. package/src/ingress/status.ts +97 -0
  63. package/src/ingress/types.ts +191 -0
  64. package/src/integration/batch.ts +58 -0
  65. package/src/integration/index.ts +29 -0
  66. package/src/integration/pipeline.ts +220 -0
  67. package/src/integration/streaming.ts +78 -0
  68. package/src/integration/websocket.ts +93 -0
  69. package/src/loader/batch.ts +137 -0
  70. package/src/loader/cost.ts +105 -0
  71. package/src/loader/index.ts +745 -0
  72. package/src/loader/ops.ts +657 -0
  73. package/src/loader/state.ts +84 -0
  74. package/src/loader/types.ts +145 -0
  75. package/src/native/ffi/constants.ts +58 -0
  76. package/src/native/ffi/selftest.ts +729 -0
  77. package/src/native/ffi/types.ts +474 -0
  78. package/src/native/ffi.ts +1775 -0
  79. package/src/native/index.ts +8 -207
  80. package/src/native/loader.ts +240 -0
  81. package/src/native/types/instances.ts +274 -0
  82. package/src/native/types.ts +401 -0
  83. package/src/runtime/builtins.ts +131 -0
  84. package/src/runtime/codec.ts +147 -0
  85. package/src/runtime/detect.ts +36 -0
  86. package/src/runtime/index.ts +28 -0
  87. package/src/runtime/native.ts +35 -0
  88. package/src/runtime/server.ts +31 -0
  89. package/src/runtime/transport.ts +64 -0
  90. package/src/runtime/types.ts +173 -0
  91. package/src/runtime/uuid.ts +45 -0
  92. package/src/runtime/websocket.ts +30 -0
  93. package/src/rust-ffi/addon.ts +10 -0
  94. package/src/rust-ffi/batch/build.ts +155 -0
  95. package/src/rust-ffi/batch/index.ts +8 -0
  96. package/src/rust-ffi/batch/types.ts +92 -0
  97. package/src/rust-ffi/client.ts +87 -0
  98. package/src/rust-ffi/context.ts +193 -0
  99. package/src/rust-ffi/index.ts +51 -541
  100. package/src/rust-ffi/into.ts +21 -0
  101. package/src/rust-ffi/options.ts +113 -0
  102. package/src/rust-ffi/packed.ts +253 -0
  103. package/src/rust-ffi/proven.ts +28 -0
  104. package/src/rust-ffi/scalar/crypto.ts +346 -0
  105. package/src/rust-ffi/scalar/factories.ts +459 -0
  106. package/src/rust-ffi/scalar/hashing.ts +74 -0
  107. package/src/rust-ffi/scalar/http.ts +337 -0
  108. package/src/rust-ffi/scalar/index.ts +31 -0
  109. package/src/rust-ffi/scalar/interface.ts +325 -0
  110. package/src/rust-ffi/scalar/json-packed.ts +175 -0
  111. package/src/rust-ffi/scalar/json.ts +61 -0
  112. package/src/rust-ffi/scalar/payload.ts +241 -0
  113. package/src/rust-ffi/text.ts +121 -0
  114. package/src/selection.json +194 -0
  115. package/src/selection.ts +73 -0
  116. package/src/shared/adaptive.ts +67 -0
  117. package/src/shared/buffer-pool.ts +264 -0
  118. package/src/shared/bytes.ts +108 -9
  119. package/src/shared/codec.ts +78 -0
  120. package/src/shared/env.ts +20 -0
  121. package/src/shared/log.ts +75 -0
  122. package/src/shared/metrics.ts +247 -0
  123. package/src/shared/packed/index.ts +10 -0
  124. package/src/shared/packed/parsers.ts +31 -0
  125. package/src/shared/packed/schema.ts +23 -0
  126. package/src/shared/packed/wire.ts +504 -0
  127. package/src/shared/proven.ts +289 -0
  128. package/src/shared/request-id.ts +67 -0
  129. package/src/shared/response.ts +104 -0
  130. package/src/shared/runtime.ts +29 -0
  131. package/src/shared/trace.ts +81 -0
  132. package/src/shared/uuid.ts +15 -0
  133. package/Cargo.toml +0 -73
  134. package/bench.ts +0 -3
  135. package/rust/batch.rs +0 -195
  136. package/rust/cookie_parser.rs +0 -70
  137. package/rust/cors.rs +0 -263
  138. package/rust/hashing.rs +0 -164
  139. package/rust/headers.rs +0 -234
  140. package/rust/hmac_sha256.rs +0 -113
  141. package/rust/http_parser.rs +0 -132
  142. package/rust/ingress.rs +0 -616
  143. package/rust/ingress_constants.rs +0 -89
  144. package/rust/ip_trust.rs +0 -185
  145. package/rust/json_ops.rs +0 -66
  146. package/rust/json_patch_ops.rs +0 -25
  147. package/rust/json_schema.rs +0 -151
  148. package/rust/json_ser.rs +0 -429
  149. package/rust/lib.rs +0 -48
  150. package/rust/method.rs +0 -141
  151. package/rust/mime_lookup.rs +0 -76
  152. package/rust/output.rs +0 -238
  153. package/rust/proxy.rs +0 -90
  154. package/rust/query_parser.rs +0 -141
  155. package/rust/random_token.rs +0 -52
  156. package/rust/rate_limit.rs +0 -203
  157. package/rust/terminal.rs +0 -95
  158. package/rust/unit_tests.rs +0 -843
  159. package/rust/url_codec.rs +0 -133
  160. package/rust/util.rs +0 -537
  161. package/rust/validation.rs +0 -107
  162. package/rust/websocket.rs +0 -23
  163. package/src/baseline/index.ts +0 -12
  164. package/src/baseline/tasks/cookie.ts +0 -22
  165. package/src/baseline/tasks/hashing.ts +0 -18
  166. package/src/baseline/tasks/hmac.ts +0 -38
  167. package/src/baseline/tasks/http.ts +0 -62
  168. package/src/baseline/tasks/json-patch.ts +0 -12
  169. package/src/baseline/tasks/json.ts +0 -50
  170. package/src/baseline/tasks/mime.ts +0 -5
  171. package/src/baseline/tasks/query.ts +0 -14
  172. package/src/baseline/tasks/token.ts +0 -15
  173. package/src/baseline/tasks/url.ts +0 -11
  174. package/src/baseline/tasks/validation.ts +0 -19
  175. package/src/baseline/tasks/websocket.ts +0 -14
  176. package/src/bench/assert.ts +0 -70
  177. package/src/bench/checks.ts +0 -227
  178. package/src/bench/checksum.ts +0 -27
  179. package/src/bench/comparisons.ts +0 -70
  180. package/src/bench/concurrent-worker.ts +0 -135
  181. package/src/bench/fixtures.ts +0 -233
  182. package/src/bench/index.ts +0 -2
  183. package/src/bench/measure.ts +0 -177
  184. package/src/bench/now.ts +0 -3
  185. package/src/bench/report.ts +0 -79
  186. package/src/bench/run.ts +0 -57
  187. package/src/bench/tasks/complex.ts +0 -232
  188. package/src/bench/tasks/concurrent.ts +0 -213
  189. package/src/bench/tasks/cookie.ts +0 -44
  190. package/src/bench/tasks/hashing.ts +0 -33
  191. package/src/bench/tasks/hmac.ts +0 -36
  192. package/src/bench/tasks/http.ts +0 -50
  193. package/src/bench/tasks/index.ts +0 -52
  194. package/src/bench/tasks/json-patch.ts +0 -21
  195. package/src/bench/tasks/json.ts +0 -33
  196. package/src/bench/tasks/mime.ts +0 -21
  197. package/src/bench/tasks/query.ts +0 -44
  198. package/src/bench/tasks/stress.ts +0 -142
  199. package/src/bench/tasks/token.ts +0 -20
  200. package/src/bench/tasks/url.ts +0 -34
  201. package/src/bench/tasks/validation.ts +0 -57
  202. package/src/bench/tasks/websocket.ts +0 -21
  203. package/src/bench/types.ts +0 -60
  204. package/src/data/json-rows.ts +0 -33
  205. package/src/ingress/packed-input.ts +0 -59
  206. package/src/shared/json.ts +0 -18
  207. package/src/shared/packed.ts +0 -316
@@ -1,56 +1,228 @@
1
1
  // src/ingress/constants.ts — SINGLE SOURCE OF TRUTH for ingress binary layout constants
2
- // These are read from the Rust native addon to eliminate duplication across:
2
+ // These are read from Rust to eliminate duplication across:
3
3
  // - src/ingress/index.ts
4
4
  // - bench/servers/ingress-server.ts
5
5
  // - Any future consumers
6
6
  //
7
- // IMPORTANT: Do NOT hardcode these values. They come from Rust's ingress_constants.rs
8
- // and are typed on the NativeAddon interface (src/native/index.ts).
7
+ // IMPORTANT: Do NOT hardcode these values. They come from Rust's
8
+ // `ingress/output.rs` (the single numeric source), projected to JS via either
9
+ // `ingress_constants.rs` (NAPI) or the C-ABI layout blob `rust/ffi.rs`
10
+ // `IngressLayout` (`castrum_ingress_layout`). Both read the same numbers, so
11
+ // they cannot drift.
9
12
 
10
- import addon from "../native";
13
+ import { getAddon } from '../native'
14
+ import { getBunFFI, type BunFFI } from '../native/ffi'
15
+
16
+ // Transport: bun:ffi is PRIMARY on Bun, so the layout is read through the
17
+ // C-ABI blob WITHOUT dlopening the napi addon. Node — or forced
18
+ // `CASTRUM_FFI_MODE=napi`, a missing symbol, or a failed bind-time self-test —
19
+ // falls back to the napi addon. This is the ONE module that touches native at
20
+ // import time; everything else in the package stays lazy.
21
+ const ffi = getBunFFI()
22
+
23
+ // Slot order mirrors the `#[repr(C)] IngressLayout` struct in rust/ffi.rs
24
+ // (38 × u32 LE). Drift is guarded by the Rust unit test
25
+ // `ingress_layout_c_abi_matches_output_source` and by the pinned values in the
26
+ // bun:ffi bind-time self-test (a reorder → self-test fails → napi fallback).
27
+ const SLOT = {
28
+ OUT_VERDICT: 0,
29
+ OUT_ERROR_CODE: 1,
30
+ OUT_STATUS: 2,
31
+ OUT_FLAGS: 3,
32
+ OUT_RATE_LIMIT: 4,
33
+ OUT_RATE_REMAINING: 5,
34
+ OUT_RATE_RESET: 6,
35
+ OUT_RETRY_AFTER: 7,
36
+ OUT_COOKIES_JSON_LEN: 8,
37
+ OUT_QUERY_JSON_LEN: 9,
38
+ OUT_HEADER_VARIANT: 10,
39
+ OUT_BODY_JSON_LEN: 11,
40
+ OUT_DATA_START: 12,
41
+ FLAG_HAS_COOKIES: 13,
42
+ FLAG_HAS_QUERY: 14,
43
+ FLAG_BODY_VALID_JSON: 15,
44
+ FLAG_SCHEMA_VALID: 16,
45
+ FLAG_CORS_ALLOWED: 17,
46
+ FLAG_IS_PREFLIGHT: 18,
47
+ FLAG_RATE_LIMITED: 19,
48
+ FLAG_HTTPS: 20,
49
+ FLAG_TRUSTED_PROXY: 21,
50
+ FLAG_BODY_TRUNCATED: 22,
51
+ HV_JSON: 23,
52
+ HV_CORS_SIMPLE: 24,
53
+ HV_CORS_PREFLIGHT: 25,
54
+ HV_RATE_ACTIVE: 26,
55
+ HV_RATE_LIMITED: 27,
56
+ HV_COUNT: 28,
57
+ ERR_NONE: 29,
58
+ ERR_CORS_PREFLIGHT: 30,
59
+ ERR_RATE_LIMITED: 31,
60
+ ERR_BODY_TOO_LARGE: 32,
61
+ ERR_INVALID_JSON: 33,
62
+ ERR_SCHEMA_VALIDATION: 34,
63
+ ERR_BAD_REQUEST: 35,
64
+ ERR_REQUEST_TOO_LARGE: 36,
65
+ ERR_INTERNAL: 37,
66
+ } as const
67
+
68
+ type Layout = Record<keyof typeof SLOT, number>
69
+
70
+ /** Read the layout via the bun:ffi C-ABI blob (Bun primary path). */
71
+ function readLayoutViaFfi(ffiLive: BunFFI): Layout {
72
+ const buf = new Uint8Array(38 * 4)
73
+ ffiLive.ingressLayout(buf)
74
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
75
+ const out = {} as Layout
76
+ for (const key of Object.keys(SLOT) as (keyof typeof SLOT)[]) {
77
+ out[key] = view.getUint32(SLOT[key] * 4, true)
78
+ }
79
+ return out
80
+ }
81
+
82
+ /** Read the layout via the napi addon (Node / fallback). */
83
+ function readLayoutViaNapi(): Layout {
84
+ const addon = getAddon()
85
+ return {
86
+ OUT_VERDICT: addon.INGRESS_OUT_VERDICT,
87
+ OUT_ERROR_CODE: addon.INGRESS_OUT_ERROR_CODE,
88
+ OUT_STATUS: addon.INGRESS_OUT_STATUS,
89
+ OUT_FLAGS: addon.INGRESS_OUT_FLAGS,
90
+ OUT_RATE_LIMIT: addon.INGRESS_OUT_RATE_LIMIT,
91
+ OUT_RATE_REMAINING: addon.INGRESS_OUT_RATE_REMAINING,
92
+ OUT_RATE_RESET: addon.INGRESS_OUT_RATE_RESET,
93
+ OUT_RETRY_AFTER: addon.INGRESS_OUT_RETRY_AFTER,
94
+ OUT_COOKIES_JSON_LEN: addon.INGRESS_OUT_COOKIES_JSON_LEN,
95
+ OUT_QUERY_JSON_LEN: addon.INGRESS_OUT_QUERY_JSON_LEN,
96
+ OUT_HEADER_VARIANT: addon.INGRESS_OUT_HEADER_VARIANT,
97
+ OUT_BODY_JSON_LEN: addon.INGRESS_OUT_BODY_JSON_LEN,
98
+ OUT_DATA_START: addon.INGRESS_OUT_DATA_START,
99
+ FLAG_HAS_COOKIES: addon.INGRESS_FLAG_HAS_COOKIES,
100
+ FLAG_HAS_QUERY: addon.INGRESS_FLAG_HAS_QUERY,
101
+ FLAG_BODY_VALID_JSON: addon.INGRESS_FLAG_BODY_VALID_JSON,
102
+ FLAG_SCHEMA_VALID: addon.INGRESS_FLAG_SCHEMA_VALID,
103
+ FLAG_CORS_ALLOWED: addon.INGRESS_FLAG_CORS_ALLOWED,
104
+ FLAG_IS_PREFLIGHT: addon.INGRESS_FLAG_IS_PREFLIGHT,
105
+ FLAG_RATE_LIMITED: addon.INGRESS_FLAG_RATE_LIMITED,
106
+ FLAG_HTTPS: addon.INGRESS_FLAG_HTTPS,
107
+ FLAG_TRUSTED_PROXY: addon.INGRESS_FLAG_TRUSTED_PROXY,
108
+ FLAG_BODY_TRUNCATED: addon.INGRESS_FLAG_BODY_TRUNCATED,
109
+ HV_JSON: addon.INGRESS_HV_JSON,
110
+ HV_CORS_SIMPLE: addon.INGRESS_HV_CORS_SIMPLE,
111
+ HV_CORS_PREFLIGHT: addon.INGRESS_HV_CORS_PREFLIGHT,
112
+ HV_RATE_ACTIVE: addon.INGRESS_HV_RATE_ACTIVE,
113
+ HV_RATE_LIMITED: addon.INGRESS_HV_RATE_LIMITED,
114
+ HV_COUNT: addon.INGRESS_HV_COUNT,
115
+ ERR_NONE: addon.INGRESS_ERR_NONE,
116
+ ERR_CORS_PREFLIGHT: addon.INGRESS_ERR_CORS_PREFLIGHT,
117
+ ERR_RATE_LIMITED: addon.INGRESS_ERR_RATE_LIMITED,
118
+ ERR_BODY_TOO_LARGE: addon.INGRESS_ERR_BODY_TOO_LARGE,
119
+ ERR_INVALID_JSON: addon.INGRESS_ERR_INVALID_JSON,
120
+ ERR_SCHEMA_VALIDATION: addon.INGRESS_ERR_SCHEMA_VALIDATION,
121
+ ERR_BAD_REQUEST: addon.INGRESS_ERR_BAD_REQUEST,
122
+ ERR_REQUEST_TOO_LARGE: addon.INGRESS_ERR_REQUEST_TOO_LARGE,
123
+ ERR_INTERNAL: addon.INGRESS_ERR_INTERNAL,
124
+ }
125
+ }
126
+
127
+ const L: Layout = ffi !== null ? readLayoutViaFfi(ffi) : readLayoutViaNapi()
11
128
 
12
129
  // ── Output buffer layout ───────────────────────────────────
13
- export const OUT_VERDICT: number = addon.INGRESS_OUT_VERDICT;
14
- export const OUT_ERROR_CODE: number = addon.INGRESS_OUT_ERROR_CODE;
15
- export const OUT_STATUS: number = addon.INGRESS_OUT_STATUS;
16
- export const OUT_FLAGS: number = addon.INGRESS_OUT_FLAGS;
17
- export const OUT_RATE_LIMIT: number = addon.INGRESS_OUT_RATE_LIMIT;
18
- export const OUT_RATE_REMAINING: number = addon.INGRESS_OUT_RATE_REMAINING;
19
- export const OUT_RATE_RESET: number = addon.INGRESS_OUT_RATE_RESET;
20
- export const OUT_RETRY_AFTER: number = addon.INGRESS_OUT_RETRY_AFTER;
21
- export const OUT_COOKIES_JSON_LEN: number = addon.INGRESS_OUT_COOKIES_JSON_LEN;
22
- export const OUT_QUERY_JSON_LEN: number = addon.INGRESS_OUT_QUERY_JSON_LEN;
23
- export const OUT_HEADER_VARIANT: number = addon.INGRESS_OUT_HEADER_VARIANT;
24
- export const OUT_BODY_JSON_LEN: number = addon.INGRESS_OUT_BODY_JSON_LEN;
25
- export const OUT_DATA_START: number = addon.INGRESS_OUT_DATA_START;
26
-
27
- // ── Flags ──────────────────────────────────────────────────
28
- export const FLAG_HAS_COOKIES: number = addon.INGRESS_FLAG_HAS_COOKIES;
29
- export const FLAG_HAS_QUERY: number = addon.INGRESS_FLAG_HAS_QUERY;
30
- export const FLAG_BODY_VALID_JSON: number = addon.INGRESS_FLAG_BODY_VALID_JSON;
31
- export const FLAG_SCHEMA_VALID: number = addon.INGRESS_FLAG_SCHEMA_VALID;
32
- export const FLAG_CORS_ALLOWED: number = addon.INGRESS_FLAG_CORS_ALLOWED;
33
- export const FLAG_IS_PREFLIGHT: number = addon.INGRESS_FLAG_IS_PREFLIGHT;
34
- export const FLAG_RATE_LIMITED: number = addon.INGRESS_FLAG_RATE_LIMITED;
35
- export const FLAG_HTTPS: number = addon.INGRESS_FLAG_HTTPS;
36
- export const FLAG_TRUSTED_PROXY: number = addon.INGRESS_FLAG_TRUSTED_PROXY;
37
- export const FLAG_BODY_TRUNCATED: number = addon.INGRESS_FLAG_BODY_TRUNCATED;
38
-
39
- // ── Header variant bits ────────────────────────────────────
40
- export const HV_JSON: number = addon.INGRESS_HV_JSON;
41
- export const HV_CORS_SIMPLE: number = addon.INGRESS_HV_CORS_SIMPLE;
42
- export const HV_CORS_PREFLIGHT: number = addon.INGRESS_HV_CORS_PREFLIGHT;
43
- export const HV_RATE_ACTIVE: number = addon.INGRESS_HV_RATE_ACTIVE;
44
- export const HV_RATE_LIMITED: number = addon.INGRESS_HV_RATE_LIMITED;
45
- export const HV_COUNT: number = addon.INGRESS_HV_COUNT;
46
-
47
- // ── Error codes ────────────────────────────────────────────
48
- export const ERR_CODE_NONE: number = addon.INGRESS_ERR_NONE;
49
- export const ERR_CODE_CORS_PREFLIGHT: number = addon.INGRESS_ERR_CORS_PREFLIGHT;
50
- export const ERR_CODE_RATE_LIMITED: number = addon.INGRESS_ERR_RATE_LIMITED;
51
- export const ERR_CODE_BODY_TOO_LARGE: number = addon.INGRESS_ERR_BODY_TOO_LARGE;
52
- export const ERR_CODE_INVALID_JSON: number = addon.INGRESS_ERR_INVALID_JSON;
53
- export const ERR_CODE_SCHEMA_VALIDATION: number = addon.INGRESS_ERR_SCHEMA_VALIDATION;
54
- export const ERR_CODE_BAD_REQUEST: number = addon.INGRESS_ERR_BAD_REQUEST;
55
- export const ERR_CODE_REQUEST_TOO_LARGE: number = addon.INGRESS_ERR_REQUEST_TOO_LARGE;
56
- export const ERR_CODE_INTERNAL: number = addon.INGRESS_ERR_INTERNAL;
130
+ // Byte offsets into the fixed header of the native ingress OUTPUT buffer.
131
+ // The header occupies [0, OUT_DATA_START); the cookie/query/body JSON sections
132
+ // follow at OUT_DATA_START (see decode/packed-sections.ts). Read through the
133
+ // C-ABI layout blob (Bun) or napi (Node) — never hardcoded.
134
+
135
+ /** Offset of the verdict byte (0 = accepted, nonzero = terminal error). */
136
+ export const OUT_VERDICT: number = L.OUT_VERDICT
137
+ /** Offset of the error-code byte (see ERR_CODE_* below). */
138
+ export const OUT_ERROR_CODE: number = L.OUT_ERROR_CODE
139
+ /** Offset of the raw u16 status. */
140
+ export const OUT_STATUS: number = L.OUT_STATUS
141
+ /** Offset of the u32 flags word (see FLAG_* below). */
142
+ export const OUT_FLAGS: number = L.OUT_FLAGS
143
+ /** Offset of the u32 rate-limit budget (U32_MAX = disabled). */
144
+ export const OUT_RATE_LIMIT: number = L.OUT_RATE_LIMIT
145
+ /** Offset of the u32 rate-limit remaining count. */
146
+ export const OUT_RATE_REMAINING: number = L.OUT_RATE_REMAINING
147
+ /** Offset of the u64 rate-limit window reset (ms epoch). */
148
+ export const OUT_RATE_RESET: number = L.OUT_RATE_RESET
149
+ /** Offset of the u64 Retry-After value (ms). */
150
+ export const OUT_RETRY_AFTER: number = L.OUT_RETRY_AFTER
151
+ /** Offset of the u32 cookies-JSON section length. */
152
+ export const OUT_COOKIES_JSON_LEN: number = L.OUT_COOKIES_JSON_LEN
153
+ /** Offset of the u32 query-JSON section length. */
154
+ export const OUT_QUERY_JSON_LEN: number = L.OUT_QUERY_JSON_LEN
155
+ /** Offset of the header-template variant byte (see HV_* below). */
156
+ export const OUT_HEADER_VARIANT: number = L.OUT_HEADER_VARIANT
157
+ /** Offset of the u32 body-JSON section length. */
158
+ export const OUT_BODY_JSON_LEN: number = L.OUT_BODY_JSON_LEN
159
+ /** Byte offset where the variable-length JSON sections begin. */
160
+ export const OUT_DATA_START: number = L.OUT_DATA_START
161
+
162
+ // ── Flags (bits within the OUT_FLAGS u32) ──────────────────
163
+ /** Request carried a cookie header (cookie-JSON section present). */
164
+ export const FLAG_HAS_COOKIES: number = L.FLAG_HAS_COOKIES
165
+ /** Request carried a query string (query-JSON section present). */
166
+ export const FLAG_HAS_QUERY: number = L.FLAG_HAS_QUERY
167
+ /** The request body parsed as valid JSON. */
168
+ export const FLAG_BODY_VALID_JSON: number = L.FLAG_BODY_VALID_JSON
169
+ /** The request body validated against the ingress schema. */
170
+ export const FLAG_SCHEMA_VALID: number = L.FLAG_SCHEMA_VALID
171
+ /** The CORS engine allowed the request origin. */
172
+ export const FLAG_CORS_ALLOWED: number = L.FLAG_CORS_ALLOWED
173
+ /** The request was a CORS preflight (OPTIONS + request-method). */
174
+ export const FLAG_IS_PREFLIGHT: number = L.FLAG_IS_PREFLIGHT
175
+ /** The request was rate-limited. */
176
+ export const FLAG_RATE_LIMITED: number = L.FLAG_RATE_LIMITED
177
+ /** The request arrived over HTTPS (trusted-proxy scheme). */
178
+ export const FLAG_HTTPS: number = L.FLAG_HTTPS
179
+ /** The request came from a trusted proxy (per the proxy config). */
180
+ export const FLAG_TRUSTED_PROXY: number = L.FLAG_TRUSTED_PROXY
181
+ /** A declared output section overran the buffer (truncated). */
182
+ export const FLAG_BODY_TRUNCATED: number = L.FLAG_BODY_TRUNCATED
183
+
184
+ // ── Header variant bits (bits within the OUT_HEADER_VARIANT byte) ──
185
+ /** Response body is JSON (`content-type: application/json`). */
186
+ export const HV_JSON: number = L.HV_JSON
187
+ /** CORS simple-request variant (Origin echoed + vary headers). */
188
+ export const HV_CORS_SIMPLE: number = L.HV_CORS_SIMPLE
189
+ /** CORS preflight variant (allow-methods/headers/max-age). */
190
+ export const HV_CORS_PREFLIGHT: number = L.HV_CORS_PREFLIGHT
191
+ /** Rate-limit active variant (`ratelimit-limit` header). */
192
+ export const HV_RATE_ACTIVE: number = L.HV_RATE_ACTIVE
193
+ /** Rate-limited variant (Retry-After emitted). */
194
+ export const HV_RATE_LIMITED: number = L.HV_RATE_LIMITED
195
+ /** Total number of header-variant combinations (32). */
196
+ export const HV_COUNT: number = L.HV_COUNT
197
+
198
+ // ── Error codes (bytes in OUT_ERROR_CODE) ──────────────────
199
+ /** No error (accepted). */
200
+ export const ERR_CODE_NONE: number = L.ERR_NONE
201
+ /** CORS preflight rejected. */
202
+ export const ERR_CODE_CORS_PREFLIGHT: number = L.ERR_CORS_PREFLIGHT
203
+ /** Rate limit exceeded. */
204
+ export const ERR_CODE_RATE_LIMITED: number = L.ERR_RATE_LIMITED
205
+ /** Request body exceeded the size guard. */
206
+ export const ERR_CODE_BODY_TOO_LARGE: number = L.ERR_BODY_TOO_LARGE
207
+ /** Request body is not valid JSON. */
208
+ export const ERR_CODE_INVALID_JSON: number = L.ERR_INVALID_JSON
209
+ /** Request body failed schema validation. */
210
+ export const ERR_CODE_SCHEMA_VALIDATION: number = L.ERR_SCHEMA_VALIDATION
211
+ /** Malformed request (bad request line / headers). */
212
+ export const ERR_CODE_BAD_REQUEST: number = L.ERR_BAD_REQUEST
213
+ /** Request headers/URL exceeded the configured limits. */
214
+ export const ERR_CODE_REQUEST_TOO_LARGE: number = L.ERR_REQUEST_TOO_LARGE
215
+ /** Internal pipeline failure (maps to 500). */
216
+ export const ERR_CODE_INTERNAL: number = L.ERR_INTERNAL
217
+
218
+ /**
219
+ * JS-only error-code sentinel for a request-body read timeout in the async
220
+ * `createIngress` path.
221
+ *
222
+ * The native pipeline never emits this value — it is a JS body-reader error
223
+ * (`REQUEST_TIMEOUT` from `readBodyWithLimit`) mapped onto the numeric
224
+ * error-code contract so the shared status/name/message helpers stay
225
+ * consistent. Deliberately not sourced from Rust (see the header comment:
226
+ * numeric layout values come from the addon; this one is a JS-level error).
227
+ */
228
+ export const ERR_CODE_REQUEST_TIMEOUT: number = -1
@@ -0,0 +1,136 @@
1
+ // src/ingress/context.ts — Result snapshotting + synthetic/error context
2
+ // builders for the async ingress API.
3
+
4
+ import type { ResponseBuildContext } from './headers/fast-templates'
5
+ import { buildTerminalResponse } from './response/terminal'
6
+ import { ERR_CODE_INTERNAL, ERR_CODE_RATE_LIMITED, HV_JSON, HV_CORS_SIMPLE } from './constants'
7
+ import type { IngressOptions, IngressResult, IngressContext } from './types'
8
+
9
+ const EMPTY_BODY = new Uint8Array(0)
10
+
11
+ /**
12
+ * Deep-snapshot a result so the caller can use it after `run()` returns.
13
+ *
14
+ * Note on aliasing: the returned `body` field aliases the request body buffer
15
+ * (`r.body`) — it is NOT copied — while `bodyJson()` returns a stable copy
16
+ * (captured once at snapshot time, safe after the live result is invalidated).
17
+ * In the async `createIngress` path the aliased buffer is the fully-
18
+ * materialized request body, which lives as long as the request and is never
19
+ * reused, so the alias is safe. Treat `body` as read-only; use `bodyJson()`
20
+ * for a defensive copy.
21
+ */
22
+ export function snapshotResult(r: IngressResult): IngressResult {
23
+ const cookies = r.cookiesJson()
24
+ const query = r.queryJson()
25
+ const bodyJson = r.bodyJson().slice()
26
+ const body = r.body
27
+
28
+ return {
29
+ status: r.status,
30
+ verdict: r.verdict,
31
+ flags: r.flags,
32
+ errorCode: r.errorCode,
33
+ terminal: r.terminal,
34
+ ok: r.ok,
35
+ https: r.https,
36
+ trustedProxy: r.trustedProxy,
37
+ hasCookies: r.hasCookies,
38
+ hasQuery: r.hasQuery,
39
+ bodyValidJson: r.bodyValidJson,
40
+ schemaValid: r.schemaValid,
41
+ corsAllowed: r.corsAllowed,
42
+ isPreflight: r.isPreflight,
43
+ rateLimited: r.rateLimited,
44
+ rateLimit: r.rateLimit,
45
+ rateRemaining: r.rateRemaining,
46
+ rateResetMs: r.rateResetMs,
47
+ retryAfterMs: r.retryAfterMs,
48
+ body,
49
+ headerVariant: r.headerVariant,
50
+ requestId: r.requestId,
51
+ bodyTruncated: r.bodyTruncated,
52
+
53
+ cookiesJson: () => cookies,
54
+ queryJson: () => query,
55
+ bodyJson: () => bodyJson,
56
+ }
57
+ }
58
+
59
+ /** Build a synthetic (terminal) context for body-size / error short-circuits. */
60
+ export function syntheticContext(
61
+ req: Request,
62
+ requestId: string,
63
+ options: IngressOptions,
64
+ responseCtx: ResponseBuildContext,
65
+ status: number,
66
+ errorCode: number,
67
+ ): IngressContext {
68
+ const corsAllowed = staticCorsAllowed(options, req)
69
+ const variant = HV_JSON | (corsAllowed ? HV_CORS_SIMPLE : 0)
70
+
71
+ const base: IngressResult = {
72
+ status,
73
+ verdict: 1,
74
+ flags: 0,
75
+ errorCode,
76
+ terminal: true,
77
+ ok: false,
78
+ https: options.https === true,
79
+ trustedProxy: options.trustProxy === true || options.trustedProxies?.enabled === true,
80
+ hasCookies: false,
81
+ hasQuery: false,
82
+ bodyValidJson: false,
83
+ schemaValid: false,
84
+ corsAllowed,
85
+ isPreflight: false,
86
+ rateLimited: errorCode === ERR_CODE_RATE_LIMITED,
87
+ rateLimit: options.rateLimit?.limit ?? 0,
88
+ rateRemaining: 0,
89
+ rateResetMs: 0,
90
+ retryAfterMs: 0,
91
+ body: EMPTY_BODY,
92
+ headerVariant: variant,
93
+ requestId,
94
+ bodyTruncated: false,
95
+
96
+ cookiesJson: () => '{}',
97
+ queryJson: () => '{}',
98
+ bodyJson: () => EMPTY_BODY,
99
+ }
100
+
101
+ return {
102
+ ...base,
103
+ response: buildTerminalResponse(responseCtx, base, req, requestId),
104
+ }
105
+ }
106
+
107
+ /** Build the internal-error context (status 500). */
108
+ export function internalContext(
109
+ req: Request,
110
+ requestId: string,
111
+ options: IngressOptions,
112
+ responseCtx: ResponseBuildContext,
113
+ ): IngressContext {
114
+ return syntheticContext(req, requestId, options, responseCtx, 500, ERR_CODE_INTERNAL)
115
+ }
116
+
117
+ /** Statically determine whether the configured CORS policy allows `req`. */
118
+ export function staticCorsAllowed(options: IngressOptions, req: Request): boolean {
119
+ const cors = options.cors
120
+ if (!cors) return false
121
+
122
+ const origin = req.headers.get('origin')
123
+ if (!origin) return false
124
+
125
+ const list = cors.allowOrigin
126
+
127
+ if (!list || list.length === 0) {
128
+ return cors.allowCredentials !== true
129
+ }
130
+
131
+ if (list.includes('*')) {
132
+ return cors.allowCredentials !== true
133
+ }
134
+
135
+ return list.includes(origin)
136
+ }
@@ -0,0 +1,121 @@
1
+ // src/ingress/decode/baked-result.ts — Zero-alloc result decoder (pre-baked
2
+ // path).
3
+ //
4
+ // Decodes the native Ingress packed output (OUT_* layout in ./constants.ts)
5
+ // into the pre-baked handler result, which carries the benchmark wire format
6
+ // ({"ok":false,"error":{...}}).
7
+ //
8
+ // NOTE: this is the pre-baked-path decoder with its own status normalization.
9
+ // The fast path has its own decoder (./fast-result.ts) — do not unify them
10
+ // (see AGENTS.md). The two decoders DO share their section/rate-limit plumbing
11
+ // via ./result-base.ts.
12
+
13
+ import {
14
+ OUT_VERDICT,
15
+ OUT_FLAGS,
16
+ OUT_RATE_LIMIT,
17
+ OUT_RATE_REMAINING,
18
+ OUT_COOKIES_JSON_LEN,
19
+ OUT_QUERY_JSON_LEN,
20
+ OUT_HEADER_VARIANT,
21
+ OUT_BODY_JSON_LEN,
22
+ OUT_DATA_START,
23
+ FLAG_BODY_VALID_JSON,
24
+ FLAG_SCHEMA_VALID,
25
+ FLAG_CORS_ALLOWED,
26
+ FLAG_IS_PREFLIGHT,
27
+ FLAG_RATE_LIMITED,
28
+ FLAG_TRUSTED_PROXY,
29
+ FLAG_BODY_TRUNCATED,
30
+ HV_JSON,
31
+ ERR_CODE_INTERNAL as ERROR_CODE_INTERNAL,
32
+ } from '../constants'
33
+ import { sectionLayout } from './packed-sections'
34
+ import { isValidResponseStatus } from '../status'
35
+ import { IngressResultBase } from './result-base'
36
+
37
+ /** Zero-alloc, reusable result decoder for the pre-baked handler path. */
38
+ export class BakedIngressResult extends IngressResultBase {
39
+ refresh(buf: Uint8Array, body: Uint8Array, view: DataView): void {
40
+ // Defensive: the native core always writes the full fixed header
41
+ // (>= OUT_DATA_START bytes) before returning `written`. The cached
42
+ // whole-buffer DataView (see viewForArrayBuffer) would otherwise decode
43
+ // stale bytes if this contract is ever violated — treat as internal error.
44
+ if (buf.byteLength < OUT_DATA_START) {
45
+ this.setInternalError()
46
+ return
47
+ }
48
+ this.body = body
49
+
50
+ const h0 = view.getUint32(OUT_VERDICT, true)
51
+ const h1 = view.getUint32(OUT_FLAGS, true)
52
+ const h2 = view.getUint32(OUT_RATE_LIMIT, true)
53
+ const h3 = view.getUint32(OUT_RATE_REMAINING, true)
54
+
55
+ const cookiesLenRaw = view.getUint32(OUT_COOKIES_JSON_LEN, true)
56
+ const queryLenRaw = view.getUint32(OUT_QUERY_JSON_LEN, true)
57
+ const headerVariant = view.getUint8(OUT_HEADER_VARIANT)
58
+ const bodyJsonLenRaw = view.getUint32(OUT_BODY_JSON_LEN, true)
59
+
60
+ // Bounds-checked section offsets shared with the fast decoder: a
61
+ // malformed/truncated buffer can never produce slices past its end.
62
+ const layout = sectionLayout(buf.byteLength, cookiesLenRaw, queryLenRaw, bodyJsonLenRaw)
63
+
64
+ if (h0 === 0 && h1 === 0) {
65
+ this.verdict = 1
66
+ this.errorCode = ERROR_CODE_INTERNAL
67
+ this.status = 500
68
+ } else {
69
+ this.verdict = h0 & 0xff
70
+ this.errorCode = (h0 >>> 8) & 0xff
71
+
72
+ const rawStatus = (h0 >>> 16) & 0xffff
73
+ // Reuse the shared status-validity helper instead of re-inlining the
74
+ // 101/200-599 check (single source of truth in ../status.ts).
75
+ this.status = isValidResponseStatus(rawStatus) ? rawStatus : 500
76
+ }
77
+
78
+ const flags = h1
79
+
80
+ this.rateRemaining = h3
81
+
82
+ this.setRateWindow(h2, (flags & FLAG_RATE_LIMITED) !== 0, view)
83
+
84
+ this.headerVariant = headerVariant
85
+
86
+ // The baked path trusts the already-clamped section lengths, so pass the
87
+ // safe body len here (the fast path re-checks bounds on read instead).
88
+ this.setSections(buf, layout, layout.safeBodyJsonLen)
89
+
90
+ this.updateOkTerminal()
91
+
92
+ this.isPreflight = (flags & FLAG_IS_PREFLIGHT) !== 0
93
+ this.corsAllowed = (flags & FLAG_CORS_ALLOWED) !== 0
94
+ this.rateLimited = (flags & FLAG_RATE_LIMITED) !== 0
95
+ this.trustedProxy = (flags & FLAG_TRUSTED_PROXY) !== 0
96
+ this.bodyValidJson = (flags & FLAG_BODY_VALID_JSON) !== 0
97
+ this.schemaValid = (flags & FLAG_SCHEMA_VALID) !== 0
98
+
99
+ this.bodyTruncated = (flags & FLAG_BODY_TRUNCATED) !== 0 || layout.truncated
100
+ }
101
+
102
+ invalidate(): void {
103
+ // The baked empty state is neutral (0/0/0) — exactly what resetShared sets.
104
+ this.resetShared()
105
+ }
106
+
107
+ setInternalError(): void {
108
+ this.invalidate()
109
+ this.status = 500
110
+ this.verdict = 1
111
+ this.errorCode = ERROR_CODE_INTERNAL
112
+ this.headerVariant = HV_JSON
113
+ this.terminal = true
114
+ this.ok = false
115
+ }
116
+
117
+ bodyJson(copy: boolean): Uint8Array {
118
+ const slice = this.bodyJsonSlice()
119
+ return (copy ? slice.slice() : slice) as Uint8Array
120
+ }
121
+ }