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.
- package/LICENSE +21 -0
- package/README.md +322 -59
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +3161 -0
- package/dist/index.js +7306 -0
- package/docs/ARCHITECTURE.md +415 -0
- package/docs/BENCHMARKS.md +307 -0
- package/docs/ENVIRONMENT.md +79 -0
- package/docs/GETTING_STARTED.md +209 -0
- package/docs/INGRESS.md +745 -0
- package/docs/REPO_MAP.md +259 -0
- package/docs/adr/0001-two-wire-formats.md +39 -0
- package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
- package/docs/adr/0003-bun-builtins-delegation.md +42 -0
- package/docs/bun-builtins-decision-matrix.md +108 -0
- package/index.ts +51 -8
- package/package.json +73 -20
- package/src/data/json-rows.ts +13 -13
- package/src/ingress/body.ts +149 -0
- package/src/ingress/constants.ts +220 -48
- package/src/ingress/context.ts +136 -0
- package/src/ingress/decode/baked-result.ts +121 -0
- package/src/ingress/decode/fast-result.ts +160 -0
- package/src/ingress/decode/packed-sections.ts +56 -0
- package/src/ingress/decode/result-base.ts +172 -0
- package/src/ingress/errors.ts +72 -0
- package/src/ingress/fast.ts +152 -1021
- package/src/ingress/handlers.ts +563 -1001
- package/src/ingress/headers/baked-templates.ts +102 -0
- package/src/ingress/headers/cors.ts +44 -0
- package/src/ingress/headers/fast-templates.ts +238 -0
- package/src/ingress/headers/hsts.ts +83 -0
- package/src/ingress/health.ts +67 -0
- package/src/ingress/index.ts +130 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +75 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +150 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +96 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/routes/common.ts +38 -0
- package/src/ingress/routes/delete.ts +14 -0
- package/src/ingress/routes/echo.ts +107 -0
- package/src/ingress/routes/fallback.ts +23 -0
- package/src/ingress/routes/head.ts +37 -0
- package/src/ingress/routes/index.ts +10 -0
- package/src/ingress/routes/json-write.ts +153 -0
- package/src/ingress/routes/options.ts +36 -0
- package/src/ingress/routes/read.ts +45 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +443 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +157 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +217 -0
- package/src/integration/streaming.ts +79 -0
- package/src/integration/websocket.ts +92 -0
- package/src/loader/batch.ts +133 -0
- package/src/loader/cost.ts +104 -0
- package/src/loader/index.ts +930 -0
- package/src/loader/ops.ts +657 -0
- package/src/native/ffi.ts +1541 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +194 -0
- package/src/native/types.ts +598 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch.ts +431 -0
- package/src/rust-ffi/client.ts +88 -0
- package/src/rust-ffi/context.ts +164 -0
- package/src/rust-ffi/index.ts +40 -541
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +42 -0
- package/src/rust-ffi/scalar/crypto.ts +265 -0
- package/src/rust-ffi/scalar/factories.ts +233 -0
- package/src/rust-ffi/scalar/hashing.ts +72 -0
- package/src/rust-ffi/scalar/http.ts +206 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +450 -0
- package/src/rust-ffi/scalar/json.ts +45 -0
- package/src/rust-ffi/scalar/payload.ts +124 -0
- package/src/rust-ffi/text.ts +95 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +77 -0
- package/src/shared/adaptive.ts +68 -0
- package/src/shared/bench-classify.ts +209 -0
- package/src/shared/buffer-pool.ts +209 -0
- package/src/shared/bytes.ts +35 -5
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +74 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed.ts +350 -154
- package/src/shared/proven.ts +596 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +34 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +31 -0
- package/Cargo.toml +0 -73
- package/bench.ts +0 -3
- package/rust/batch.rs +0 -195
- package/rust/cookie_parser.rs +0 -70
- package/rust/cors.rs +0 -263
- package/rust/hashing.rs +0 -164
- package/rust/headers.rs +0 -234
- package/rust/hmac_sha256.rs +0 -113
- package/rust/http_parser.rs +0 -132
- package/rust/ingress.rs +0 -616
- package/rust/ingress_constants.rs +0 -89
- package/rust/ip_trust.rs +0 -185
- package/rust/json_ops.rs +0 -66
- package/rust/json_patch_ops.rs +0 -25
- package/rust/json_schema.rs +0 -151
- package/rust/json_ser.rs +0 -429
- package/rust/lib.rs +0 -48
- package/rust/method.rs +0 -141
- package/rust/mime_lookup.rs +0 -76
- package/rust/output.rs +0 -238
- package/rust/proxy.rs +0 -90
- package/rust/query_parser.rs +0 -141
- package/rust/random_token.rs +0 -52
- package/rust/rate_limit.rs +0 -203
- package/rust/terminal.rs +0 -95
- package/rust/unit_tests.rs +0 -843
- package/rust/url_codec.rs +0 -133
- package/rust/util.rs +0 -537
- package/rust/validation.rs +0 -107
- package/rust/websocket.rs +0 -23
- package/src/baseline/index.ts +0 -12
- package/src/baseline/tasks/cookie.ts +0 -22
- package/src/baseline/tasks/hashing.ts +0 -18
- package/src/baseline/tasks/hmac.ts +0 -38
- package/src/baseline/tasks/http.ts +0 -62
- package/src/baseline/tasks/json-patch.ts +0 -12
- package/src/baseline/tasks/json.ts +0 -50
- package/src/baseline/tasks/mime.ts +0 -5
- package/src/baseline/tasks/query.ts +0 -14
- package/src/baseline/tasks/token.ts +0 -15
- package/src/baseline/tasks/url.ts +0 -11
- package/src/baseline/tasks/validation.ts +0 -19
- package/src/baseline/tasks/websocket.ts +0 -14
- package/src/bench/assert.ts +0 -70
- package/src/bench/checks.ts +0 -227
- package/src/bench/checksum.ts +0 -27
- package/src/bench/comparisons.ts +0 -70
- package/src/bench/concurrent-worker.ts +0 -135
- package/src/bench/fixtures.ts +0 -233
- package/src/bench/index.ts +0 -2
- package/src/bench/measure.ts +0 -177
- package/src/bench/now.ts +0 -3
- package/src/bench/report.ts +0 -79
- package/src/bench/run.ts +0 -57
- package/src/bench/tasks/complex.ts +0 -232
- package/src/bench/tasks/concurrent.ts +0 -213
- package/src/bench/tasks/cookie.ts +0 -44
- package/src/bench/tasks/hashing.ts +0 -33
- package/src/bench/tasks/hmac.ts +0 -36
- package/src/bench/tasks/http.ts +0 -50
- package/src/bench/tasks/index.ts +0 -52
- package/src/bench/tasks/json-patch.ts +0 -21
- package/src/bench/tasks/json.ts +0 -33
- package/src/bench/tasks/mime.ts +0 -21
- package/src/bench/tasks/query.ts +0 -44
- package/src/bench/tasks/stress.ts +0 -142
- package/src/bench/tasks/token.ts +0 -20
- package/src/bench/tasks/url.ts +0 -34
- package/src/bench/tasks/validation.ts +0 -57
- package/src/bench/tasks/websocket.ts +0 -21
- package/src/bench/types.ts +0 -60
- package/src/ingress/packed-input.ts +0 -59
- 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]
|