castrum 0.7.0 → 0.9.0

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 (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -59
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +3161 -0
  5. package/dist/index.js +7306 -0
  6. package/docs/ARCHITECTURE.md +415 -0
  7. package/docs/BENCHMARKS.md +307 -0
  8. package/docs/ENVIRONMENT.md +79 -0
  9. package/docs/GETTING_STARTED.md +209 -0
  10. package/docs/INGRESS.md +745 -0
  11. package/docs/REPO_MAP.md +259 -0
  12. package/docs/adr/0001-two-wire-formats.md +39 -0
  13. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  14. package/docs/adr/0003-bun-builtins-delegation.md +42 -0
  15. package/docs/bun-builtins-decision-matrix.md +108 -0
  16. package/index.ts +51 -8
  17. package/package.json +73 -20
  18. package/src/data/json-rows.ts +13 -13
  19. package/src/ingress/body.ts +149 -0
  20. package/src/ingress/constants.ts +220 -48
  21. package/src/ingress/context.ts +136 -0
  22. package/src/ingress/decode/baked-result.ts +121 -0
  23. package/src/ingress/decode/fast-result.ts +160 -0
  24. package/src/ingress/decode/packed-sections.ts +56 -0
  25. package/src/ingress/decode/result-base.ts +172 -0
  26. package/src/ingress/errors.ts +72 -0
  27. package/src/ingress/fast.ts +152 -1021
  28. package/src/ingress/handlers.ts +563 -1001
  29. package/src/ingress/headers/baked-templates.ts +102 -0
  30. package/src/ingress/headers/cors.ts +44 -0
  31. package/src/ingress/headers/fast-templates.ts +238 -0
  32. package/src/ingress/headers/hsts.ts +83 -0
  33. package/src/ingress/health.ts +67 -0
  34. package/src/ingress/index.ts +130 -405
  35. package/src/ingress/metrics.ts +132 -0
  36. package/src/ingress/options.ts +197 -0
  37. package/src/ingress/packing/gather-raw-headers.ts +75 -0
  38. package/src/ingress/packing/header-packing.ts +75 -0
  39. package/src/ingress/packing/input-packer.ts +150 -0
  40. package/src/ingress/packing/scratch.ts +50 -0
  41. package/src/ingress/packing/select-headers.ts +96 -0
  42. package/src/ingress/response/error-bodies.ts +75 -0
  43. package/src/ingress/response/terminal.ts +59 -0
  44. package/src/ingress/routes/common.ts +38 -0
  45. package/src/ingress/routes/delete.ts +14 -0
  46. package/src/ingress/routes/echo.ts +107 -0
  47. package/src/ingress/routes/fallback.ts +23 -0
  48. package/src/ingress/routes/head.ts +37 -0
  49. package/src/ingress/routes/index.ts +10 -0
  50. package/src/ingress/routes/json-write.ts +153 -0
  51. package/src/ingress/routes/options.ts +36 -0
  52. package/src/ingress/routes/read.ts +45 -0
  53. package/src/ingress/server-node.ts +376 -0
  54. package/src/ingress/server.ts +443 -0
  55. package/src/ingress/shared.ts +132 -0
  56. package/src/ingress/status.ts +97 -0
  57. package/src/ingress/types.ts +157 -0
  58. package/src/integration/batch.ts +58 -0
  59. package/src/integration/index.ts +29 -0
  60. package/src/integration/pipeline.ts +217 -0
  61. package/src/integration/streaming.ts +79 -0
  62. package/src/integration/websocket.ts +92 -0
  63. package/src/loader/batch.ts +133 -0
  64. package/src/loader/cost.ts +104 -0
  65. package/src/loader/index.ts +930 -0
  66. package/src/loader/ops.ts +657 -0
  67. package/src/native/ffi.ts +1541 -0
  68. package/src/native/index.ts +8 -207
  69. package/src/native/loader.ts +194 -0
  70. package/src/native/types.ts +598 -0
  71. package/src/rust-ffi/addon.ts +10 -0
  72. package/src/rust-ffi/batch.ts +431 -0
  73. package/src/rust-ffi/client.ts +88 -0
  74. package/src/rust-ffi/context.ts +164 -0
  75. package/src/rust-ffi/index.ts +40 -541
  76. package/src/rust-ffi/options.ts +113 -0
  77. package/src/rust-ffi/packed.ts +253 -0
  78. package/src/rust-ffi/proven.ts +42 -0
  79. package/src/rust-ffi/scalar/crypto.ts +265 -0
  80. package/src/rust-ffi/scalar/factories.ts +233 -0
  81. package/src/rust-ffi/scalar/hashing.ts +72 -0
  82. package/src/rust-ffi/scalar/http.ts +206 -0
  83. package/src/rust-ffi/scalar/index.ts +31 -0
  84. package/src/rust-ffi/scalar/interface.ts +450 -0
  85. package/src/rust-ffi/scalar/json.ts +45 -0
  86. package/src/rust-ffi/scalar/payload.ts +124 -0
  87. package/src/rust-ffi/text.ts +95 -0
  88. package/src/selection.json +194 -0
  89. package/src/selection.ts +77 -0
  90. package/src/shared/adaptive.ts +68 -0
  91. package/src/shared/bench-classify.ts +209 -0
  92. package/src/shared/buffer-pool.ts +209 -0
  93. package/src/shared/bytes.ts +35 -5
  94. package/src/shared/env.ts +20 -0
  95. package/src/shared/log.ts +74 -0
  96. package/src/shared/metrics.ts +247 -0
  97. package/src/shared/packed.ts +350 -154
  98. package/src/shared/proven.ts +596 -0
  99. package/src/shared/request-id.ts +67 -0
  100. package/src/shared/response.ts +104 -0
  101. package/src/shared/runtime.ts +34 -0
  102. package/src/shared/trace.ts +81 -0
  103. package/src/shared/uuid.ts +31 -0
  104. package/Cargo.toml +0 -73
  105. package/bench.ts +0 -3
  106. package/rust/batch.rs +0 -195
  107. package/rust/cookie_parser.rs +0 -70
  108. package/rust/cors.rs +0 -263
  109. package/rust/hashing.rs +0 -164
  110. package/rust/headers.rs +0 -234
  111. package/rust/hmac_sha256.rs +0 -113
  112. package/rust/http_parser.rs +0 -132
  113. package/rust/ingress.rs +0 -616
  114. package/rust/ingress_constants.rs +0 -89
  115. package/rust/ip_trust.rs +0 -185
  116. package/rust/json_ops.rs +0 -66
  117. package/rust/json_patch_ops.rs +0 -25
  118. package/rust/json_schema.rs +0 -151
  119. package/rust/json_ser.rs +0 -429
  120. package/rust/lib.rs +0 -48
  121. package/rust/method.rs +0 -141
  122. package/rust/mime_lookup.rs +0 -76
  123. package/rust/output.rs +0 -238
  124. package/rust/proxy.rs +0 -90
  125. package/rust/query_parser.rs +0 -141
  126. package/rust/random_token.rs +0 -52
  127. package/rust/rate_limit.rs +0 -203
  128. package/rust/terminal.rs +0 -95
  129. package/rust/unit_tests.rs +0 -843
  130. package/rust/url_codec.rs +0 -133
  131. package/rust/util.rs +0 -537
  132. package/rust/validation.rs +0 -107
  133. package/rust/websocket.rs +0 -23
  134. package/src/baseline/index.ts +0 -12
  135. package/src/baseline/tasks/cookie.ts +0 -22
  136. package/src/baseline/tasks/hashing.ts +0 -18
  137. package/src/baseline/tasks/hmac.ts +0 -38
  138. package/src/baseline/tasks/http.ts +0 -62
  139. package/src/baseline/tasks/json-patch.ts +0 -12
  140. package/src/baseline/tasks/json.ts +0 -50
  141. package/src/baseline/tasks/mime.ts +0 -5
  142. package/src/baseline/tasks/query.ts +0 -14
  143. package/src/baseline/tasks/token.ts +0 -15
  144. package/src/baseline/tasks/url.ts +0 -11
  145. package/src/baseline/tasks/validation.ts +0 -19
  146. package/src/baseline/tasks/websocket.ts +0 -14
  147. package/src/bench/assert.ts +0 -70
  148. package/src/bench/checks.ts +0 -227
  149. package/src/bench/checksum.ts +0 -27
  150. package/src/bench/comparisons.ts +0 -70
  151. package/src/bench/concurrent-worker.ts +0 -135
  152. package/src/bench/fixtures.ts +0 -233
  153. package/src/bench/index.ts +0 -2
  154. package/src/bench/measure.ts +0 -177
  155. package/src/bench/now.ts +0 -3
  156. package/src/bench/report.ts +0 -79
  157. package/src/bench/run.ts +0 -57
  158. package/src/bench/tasks/complex.ts +0 -232
  159. package/src/bench/tasks/concurrent.ts +0 -213
  160. package/src/bench/tasks/cookie.ts +0 -44
  161. package/src/bench/tasks/hashing.ts +0 -33
  162. package/src/bench/tasks/hmac.ts +0 -36
  163. package/src/bench/tasks/http.ts +0 -50
  164. package/src/bench/tasks/index.ts +0 -52
  165. package/src/bench/tasks/json-patch.ts +0 -21
  166. package/src/bench/tasks/json.ts +0 -33
  167. package/src/bench/tasks/mime.ts +0 -21
  168. package/src/bench/tasks/query.ts +0 -44
  169. package/src/bench/tasks/stress.ts +0 -142
  170. package/src/bench/tasks/token.ts +0 -20
  171. package/src/bench/tasks/url.ts +0 -34
  172. package/src/bench/tasks/validation.ts +0 -57
  173. package/src/bench/tasks/websocket.ts +0 -21
  174. package/src/bench/types.ts +0 -60
  175. package/src/ingress/packed-input.ts +0 -59
  176. package/src/shared/json.ts +0 -18
@@ -0,0 +1,415 @@
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`, 45 `castrum_*` symbols) and Bun JIT-calls them
35
+ directly (~10-20ns crossing vs ~100-350ns N-API). A bind-time self-test guards
36
+ correctness: on ANY failure the layer is disabled and calls fall back to napi.
37
+ `CASTRUM_FFI_MODE=auto|ffi|napi` selects the transport (default `auto`).
38
+ - **NAPI (napi-rs v3) — the fallback** — used under Node.js, when `bun:ffi` is
39
+ unavailable (self-test failure, `CASTRUM_FFI_MODE=napi`), and for surfaces with no
40
+ C-ABI export (stateful instances, `rust.batch.*` / `rust.packed.*`, `text.*`, and
41
+ structured-return scalars like `jsonParse` / `jwtVerify`).
42
+
43
+ Shared characteristics:
44
+
45
+ - **Zero-copy buffer sharing**: `Uint8Array` objects are passed by reference to Rust, avoiding serialization
46
+ - **Synchronous calls**: All functions are synchronous, running on the main JS thread
47
+ - **Thread pool**: Rayon is available for parallel CPU-bound work
48
+
49
+ ---
50
+
51
+ ## Module Architecture
52
+
53
+ ### Rust Modules
54
+
55
+ One cdylib crate (`Cargo [lib] → rust/lib.rs`) decomposed into domain folders.
56
+ `lib.rs` is a declaration hub with a module map; `util/mod.rs` re-exports the
57
+ shared-infra names so legacy `crate::util::*` call sites keep working.
58
+
59
+ ```
60
+ rust/
61
+ ├── lib.rs ── Crate root: folder declarations + module map, global allocator
62
+ ├── test_support.rs ── shared #[cfg(test)] helpers (pack_headers, decode_packed_pairs, Rng)
63
+ ├── unit_tests.rs ── cross-module test suite
64
+ │
65
+ ├── util/ ── Shared infrastructure (mod.rs re-exports crate::util::*)
66
+ │ ├── bytes.rs ── byte primitives (word-compare, hex, %XX decode, cookie_pairs)
67
+ │ ├── packed.rs ── zero-alloc packed iterators + byte writers (VecWriter, PackedIter)
68
+ │ ├── batch.rs ── aggregate packed batch napi APIs (bitset/count/sum/direct)
69
+ │ ├── batch_core.rs ── generic rayon-parallel batch helpers
70
+ │ ├── threadpool.rs ── rayon pool init + parallelism heuristic
71
+ │ └── validation.rs ── email / UUID / IPv4 / IPv6 validators
72
+ │
73
+ ├── http/ ── HTTP wire formats & parsing
74
+ │ ├── headers.rs ── zero-alloc packed-header parser (HeaderRefs)
75
+ │ ├── method.rs ── HTTP method classification
76
+ │ ├── http_parser.rs ── HTTP request line + headers → packed request (httparse)
77
+ │ ├── query_parser.rs ── query/form string → packed pairs
78
+ │ ├── cookie_parser.rs ── cookie header → packed pairs
79
+ │ ├── form.rs ── x-www-form-urlencoded parser + FormParser instance
80
+ │ ├── media_type.rs ── Content-Type parser + MediaTypeParser / MediaTypeMatcher
81
+ │ ├── url_codec.rs ── percent-encoding encode/decode
82
+ │ ├── url_join.rs ── RFC 3986 url_resolve + query builder (UrlBuilder)
83
+ │ ├── etag.rs ── etag / http_date + ConditionalRequest (304)
84
+ │ ├── accept.rs ── Accept-Encoding negotiation (AcceptNegotiator)
85
+ │ ├── mime_lookup.rs ── extension → MIME (phf table)
86
+ │ └── multipart.rs ── multipart/form-data parser (+ limits)
87
+ │
88
+ ├── crypto/ ── Auth & hashing (compiled-once instances)
89
+ │ ├── hmac_sha256.rs ── HMAC-SHA256 (+ HmacSigner)
90
+ │ ├── cookie_sign.rs ── signed cookies (CookieSigner)
91
+ │ ├── csrf.rs ── CSRF tokens (CsrfProtector)
92
+ │ ├── jwt.rs ── HS256 JWT sign/verify (+ JwtSigner, precomputed header)
93
+ │ ├── aead.rs ── AES-256-GCM / chacha20-poly1305 (+ AeadCipher)
94
+ │ ├── argon2.rs ── argon2id password hashing (+ Argon2Hasher)
95
+ │ ├── base64.rs ── base64/base64url/hex codecs (+ Base64Codec)
96
+ │ ├── hashing.rs ── FNV-1a / XXH3 / crc32
97
+ │ └── random_token.rs ── random hex tokens
98
+ │
99
+ ├── json/ ── JSON & schema
100
+ │ ├── json_ops.rs ── zero-DOM validate/sum + DOM parse
101
+ │ ├── json_ser.rs ── zero-alloc JSON escaping + cookie/query → JSON writers
102
+ │ ├── json_patch_ops.rs ── RFC 6902 JSON patch
103
+ │ ├── json_schema.rs ── SchemaValidator napi class (fast + jsonschema fallback)
104
+ │ └── fast_schema/ ── zero-DOM draft-07 JSON Schema fast path
105
+ │ └── mod.rs + types.rs / cursor.rs / compile.rs / validate.rs / errors.rs / email.rs (format:"email" replica) / tests.rs
106
+ │
107
+ ├── payload/ ── Output & streaming
108
+ │ ├── compress.rs ── gzip (zlib-rs) + brotli + batch
109
+ │ ├── sse.rs ── SSE event framing + batch
110
+ │ ├── ws_frames.rs ── RFC 6455 frame codec + batch
111
+ │ ├── websocket.rs ── WebSocket accept-key
112
+ │ └── template.rs ── minijinja rendering (TemplateRenderer) + batch
113
+ │
114
+ └── ingress/ ── The ingress pipeline
115
+ ├── mod.rs ── thin napi boundary: Ingress class + entry points
116
+ ├── pipeline.rs ── core 8-stage pipeline (IngressInner::handle_packed,
117
+ │ write_body_sections, BodySections, IngressSchema)
118
+ ├── tests.rs ── ingress unit tests
119
+ ├── options.rs ── napi option structs + Limits
120
+ ├── time.rs ── monotonic/wall-clock helpers
121
+ ├── packed.rs ── packed-input readers + builder
122
+ ├── cors.rs ── CORS engine (origin matching, preflight evaluation)
123
+ ├── proxy.rs ── HTTPS detection, URL extraction
124
+ ├── ip_trust.rs ── IP resolution (X-Forwarded-For, X-Real-IP)
125
+ ├── rate_limit.rs ── token-bucket rate limiter (per-IP keyed)
126
+ ├── terminal.rs ── terminal response builders (errors)
127
+ ├── output.rs ── SINGLE NUMERIC SOURCE for the output-buffer layout
128
+ └── ingress_constants.rs ── NAPI projection of output.rs (no drift)
129
+ ```
130
+
131
+ ### TypeScript Modules
132
+
133
+ ```
134
+ src/
135
+ ├── ingress/ ── HTTP ingress pipeline (TS layer), decomposed by task
136
+ │ ├── index.ts ── Public API barrel + async factory
137
+ │ ├── fast.ts ── Thin: createIngressFast (packed input) + re-exports
138
+ │ ├── handlers.ts ── Thin: createIngressHandler (packs frame in JS → handleRequestPacked) + response builders
139
+ │ ├── server.ts ── createIngressServer (Bun.serve builder) + buildRouteHandlers + gracefulShutdown
140
+ │ ├── server-node.ts ── createIngressServerNode (node:http adapter, same route handlers)
141
+ │ ├── constants.ts ── Layout constants (from Rust NAPI)
142
+ │ ├── shared.ts ── JS constants shared by both paths
143
+ │ ├── status.ts ── Status normalization helpers (both paths)
144
+ │ ├── errors.ts ── Fast-path error-code name/message mapping
145
+ │ ├── options.ts ── IngressFast option types + validation + trustProxy warning
146
+ │ ├── types.ts ── Public API types
147
+ │ ├── body.ts ── Streaming request-body reader (size guard + bodyTimeoutMs)
148
+ │ ├── context.ts ── Result snapshot + synthetic/error context builders
149
+ │ ├── packing/ ── header-packing, input-packer, gather-raw-headers
150
+ │ ├── headers/ ── cors, hsts, fast-templates, baked-templates
151
+ │ ├── decode/ ── fast-result, baked-result (two decoders, NOT unified)
152
+ │ ├── response/ ── terminal (fast), error-bodies (pre-baked)
153
+ │ └── routes/ ── read/head/json-write/echo/fallback factories
154
+ │
155
+ ├── shared/
156
+ │ ├── runtime.ts ── isBun/isNode runtime detection (single seam)
157
+ │ ├── log.ts ── createStructuredLogger (CASTRUM_LOG_LEVEL-gated JSON lines)
158
+ │ ├── request-id.ts ── zero-alloc request ID generator
159
+ │ ├── buffer-pool.ts ── reusable output-buffer pool
160
+ │ ├── response.ts ── pooledBodyResponse (zero-copy response)
161
+ │ ├── bytes.ts ── shared TextEncoder/Decoder singletons
162
+ │ ├── packed.ts ── packed-wire unpackers + packers
163
+ │ └── proven.ts ── PROVEN_SURFACE registry (single source for `proven`)
164
+ │
165
+ ├── native/ ── Native transport: bun:ffi (primary on Bun) + NAPI fallback
166
+ │ ├── types.ts ── NativeAddon interface + instance types
167
+ │ ├── loader.ts ── Path resolution (multi-root + env override) + getAddon/lazyAddon
168
+ │ ├── ffi.ts ── bun:ffi C-ABI bindings (Bun-only; bind-time self-test +
169
+ │ │ CASTRUM_FFI_MODE gating; castrum_ingress_layout blob)
170
+ │ └── index.ts ── Barrel
171
+ │
172
+ ├── rust-ffi/ ── Rust FFI bindings
173
+ │ ├── options.ts ── RustOptions + input-normalization helpers
174
+ │ ├── addon.ts ── shared lazy addon proxy
175
+ │ ├── context.ts ── per-instance state + namespace helpers
176
+ │ ├── text.ts ── string namespace
177
+ │ ├── batch.ts ── array-of-bytes namespace
178
+ │ ├── packed.ts ── raw packed-wire namespace
179
+ │ ├── scalar/ ── scalar/feature methods (interface + per-domain builders)
180
+ │ ├── client.ts ── createRust factory + default `rust`
181
+ │ └── index.ts ── Barrel
182
+ │
183
+ ├── baseline/ ── JS baseline implementations
184
+ │ ├── index.ts ── Aggregator
185
+ │ └── tasks/ ── 21 baseline tasks (cookie, hashing, hmac, http, json,
186
+ │ json-patch, mime, query, token, url, validation,
187
+ │ websocket, aead, compress, jwt, multipart, password,
188
+ │ streaming, template, pbkdf2, bun-builtins) — full list in docs/REPO_MAP.md
189
+ │
190
+ ├── bench/ ── Benchmark framework
191
+ │ ├── index.ts ── Benchmark orchestrator
192
+ │ ├── assert.ts ── Result assertions
193
+ │ ├── checks.ts ── Correctness checks
194
+ │ ├── checksum.ts ── Checksum verification
195
+ │ ├── comparisons.ts ── Comparison reports
196
+ │ ├── concurrent-worker.ts ── Concurrent worker pool
197
+ │ ├── fixtures.ts ── Test fixtures
198
+ │ ├── measure.ts ── Measurement utilities
199
+ │ ├── now.ts ── High-resolution timer
200
+ │ ├── report.ts ── Report generation
201
+ │ ├── run.ts ── Benchmark runner
202
+ │ ├── types.ts ── Type definitions
203
+ │ └── tasks/ ── 33 benchmark tasks (complex, concurrent, cookie,
204
+ │ hashing, hmac, http, json, json-patch, mime, query,
205
+ │ stress, token, url, validation, websocket, aead,
206
+ │ compress, jwt, password, streaming, template,
207
+ │ json-schema, media-type, form, etag, accept,
208
+ │ encoding, cookie-sign, csrf, url-join, multipart,
209
+ │ loader, bun-builtins) — see docs/REPO_MAP.md
210
+ │
211
+ ├── data/ ── Benchmark-fixture data (internal)
212
+ │ └── json-rows.ts ── JSON row serialization
213
+ ```
214
+
215
+ > **Two ingress consumption paths** (see `docs/INGRESS.md`):
216
+ > 1. `src/ingress/fast.ts` (`createIngressFast`) — JS packs headers
217
+ > (`IngressInputPacker`) → Rust `handle_request_packed`. Wire format from
218
+ > `buildTerminalResponse`: `{"error":{code,status,message,requestId}}`,
219
+ > `x-ratelimit-*`.
220
+ > 2. `src/ingress/handlers.ts` (`createIngressHandler` + route factories +
221
+ > `createIngressServer`) — JS packs the request frame (`IngressInputPacker`
222
+ > + `gatherRawHeadersPacked`) and drives the SAME native core as path 1
223
+ > (`handle_request_packed`). Different wire format: success
224
+ > `{"ok":true,...,"requestId":...}`, errors
225
+ > `{"ok":false,"error":{code,message}}`, `ratelimit-*` headers. The
226
+ > benchmark server (`bench/servers/ingress-server.ts`) uses this path and
227
+ > re-exports the pre-baked functions.
228
+
229
+ ## Node.js Compatibility
230
+
231
+ **Bun is the primary target.** Bun resolves the `bun` exports condition → raw
232
+ `index.ts` (native TS execution, zero startup cost). For Node.js consumers, the
233
+ package ships a compiled ESM bundle built by `bun run build:js`:
234
+
235
+ - `package.json` `exports` → `{ types: ./dist/index.d.ts, bun: ./index.ts,
236
+ node: ./dist/index.js, default: ./dist/index.js }`.
237
+ - `dist/index.js` is a single-file ESM bundle (all runtime deps inlined); Node
238
+ requires `>= 20.3` (N-API floor).
239
+ - The addon loader (`src/native/loader.ts`) resolves `castrum.<platform>-<arch>.node`
240
+ from multiple candidate roots so the same loader works from `src/native/…`
241
+ (Bun, source layout) and `dist/…` (Node, bundled layout), plus the
242
+ `CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH` override.
243
+ - `src/shared/runtime.ts` is the single runtime-detection seam (`isBun`/`isNode`).
244
+ - `createIngressServer` (Bun.serve) is Bun-only; `createIngressServerNode`
245
+ (`src/ingress/server-node.ts`) serves the SAME route handlers over `node:http`,
246
+ sharing `buildRouteHandlers` in `server.ts`. The `BakedServer.server` type is a
247
+ runtime-agnostic `ServerHandle` so Node TS consumers don't need `@types/bun`.
248
+ - The benchmark tooling (`bench/`, `bun:test` suites) remains Bun-only by design.
249
+
250
+ ---
251
+
252
+ ## Data Flow
253
+
254
+ ### Ingress Pipeline (Request Processing)
255
+
256
+ ```
257
+ Request
258
+ │
259
+ ▼
260
+ ┌──────────────────────────────────────────────────────────────┐
261
+ │ TypeScript: packHeaders() │
262
+ │ - Pre-encoded header names (cookie, origin, xff, etc.) │
263
+ │ - Binary packing with length-prefixed (u16 + u32) format │
264
+ │ - TLS header buffer (8KB, recycled across calls) │
265
+ └──────────────────┬───────────────────────────────────────────┘
266
+ │ packed headers + URL bytes + method kind
267
+ ▼
268
+ ┌──────────────────────────────────────────────────────────────┐
269
+ │ Rust: Ingress::handle_request_packed() │
270
+ │ │
271
+ │ 1. Parse packed input (method, url, ip, rid, headers) │
272
+ │ 2. IP trust resolution │
273
+ │ 3. HTTPS detection │
274
+ │ 4. CORS evaluation │
275
+ │ 5. Rate limiting check │
276
+ │ 6. Body size guard │
277
+ │ 7. JSON validation & schema check │
278
+ │ 8. Cookie parsing → output buffer │
279
+ │ 9. Query parsing → output buffer │
280
+ │ 10. Optional metadata JSON │
281
+ │ 11. Write output header (verdict, status, flags, ...) │
282
+ └──────────────────┬───────────────────────────────────────────┘
283
+ │ output buffer
284
+ ▼
285
+ ┌──────────────────────────────────────────────────────────────┐
286
+ │ TypeScript: FastIngressResult.refresh() │
287
+ │ - Read output header fields from binary buffer │
288
+ │ - Store slices for lazy decode (cookies, query, body) │
289
+ │ - Caller inspects result and builds Response │
290
+ └──────────────────────────────────────────────────────────────┘
291
+ ```
292
+
293
+ ### Batch Processing
294
+
295
+ ```
296
+ Multiple Inputs
297
+ │
298
+ ▼
299
+ ┌──────────────────────────────────────────────────────────────┐
300
+ │ TypeScript: packBatch() │
301
+ │ - Pack each input into binary format with length prefix │
302
+ │ - Single Uint8Array sent to Rust │
303
+ └──────────────────┬───────────────────────────────────────────┘
304
+ │ packed batch
305
+ ▼
306
+ ┌──────────────────────────────────────────────────────────────┐
307
+ │ Rust: SchemaValidator.validateBatchPacked() │
308
+ │ - Iterate over packed inputs in a tight loop │
309
+ │ - Process each through validation pipeline │
310
+ │ - Write results as packed output (bitset or length-prefixed)│
311
+ └──────────────────┬───────────────────────────────────────────┘
312
+ │ packed results
313
+ ▼
314
+ ┌──────────────────────────────────────────────────────────────┐
315
+ │ TypeScript: unpackBitset() / unpackByteResults() │
316
+ │ - Extract individual results from packed output │
317
+ │ - Map back to original inputs │
318
+ └──────────────────────────────────────────────────────────────┘
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Output Buffer Layout
324
+
325
+ 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`.
326
+
327
+ ```
328
+ Offset (bytes)
329
+ ├─ 0: OUT_VERDICT (u8) 0=pass, 1=terminal
330
+ ├─ 1: OUT_ERROR_CODE (u8) Error code enum
331
+ ├─ 2: OUT_STATUS (u16) HTTP status code (LE)
332
+ ├─ 4: OUT_FLAGS (u32) Bitfield of FLAG_* values (LE)
333
+ ├─ 8: OUT_RATE_LIMIT (u32) Rate limit ceiling (LE)
334
+ ├─ 12: OUT_RATE_REMAINING (u32) Remaining requests (LE)
335
+ ├─ 16: OUT_RATE_RESET (u64) Rate limit reset timestamp ms (LE)
336
+ ├─ 24: OUT_RETRY_AFTER (u64) Retry-After duration ms (LE)
337
+ ├─ 32: OUT_COOKIES_JSON_LEN (u32) Cookie JSON length (LE)
338
+ ├─ 36: OUT_QUERY_JSON_LEN (u32) Query JSON length (LE)
339
+ ├─ 40: OUT_HEADER_VARIANT (u8) Header variant index
340
+ ├─ 44: OUT_BODY_JSON_LEN (u32) Body metadata JSON length (LE)
341
+ ├─ 48: OUT_DATA_START ─── Variable-length data starts here
342
+ │ ├── Cookie JSON string (if present)
343
+ │ ├── Query JSON string (if present)
344
+ │ └── Body metadata JSON (if emitMetadataJson enabled)
345
+ ```
346
+
347
+ ### Flags (bitfield)
348
+
349
+ | Flag | Bit | Description |
350
+ |------|-----|-------------|
351
+ | `FLAG_HAS_COOKIES` | 0 | Cookie data present in output |
352
+ | `FLAG_HAS_QUERY` | 1 | Query data present in output |
353
+ | `FLAG_BODY_VALID_JSON` | 2 | Body is valid JSON |
354
+ | `FLAG_SCHEMA_VALID` | 3 | Body passes JSON Schema validation |
355
+ | `FLAG_CORS_ALLOWED` | 4 | CORS origin allowed |
356
+ | `FLAG_IS_PREFLIGHT` | 5 | Request is a CORS preflight |
357
+ | `FLAG_RATE_LIMITED` | 6 | Rate limit exceeded |
358
+ | `FLAG_HTTPS` | 7 | Request is HTTPS (detected or configured) |
359
+ | `FLAG_TRUSTED_PROXY` | 8 | Request from trusted proxy |
360
+ | `FLAG_BODY_TRUNCATED` | 9 | Output data truncated due to buffer size |
361
+
362
+ ### Error Codes
363
+
364
+ | Code | Value | Description |
365
+ |------|-------|-------------|
366
+ | `ERR_CODE_NONE` | 0 | No error |
367
+ | `ERR_CODE_CORS_PREFLIGHT` | 1 | CORS preflight rejected |
368
+ | `ERR_CODE_RATE_LIMITED` | 2 | Rate limit exceeded |
369
+ | `ERR_CODE_BODY_TOO_LARGE` | 3 | Request body exceeds maxBodyBytes |
370
+ | `ERR_CODE_INVALID_JSON` | 4 | Invalid JSON in body |
371
+ | `ERR_CODE_SCHEMA_VALIDATION` | 5 | Schema validation failed |
372
+ | `ERR_CODE_BAD_REQUEST` | 6 | Malformed request |
373
+ | `ERR_CODE_REQUEST_TOO_LARGE` | 7 | Request headers/URL too large |
374
+ | `ERR_CODE_INTERNAL` | 8 | Internal server error |
375
+
376
+ ---
377
+
378
+ ## Memory Management
379
+
380
+ 1. **Global allocator**: mimalloc (`MiMalloc`) for fast, scalable allocation
381
+ 2. **Buffer reuse**: Output buffers are recycled across requests where possible
382
+ 3. **Zero-copy**: `Uint8Array` passed to Rust functions are used in-place, not copied
383
+ 4. **Slice overlap detection**: Rust checks for input/output buffer overlap and copies if needed
384
+ 5. **Thread-local caches**: Header buffers use thread-local recycling to reduce allocation pressure
385
+
386
+ ---
387
+
388
+ ## Concurrency Model
389
+
390
+ - **Synchronous calls**: Run on the main JS thread (Bun's event loop)
391
+ - **Rayon**: Available for parallel CPU-bound work (e.g., batch validation)
392
+ - **Rate limiter**: Uses `parking_lot::Mutex` for low-contention access
393
+
394
+ ---
395
+
396
+ ## Dependency Graph
397
+
398
+ ```
399
+ [ingress] ────┬─── [cors] ──── [headers] ──── [method]
400
+ ├─── [rate_limit] ─── [ip_trust]
401
+ ├─── [proxy]
402
+ ├─── [output] ──── [terminal]
403
+ ├─── [json_ser] ──── [util]
404
+ ├─── [query_parser]
405
+ └─── [cookie_parser]
406
+ [hashing] ──── [hmac_sha256]
407
+ [validation]
408
+ [json_ops] ──── [json_patch_ops]
409
+ [json_schema]
410
+ [url_codec]
411
+ [mime_lookup]
412
+ [websocket]
413
+ [random_token]
414
+ [batch]
415
+ [http_parser]