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,53 @@
1
+ # Native route stack — the `@ignex/native` wire (route-wire v3)
2
+
3
+ `rust/ingress/native_route.rs` exposes the **per-route native stack**: a route
4
+ descriptor compiles once into a pre-baked `NativeRoute`, then each request runs
5
+ ONE native call. It is the LIVE external wire consumed by `@ignex/native`'s
6
+ `createNativeRoute` — a `castrum` consumer that compiles routes natively.
7
+
8
+ > **History**: the old `rust/route.rs` was dead external-project wire (a THIRD
9
+ > wire format, never bound in `src/native/ffi.ts`) and was removed in v0.9.0.
10
+ > `native_route.rs` implements the now-live ignex contract and supersedes it.
11
+
12
+ ## Route-wire v3
13
+
14
+ - Magic `ROUT` (0x524f5554), version 3.
15
+ - Stage tags: `parseQuery = 0 … requireJsonBody = 5`.
16
+ - Result layout: `[flags u32][errorCode u32]` + optional query/cookie pair
17
+ sections.
18
+
19
+ Descriptor/stage/part tags + result layout must match `route-wire.ts` EXACTLY —
20
+ `ROUTE_DESC_VERSION` bumps on any wire change (a mismatched compiler/addon must
21
+ be a hard reject, never a silent misparse).
22
+
23
+ ## Parse semantics (LENIENT)
24
+
25
+ Byte-parity with ignex's JS `queryPairs` / `cookiePairs`: malformed `%ZZ` /
26
+ invalid-UTF-8 `%FF` pass through raw, `+` → space, `%2B` → `+`, cookies trim +
27
+ DQUOTE-unwrap the VALUE but not the name, no cookie URL-decoding. Do NOT reuse
28
+ the strict scalar `query_parser` for this wire.
29
+
30
+ ## Validation
31
+
32
+ The stack validates the BODY only (via `IngressSchema`); a non-body schema in
33
+ the descriptor is an unsupported feature → fail compile so the caller falls
34
+ back to JS. `requireJsonBody` → 400; `validateBody` schema fail → 422;
35
+ first-failure-wins in stage order.
36
+
37
+ ## Surfaces
38
+
39
+ - `castrum_route_compile` / `castrum_route_run` / `castrum_route_destroy`
40
+ (C-ABI, needed-size convention, panic-guarded, immutable `&self` run).
41
+ - napi `Route` class (Node / fallback path).
42
+
43
+ ## Tests / parity
44
+
45
+ - `rust/ingress/native_route.rs` unit tests + `rust/ffi.rs` C-ABI tests.
46
+ - `test/unit/ingress/native-route.test.ts`.
47
+ - `scripts/verify-native-route.ts` + ignex's `route-wire.test.ts` /
48
+ `packages/native/test/route.test.ts`.
49
+
50
+ > **Naming note (flux → ignex)**: the external consumer was historically called
51
+ > `@flux/native` (castrum v0.8.0, guarded by `test/compat/flux-contract.test.ts`).
52
+ > The current live wire is `@ignex/native`. Both names appear in changelogs;
53
+ > `@ignex/native` is the active contract.
@@ -0,0 +1,279 @@
1
+ # castrum — Repo Map (what is where, and why it works this way)
2
+
3
+ This is the **navigation doc**. It tells you where everything lives, how the
4
+ pieces connect, and how to run/build/test/bench/publish. If you are brand new,
5
+ read [`GETTING_STARTED.md`](./GETTING_STARTED.md) first; if you want deep-dive
6
+ architecture, read [`ARCHITECTURE.md`](./ARCHITECTURE.md). If you are an AI
7
+ agent editing this repo, [`AGENTS.md`](../AGENTS.md) is the agent-facing map.
8
+
9
+ ---
10
+
11
+ ## 1. What this project is
12
+
13
+ `castrum` is a hybrid **Bun (TypeScript) + Rust** package:
14
+
15
+ - A **Rust cdylib** (`castrum.<platform>-<arch>.node`, built with napi-rs,
16
+ ALSO exporting `extern "C"` symbols for Bun's `bun:ffi` — see
17
+ `rust/ffi.rs`) provides the performance-critical primitives (parsers,
18
+ crypto, hashing, JSON, compression, and the HTTP **ingress pipeline**).
19
+ Under Bun, `bun:ffi` is the PRIMARY transport; NAPI is the fallback
20
+ (Node, `CASTRUM_FFI_MODE=napi`, or a failed ffi self-test).
21
+ - **TypeScript** (`src/`) provides the ergonomic public API: the flat `rust.*`
22
+ FFI client and the ingress layer.
23
+ - It ships benchmarks: a **CPU benchmark** (`bench.ts` → `src/bench`) and an
24
+ **HTTP benchmark** (`bench/run-bench.ts` + `bench/servers/*`).
25
+
26
+ Bun is the primary runtime; Node.js ≥20.3 is supported via a compiled ESM entry
27
+ (`dist/`).
28
+
29
+ ---
30
+
31
+ ## 2. Command cheat-sheet
32
+
33
+ | Task | Command | Notes |
34
+ |------|---------|-------|
35
+ | Install deps | `bun install` | |
36
+ | Build Rust addon (release) | `bun run build` | `napi build --release --platform` |
37
+ | Build Rust addon (LOCAL max perf) | `bun run build:perf` | x86-64-v3 + AVX2 — **never for publish** |
38
+ | Build Rust addon (debug) | `bun run build:debug` | |
39
+ | Build compiled JS entry (Node) | `bun run build:js` | bundle + types → `dist/` (gitignored) |
40
+ | TS unit tests | `bun test` | ~540 tests, `test/unit/**` |
41
+ | Rust unit tests | `bun run test:rust` | `cargo test`, ~440 tests |
42
+ | Node smoke tests | `bun run test:node` | explicit `.mjs` paths (Node 24 rejects dir arg) |
43
+ | Installed-tarball e2e | `bun run verify:install` | pack → install → import from `node_modules` |
44
+ | Typecheck | `bun run typecheck` | `bunx tsc --noEmit` (only `index.ts`, `bench.ts`, `src/`) |
45
+ | CPU benchmark + correctness | `bun run check` | == `bun bench.ts`; writes `bench/results/cpu/` |
46
+ | HTTP bench (all servers) | `bun run bench:http` | |
47
+ | HTTP smoke (fast sanity) | `bun run bench:http:smoke` | **the wire-format guard** (CI-gated) |
48
+ | Startup bench | `bun run bench:startup` | import + first-call timing |
49
+ | JS lint (Biome) | `bun run lint` / `lint:fix` | `bunx biome check [--write] .` |
50
+ | JS format (Biome) | `bun run format` | `bunx biome format --write .` |
51
+ | Version consistency | `bun run check:version` | package.json ↔ Cargo.toml ↔ CHANGELOG |
52
+ | JS dependency audit | `bun run audit` | `bun audit` (reports advisories) |
53
+ | Cargo deny audit | `bun run deny` | `cargo deny check` |
54
+ | Publish (manual, single-platform) | `bun run publish:manual[:dry]` | see §6 |
55
+
56
+ ---
57
+
58
+ ## 3. Directory map
59
+
60
+ ### Source (what you edit)
61
+
62
+ ```
63
+ index.ts Package entry (Bun). Re-exports src/rust-ffi, shared helpers, src/ingress.
64
+ bench.ts CPU benchmark entry point → src/bench/run.ts.
65
+
66
+ examples/ Runnable sample app (basic-server.ts + README) — see also README Quick Start.
67
+ basic-server.ts Minimal pre-baked ingress server + rust.* primitives.
68
+
69
+ src/
70
+ native/ The native transport layer: bun:ffi (primary on Bun) + NAPI fallback.
71
+ types.ts NativeAddon module-surface interface (mirrors index.d.ts).
72
+ types/instances.ts Per-class INSTANCE types (Ingress, SchemaValidator, JwtSigner, ...) + result types.
73
+ loader.ts Addon path resolution (multi-root) + getAddon()/lazyAddon().
74
+ ffi.ts bun:ffi C-ABI bindings (Bun-only): lazy bind + bind-time self-test,
75
+ CASTRUM_FFI_MODE gating, castrum_ingress_layout blob for constants.ts.
76
+ ffi/ ffi/types.ts (BunFFI interface) + constants.ts + selftest.ts (bind-time self-test).
77
+ index.ts Barrel.
78
+ runtime/ THE RUNTIME ADAPTER seam (Bun/Node) — selected once at load.
79
+ detect.ts Cached runtime detection — the ONLY place `typeof Bun` is checked.
80
+ types.ts RuntimeAdapter contract (codec/uuid/env/builtins/transport/server/websocket).
81
+ codec.ts Runtime-native UTF-8 codec (Bun transfer vs TextEncoder/Decoder).
82
+ uuid.ts uuidv7 (Bun.randomUUIDv7 vs crypto.randomUUID).
83
+ builtins.ts BUN_WINS delegation registry (single source; `has(op)`).
84
+ transport.ts bun:ffi-first `resolve(op)` + ffi/napi surfaces (lazy bind).
85
+ native.ts FFI-facing seam (codec/uuid/env/builtins/transport) — imported by rust-ffi.
86
+ server.ts createIngressServer: Bun.serve on Bun, node:http on Node.
87
+ websocket.ts 101 upgrade on Bun; clear Bun-only error on Node.
88
+ index.ts FULL adapter `runtime` (native + server + websocket) — for the public API.
89
+ rust-ffi/ The flat `rust.*` FFI client (TS wrappers over the addon).
90
+ client.ts createRust() factory + the default `rust` instance + configure().
91
+ scalar/ / text.ts / packed.ts namespaces (buildScalar, buildText, ...).
92
+ batch/ Array-of-bytes batch namespace: types.ts (RustBatch interface) + build.ts (impl) + index.ts.
93
+ context.ts Per-instance state (caches, rayon-pool bookkeeping, runtime seam).
94
+ options.ts RustOptions + rayon-thread resolution + coercion helpers.
95
+ addon.ts The single shared lazy addon proxy.
96
+ index.ts Barrel (exports rust, createRust, types).
97
+ shared/ Cross-cutting helpers.
98
+ runtime.ts Facade over src/runtime/detect.ts (isBun/isNode — cached).
99
+ codec.ts Facade over src/runtime/codec.ts.
100
+ uuid.ts Facade over src/runtime/uuid.ts.
101
+ bytes.ts encoder/decoder (Uint8Array|string union normalization) + toPlainBuffer.
102
+ packed/ Packed wire-format helpers (split from the old packed.ts monolith).
103
+ wire.ts PURE byte encode/decode (u32/bitset/i64/byte-results/multipart, pairs, pack-scratch) — no addon.
104
+ schema.ts SchemaValidator alias + schemaValidateBatch/Count.
105
+ parsers.ts lazy-addon string parsers (parseQueryString/parseCookieHeader/parseFormBody).
106
+ index.ts Barrel (existing `../shared/packed` imports unchanged).
107
+ request-id.ts Zero-alloc request-id generator (both ingress paths).
108
+ buffer-pool.ts Generic reusable byte-buffer pool (pooled ingress output).
109
+ response.ts pooledBodyResponse (releases the pool on body consume).
110
+ log.ts createStructuredLogger (CASTRUM_LOG_LEVEL-gated JSON lines).
111
+ ingress/ The HTTP ingress pipeline (two paths — see §4.2).
112
+ fast.ts PATH 1: createIngressFast (packed-input via handleRequestPacked).
113
+ handlers.ts PATH 2: createIngressHandler (JS-packs the frame via IngressInputPacker
114
+ + gatherRawHeadersPacked → handleRequestPacked).
115
+ index.ts Barrel + createIngress / createIngressSync convenience factories.
116
+ shared.ts JS constants + buildHeaderPlan + assertSyncCallback (shared by both paths).
117
+ constants.ts Binary-layout constants read from Rust at runtime (SINGLE SOURCE).
118
+ status.ts / errors.ts / options.ts / types.ts / body.ts / context.ts
119
+ packing/ header-packing.ts, input-packer.ts, gather-raw-headers.ts
120
+ headers/ cors.ts, hsts.ts, fast-templates.ts, baked-templates.ts
121
+ decode/ fast-result.ts, baked-result.ts (TWO decoders — see §4.2)
122
+ response/ terminal.ts, error-bodies.ts
123
+ routes/ read/head/json-write/echo/fallback factories + common.ts
124
+ server.ts createIngressServer (Bun.serve) + buildRouteHandlers + gracefulShutdown.
125
+ server-node.ts createIngressServerNode (node:http adapter, same route handlers).
126
+ integration/ Framework integration (wraps path 2 — createIngressHandler).
127
+ pipeline.ts createPipeline (framework-agnostic request stage for Hono/Elysia/Bun.serve).
128
+ websocket.ts createWebSocketUpgrade (RFC 6455 101 handshake + subprotocol).
129
+ streaming.ts sseResponse (SSE frames via rust.sseEncodeEvent).
130
+ baseline/ Pure-JS baseline implementations (benchmark reference). Internal.
131
+ bench/ CPU benchmark framework (tasks, measure, report, checks, comparisons).
132
+ data/ json-rows.ts fixture generator (benchmark-only).
133
+ ```
134
+
135
+ ```
136
+ rust/ ONE cdylib crate (Cargo [lib] → lib.rs).
137
+ lib.rs Declaration hub + module map + crate docs.
138
+ util/ Shared infrastructure: bytes, packed, batch (+batch_core), threadpool, validation.
139
+ http/ HTTP wire formats & parsing: headers, method, http/cookie/query parsers,
140
+ form, media_type, url_codec, url_join, etag, accept, mime_lookup, multipart.
141
+ crypto/ Auth & hashing: hmac_sha256, cookie_sign, csrf, jwt, aead, argon2,
142
+ base64, hashing (fnv/crc32/xxh3), random_token.
143
+ json/ JSON & schema: json_ops, json_ser, json_patch_ops, json_schema (napi),
144
+ fast_schema/ (zero-DOM engine — pure, no napi).
145
+ payload/ Output & streaming: compress, sse, ws_frames, websocket, template.
146
+ ingress/ THE ingress pipeline: mod.rs (napi boundary), pipeline.rs (core 8-stage),
147
+ options/time/packed, cors, proxy, ip_trust, rate_limit, terminal,
148
+ output.rs (single numeric layout source), ingress_constants.rs (napi projection).
149
+ ffi.rs #[no_mangle] extern "C" exports (79 castrum_* symbols — 71 direct + 4
150
+ validator_c_abi! + 4 compress_to_out!; parity guarded by
151
+ test/unit/features/ffi-symbol-parity.test.ts) for Bun's bun:ffi
152
+ primary transport, incl. castrum_ingress_layout (the layout blob).
153
+ unit_tests.rs / test_support.rs Cross-module Rust test suites.
154
+ ```
155
+
156
+ ### Generated / ignored (do NOT edit)
157
+
158
+ | Path | What it is | Why ignored |
159
+ |------|-----------|-------------|
160
+ | `dist/` | Compiled ESM entry + types for Node | Built by `build:js`; gitignored |
161
+ | `index.js` / `index.d.ts` | NAPI-RS generated loader/declarations | Regenerated by `napi build`; gitignored |
162
+ | `castrum.<platform>-<arch>.node` | The native addon binary | Built locally; gitignored |
163
+ | `bench/results/` | Bench reports (cpu/, http/) | Generated; gitignored |
164
+ | `target/` | Cargo build output | gitignored |
165
+ | `node_modules/` | Deps | gitignored |
166
+ | `artifacts/` | CI publish staging dir | gitignored |
167
+
168
+ ---
169
+
170
+ ## 4. How the pieces connect (the 5 cross-cutting flows)
171
+
172
+ ### 4.1 Constants never drift (single numeric source)
173
+ `rust/ingress/output.rs` defines every ingress layout constant once → projected
174
+ to JS two ways: `rust/ingress/ingress_constants.rs` via `#[napi] const` (NAPI)
175
+ AND `rust/ffi.rs` `IngressLayout` via `castrum_ingress_layout` (C ABI, primary
176
+ on Bun) → `src/ingress/constants.ts` reads them at runtime (ffi blob on Bun,
177
+ napi on Node). Drift is pinned by the Rust unit test
178
+ `ingress_layout_c_abi_matches_output_source` and the bun:ffi bind-time
179
+ self-test. **Never hardcode a layout value** — if you change the layout, change
180
+ `output.rs` and nothing else.
181
+
182
+ ### 4.2 The TWO ingress paths (do not conflate)
183
+ 1. **`src/ingress/fast.ts`** — `createIngressFast(options)`. JS packs request
184
+ headers into a binary buffer (`IngressInputPacker`), Rust parses via
185
+ `handleRequestPacked`. Responses: `{"error":{code,status,message,requestId}}`
186
+ with `x-ratelimit-*` headers.
187
+ 2. **`src/ingress/handlers.ts`** — `createIngressHandler(options, runtime)`.
188
+ The request frame is packed in JS (`IngressInputPacker` +
189
+ `gatherRawHeadersPacked`) and driven through the SAME native core as path 1
190
+ (`handleRequestPacked`), writing into a **pooled** output buffer. Responses:
191
+ `{"ok":true,...,"requestId":...}` (success) / `{"ok":false,"error":{code,
192
+ message}}` (errors) with `ratelimit-*` headers.
193
+
194
+ These wire formats are a **benchmark contract** (`bench/load.ts` validates
195
+ them) — they are intentionally NOT unified. The two decoders
196
+ (`decode/fast-result.ts` vs `decode/baked-result.ts`) and the two header-template
197
+ builders (`headers/fast-templates.ts` vs `headers/baked-templates.ts`) exist for
198
+ this reason. **Shared between both paths**: `generateRequestId`
199
+ (`src/shared/request-id.ts`) and `buildHeaderPlan` / `assertSyncCallback`
200
+ (`src/ingress/shared.ts`).
201
+
202
+ ### 4.3 Addon loading + runtime seam
203
+ - `src/native/loader.ts` resolves the `.node` from multiple roots so it works
204
+ from both the source layout (`src/…`) and the bundled layout (`dist/…`).
205
+ Honors `CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH`.
206
+ - Runtime detection lives **only** in `src/runtime/detect.ts` (resolved once at
207
+ load; `src/shared/runtime.ts` is a thin facade). The public
208
+ `createIngressServer` (via the adapter's `server`) picks `Bun.serve` on Bun
209
+ and the `node:http` backend on Node; `createIngressServerNode` stays for
210
+ pinned Node users (same route handlers via `buildRouteHandlers`).
211
+ - ⚠️ `src/ingress/constants.ts` is the **one module that dlopens the addon at
212
+ import time** (its constants are needed as soon as ingress is imported).
213
+ Everything else is lazy.
214
+
215
+ ### 4.4 The runtime adapter
216
+ `src/runtime/` is the single seam for every Bun-vs-Node and transport decision
217
+ (detection, codec, uuid, env, the BUN_WINS built-in delegation, the bun:ffi-vs-
218
+ napi transport, the ingress server backend, websocket) — selected ONCE at
219
+ module load. The FFI layer imports the seam (`src/runtime/native.ts`); the
220
+ public API imports the full `runtime` (`src/runtime/index.ts`). No
221
+ `isBun()` / `getBunFFI()` branches in builders.
222
+
223
+ ### 4.5 Pooled output buffers (path 2)
224
+ `createIngressHandler` owns a `BufferPool` (`src/shared/buffer-pool.ts`) sized
225
+ by `runtime.outputBufferSize`. `run()` packs the frame in JS (`IngressInputPacker`
226
+ + `gatherRawHeadersPacked`) and writes via `handleRequestPacked`
227
+ into the pooled buffer, passing the exact written subarray to
228
+ `BakedIngressResult.refresh`. In copy
229
+ mode the buffer is released at the end of `run()`; in zero-copy mode
230
+ `ingress.zeroCopyResponse()` serves a `pooledBodyResponse`
231
+ (`src/shared/response.ts`) that keeps the buffer in flight until the body is
232
+ consumed.
233
+
234
+ ---
235
+
236
+ ## 5. Testing & benchmarks
237
+
238
+ - **TS**: `bun test` → `test/unit/**` (`ingress/`, `shared/`, `features/`).
239
+ - **Rust**: `cargo test` → per-module `#[cfg(test)] mod tests` + cross-module
240
+ `rust/unit_tests.rs`.
241
+ - **Node**: `test/integration/node-smoke.test.mjs` + `node-enterprise.test.mjs`
242
+ via `node --test` (run with `bun run test:node`).
243
+ - **CPU bench**: `bun run check` (correctness checks + comparisons). Sub-µs ops
244
+ are batched (`CASTRUM_BENCH_BATCH_SIZE`). Report → `bench/results/cpu/`.
245
+ - **HTTP bench**: `bench/run-bench.ts` + `bench/load.ts` (load generator that
246
+ validates response SHAPE) + `bench/servers/{bun,elysia,ingress}-server.ts`.
247
+ `bench:http:smoke` is the fast CI wire-format gate.
248
+
249
+ ---
250
+
251
+ ## 6. Publishing (npm)
252
+
253
+ - Package name `castrum` (unscoped, public). Native binary name `castrum`
254
+ (declared in package.json `napi.binaryName` + Cargo.toml — keep in sync).
255
+ - **Multi-platform (recommended)**: push a `v*` tag → `.github/workflows/ci.yml`
256
+ builds each platform addon, the `publish` job downloads them into
257
+ `./artifacts`, stages them, and runs `npm publish` (needs `NPM_TOKEN`).
258
+ - **Manual single-platform**: `bun run publish:manual --increment minor`
259
+ (syncs package.json ↔ Cargo.toml ↔ CHANGELOG, tags, builds, publishes with
260
+ `CASTRUM_PUBLISH_ALLOW_PARTIAL=1`). `--dry-run` plans only.
261
+ - `prepublishOnly` runs `build:js` + `scripts/prepublish.mjs` (stages
262
+ `artifacts/*.node` and verifies every `napi.targets` maps to a `.node`).
263
+ - Run `bun run check:version` before tagging.
264
+
265
+ ---
266
+
267
+ ## 7. Cross-links
268
+
269
+ | Doc | Purpose |
270
+ |-----|---------|
271
+ | [`GETTING_STARTED.md`](./GETTING_STARTED.md) | Beginner tutorial (intern-friendly) |
272
+ | [`API.md`](./API.md) | The complete public API reference |
273
+ | [`CASE_STUDY.md`](./CASE_STUDY.md) | Data-driven case study: how castrum was built and measured |
274
+ | [`ARCHITECTURE.md`](./ARCHITECTURE.md) | Deep-dive: modules, data flow, output layout, memory, concurrency |
275
+ | [`INGRESS.md`](./INGRESS.md) | The pre-baked ingress API + route factories + servers |
276
+ | [`BENCHMARKS.md`](./BENCHMARKS.md) | Benchmark scenarios, report format, how to run |
277
+ | [`ENVIRONMENT.md`](./ENVIRONMENT.md) | All `CASTRUM_*` env vars (+ legacy aliases) |
278
+ | [`AGENTS.md`](../AGENTS.md) | AI-agent editing guidance (commands, constraints, gotchas) |
279
+ | `README.md` | Public-facing overview + quick start |
@@ -0,0 +1,39 @@
1
+ # ADR-0001: The two ingress wire formats are a contract, not a bug
2
+
3
+ - **Status**: Accepted (2026-08-06)
4
+ - **Deciders**: maintainers (via benchmark + hardening passes)
5
+
6
+ ## Context
7
+
8
+ The ingress layer ships two paths that consume the SAME native core
9
+ (`handleRequestPacked`):
10
+
11
+ 1. `src/ingress/fast.ts` — `createIngressFast`; JS packs headers via
12
+ `IngressInputPacker`; errors are `{"error":{code,status,message,requestId}}`
13
+ with `x-ratelimit-*` headers.
14
+ 2. `src/ingress/handlers.ts` — `createIngressHandler`; JS packs the full frame;
15
+ success is Rust-generated `{"ok":true,...,"requestId":...}`, errors are
16
+ `{"ok":false,"error":{"code","message"}}`, with `ratelimit-*` headers.
17
+
18
+ They also use separate result decoders (`FastIngressResult` vs
19
+ `BakedIngressResult`) and separate header-template builders.
20
+
21
+ ## Decision
22
+
23
+ **Do not unify them.** The HTTP benchmark load generator (`bench/load.ts`)
24
+ requires `ok === true` + `requestId: string` on success and `error.code` /
25
+ `error.message` on errors — i.e. path 2's format. The two formats are a
26
+ **benchmark contract**: the benchmark server uses path 2, and changing its
27
+ wire shape invalidates baselines.
28
+
29
+ ## Consequences
30
+
31
+ - Both paths stay hot and zero-alloc.
32
+ - Duplicated decoders/templates are intentional; sharing them is
33
+ **forbidden** by AGENTS.md (it would couple two differently-shaped outputs).
34
+ - New contributors must not "helpfully" unify the formats.
35
+
36
+ ## See also
37
+
38
+ - AGENTS.md §"Ingress: the two paths (do NOT conflate)"
39
+ - docs/INGRESS.md §"Wire format"
@@ -0,0 +1,38 @@
1
+ # ADR-0002: Zero-DOM fast path for JSON Schema validation
2
+
3
+ - **Status**: Accepted (2026-08-07), extended 2026-08-11
4
+ - **Deciders**: maintainers (measured via `bun run check`)
5
+
6
+ ## Context
7
+
8
+ `jsonschema::Validator::is_valid` requires a `serde_json::Value` — a full DOM
9
+ per document, measured at ~95% of validation cost. That made the Rust schema
10
+ path parity with `ajv` at best, so castrum couldn't match its own zero-DOM
11
+ wins (json_valid / json_sum / packed parsers).
12
+
13
+ ## Decision
14
+
15
+ Implement a **zero-DOM draft-07 fast path** (`rust/json/fast_schema/`) that
16
+ compiles the common keyword subset into a `FastNode` AST and validates raw
17
+ bytes with a zero-alloc cursor. Unsupported keywords → `compile()` returns
18
+ `Err` → the caller falls back to the authoritative `jsonschema` crate (DOM
19
+ path). Parity is **byte-for-byte gated** against the crate
20
+ (`rust/json/fast_schema/tests.rs`).
21
+
22
+ Draft-07 is pinned because `jsonschema` 0.48's default draft is 2020-12 whose
23
+ `items` / `$ref`-sibling / boolean-exclusive semantics differ; both
24
+ `SchemaValidator` and `IngressSchema` build the reference with
25
+ `.with_draft(Draft7)` + `should_validate_formats(true)`.
26
+
27
+ ## Consequences
28
+
29
+ - Fast path: scalar ~1.2–2.4x, batch ~1.0–1.56x vs `ajv` (release build).
30
+ - A `$schema` declaring non-draft-07 (or unsupported keywords) still falls
31
+ back — versatility preserved.
32
+ - The fast path MUST stay byte-parity with the crate for the supported
33
+ keyword surface; any divergence is a bug.
34
+
35
+ ## See also
36
+
37
+ - rust/json/fast_schema/tests.rs (parity corpus)
38
+ - docs/ARCHITECTURE.md §JSON & schema
@@ -0,0 +1,43 @@
1
+ # ADR-0003: Don't reinvent the wheel — benchmark, then delegate to Bun built-ins
2
+
3
+ - **Status**: Accepted (2026-08-11)
4
+ - **Deciders**: maintainers (measured on Bun 1.4.0 / release addon)
5
+
6
+ ## Context
7
+
8
+ castrum ships Rust ops that overlap with Bun's native built-ins
9
+ (`Bun.hash`, `Bun.password`, `Bun.CryptoHasher`, `Bun.gzipSync`,
10
+ `Bun.randomUUIDv7`, `crypto.subtle`, `JSON.parse`, `URLSearchParams`). For the
11
+ same workload, the in-process Bun implementation can beat an FFI-crossing Rust
12
+ op — spending effort building or keeping such ops is reinventing the wheel.
13
+
14
+ ## Decision
15
+
16
+ Every overlapping op is **benchmarked against its Bun built-in** before it is
17
+ built, kept, or delegated. Results live in
18
+ `docs/bun-builtins-decision-matrix.md`. Rules:
19
+
20
+ 1. **Delegate to Bun** at the selection/delegation layer where Bun wins
21
+ clearly: non-crypto hashing (`Bun.hash.crc32` ~3x, `xxHash3` ~4x),
22
+ gzip (`Bun.gzipSync`/`gunzipSync`), `Bun.randomUUIDv7` (~2x), HMAC
23
+ (`Bun.CryptoHasher` ~1.2x).
24
+ 2. **Keep rust** where it wins or Bun has no synchronous equivalent:
25
+ argon2id password hashing (~1.8–2x vs `Bun.password`), bcrypt verify
26
+ (~1.5x), brotli, PBKDF2, zero-DOM JSON/parsers.
27
+ 3. **Don't build** what Bun already wins at (UUIDv7 → `uuidv7()` delegates to
28
+ `Bun.randomUUIDv7`, falls back to `crypto.randomUUID`).
29
+
30
+ ## Consequences
31
+
32
+ - New `rust.*` primitives ship with a Bun-built-in baseline + decision-matrix
33
+ entry (`xxh3`, `passwordHashBcrypt`/`verifyBcrypt`, `pbkdf2Sha256`).
34
+ - The Bun/JS baselines are recorded honestly in the decision matrix
35
+ (e.g. `xxh3` is measured `Bun.hash.xxHash3` ~4x faster — the public
36
+ `rust.xxh3` delegates to it under Bun via the runtime adapter's `builtins`).
37
+ - Diagnostic benchmark tasks use `diag:` names so they are never mistaken for
38
+ shipped-op `native:`/`rust:` comparisons.
39
+
40
+ ## See also
41
+
42
+ - docs/bun-builtins-decision-matrix.md
43
+ - src/bench/tasks/bun-builtins.ts
@@ -0,0 +1,115 @@
1
+ # Bun Built-ins vs castrum — Decision Matrix
2
+
3
+ > **Question this answers**: for the same workload, does a Bun native built-in
4
+ > beat the castrum Rust op? Where it does, castrum should stop reinventing the
5
+ > wheel and delegate to Bun; where castrum wins (or Bun
6
+ > has no sync equivalent), the Rust op stands.
7
+
8
+ ## Methodology
9
+
10
+ - **Runtime**: Bun **1.4.0** (the primary target).
11
+ - **Addon**: castrum **release** build (`bun run build`, baseline CPU — the
12
+ shipped profile).
13
+ - **Driver**: `bun run check` (CPU benchmark → `bench/results/cpu/latest.json`).
14
+ Each pair runs on the SAME input (fixture payloads, see table).
15
+ - **Naming**: diagnostic comparisons use `diag:` task names so they are kept out
16
+ of the shipped-op `native:`/`rust:` comparisons (see `src/bench/comparisons.ts`).
17
+ - **Honesty guard**: every Bun API is verified to actually execute real work at
18
+ runtime before a result is trusted. This is important — the first run
19
+ produced false "wins" (brotli 156×, validators 3–5×) because this Bun
20
+ version has **no** `Bun.brotliCompressSync` and **no** `Bun.validators`, and
21
+ the feature-detect fell through to an empty return. Those pairs were removed,
22
+ not reported.
23
+ - Ratio is **nativeAvgMs / rustAvgMs** (>1 = Bun faster; <1 = rust faster).
24
+
25
+ ## Results (Bun 1.4.0, release addon, 2026-08-11)
26
+
27
+ | castrum op | Bun built-in | Input | Winner | Ratio |
28
+ |---|---|---|---|---|
29
+ | `rust.fnv1a64` | `Bun.hash` (wyhash) | 42 B | Bun (parity) | 1.06× |
30
+ | `rust.crc32` | `Bun.hash.crc32` | 42 B | **Bun** | 2.8–8.4× |
31
+ | `rust.xxh3` (new) | `Bun.hash.xxHash3` | 42 B | **Bun** | 4.15× |
32
+ | `rust.hmacSha256` | `Bun.CryptoHasher("sha256", key)` | 32 B | Bun (mild) | 1.1–1.4× |
33
+ | `rust.passwordHash` (argon2id) | `Bun.password.hashSync` (argon2id) | 12 B | **rust** | 0.55× (rust 1.83×) |
34
+ | `rust.passwordVerify` | `Bun.password.verifySync` | 12 B | **rust** | 0.53× (rust 1.88×) |
35
+ | `rust.passwordHashBcrypt` (new) | `Bun.password.hashSync` (bcrypt) | 12 B | Bun (mild) | 1.24× |
36
+ | `rust.passwordVerifyBcrypt` (new) | `Bun.password.verifySync` (bcrypt) | 12 B | **rust** | 0.67× (rust 1.49×) |
37
+ | `rust.pbkdf2Sha256` (new) | `node:crypto.pbkdf2Sync` | 12 B | rust (parity) | 0.93× (rust 1.08×) |
38
+ | `rust.randomToken` (16 B) | `Bun.randomUUIDv7` | — | **Bun** | 1.62× |
39
+ | `rust.gzipCompress` | `Bun.gzipSync` | 11 179 B | **Bun** | 2.02× |
40
+ | `rust.gzipDecompress` | `Bun.gunzipSync` | 1 045 B | **Bun** | 1.38× |
41
+ | `rust.brotliCompress` | *no sync Bun API* | — | N/A | — |
42
+ | `rust.brotliDecompress` | *no sync Bun API* | — | N/A | — |
43
+ | `rust.validateEmail/Uuid/Ipv4/Ipv6` | `Bun.validators` (*absent in 1.4*) | — | N/A | — |
44
+
45
+ > Same-cost note: `Bun.password.hashSync` ran at `memoryCost 4096 / timeCost 1`
46
+ > (4 MiB, 1 iter) matching the rust bench cost (`mCost 4096 / tCost 1`) — the
47
+ > rust argon2id win is cost-for-cost, not a cost cheat.
48
+
49
+ > Transport note: "FFI-crossing Rust op" below means any Rust call over the
50
+ > native bridge. Under Bun that bridge is now `bun:ffi` (the PRIMARY transport);
51
+ > under Node it is the napi fallback. This Bun-builtin-vs-Rust axis is
52
+ > independent of the ffi-vs-napi transport axis — these decisions recommend
53
+ > WHICH implementation to call (`rust.*` vs a Bun built-in), not which transport
54
+ > carries the call.
55
+
56
+ ## Decisions
57
+
58
+ ### → Delegate to Bun at the selection layer (Bun wins)
59
+ These rust ops lose to Bun's native implementation on the SAME workload — the
60
+ FFI crossing cannot beat Bun's in-process C++:
61
+
62
+ This table mirrors `BUILTIN_OPS` in `src/runtime/builtins.ts` (the 11 ops that
63
+ delegate to Bun built-ins under Bun; `src/selection.ts` derives from it). `fnv1a64` is parity/either but is NOT delegated
64
+ (`Bun.hash` is wyhash — a different algorithm — and rust `fnv1a64` stays the
65
+ benchmarked impl).
66
+
67
+ | Op | Recommendation |
68
+ |---|---|
69
+ | `crc32` | Prefer `Bun.hash.crc32`; keep rust as fallback (pure-TS path already exists). |
70
+ | `xxh3` | Prefer `Bun.hash.xxHash3` (~4×); keep the rust export as the Node/non-Bun fast path. The runtime adapter delegates the public `rust.xxh3` to Bun under Bun. |
71
+ | `gzipCompress` | Prefer `Bun.gzipSync` (~2×). **`gzipDecompress` is deliberately NOT delegated** — `Bun.gunzipSync` has no decompression-bomb cap; the rust surface keeps its native 64 MiB-capped path under Bun. |
72
+ | `randomToken` | Prefer `Bun.randomUUIDv7()` (or `crypto.getRandomValues`) for token-sized output; keep rust for byte-precise control. |
73
+ | `hmacSha256` | Prefer `Bun.CryptoHasher("sha256", key)` (1.17× — mild; keep rust batch path, which wins on larger inputs). |
74
+ | `urlEncode` / `urlDecode` | Prefer `encodeURIComponent` / `decodeURIComponent` (JSC string builtins — ~11.5×, zero alloc). |
75
+ | `base64Encode` / `base64UrlEncode` | Prefer `Buffer.toString('base64'|'base64url')`. |
76
+ | `hexEncode` | Prefer `Buffer.toString('hex')`. |
77
+ | `httpDate` | Prefer `Date.toUTCString()`. |
78
+
79
+ **Delegation rule of thumb**: rust stays the default only where it beats its
80
+ **Bun** baseline (not just its JS baseline). These are the ops where rust
81
+ genuinely wins or Bun has no equivalent.
82
+
83
+ ### → Keep rust (rust wins or Bun has no equivalent)
84
+ | Op | Rationale |
85
+ |---|---|
86
+ | `passwordHash` / `passwordVerify` (argon2id) | rust 1.83–1.88× faster than `Bun.password` at equal cost. **Do not delegate.** |
87
+ | `passwordHashBcrypt` / `passwordVerifyBcrypt` (new) | Hash ≈ parity (Bun 1.24×), verify rust 1.49×. Keep rust — self-contained and works under Node. |
88
+ | `pbkdf2Sha256` (new) | Bun has no sync PBKDF2; rust ≈ parity with node:crypto (1.08×). Keep rust. |
89
+ | `brotliCompress` / `brotliDecompress` | Bun 1.4 has no synchronous brotli; keep rust (async `CompressionStream` is not drop-in). |
90
+ | `validateEmail` / `validateUuid` / `validateIpv4` / `validateIpv6` | `Bun.validators` absent in 1.4; keep rust. Re-check when Bun adds it. |
91
+ | `jsonValid` / `jsonSumIds` / packed parsers / ingress | No Bun sync equivalent for the zero-DOM / zero-copy semantics; keep rust. |
92
+
93
+ ### → Implemented primitives (gaps closed)
94
+ | Primitive | Outcome |
95
+ |---|---|
96
+ | `rust.xxh3` (XXH3-64) | Exposed; measured `Bun.hash.xxHash3` 4.15× faster → the runtime adapter delegates the public `rust.xxh3` to Bun under Bun (Node keeps the addon). |
97
+ | `rust.passwordHashBcrypt` / `rust.passwordVerifyBcrypt` | bcrypt `$2b$` PHC; hash parity, verify rust 1.49×. |
98
+ | `rust.pbkdf2Sha256` | PBKDF2-HMAC-SHA256; parity with `node:crypto`, the only sync option in Bun. |
99
+ | UUIDv7 (`uuidv7()`) | `Bun.randomUUIDv7` already wins — delegated to Bun (`crypto.randomUUID` on Node), not built in Rust. |
100
+
101
+ ## Summary
102
+
103
+ **The "don't reinvent the wheel" takeaway:** for non-cryptographic hashing,
104
+ random tokens, and gzip, Bun 1.4's built-ins are already faster than any
105
+ FFI-crossing Rust op — the selection layer should prefer them. Rust retains a clear,
106
+ defensible moat exactly where it matters: **argon2id password hashing
107
+ (≈1.9× vs Bun)**, the **zero-DOM / zero-copy parsers and JSON paths** (no Bun
108
+ sync equivalent), and **brotli** (no Bun sync API).
109
+
110
+ ## Re-run
111
+
112
+ ```sh
113
+ bun run build # release addon
114
+ bun run check # writes latest.json; grep the "vs Bun" lines
115
+ ```
package/index.ts CHANGED
@@ -1,8 +1,36 @@
1
- export * from "./src/rust-ffi";
1
+ // index.ts — castrum package entry (barrel).
2
+ //
3
+ // Re-exports the full public API: the flat `rust.*` FFI surface
4
+ // (src/rust-ffi), the runtime selection hints (src/selection), and the
5
+ // higher-order loader (src/loader). See AGENTS.md for the layout map.
6
+ //
7
+ // EAGER-DLOPEN CONTRACT: this barrel unconditionally re-exports `src/ingress`
8
+ // → `src/ingress/constants.ts`, which is the ONE module that touches native at
9
+ // import time (it reads the Rust binary layout and runs the bind-time
10
+ // self-test). This is intentional — even `import { rust }` pays the eager
11
+ // load. Do NOT add more eager-native modules; keep everything else lazy (see
12
+ // test/unit/features/import-contract.test.ts).
2
13
 
3
- export * as native from "./src/baseline";
14
+ export * from './src/rust-ffi'
4
15
 
5
- export { encoder, decoder } from "./src/shared/bytes";
16
+ // Native-vs-JS selection (OWNED HERE, not by consumers): `opImpl(op)` is the
17
+ // benchmark-driven recommendation for which operations should use the Rust
18
+ // addon vs a pure-JS implementation. Consumers read it once at load time and
19
+ // bind each op to a fixed impl. Source: rust/selection.rs (audited by
20
+ // `bun run check:selection`). See src/selection.ts.
21
+ export { opImpl, isNativeOp, opDecision } from './src/selection'
22
+ export type { OpImpl, OpDecision } from './src/selection'
23
+
24
+ export { encoder, decoder } from './src/shared/bytes'
25
+ // Bounded EWMA adaptive-estimate utility (drives BufferPool adaptive sizing
26
+ // and reusable runtime self-optimization decisions).
27
+ export { AdaptiveEstimate } from './src/shared/adaptive'
28
+ export type { AdaptiveEstimateOptions } from './src/shared/adaptive'
29
+ // UUIDv7 generation — delegates to Bun.randomUUIDv7, crypto.randomUUID on Node.
30
+ export { uuidv7 } from './src/shared/uuid'
31
+ // Zero-dep metrics registry (counters/gauges/histograms + Prometheus render).
32
+ export { createMetrics, DEFAULT_BUCKETS } from './src/shared/metrics'
33
+ export type { MetricsRegistry, Counter, Gauge, Histogram } from './src/shared/metrics'
6
34
 
7
35
  export {
8
36
  packBatch,
@@ -12,13 +40,21 @@ export {
12
40
  pairsToObject,
13
41
  parseQueryString,
14
42
  parseCookieHeader,
43
+ parseFormBody,
15
44
  unpackBitset,
16
45
  unpackByteResults,
17
46
  unpackI64ArrayAsBigInt,
18
47
  unpackU32Array,
19
- } from "./src/shared/packed";
20
- export type { Pair, ParsedHttpRequestPacked } from "./src/shared/packed";
48
+ } from './src/shared/packed'
49
+ export type { Pair, ParsedHttpRequestPacked } from './src/shared/packed'
50
+
51
+ export * from './src/ingress'
52
+
53
+ // Framework-agnostic integration helpers: run the ingress pipeline as a
54
+ // request stage (createPipeline), RFC 6455 upgrade (createWebSocketUpgrade),
55
+ // and SSE framing (sseResponse). See docs/INGRESS.md.
56
+ export * from './src/integration'
21
57
 
22
- export { jsonRowsBytes, createJsonRows } from "./src/data/json-rows";
23
- export type { JsonRow } from "./src/data/json-rows";
24
- export * from "./src/ingress";
58
+ // Higher-order loader (HFC) over the curated op set: createLoader / loader
59
+ // dispatch single items, packed batches, and load() coalescing. See README.
60
+ export * from './src/loader'