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
@@ -0,0 +1,507 @@
1
+ # Architecture Guide
2
+
3
+ ## Overview
4
+
5
+ castrum is a **hybrid Bun + Rust** runtime benchmark package that provides high-performance backend primitives through a native bridge with **two transports**: `bun:ffi` (the C-ABI fast path, PRIMARY under Bun) and NAPI via napi-rs (the fallback — Node, forced `CASTRUM_FFI_MODE=napi`, or a failed ffi self-test). The architecture is designed for zero-copy data transfer between TypeScript and Rust, with batching support for high-throughput scenarios.
6
+
7
+ ---
8
+
9
+ ## Two-Layer Architecture
10
+
11
+ ```
12
+ ┌─────────────────────────────────────────────────────────────┐
13
+ │ TypeScript Layer │
14
+ │ │
15
+ │ Purpose: API ergonomics, runtime safety, async handling │
16
+ │ Runtime: Bun (JavaScriptCore) │
17
+ │ Pattern: Factory functions, lazy evaluation, batching │
18
+ └──────────↕ Native Bridge: bun:ffi (Bun, primary) + NAPI (fallback) ↕──────────┘
19
+ │ │
20
+ │ Rust Layer (cdylib) │
21
+ │ │
22
+ │ Purpose: Performance-critical computation, I/O handling │
23
+ │ Allocator: mimalloc (global) │
24
+ │ Pattern: Stateless functions, pre-allocated buffers │
25
+ └─────────────────────────────────────────────────────────────┘
26
+ ```
27
+
28
+ ### Native Bridge (two transports)
29
+
30
+ The bridge between TypeScript and Rust has **two transports**, both loading the SAME
31
+ cdylib (built with napi-rs):
32
+
33
+ - **`bun:ffi` (PRIMARY on Bun)** — `src/native/ffi.ts` `dlopen`s the addon's
34
+ `extern "C"` exports (`rust/ffi.rs`, 79 `castrum_*` symbols — 71 direct + 4
35
+ `validator_c_abi!` + 4 `compress_to_out!`, parity guarded by
36
+ `test/unit/features/ffi-symbol-parity.test.ts`) and Bun JIT-calls them
37
+ directly (~10-20ns crossing vs ~100-350ns N-API). A bind-time self-test guards
38
+ correctness: on ANY failure the layer is disabled and calls fall back to napi.
39
+ `CASTRUM_FFI_MODE=auto|ffi|napi` selects the transport (default `auto`).
40
+ - **NAPI (napi-rs v3) — the fallback** — used under Node.js, when `bun:ffi` is
41
+ unavailable (self-test failure, `CASTRUM_FFI_MODE=napi`), and for surfaces with no
42
+ C-ABI export. Since Phase 6 the compiled-once instances are NOT napi-only — their
43
+ hot per-call ops route through the C-ABI via an opaque-`usize` handle
44
+ (`conditional_is_not_modified`, `media_type_matcher_matches`,
45
+ `accept_negotiator_negotiate`, `jwt_signer_sign`/`_verify`, `template_render`,
46
+ `schema_validator_validate`, `rate_limiter_check*`, `url_builder_resolve`), and
47
+ `jwtVerify` has `castrum_jwt_verify`. Still napi-only: `rust.batch.*` /
48
+ `rust.packed.*`, `text.*`, and the DOM `jsonParse` (only `castrum_json_parse_packed`
49
+ exists).
50
+
51
+ Shared characteristics:
52
+
53
+ - **Zero-copy buffer sharing**: `Uint8Array` objects are passed by reference to Rust, avoiding serialization
54
+ - **Synchronous calls**: All functions are synchronous, running on the main JS thread
55
+ - **Thread pool**: Rayon is available for parallel CPU-bound work
56
+
57
+ ### Dual-binary CPU-detect (x86-64-v3 SIMD variant)
58
+
59
+ On `linux-x64` the package ships a second artifact, `castrum.linux-x64-v3-gnu.node`,
60
+ built with `-C target-cpu=x86-64-v3 -C target-feature=+avx2,+bmi2,+fma,+sse4.2`
61
+ (`scripts/build-v3.sh`, package script `build:v3`; `build:all` builds both). The
62
+ loader (`src/native/loader.ts`) picks the v3 variant at runtime when the host
63
+ CPU exposes AVX2+BMI2+FMA+SSE4.2 (read from `/proc/cpuinfo`), falling back to the
64
+ baseline artifact otherwise — a v3 binary dlopened on an unsupported CPU would
65
+ SIGILL (not catchable from JS), so detection is conservative and requires ALL
66
+ four flags. The v3 variant is OPTIONAL (an absent v3 file resolves to baseline),
67
+ so `build:v3` is never a hard publish requirement.
68
+
69
+ ---
70
+
71
+ ## Module Architecture
72
+
73
+ > **Canonical tree**: [`REPO_MAP.md`](./REPO_MAP.md) is the single-source module map
74
+ > (src/ + rust/ trees, kept in sync). The trees below are the deep-dive annotations.
75
+
76
+ ### Rust Modules
77
+
78
+ One cdylib crate (`Cargo [lib] → rust/lib.rs`) decomposed into domain folders.
79
+ `lib.rs` is a declaration hub with a module map; `util/mod.rs` re-exports the
80
+ shared-infra names so legacy `crate::util::*` call sites keep working.
81
+
82
+ ```
83
+ rust/
84
+ ├── lib.rs ── Crate root: folder declarations + module map, global allocator
85
+ ├── test_support.rs ── shared #[cfg(test)] helpers (pack_headers, decode_packed_pairs, Rng)
86
+ ├── unit_tests.rs ── cross-module test suite
87
+ │
88
+ ├── util/ ── Shared infrastructure (mod.rs re-exports crate::util::*)
89
+ │ ├── bytes.rs ── byte primitives (word-compare, hex, %XX decode, cookie_pairs)
90
+ │ ├── packed.rs ── zero-alloc packed iterators + byte writers (VecWriter, PackedIter)
91
+ │ ├── batch.rs ── aggregate packed batch napi APIs (bitset/count/sum/direct)
92
+ │ ├── batch_core.rs ── generic rayon-parallel batch helpers
93
+ │ ├── threadpool.rs ── rayon pool init + parallelism heuristic
94
+ │ └── validation.rs ── email / UUID / IPv4 / IPv6 validators
95
+ │
96
+ ├── http/ ── HTTP wire formats & parsing
97
+ │ ├── headers.rs ── zero-alloc packed-header parser (HeaderRefs)
98
+ │ ├── method.rs ── HTTP method classification
99
+ │ ├── http_parser.rs ── HTTP request line + headers → packed request (httparse)
100
+ │ ├── query_parser.rs ── query/form string → packed pairs
101
+ │ ├── cookie_parser.rs ── cookie header → packed pairs
102
+ │ ├── form.rs ── x-www-form-urlencoded parser + FormParser instance
103
+ │ ├── media_type.rs ── Content-Type parser + MediaTypeParser / MediaTypeMatcher
104
+ │ ├── url_codec.rs ── percent-encoding encode/decode
105
+ │ ├── url_join.rs ── RFC 3986 url_resolve + query builder (UrlBuilder)
106
+ │ ├── etag.rs ── etag / http_date + ConditionalRequest (304)
107
+ │ ├── accept.rs ── Accept-Encoding negotiation (AcceptNegotiator)
108
+ │ ├── mime_lookup.rs ── extension → MIME (phf table)
109
+ │ └── multipart.rs ── multipart/form-data parser (+ limits)
110
+ │
111
+ ├── crypto/ ── Auth & hashing (compiled-once instances)
112
+ │ ├── hmac_sha256.rs ── HMAC-SHA256 (+ HmacSigner)
113
+ │ ├── cookie_sign.rs ── signed cookies (CookieSigner)
114
+ │ ├── csrf.rs ── CSRF tokens (CsrfProtector)
115
+ │ ├── jwt.rs ── HS256 JWT sign/verify (+ JwtSigner, precomputed header)
116
+ │ ├── aead.rs ── AES-256-GCM / chacha20-poly1305 (+ AeadCipher)
117
+ │ ├── argon2.rs ── argon2id password hashing (+ Argon2Hasher)
118
+ │ ├── base64.rs ── base64/base64url/hex codecs (+ Base64Codec)
119
+ │ ├── hashing.rs ── FNV-1a / XXH3 / crc32
120
+ │ └── random_token.rs ── random hex tokens
121
+ │
122
+ ├── json/ ── JSON & schema
123
+ │ ├── json_ops.rs ── zero-DOM validate/sum + DOM parse (sonic Value → JS)
124
+ │ ├── napi_marshal.rs ── sonic_rs::Value → napi walker (sonic_value_to_js, napi
125
+ │ │ serde-json Number parity) + sonic_values_equal
126
+ │ │ (jsonschema cmp::equal; shared by jwt_verify, json_parse,
127
+ │ │ fast_schema enum/const/uniqueItems)
128
+ │ ├── json_ser.rs ── zero-alloc JSON escaping + cookie/query → JSON writers
129
+ │ ├── json_patch_ops.rs ── CUSTOM sonic RFC 6902 engine (Pointer ~0/~1 unescape +
130
+ │ │ add/remove/replace/move/copy/test incl. array `-`/bounds +
131
+ │ │ 1==1.0; json-patch/jsonptr deps REMOVED)
132
+ │ ├── json_schema.rs ── SchemaValidator napi class (fast + jsonschema fallback)
133
+ │ └── fast_schema/ ── zero-DOM draft-07 JSON Schema fast path
134
+ │ └── mod.rs + types.rs / cursor.rs / compile.rs / validate.rs / errors.rs / email.rs (format:"email" replica) / tests.rs
135
+ │
136
+ ├── payload/ ── Output & streaming
137
+ │ ├── compress.rs ── gzip (zlib-rs) + brotli + batch
138
+ │ ├── sse.rs ── SSE event framing + batch
139
+ │ ├── ws_frames.rs ── RFC 6455 frame codec + batch
140
+ │ ├── websocket.rs ── WebSocket accept-key
141
+ │ └── template.rs ── minijinja rendering (TemplateRenderer) + batch
142
+ │
143
+ └── ingress/ ── The ingress pipeline
144
+ ├── mod.rs ── thin napi boundary: Ingress class + entry points
145
+ ├── pipeline.rs ── core 8-stage pipeline (IngressInner::handle_packed,
146
+ │ write_body_sections, BodySections, IngressSchema)
147
+ ├── tests.rs ── ingress unit tests
148
+ ├── options.rs ── napi option structs + Limits
149
+ ├── time.rs ── monotonic/wall-clock helpers
150
+ ├── packed.rs ── packed-input readers + builder
151
+ ├── cors.rs ── CORS engine (origin matching, preflight evaluation)
152
+ ├── proxy.rs ── HTTPS detection, URL extraction
153
+ ├── ip_trust.rs ── IP resolution (X-Forwarded-For, X-Real-IP)
154
+ ├── rate_limit.rs ── token-bucket rate limiter (per-IP keyed)
155
+ ├── terminal.rs ── terminal response builders (errors)
156
+ ├── output.rs ── SINGLE NUMERIC SOURCE for the output-buffer layout
157
+ └── ingress_constants.rs ── NAPI projection of output.rs (no drift)
158
+ ```
159
+
160
+ ### TypeScript Modules
161
+
162
+ ```
163
+ src/
164
+ ├── ingress/ ── HTTP ingress pipeline (TS layer), decomposed by task
165
+ │ ├── index.ts ── Public API barrel + async factory
166
+ │ ├── fast.ts ── Thin: createIngressFast (packed input) + re-exports
167
+ │ ├── handlers.ts ── Thin: createIngressHandler (packs frame in JS → handleRequestPacked) + response builders
168
+ │ ├── server.ts ── createIngressServer (Bun.serve builder) + buildRouteHandlers + gracefulShutdown
169
+ │ ├── server-node.ts ── createIngressServerNode (node:http adapter, same route handlers; the runtime adapter picks the backend)
170
+ │ ├── constants.ts ── Layout constants (from Rust NAPI)
171
+ │ ├── shared.ts ── JS constants shared by both paths
172
+ │ ├── status.ts ── Status normalization helpers (both paths)
173
+ │ ├── errors.ts ── Fast-path error-code name/message mapping
174
+ │ ├── options.ts ── IngressFast option types + validation + trustProxy warning
175
+ │ ├── types.ts ── Public API types
176
+ │ ├── body.ts ── Streaming request-body reader (size guard + bodyTimeoutMs)
177
+ │ ├── context.ts ── Result snapshot + synthetic/error context builders
178
+ │ ├── packing/ ── header-packing, input-packer, gather-raw-headers
179
+ │ ├── headers/ ── cors, hsts, fast-templates, baked-templates
180
+ │ ├── decode/ ── fast-result, baked-result (two decoders, NOT unified)
181
+ │ ├── response/ ── terminal (fast), error-bodies (pre-baked)
182
+ │ └── routes/ ── read/head/json-write/echo/fallback factories
183
+ │
184
+ ├── shared/
185
+ │ ├── runtime.ts ── facade over src/runtime/detect.ts (isBun/isNode — cached)
186
+ │ ├── codec.ts ── facade over src/runtime/codec.ts (Bun-native UTF-8 codec)
187
+ │ ├── uuid.ts ── facade over src/runtime/uuid.ts
188
+ │ ├── log.ts ── createStructuredLogger (CASTRUM_LOG_LEVEL-gated JSON lines)
189
+ │ ├── request-id.ts ── zero-alloc request ID generator
190
+ │ ├── buffer-pool.ts ── reusable output-buffer pool
191
+ │ ├── response.ts ── pooledBodyResponse (zero-copy response)
192
+ │ ├── bytes.ts ── codec-backed encoder/decoder + toBytes/toText normalizers
193
+ │ └── packed.ts ── packed-wire unpackers + packers
194
+ │
195
+ ├── native/ ── Native transport: bun:ffi (primary on Bun) + NAPI fallback
196
+ │ ├── types.ts ── NativeAddon interface + instance types
197
+ │ ├── loader.ts ── Path resolution (multi-root + env override) + getAddon/lazyAddon
198
+ │ ├── ffi.ts ── bun:ffi C-ABI bindings (Bun-only; bind-time self-test +
199
+ │ │ CASTRUM_FFI_MODE gating; castrum_ingress_layout blob)
200
+ │ └── index.ts ── Barrel
201
+ │
202
+ ├── runtime/ ── RUNTIME ADAPTER seam (Bun/Node), selected once at load
203
+ │ ├── detect.ts ── cached runtime detection (the ONLY place `typeof Bun` is checked)
204
+ │ ├── types.ts ── RuntimeAdapter contract (codec/uuid/env/builtins/transport/server/websocket)
205
+ │ ├── codec.ts ── runtime-native UTF-8 codec (Bun transfer vs TextEncoder/Decoder)
206
+ │ ├── uuid.ts ── uuidv7 (Bun.randomUUIDv7 vs crypto.randomUUID)
207
+ │ ├── builtins.ts ── BUN_WINS delegation registry (single source, `has(op)`)
208
+ │ ├── transport.ts ── bun:ffi-first `resolve(op)` + ffi/napi surfaces (lazy bind)
209
+ │ ├── native.ts ── FFI-facing seam (codec/uuid/env/builtins/transport) — imported by rust-ffi
210
+ │ ├── server.ts ── createIngressServer: Bun.serve on Bun, node:http on Node
211
+ │ ├── websocket.ts ── 101 upgrade on Bun; clear Bun-only error on Node
212
+ │ └── index.ts ── FULL adapter `runtime` (native + server + websocket) — for the public API
213
+ │
214
+ ├── rust-ffi/ ── Rust FFI bindings
215
+ │ ├── options.ts ── RustOptions + input-normalization helpers
216
+ │ ├── addon.ts ── shared lazy addon proxy
217
+ │ ├── context.ts ── per-instance state + namespace helpers
218
+ │ ├── text.ts ── string namespace
219
+ │ ├── batch.ts ── array-of-bytes namespace
220
+ │ ├── packed.ts ── raw packed-wire namespace
221
+ │ ├── scalar/ ── scalar/feature methods (interface + per-domain builders)
222
+ │ ├── client.ts ── createRust factory + default `rust`
223
+ │ └── index.ts ── Barrel
224
+ │
225
+ ├── baseline/ ── JS baseline implementations
226
+ │ ├── index.ts ── Aggregator
227
+ │ └── tasks/ ── 21 baseline tasks (cookie, hashing, hmac, http, json,
228
+ │ json-patch, mime, query, token, url, validation,
229
+ │ websocket, aead, compress, jwt, multipart, password,
230
+ │ streaming, template, pbkdf2, bun-builtins) — full list in docs/REPO_MAP.md
231
+ │
232
+ ├── bench/ ── Benchmark framework
233
+ │ ├── index.ts ── Benchmark orchestrator
234
+ │ ├── assert.ts ── Result assertions
235
+ │ ├── checks.ts ── Correctness checks
236
+ │ ├── checksum.ts ── Checksum verification
237
+ │ ├── comparisons.ts ── Comparison reports
238
+ │ ├── concurrent-worker.ts ── Concurrent worker pool
239
+ │ ├── fixtures.ts ── Test fixtures
240
+ │ ├── measure.ts ── Measurement utilities
241
+ │ ├── now.ts ── High-resolution timer
242
+ │ ├── report.ts ── Report generation
243
+ │ ├── run.ts ── Benchmark runner
244
+ │ ├── types.ts ── Type definitions
245
+ │ └── tasks/ ── 33 benchmark tasks (complex, concurrent, cookie,
246
+ │ hashing, hmac, http, json, json-patch, mime, query,
247
+ │ stress, token, url, validation, websocket, aead,
248
+ │ compress, jwt, password, streaming, template,
249
+ │ json-schema, media-type, form, etag, accept,
250
+ │ encoding, cookie-sign, csrf, url-join, multipart,
251
+ │ loader, bun-builtins) — see docs/REPO_MAP.md
252
+ │
253
+ ├── data/ ── Benchmark-fixture data (internal)
254
+ │ └── json-rows.ts ── JSON row serialization
255
+ ```
256
+
257
+ > **Two ingress consumption paths** (see `docs/INGRESS.md`):
258
+ > 1. `src/ingress/fast.ts` (`createIngressFast`) — JS packs headers
259
+ > (`IngressInputPacker`) → Rust `handle_request_packed`. Wire format from
260
+ > `buildTerminalResponse`: `{"error":{code,status,message,requestId}}`,
261
+ > `x-ratelimit-*`.
262
+ > 2. `src/ingress/handlers.ts` (`createIngressHandler` + route factories +
263
+ > `createIngressServer`) — JS packs the request frame (`IngressInputPacker`
264
+ > + `gatherRawHeadersPacked`) and drives the SAME native core as path 1
265
+ > (`handle_request_packed`). Different wire format: success
266
+ > `{"ok":true,...,"requestId":...}`, errors
267
+ > `{"ok":false,"error":{code,message}}`, `ratelimit-*` headers. The
268
+ > benchmark server (`bench/servers/ingress-server.ts`) uses this path and
269
+ > re-exports the pre-baked functions.
270
+
271
+ ## Node.js Compatibility
272
+
273
+ **Bun is the primary target.** Bun resolves the `bun` exports condition → raw
274
+ `index.ts` (native TS execution, zero startup cost). For Node.js consumers, the
275
+ package ships a compiled ESM bundle built by `bun run build:js`:
276
+
277
+ - `package.json` `exports` → `{ types: ./dist/index.d.ts, bun: ./index.ts,
278
+ node: ./dist/index.js, default: ./dist/index.js }`.
279
+ - `dist/index.js` is a single-file ESM bundle (all runtime deps inlined); Node
280
+ requires `>= 20.3` (N-API floor).
281
+ - The addon loader (`src/native/loader.ts`) resolves `castrum.<platform>-<arch>.node`
282
+ from multiple candidate roots so the same loader works from `src/native/…`
283
+ (Bun, source layout) and `dist/…` (Node, bundled layout), plus the
284
+ `CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH` override.
285
+ - `src/runtime/detect.ts` is the single runtime-detection seam (the ONLY place
286
+ `typeof Bun` is checked); `src/shared/runtime.ts` is a thin facade over it.
287
+ - The public `createIngressServer` is runtime-adaptive: it delegates to the
288
+ Bun.serve builder (`src/ingress/server.ts`) on Bun and to `createIngressServerNode`
289
+ (`src/ingress/server-node.ts`, same route handlers over `node:http`) on Node,
290
+ via `src/runtime/server.ts`. The `BakedServer.server` type is a
291
+ runtime-agnostic `ServerHandle` so Node TS consumers don't need `@types/bun`.
292
+ - The benchmark tooling (`bench/`, `bun:test` suites) remains Bun-only by design.
293
+
294
+ ---
295
+
296
+ ## Data Flow
297
+
298
+ ### Ingress Pipeline (Request Processing)
299
+
300
+ ```
301
+ Request
302
+ │
303
+ ▼
304
+ ┌──────────────────────────────────────────────────────────────┐
305
+ │ TypeScript: packHeaders() │
306
+ │ - Pre-encoded header names (cookie, origin, xff, etc.) │
307
+ │ - Binary packing with length-prefixed (u16 + u32) format │
308
+ │ - TLS header buffer (8KB, recycled across calls) │
309
+ └──────────────────┬───────────────────────────────────────────┘
310
+ │ packed headers + URL bytes + method kind
311
+ ▼
312
+ ┌──────────────────────────────────────────────────────────────┐
313
+ │ Rust: Ingress::handle_request_packed() │
314
+ │ │
315
+ │ 1. Parse packed input (method, url, ip, rid, headers) │
316
+ │ 2. IP trust resolution │
317
+ │ 3. HTTPS detection │
318
+ │ 4. CORS evaluation │
319
+ │ 5. Rate limiting check │
320
+ │ 6. Body size guard │
321
+ │ 7. JSON validation & schema check │
322
+ │ 8. Cookie parsing → output buffer │
323
+ │ 9. Query parsing → output buffer │
324
+ │ 10. Optional metadata JSON │
325
+ │ 11. Write output header (verdict, status, flags, ...) │
326
+ └──────────────────┬───────────────────────────────────────────┘
327
+ │ output buffer
328
+ ▼
329
+ ┌──────────────────────────────────────────────────────────────┐
330
+ │ TypeScript: FastIngressResult.refresh() │
331
+ │ - Read output header fields from binary buffer │
332
+ │ - Store slices for lazy decode (cookies, query, body) │
333
+ │ - Caller inspects result and builds Response │
334
+ └──────────────────────────────────────────────────────────────┘
335
+ ```
336
+
337
+ ### Batch Processing
338
+
339
+ ```
340
+ Multiple Inputs
341
+ │
342
+ ▼
343
+ ┌──────────────────────────────────────────────────────────────┐
344
+ │ TypeScript: packBatch() │
345
+ │ - Pack each input into binary format with length prefix │
346
+ │ - Single Uint8Array sent to Rust │
347
+ └──────────────────┬───────────────────────────────────────────┘
348
+ │ packed batch
349
+ ▼
350
+ ┌──────────────────────────────────────────────────────────────┐
351
+ │ Rust: SchemaValidator.validateBatchPacked() │
352
+ │ - Iterate over packed inputs in a tight loop │
353
+ │ - Process each through validation pipeline │
354
+ │ - Write results as packed output (bitset or length-prefixed)│
355
+ └──────────────────┬───────────────────────────────────────────┘
356
+ │ packed results
357
+ ▼
358
+ ┌──────────────────────────────────────────────────────────────┐
359
+ │ TypeScript: unpackBitset() / unpackByteResults() │
360
+ │ - Extract individual results from packed output │
361
+ │ - Map back to original inputs │
362
+ └──────────────────────────────────────────────────────────────┘
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Output Buffer Layout
368
+
369
+ The output buffer is a binary format shared between Rust and TypeScript. All offsets are defined in `rust/ingress/output.rs` and exported via `rust/ingress/ingress_constants.rs` to `src/ingress/constants.ts`.
370
+
371
+ ```
372
+ Offset (bytes)
373
+ ├─ 0: OUT_VERDICT (u8) 0=pass, 1=terminal
374
+ ├─ 1: OUT_ERROR_CODE (u8) Error code enum
375
+ ├─ 2: OUT_STATUS (u16) HTTP status code (LE)
376
+ ├─ 4: OUT_FLAGS (u32) Bitfield of FLAG_* values (LE)
377
+ ├─ 8: OUT_RATE_LIMIT (u32) Rate limit ceiling (LE)
378
+ ├─ 12: OUT_RATE_REMAINING (u32) Remaining requests (LE)
379
+ ├─ 16: OUT_RATE_RESET (u64) Rate limit reset timestamp ms (LE)
380
+ ├─ 24: OUT_RETRY_AFTER (u64) Retry-After duration ms (LE)
381
+ ├─ 32: OUT_COOKIES_JSON_LEN (u32) Cookie JSON length (LE)
382
+ ├─ 36: OUT_QUERY_JSON_LEN (u32) Query JSON length (LE)
383
+ ├─ 40: OUT_HEADER_VARIANT (u8) Header variant index
384
+ ├─ 44: OUT_BODY_JSON_LEN (u32) Body metadata JSON length (LE)
385
+ ├─ 48: OUT_DATA_START ─── Variable-length data starts here
386
+ │ ├── Cookie JSON string (if present)
387
+ │ ├── Query JSON string (if present)
388
+ │ └── Body metadata JSON (if emitMetadataJson enabled)
389
+ ```
390
+
391
+ ### Flags (bitfield)
392
+
393
+ | Flag | Bit | Description |
394
+ |------|-----|-------------|
395
+ | `FLAG_HAS_COOKIES` | 0 | Cookie data present in output |
396
+ | `FLAG_HAS_QUERY` | 1 | Query data present in output |
397
+ | `FLAG_BODY_VALID_JSON` | 2 | Body is valid JSON |
398
+ | `FLAG_SCHEMA_VALID` | 3 | Body passes JSON Schema validation |
399
+ | `FLAG_CORS_ALLOWED` | 4 | CORS origin allowed |
400
+ | `FLAG_IS_PREFLIGHT` | 5 | Request is a CORS preflight |
401
+ | `FLAG_RATE_LIMITED` | 6 | Rate limit exceeded |
402
+ | `FLAG_HTTPS` | 7 | Request is HTTPS (detected or configured) |
403
+ | `FLAG_TRUSTED_PROXY` | 8 | Request from trusted proxy |
404
+ | `FLAG_BODY_TRUNCATED` | 9 | Output data truncated due to buffer size |
405
+
406
+ ### Error Codes
407
+
408
+ | Code | Value | Description |
409
+ |------|-------|-------------|
410
+ | `ERR_CODE_NONE` | 0 | No error |
411
+ | `ERR_CODE_CORS_PREFLIGHT` | 1 | CORS preflight rejected |
412
+ | `ERR_CODE_RATE_LIMITED` | 2 | Rate limit exceeded |
413
+ | `ERR_CODE_BODY_TOO_LARGE` | 3 | Request body exceeds maxBodyBytes |
414
+ | `ERR_CODE_INVALID_JSON` | 4 | Invalid JSON in body |
415
+ | `ERR_CODE_SCHEMA_VALIDATION` | 5 | Schema validation failed |
416
+ | `ERR_CODE_BAD_REQUEST` | 6 | Malformed request |
417
+ | `ERR_CODE_REQUEST_TOO_LARGE` | 7 | Request headers/URL too large |
418
+ | `ERR_CODE_INTERNAL` | 8 | Internal server error |
419
+
420
+ ---
421
+
422
+ ## Memory Management
423
+
424
+ 1. **Global allocator**: mimalloc (`MiMalloc`) for fast, scalable allocation
425
+ 2. **Buffer reuse**: Output buffers are recycled across requests where possible
426
+ 3. **Zero-copy**: `Uint8Array` passed to Rust functions are used in-place, not copied
427
+ 4. **Slice overlap detection**: Rust checks for input/output buffer overlap and copies if needed
428
+ 5. **Thread-local caches**: Header buffers use thread-local recycling to reduce allocation pressure
429
+
430
+ ---
431
+
432
+ ## JSON & zero-DOM architecture
433
+
434
+ - **`sonic_rs::Value` is the primary DOM** on every crate boundary that allows
435
+ it (compact 16-byte tagged nodes, borrowed/inline strings, zero per-key heap
436
+ `String`). `serde_json::Value` REMAINS only where a crate is hardwired to it
437
+ and cannot be swapped: the jsonschema 0.48 DOM fallback
438
+ (`json_schema.rs` + `pipeline.rs::IngressSchema`), `selection.rs`, and the
439
+ napi JS-object input of `jwt_sign` / `JwtSigner::sign`.
440
+ - **`rust/json/napi_marshal.rs`** is the single sonic→JS bridge:
441
+ `sonic_value_to_js` walks a `sonic_rs::Value` into a napi value with
442
+ **byte-parity** to napi's `serde-json` `ToNapiValue for serde_json::Number`
443
+ (PosInt u64 ≤ u32::MAX → Number, larger → BigInt; NegInt within ±2^53-1 →
444
+ Number, outside → BigInt; Float → Number). `sonic_values_equal` replicates
445
+ jsonschema `cmp::equal` (exact cross-type numeric equality + insertion-order
446
+ object keys) for fast_schema enum/const/uniqueItems and is unit-pinned against
447
+ a serde_json reference over a value corpus.
448
+ - **Zero-DOM hot paths** (no `serde_json::Value` on the per-request path):
449
+ - `castrum_json_parse_packed` (FFI) parses ONCE with sonic-rs and emits a
450
+ typed token stream (deduped string table) that the JS decoder assembles
451
+ with NO second JSON text parse.
452
+ - JWT verify (`verify_token_with_key`) decodes the payload ONCE, checks
453
+ `exp`/`nbf`/`iat` over `sonic_rs::Value`, and returns the payload BYTES
454
+ (no re-serialize); `jwt_verify` / `JwtSigner::verify` marshal via the
455
+ walker. JWT sign (`jwtSignBytes`, `JwtSigner::signBytes`,
456
+ `castrum_jwt_sign_bytes*`) injects `iat`/`exp` into a sonic object
457
+ (`inject_and_payload_b64_sonic`) and serializes with `sonic_rs::to_vec`.
458
+ - fast_schema enum/const/uniqueItems re-parse the doc into `sonic_rs::Value`
459
+ and compare with `sonic_values_equal` (no `Vec<serde_json::Value>`).
460
+ - minijinja contexts are `sonic_rs::Value` (implements `Serialize`).
461
+ - **Custom RFC 6902 patch engine** (`json_patch_ops.rs`): no longer round-trips
462
+ through the serde_json-DOM `json-patch` crate. A `Pointer` type (`~0`/`~1`
463
+ unescape, RFC 6901) + `PatchOp` parser + applicator (add incl. array `-` and
464
+ bounds, remove, replace, move, copy, test incl. `1 == 1.0`) runs directly on
465
+ `sonic_rs::Value`, keeping `JsonPatchError` semantics, the input/output size
466
+ guards, and the packed batch entry points. `json-patch` and `jsonptr` deps
467
+ are removed.
468
+ - **Serialization determinism (`sonic sort_keys`)**: sonic's owned `Value`
469
+ backs MUTABLE objects with `AHashMap` (non-deterministic iteration order), so
470
+ the crate enables `sort_keys` (`Cargo.toml`), swapping the owned map to
471
+ `BTreeMap` → deterministic sorted serialization. This is a hard wire-format
472
+ requirement: JWT sign and the patch engine must emit byte-identical output
473
+ across the FFI/napi/scalar/bulk transports. Parsed (unmutated) values keep
474
+ the shared Vec (insertion order) — only `as_object_mut()` + `insert` switches
475
+ to the map — so `sonic_values_equal` / `sonic_value_to_js` (which iterate the
476
+ shared representation) keep insertion-order semantics.
477
+
478
+ ---
479
+
480
+ ## Concurrency Model
481
+
482
+ - **Synchronous calls**: Run on the main JS thread (Bun's event loop)
483
+ - **Rayon**: Available for parallel CPU-bound work (e.g., batch validation)
484
+ - **Rate limiter**: Uses `parking_lot::Mutex` for low-contention access
485
+
486
+ ---
487
+
488
+ ## Dependency Graph
489
+
490
+ ```
491
+ [ingress] ────┬─── [cors] ──── [headers] ──── [method]
492
+ ├─── [rate_limit] ─── [ip_trust]
493
+ ├─── [proxy]
494
+ ├─── [output] ──── [terminal]
495
+ ├─── [json_ser] ──── [util]
496
+ ├─── [query_parser]
497
+ └─── [cookie_parser]
498
+ [hashing] ──── [hmac_sha256]
499
+ [validation]
500
+ [json_ops] ──── [json_patch_ops]
501
+ [json_schema]
502
+ [url_codec]
503
+ [mime_lookup]
504
+ [websocket]
505
+ [random_token]
506
+ [batch]
507
+ [http_parser]