castrum 0.7.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/Cargo.toml +73 -0
- package/README.md +432 -0
- package/bench.ts +3 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/index.ts +24 -0
- package/package.json +94 -0
- package/rust/batch.rs +195 -0
- package/rust/cookie_parser.rs +70 -0
- package/rust/cors.rs +263 -0
- package/rust/hashing.rs +164 -0
- package/rust/headers.rs +234 -0
- package/rust/hmac_sha256.rs +113 -0
- package/rust/http_parser.rs +132 -0
- package/rust/ingress.rs +616 -0
- package/rust/ingress_constants.rs +89 -0
- package/rust/ip_trust.rs +185 -0
- package/rust/json_ops.rs +66 -0
- package/rust/json_patch_ops.rs +25 -0
- package/rust/json_schema.rs +151 -0
- package/rust/json_ser.rs +429 -0
- package/rust/lib.rs +48 -0
- package/rust/method.rs +141 -0
- package/rust/mime_lookup.rs +76 -0
- package/rust/output.rs +238 -0
- package/rust/proxy.rs +90 -0
- package/rust/query_parser.rs +141 -0
- package/rust/random_token.rs +52 -0
- package/rust/rate_limit.rs +203 -0
- package/rust/terminal.rs +95 -0
- package/rust/unit_tests.rs +843 -0
- package/rust/url_codec.rs +133 -0
- package/rust/util.rs +537 -0
- package/rust/validation.rs +107 -0
- package/rust/websocket.rs +23 -0
- package/src/baseline/index.ts +12 -0
- package/src/baseline/tasks/cookie.ts +22 -0
- package/src/baseline/tasks/hashing.ts +18 -0
- package/src/baseline/tasks/hmac.ts +38 -0
- package/src/baseline/tasks/http.ts +62 -0
- package/src/baseline/tasks/json-patch.ts +12 -0
- package/src/baseline/tasks/json.ts +50 -0
- package/src/baseline/tasks/mime.ts +5 -0
- package/src/baseline/tasks/query.ts +14 -0
- package/src/baseline/tasks/token.ts +15 -0
- package/src/baseline/tasks/url.ts +11 -0
- package/src/baseline/tasks/validation.ts +19 -0
- package/src/baseline/tasks/websocket.ts +14 -0
- package/src/bench/assert.ts +70 -0
- package/src/bench/checks.ts +227 -0
- package/src/bench/checksum.ts +27 -0
- package/src/bench/comparisons.ts +70 -0
- package/src/bench/concurrent-worker.ts +135 -0
- package/src/bench/fixtures.ts +233 -0
- package/src/bench/index.ts +2 -0
- package/src/bench/measure.ts +177 -0
- package/src/bench/now.ts +3 -0
- package/src/bench/report.ts +79 -0
- package/src/bench/run.ts +57 -0
- package/src/bench/tasks/complex.ts +232 -0
- package/src/bench/tasks/concurrent.ts +213 -0
- package/src/bench/tasks/cookie.ts +44 -0
- package/src/bench/tasks/hashing.ts +33 -0
- package/src/bench/tasks/hmac.ts +36 -0
- package/src/bench/tasks/http.ts +50 -0
- package/src/bench/tasks/index.ts +52 -0
- package/src/bench/tasks/json-patch.ts +21 -0
- package/src/bench/tasks/json.ts +33 -0
- package/src/bench/tasks/mime.ts +21 -0
- package/src/bench/tasks/query.ts +44 -0
- package/src/bench/tasks/stress.ts +142 -0
- package/src/bench/tasks/token.ts +20 -0
- package/src/bench/tasks/url.ts +34 -0
- package/src/bench/tasks/validation.ts +57 -0
- package/src/bench/tasks/websocket.ts +21 -0
- package/src/bench/types.ts +60 -0
- package/src/data/json-rows.ts +33 -0
- package/src/ingress/constants.ts +56 -0
- package/src/ingress/fast.ts +1082 -0
- package/src/ingress/handlers.ts +1202 -0
- package/src/ingress/index.ts +457 -0
- package/src/ingress/packed-input.ts +59 -0
- package/src/native/index.ts +207 -0
- package/src/rust-ffi/index.ts +555 -0
- package/src/shared/bytes.ts +36 -0
- package/src/shared/json.ts +18 -0
- package/src/shared/packed.ts +316 -0
package/Cargo.toml
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "castrum"
|
|
3
|
+
# Keep in sync with package.json "version" (single source of truth: the npm package).
|
|
4
|
+
version = "0.7.0"
|
|
5
|
+
edition = "2021"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "castrum"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
path = "rust/lib.rs"
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
napi = { version = "3", features = ["napi10"] }
|
|
14
|
+
napi-derive = "3"
|
|
15
|
+
|
|
16
|
+
serde = { version = "1", features = ["derive"] }
|
|
17
|
+
serde_json = "1"
|
|
18
|
+
sonic-rs = "0.5"
|
|
19
|
+
|
|
20
|
+
# default-features=false: drop resolve-http + tls-aws-lc-rs which pull in
|
|
21
|
+
# reqwest -> hyper/h2/tokio/rustls (large binary, attack surface). Format
|
|
22
|
+
# validators (email etc.) are built-in and NOT feature-gated in 0.48.
|
|
23
|
+
jsonschema = { version = "0.48", default-features = false, features = ["resolve-file"] }
|
|
24
|
+
json-patch = "4"
|
|
25
|
+
aws-lc-rs = "1"
|
|
26
|
+
base64 = "0.23"
|
|
27
|
+
crc32fast = "1"
|
|
28
|
+
httparse = "1"
|
|
29
|
+
mime_guess = "2"
|
|
30
|
+
phf = { version = "0.14", features = ["macros"] }
|
|
31
|
+
getrandom = "0.4"
|
|
32
|
+
memchr = "2"
|
|
33
|
+
simdutf8 = "0.1"
|
|
34
|
+
email_address = "0.2"
|
|
35
|
+
fast_chemail = { version = "0.9", optional = true }
|
|
36
|
+
rayon = "1"
|
|
37
|
+
# mimalloc is set as the Rust #[global_allocator] (see rust/lib.rs). We DELIBERATELY
|
|
38
|
+
# do NOT enable the "override" feature: it interposes libc malloc/free at the
|
|
39
|
+
# process level, which is an embedding hazard when this cdylib is dlopen'd into
|
|
40
|
+
# Bun (which itself embeds mimalloc -> symbol clashes). The Rust-level global
|
|
41
|
+
# allocator keeps most of the allocation win without replacing the host malloc.
|
|
42
|
+
mimalloc = { version = "0.1", features = ["local_dynamic_tls"] }
|
|
43
|
+
core_affinity = "0.8"
|
|
44
|
+
|
|
45
|
+
parking_lot = "0.12"
|
|
46
|
+
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
|
47
|
+
ipnet = "2"
|
|
48
|
+
lru = "0.12"
|
|
49
|
+
|
|
50
|
+
[build-dependencies]
|
|
51
|
+
napi-build = "2"
|
|
52
|
+
|
|
53
|
+
[features]
|
|
54
|
+
default = ["fast-email"]
|
|
55
|
+
fast-email = ["dep:fast_chemail"]
|
|
56
|
+
|
|
57
|
+
[profile.release]
|
|
58
|
+
opt-level = 3
|
|
59
|
+
lto = "fat"
|
|
60
|
+
codegen-units = 1
|
|
61
|
+
strip = "symbols"
|
|
62
|
+
debug = 0
|
|
63
|
+
incremental = false
|
|
64
|
+
# Safety: this is an embedded cdylib loaded into the Bun process. panic = "unwind"
|
|
65
|
+
# (the default) lets napi's catch_unwind convert Rust panics into JS exceptions
|
|
66
|
+
# instead of aborting the host process. overflow-checks = true catches integer
|
|
67
|
+
# overflow in release; the cost is negligible relative to the safety gain.
|
|
68
|
+
overflow-checks = true
|
|
69
|
+
debug-assertions = false
|
|
70
|
+
|
|
71
|
+
[profile.release.package."*"]
|
|
72
|
+
opt-level = 3
|
|
73
|
+
codegen-units = 1
|
package/README.md
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# castrum
|
|
2
|
+
|
|
3
|
+
**Enterprise-Grade Bun + Rust FFI Runtime Benchmark Package**
|
|
4
|
+
|
|
5
|
+
A high-performance Bun backend framework with Rust-accelerated FFI functions, providing production-grade HTTP ingress pipeline, validation, hashing, JSON processing, and more — all with benchmark-grade performance.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture Overview
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ TypeScript (Bun) Layer │
|
|
14
|
+
│ │
|
|
15
|
+
│ index.ts (entry point) │
|
|
16
|
+
│ ├── src/rust-ffi/ Raw Rust FFI bindings │
|
|
17
|
+
│ ├── src/native/ NAPI native addon loader │
|
|
18
|
+
│ ├── src/ingress/ High-performance HTTP ingress pipeline │
|
|
19
|
+
│ ├── src/baseline/ JS baseline (benchmark reference) │
|
|
20
|
+
│ ├── src/data/ Data serialization utilities │
|
|
21
|
+
│ └── src/shared/ Shared constants and helpers │
|
|
22
|
+
│ │
|
|
23
|
+
├─────────────────── NAPI Bridge (napi-rs) ──────────────────────┤
|
|
24
|
+
│ │
|
|
25
|
+
│ Rust (cdylib) Layer │
|
|
26
|
+
│ │
|
|
27
|
+
│ rust/lib.rs │
|
|
28
|
+
│ ├── Core Pipeline: ingress, cors, rate_limit, ip_trust │
|
|
29
|
+
│ ├── Parsers: http_parser, query_parser, cookie_parser │
|
|
30
|
+
│ ├── Validation: validation (email/uuid/IP) │
|
|
31
|
+
│ ├── JSON: json_ops, json_patch_ops, json_schema, │
|
|
32
|
+
│ │ json_ser │
|
|
33
|
+
│ ├── Crypto: hashing, hmac_sha256, random_token │
|
|
34
|
+
│ ├── Data: url_codec, mime_lookup, websocket │
|
|
35
|
+
│ ├── Batch: batch │
|
|
36
|
+
│ ├── Utilities: util, method, headers, output, terminal │
|
|
37
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Install dependencies
|
|
44
|
+
bun install
|
|
45
|
+
|
|
46
|
+
# Build Rust native addon (release)
|
|
47
|
+
bun run build
|
|
48
|
+
|
|
49
|
+
# Run benchmarks
|
|
50
|
+
bun bench.ts
|
|
51
|
+
|
|
52
|
+
# Run tests
|
|
53
|
+
bun test
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Build
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Production build
|
|
60
|
+
bun run build
|
|
61
|
+
|
|
62
|
+
# Debug build
|
|
63
|
+
bun run build:debug
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Benchmarks
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Full benchmark suite
|
|
70
|
+
bun bench.ts
|
|
71
|
+
|
|
72
|
+
# HTTP server benchmarks
|
|
73
|
+
bun run bench:http
|
|
74
|
+
|
|
75
|
+
# Compare with specific frameworks
|
|
76
|
+
bun run bench:http:elysia # vs Elysia
|
|
77
|
+
bun run bench:http:ingress # vs Ingress (native)
|
|
78
|
+
bun run bench:http:bun-only # vs raw Bun
|
|
79
|
+
|
|
80
|
+
# Load patterns
|
|
81
|
+
bun run bench:http:smoke # smoke test
|
|
82
|
+
bun run bench:http:stress # stress test
|
|
83
|
+
bun run bench:http:heavy # heavy JSON
|
|
84
|
+
bun run bench:http:crud # CRUD operations
|
|
85
|
+
bun run bench:http:soak # soak (endurance) test
|
|
86
|
+
bun run bench:http:storm # storm (burst) test
|
|
87
|
+
bun run bench:http:boundary # boundary conditions
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Core Exports
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { rust, native, rustBatch } from "castrum";
|
|
96
|
+
|
|
97
|
+
// `rust` — All Rust FFI native implementations (flat, complete API)
|
|
98
|
+
// `native` — JavaScript/Bun baseline implementations for benchmarking
|
|
99
|
+
// `rustBatch` — Back-compat alias === rust.batch
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Rust Utility API
|
|
103
|
+
|
|
104
|
+
`rust` is a single flat object exposing **every** native Rust utility with
|
|
105
|
+
optimized defaults baked in. Text-oriented operations also have ergonomic
|
|
106
|
+
string variants under `rust.text`.
|
|
107
|
+
|
|
108
|
+
| Namespace | Description |
|
|
109
|
+
|-----------|-------------|
|
|
110
|
+
| `rust.crc32(...)`, `rust.jsonValid(...)`, … | Scalar utilities (bytes in → typed out) |
|
|
111
|
+
| `rust.text.mimeFromExtension(".js")`, … | String ergonomics (string in → string/bool out) |
|
|
112
|
+
| `rust.batch.jsonValid(docs)`, … | High-throughput batch (arrays → unpacked results) |
|
|
113
|
+
| `rust.packed.jsonValidBatchPacked(p)`, … | Raw packed low-level variants + metadata |
|
|
114
|
+
| `rust.configure({ ... })` | Override defaults on the shared instance |
|
|
115
|
+
|
|
116
|
+
#### Scalar utilities
|
|
117
|
+
|
|
118
|
+
| Function | Returns |
|
|
119
|
+
|----------|---------|
|
|
120
|
+
| `crc32(input)` | `number` — CRC32 checksum |
|
|
121
|
+
| `fnv1a64(input)` | `bigint` — FNV-1a 64-bit hash |
|
|
122
|
+
| `hmacSha256(key, data)` | `Uint8Array` — HMAC-SHA256 signature |
|
|
123
|
+
| `hmacSha256Verify(key, data, sig)` | `boolean` |
|
|
124
|
+
| `jsonValid(input)` | `boolean` — JSON validity check |
|
|
125
|
+
| `jsonSumIds(input)` | `bigint` — sum of numeric `id` fields |
|
|
126
|
+
| `jsonPatch(doc, patch)` | `Uint8Array` — RFC 6902 result |
|
|
127
|
+
| `mimeFromExtension(ext)` | `Uint8Array` — MIME type |
|
|
128
|
+
| `randomToken(byteLen)` | `Uint8Array` — CSPRNG token |
|
|
129
|
+
| `urlEncode(input)` / `urlDecode(input)` | `Uint8Array` — percent encode/decode |
|
|
130
|
+
| `urlDecodeBytes(input)` | `Uint8Array` — strict %-decode (no UTF-8 check) |
|
|
131
|
+
| `validateEmail/Uuid/Ipv4/Ipv6(input)` | `boolean` |
|
|
132
|
+
| `wsAcceptKey(key)` | `Uint8Array` — WebSocket accept key |
|
|
133
|
+
|
|
134
|
+
Low-level / packed variants: `rust.httpParseRequestPacked`, `rust.queryParsePacked`,
|
|
135
|
+
`rust.cookieParsePacked`.
|
|
136
|
+
|
|
137
|
+
#### String ergonomics (`rust.text`)
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
rust.text.mimeFromExtension(".js"); // "text/javascript"
|
|
141
|
+
rust.text.mimeFromExtension("html"); // "text/html"
|
|
142
|
+
rust.text.urlEncode("a b"); // "a%20b"
|
|
143
|
+
rust.text.urlDecode("a%20b"); // "a b"
|
|
144
|
+
rust.text.wsAcceptKey(keyBase64); // base64 accept key
|
|
145
|
+
rust.text.validateEmail("a@b.co"); // true
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Batch operations (`rust.batch`)
|
|
149
|
+
|
|
150
|
+
All batch helpers accept `Uint8Array[]` and return unpacked, ready-to-use results:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const docs = [encoder.encode('{"id":1}'), encoder.encode('{"id":2}')];
|
|
154
|
+
|
|
155
|
+
rust.batch.jsonValid(docs); // Uint8Array bitset (1 per doc)
|
|
156
|
+
rust.batch.crc32(docs); // Uint32Array
|
|
157
|
+
rust.batch.jsonSumIds(docs); // BigInt64Array
|
|
158
|
+
rust.batch.queryParse(docs); // Uint8Array[]
|
|
159
|
+
rust.batch.schemaValidate(validator, docs); // bitset
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Raw packed + metadata variants (advanced): `rust.packed.*` mirrors the native
|
|
163
|
+
functions 1:1 (`jsonValidBatchPacked`, `jsonValidBatchCountPacked`,
|
|
164
|
+
`queryParseBatchTotalLenPacked`, …).
|
|
165
|
+
|
|
166
|
+
#### Configuring defaults
|
|
167
|
+
|
|
168
|
+
Defaults are selected automatically, and can be overridden:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// Defaults:
|
|
172
|
+
// rayonThreads = max(1, hardwareConcurrency - 1)
|
|
173
|
+
// (env override: CASTRUM_RAYON_THREADS / RUST_RAYON_THREADS)
|
|
174
|
+
// mimeCache = true
|
|
175
|
+
// hmacCache = true
|
|
176
|
+
|
|
177
|
+
// Override per-instance options on the shared instance:
|
|
178
|
+
rust.configure({ mimeCache: false, hmacCache: false });
|
|
179
|
+
|
|
180
|
+
// Or create an isolated instance with custom defaults:
|
|
181
|
+
import { createRust } from "castrum";
|
|
182
|
+
const myRust = createRust({ mimeCache: false });
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
> **Thread pool note**: the rayon pool is process-wide and initialized **once**
|
|
186
|
+
> (native `OnceLock`) — the first initialization wins. Because importing the
|
|
187
|
+
> module initializes it with the default, set
|
|
188
|
+
> `CASTRUM_RAYON_THREADS` / `RUST_RAYON_THREADS` (or call
|
|
189
|
+
> `rust.configure({ rayonThreads })` / `createRust({ rayonThreads })` before any
|
|
190
|
+
> other pool use) to tune it. `mimeCache` / `hmacCache` are per-instance and can
|
|
191
|
+
> be toggled any time.
|
|
192
|
+
|
|
193
|
+
### Sub-modules
|
|
194
|
+
|
|
195
|
+
| Import | Description |
|
|
196
|
+
|--------|-------------|
|
|
197
|
+
| `import { encoder, decoder } from "castrum"` | Text encode/decode utilities |
|
|
198
|
+
| `import { packBatch, packPairs, unpackBitset, unpackByteResults, unpackI64ArrayAsBigInt, unpackU32Array } from "castrum"` | Batch/pair packing + unpacking utilities |
|
|
199
|
+
| `import { readPairsPacked, pairsToObject, readHttpPacked } from "castrum"` | Decode packed pairs / parsed HTTP buffers |
|
|
200
|
+
| `import { parseQueryString, parseCookieHeader } from "castrum"` | High-level string parsers (no hand-packing) |
|
|
201
|
+
| `import { jsonRowsBytes, createJsonRows } from "castrum"` | JSON row serialization |
|
|
202
|
+
| `import { createIngress, createIngressSync, createIngressFast } from "castrum"` | HTTP ingress pipeline (low-level) |
|
|
203
|
+
| `import { createIngressHandler, createIngressServer, readHandler, jsonWriteHandler, echoHandler, fallbackHandler } from "castrum"` | **Pre-baked** ingress handlers — route factories + Bun.serve builder |
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { parseQueryString, parseCookieHeader } from "castrum";
|
|
207
|
+
|
|
208
|
+
parseQueryString("a=1&b=2&tag=a&tag=b"); // { a: "1", b: "2", tag: ["a", "b"] }
|
|
209
|
+
parseCookieHeader("session=abc; theme=dark"); // { session: "abc", theme: "dark" }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
> **Environment variables**: See [docs/ENVIRONMENT.md](./docs/ENVIRONMENT.md) for the
|
|
213
|
+
> full reference of `INGRESS_*`, `CASTRUM_*` and `RUST_*` runtime variables.
|
|
214
|
+
|
|
215
|
+
### Ingress Pipeline
|
|
216
|
+
|
|
217
|
+
The ingress system is a high-performance request processing pipeline that handles:
|
|
218
|
+
|
|
219
|
+
- **Method routing** — GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
|
|
220
|
+
- **CORS evaluation** — Origin validation, preflight handling
|
|
221
|
+
- **Rate limiting** — Token-bucket per IP with configurable window
|
|
222
|
+
- **IP trust & proxy** — X-Forwarded-For, X-Real-IP resolution
|
|
223
|
+
- **HTTPS detection** — X-Forwarded-Proto based
|
|
224
|
+
- **Cookie parsing** — Structured JSON output
|
|
225
|
+
- **Query parsing** — Key-value pair extraction
|
|
226
|
+
- **JSON body validation** — Syntax + optional schema validation
|
|
227
|
+
- **Body size guarding** — Configurable max body size
|
|
228
|
+
- **Security headers** — HSTS, CSP, X-Frame-Options, etc.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// Fast synchronous handler (zero allocations per call)
|
|
232
|
+
import { createIngressFast } from "castrum";
|
|
233
|
+
|
|
234
|
+
const handler = createIngressFast({
|
|
235
|
+
cors: { allowOrigin: ["https://example.com"] },
|
|
236
|
+
rateLimit: { limit: 100, windowMs: 60_000 },
|
|
237
|
+
parseCookies: true,
|
|
238
|
+
parseQuery: true,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Inside a Bun server:
|
|
242
|
+
Bun.serve({
|
|
243
|
+
port: 3000,
|
|
244
|
+
async fetch(req) {
|
|
245
|
+
return handler.run(req, req.headers.get("x-forwarded-for"), null, "", (result) => {
|
|
246
|
+
if (result.ok) {
|
|
247
|
+
return new Response("OK", { status: 200 });
|
|
248
|
+
}
|
|
249
|
+
return new Response("Error", { status: result.status });
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Pre-baked handlers (recommended for servers)
|
|
256
|
+
|
|
257
|
+
`src/ingress/handlers.ts` provides ready-to-use route handlers and a `Bun.serve`
|
|
258
|
+
builder so **any system can consume the optimized ingress pipeline in a few
|
|
259
|
+
lines** — no need to hand-build responses or manage header templates:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { createIngressHandler, createIngressServer } from "castrum";
|
|
263
|
+
|
|
264
|
+
const ingress = createIngressHandler({
|
|
265
|
+
parseCookies: true,
|
|
266
|
+
parseQuery: true,
|
|
267
|
+
cors: { allowOrigin: ["https://app.example.com"] },
|
|
268
|
+
rateLimit: { limit: 1000, windowMs: 60_000 },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
createIngressServer({
|
|
272
|
+
port: 3000,
|
|
273
|
+
routes: {
|
|
274
|
+
"/health": { read: ingress }, // GET + HEAD
|
|
275
|
+
"/api/users": { read: ingress, write: ingress }, // + POST/PUT/PATCH
|
|
276
|
+
"/api/echo": { echo: ingress }, // POST echo
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The route factories (`readHandler`, `headHandler`, `jsonWriteHandler`,
|
|
282
|
+
`echoHandler`, `fallbackHandler`) are runtime-agnostic — use them with any
|
|
283
|
+
fetch-style server. The benchmark server (`bench/servers/ingress-server.ts`)
|
|
284
|
+
is built entirely from these and re-exports them. See
|
|
285
|
+
[docs/INGRESS.md](./docs/INGRESS.md) for the full pre-baked API reference.
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Project Structure
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
castrum/
|
|
294
|
+
├── index.ts # Entry point / public API
|
|
295
|
+
├── AGENTS.md # AI agent instructions (commands, constraints, gotchas)
|
|
296
|
+
├── bench.ts # Benchmark runner
|
|
297
|
+
├── build.rs # NAPI build script
|
|
298
|
+
├── Cargo.toml # Rust dependencies & build config
|
|
299
|
+
├── package.json # Node/Bun package config
|
|
300
|
+
├── tsconfig.json # TypeScript config
|
|
301
|
+
│
|
|
302
|
+
├── rust/ # Rust source (cdylib)
|
|
303
|
+
│ ├── lib.rs # Crate root, module declarations
|
|
304
|
+
│ ├── ingress.rs # NAPI ingress class
|
|
305
|
+
│ ├── cors.rs # CORS engine
|
|
306
|
+
│ ├── rate_limit.rs # Keyed rate limiter
|
|
307
|
+
│ ├── ip_trust.rs # IP trust & proxy resolution
|
|
308
|
+
│ ├── http_parser.rs # HTTP request parser
|
|
309
|
+
│ ├── query_parser.rs # Query string parser
|
|
310
|
+
│ ├── cookie_parser.rs # Cookie header parser
|
|
311
|
+
│ ├── validation.rs # Email/UUID/IP validation
|
|
312
|
+
│ ├── hashing.rs # FNV-1a & fast hashing
|
|
313
|
+
│ ├── hmac_sha256.rs # HMAC-SHA256
|
|
314
|
+
│ ├── json_ops.rs # JSON operations
|
|
315
|
+
│ ├── json_patch_ops.rs # JSON Patch operations
|
|
316
|
+
│ ├── json_schema.rs # JSON Schema validation
|
|
317
|
+
│ ├── json_ser.rs # JSON serialization utilities
|
|
318
|
+
│ ├── url_codec.rs # URL encode/decode
|
|
319
|
+
│ ├── mime_lookup.rs # MIME type lookup
|
|
320
|
+
│ ├── websocket.rs # WebSocket utilities
|
|
321
|
+
│ ├── random_token.rs # Secure random token generation
|
|
322
|
+
│ ├── batch.rs # Batch processing
|
|
323
|
+
│ ├── method.rs # HTTP method enum
|
|
324
|
+
│ ├── headers.rs # Header packing
|
|
325
|
+
│ ├── output.rs # Output buffer layout
|
|
326
|
+
│ ├── terminal.rs # Terminal response writers
|
|
327
|
+
│ ├── proxy.rs # Proxy detection utilities
|
|
328
|
+
│ ├── util.rs # Core utilities
|
|
329
|
+
│ └── ingress_constants.rs # NAPI-exported constants
|
|
330
|
+
│
|
|
331
|
+
├── src/ # TypeScript source
|
|
332
|
+
│ ├── ingress/ # Ingress pipeline (TS layer)
|
|
333
|
+
│ │ ├── index.ts # Public API + async factory
|
|
334
|
+
│ │ ├── fast.ts # Fast synchronous handler (packed input)
|
|
335
|
+
│ │ ├── handlers.ts # PRE-BAKED handlers + createIngressServer
|
|
336
|
+
│ │ ├── constants.ts # Layout constants (from Rust)
|
|
337
|
+
│ │ └── packed-input.ts # Input buffer packing
|
|
338
|
+
│ ├── native/ # Native addon loader
|
|
339
|
+
│ │ └── index.ts # Module loading + type definitions
|
|
340
|
+
│ ├── rust-ffi/ # Rust FFI bindings
|
|
341
|
+
│ │ └── index.ts # Flat, complete FFI API
|
|
342
|
+
│ ├── baseline/ # JS baseline implementations
|
|
343
|
+
│ │ ├── index.ts # Aggregator
|
|
344
|
+
│ │ └── tasks/ # Individual baseline implementations
|
|
345
|
+
│ ├── bench/ # Benchmark framework
|
|
346
|
+
│ ├── data/ # Data serialization
|
|
347
|
+
│ └── shared/ # Shared utilities
|
|
348
|
+
│
|
|
349
|
+
├── bench/ # HTTP benchmark servers
|
|
350
|
+
│ ├── servers/ # bun-server.ts, elysia-server.ts, ingress-server.ts (+ shared.ts)
|
|
351
|
+
│ ├── load.ts # HTTP load generator & scenarios
|
|
352
|
+
│ └── run-bench.ts # Benchmark runner
|
|
353
|
+
│
|
|
354
|
+
├── test/ # Tests (see CONTRIBUTING.md → Testing Requirements)
|
|
355
|
+
├── docs/ # Documentation
|
|
356
|
+
└── scripts/ # Build/utility scripts
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Performance Characteristics
|
|
362
|
+
|
|
363
|
+
All Rust FFI functions are designed for **zero-allocation** paths where possible:
|
|
364
|
+
|
|
365
|
+
- **Ingress pipeline**: Single output buffer written by Rust, read by JS — minimal copies
|
|
366
|
+
- **Validation functions**: Pure CPU-bound, 10-100x faster than JS equivalents
|
|
367
|
+
- **Hashing**: SIMD-optimized crates under the hood
|
|
368
|
+
- **Batch operations**: Packed binary format minimizes NAPI boundary crossings
|
|
369
|
+
- **JSON parsing**: Uses `sonic-rs` (Rust) — one of the fastest JSON parsers available
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Testing
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Run all tests
|
|
377
|
+
bun test
|
|
378
|
+
|
|
379
|
+
# Run specific test file
|
|
380
|
+
bun test test/unit/ingress/fast.test.ts
|
|
381
|
+
|
|
382
|
+
# Run tests with coverage
|
|
383
|
+
bun test --coverage
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Publishing
|
|
389
|
+
|
|
390
|
+
The package ships **all platform native addons in a single tarball**, so a release
|
|
391
|
+
needs every `napi.targets` platform artifact present (see `scripts/prepublish.mjs`).
|
|
392
|
+
|
|
393
|
+
### Automatic (recommended)
|
|
394
|
+
|
|
395
|
+
CI builds every platform addon and publishes to npm when you push a `v*` tag.
|
|
396
|
+
Bump `version` in `package.json` (and `Cargo.toml`), add a `CHANGELOG.md` entry,
|
|
397
|
+
then:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
git tag v0.6.0 && git push origin v0.6.0
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Manual
|
|
404
|
+
|
|
405
|
+
To build and publish a release by hand (no CI needed; npm logged in or `NPM_TOKEN`
|
|
406
|
+
exported):
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
bun run publish:manual # build + publish current version
|
|
410
|
+
bun run publish:manual -- --increment minor # bump version + tag + push + publish
|
|
411
|
+
bun run publish:manual:dry # print the plan, change nothing
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`publish:manual` bumps the version (`--increment`), creates + pushes the `v<version>`
|
|
415
|
+
git tag via `bun pm version` (keeping `Cargo.toml`, `Cargo.lock`, and `CHANGELOG.md`
|
|
416
|
+
in sync), builds the host addon locally, and runs `npm publish --access public`.
|
|
417
|
+
|
|
418
|
+
> **Platform note:** because only the current platform can be built locally, the
|
|
419
|
+
> manual publish ships a **single-platform tarball** (it sets
|
|
420
|
+
> `CASTRUM_PUBLISH_ALLOW_PARTIAL=1` so `prepublishOnly` doesn't fail on the missing
|
|
421
|
+
> platforms). For a full multi-platform tarball, push a `v*` tag and let the CI
|
|
422
|
+
> workflow build + publish.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT
|
|
429
|
+
|
|
430
|
+
## Contributing
|
|
431
|
+
|
|
432
|
+
Please see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
package/bench.ts
ADDED
|
Binary file
|
package/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export * from "./src/rust-ffi";
|
|
2
|
+
|
|
3
|
+
export * as native from "./src/baseline";
|
|
4
|
+
|
|
5
|
+
export { encoder, decoder } from "./src/shared/bytes";
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
packBatch,
|
|
9
|
+
packPairs,
|
|
10
|
+
readPairsPacked,
|
|
11
|
+
readHttpPacked,
|
|
12
|
+
pairsToObject,
|
|
13
|
+
parseQueryString,
|
|
14
|
+
parseCookieHeader,
|
|
15
|
+
unpackBitset,
|
|
16
|
+
unpackByteResults,
|
|
17
|
+
unpackI64ArrayAsBigInt,
|
|
18
|
+
unpackU32Array,
|
|
19
|
+
} from "./src/shared/packed";
|
|
20
|
+
export type { Pair, ParsedHttpRequestPacked } from "./src/shared/packed";
|
|
21
|
+
|
|
22
|
+
export { jsonRowsBytes, createJsonRows } from "./src/data/json-rows";
|
|
23
|
+
export type { JsonRow } from "./src/data/json-rows";
|
|
24
|
+
export * from "./src/ingress";
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "castrum",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Enterprise-grade Bun + Rust runtime: Rust-accelerated FFI primitives, a production-grade HTTP ingress pipeline, validation, hashing, and JSON processing with benchmark-grade performance.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.ts",
|
|
8
|
+
"module": "index.ts",
|
|
9
|
+
"types": "index.ts",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"bun",
|
|
13
|
+
"rust",
|
|
14
|
+
"napi",
|
|
15
|
+
"ffi",
|
|
16
|
+
"ingress",
|
|
17
|
+
"http",
|
|
18
|
+
"validation",
|
|
19
|
+
"hashing",
|
|
20
|
+
"json",
|
|
21
|
+
"performance",
|
|
22
|
+
"benchmark"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Axylabs/flux-rs.git"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./index.ts"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.ts",
|
|
36
|
+
"bench.ts",
|
|
37
|
+
"src",
|
|
38
|
+
"rust",
|
|
39
|
+
"Cargo.toml",
|
|
40
|
+
"README.md",
|
|
41
|
+
"*.node"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "napi build --release --platform",
|
|
45
|
+
"bench": "bun run build && bun bench.ts",
|
|
46
|
+
"check": "bun bench.ts",
|
|
47
|
+
"test": "bun test",
|
|
48
|
+
"build:debug": "napi build --platform",
|
|
49
|
+
"artifacts": "napi artifacts",
|
|
50
|
+
"publish:manual": "node scripts/publish-manual.mjs",
|
|
51
|
+
"publish:manual:dry": "node scripts/publish-manual.mjs --dry-run",
|
|
52
|
+
"prepublishOnly": "node scripts/prepublish.mjs",
|
|
53
|
+
"bench:http": "bun run bench/run-bench.ts",
|
|
54
|
+
"bench:http:smoke": "bun run bench/run-bench.ts smoke",
|
|
55
|
+
"bench:http:stress": "bun run bench/run-bench.ts stress",
|
|
56
|
+
"bench:http:heavy": "bun run bench/run-bench.ts heavy-json",
|
|
57
|
+
"bench:http:crud": "bun run bench/run-bench.ts crud",
|
|
58
|
+
"bench:http:spike": "bun run bench/run-bench.ts spike",
|
|
59
|
+
"bench:http:soak": "bun run bench/run-bench.ts soak",
|
|
60
|
+
"bench:http:storm": "bun run bench/run-bench.ts storm",
|
|
61
|
+
"bench:http:boundary": "bun run bench/run-bench.ts boundary",
|
|
62
|
+
"bench:http:elysia": "bun run bench/run-bench.ts '' elysia",
|
|
63
|
+
"bench:http:ingress": "bun run bench/run-bench.ts '' ingress",
|
|
64
|
+
"bench:http:bun-only": "bun run bench/run-bench.ts '' bun",
|
|
65
|
+
"bench:http:all-heavy": "bun run bench/run-bench.ts 1"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"crc-32": "^1.2.2",
|
|
69
|
+
"fast-json-patch": "^3.1.1",
|
|
70
|
+
"mime-types": "^3.0.2",
|
|
71
|
+
"validator": "^13.15.35"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@elysia/cors": "^1.4.2",
|
|
75
|
+
"@napi-rs/cli": "^3.7.4",
|
|
76
|
+
"@types/bun": "^1.3.14",
|
|
77
|
+
"@types/mime-types": "^3.0.1",
|
|
78
|
+
"@types/validator": "^13.15.10",
|
|
79
|
+
"elysia": "^1.4.0"
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"bun": ">=1.1.0"
|
|
83
|
+
},
|
|
84
|
+
"napi": {
|
|
85
|
+
"binaryName": "castrum",
|
|
86
|
+
"targets": [
|
|
87
|
+
"aarch64-apple-darwin",
|
|
88
|
+
"aarch64-unknown-linux-gnu",
|
|
89
|
+
"x86_64-apple-darwin",
|
|
90
|
+
"x86_64-pc-windows-msvc",
|
|
91
|
+
"x86_64-unknown-linux-gnu"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|