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.
- package/LICENSE +21 -0
- package/README.md +154 -368
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +2763 -0
- package/dist/index.js +8707 -0
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +507 -0
- package/docs/BENCHMARKS.md +343 -0
- package/docs/CASE_STUDY.md +240 -0
- package/docs/ENVIRONMENT.md +81 -0
- package/docs/FFI_BUN_GUIDE.md +327 -0
- package/docs/GETTING_STARTED.md +192 -0
- package/docs/INGRESS-ROUTER.md +81 -0
- package/docs/INGRESS.md +746 -0
- package/docs/NATIVE-ROUTE.md +53 -0
- package/docs/REPO_MAP.md +279 -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 +43 -0
- package/docs/bun-builtins-decision-matrix.md +115 -0
- package/index.ts +44 -8
- package/package.json +98 -33
- package/src/ingress/body.ts +289 -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 +639 -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 +68 -0
- package/src/ingress/index.ts +142 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +139 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +164 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +102 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/router.ts +237 -0
- package/src/ingress/routes/common.ts +79 -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 +27 -0
- package/src/ingress/routes/index.ts +11 -0
- package/src/ingress/routes/json-write.ts +129 -0
- package/src/ingress/routes/options.ts +26 -0
- package/src/ingress/routes/read.ts +35 -0
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +475 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +191 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +220 -0
- package/src/integration/streaming.ts +78 -0
- package/src/integration/websocket.ts +93 -0
- package/src/loader/batch.ts +137 -0
- package/src/loader/cost.ts +105 -0
- package/src/loader/index.ts +745 -0
- package/src/loader/ops.ts +657 -0
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +729 -0
- package/src/native/ffi/types.ts +474 -0
- package/src/native/ffi.ts +1775 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +240 -0
- package/src/native/types/instances.ts +274 -0
- package/src/native/types.ts +401 -0
- package/src/runtime/builtins.ts +131 -0
- package/src/runtime/codec.ts +147 -0
- package/src/runtime/detect.ts +36 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/native.ts +35 -0
- package/src/runtime/server.ts +31 -0
- package/src/runtime/transport.ts +64 -0
- package/src/runtime/types.ts +173 -0
- package/src/runtime/uuid.ts +45 -0
- package/src/runtime/websocket.ts +30 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch/build.ts +155 -0
- package/src/rust-ffi/batch/index.ts +8 -0
- package/src/rust-ffi/batch/types.ts +92 -0
- package/src/rust-ffi/client.ts +87 -0
- package/src/rust-ffi/context.ts +193 -0
- package/src/rust-ffi/index.ts +51 -541
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +28 -0
- package/src/rust-ffi/scalar/crypto.ts +346 -0
- package/src/rust-ffi/scalar/factories.ts +459 -0
- package/src/rust-ffi/scalar/hashing.ts +74 -0
- package/src/rust-ffi/scalar/http.ts +337 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +325 -0
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +61 -0
- package/src/rust-ffi/scalar/payload.ts +241 -0
- package/src/rust-ffi/text.ts +121 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +73 -0
- package/src/shared/adaptive.ts +67 -0
- package/src/shared/buffer-pool.ts +264 -0
- package/src/shared/bytes.ts +108 -9
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +75 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/packed/wire.ts +504 -0
- package/src/shared/proven.ts +289 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +29 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +15 -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/data/json-rows.ts +0 -33
- package/src/ingress/packed-input.ts +0 -59
- package/src/shared/json.ts +0 -18
- package/src/shared/packed.ts +0 -316
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 castrum contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,262 +1,40 @@
|
|
|
1
1
|
# castrum
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Rust-accelerated primitives and a production-grade HTTP pipeline for Bun — and Node.js.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
castrum is a hybrid TypeScript + Rust package. A single Rust addon does the
|
|
6
|
+
performance-critical work — hashing, JSON validation, parsing, crypto,
|
|
7
|
+
compression, and a full HTTP request pipeline — while TypeScript gives you a
|
|
8
|
+
small, flat API. Bun talks to the addon through `bun:ffi` (a ~10–20 ns C-ABI
|
|
9
|
+
call); Node.js falls back to NAPI automatically.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
## Why castrum
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
- **Native speed without the plumbing** — call `rust.crc32()`, `rust.jsonValid()`,
|
|
14
|
+
`rust.validateEmail()`, … directly from TypeScript. No WASM, no subprocess.
|
|
15
|
+
- **A real HTTP server in a few lines** — `createIngressServer` wires CORS, rate
|
|
16
|
+
limiting, body-size guards, JSON-schema validation, and security headers for you.
|
|
17
|
+
- **Zero-copy hot paths** — request data travels in packed binary buffers shared
|
|
18
|
+
with Rust, so there's no serialization and no per-request allocation.
|
|
19
|
+
- **Honest performance data** — the CPU benchmark (`bun run check`) races every
|
|
20
|
+
`rust.*` op against a pure-JS baseline and writes a machine-readable report, so
|
|
21
|
+
you always know what to reach for.
|
|
22
|
+
- **Bun first, Node ready** — the same API runs on Node ≥ 20.3 via a compiled ESM
|
|
23
|
+
entry.
|
|
10
24
|
|
|
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
|
-
```
|
|
25
|
+
> **Want the full story?** See the [case study](./docs/CASE_STUDY.md) for how
|
|
26
|
+
> castrum was built and measured — and where native speed did (and didn't) pay off.
|
|
55
27
|
|
|
56
|
-
##
|
|
28
|
+
## Quick start
|
|
57
29
|
|
|
58
|
-
|
|
59
|
-
# Production build
|
|
60
|
-
bun run build
|
|
61
|
-
|
|
62
|
-
# Debug build
|
|
63
|
-
bun run build:debug
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Benchmarks
|
|
30
|
+
### 1. Install
|
|
67
31
|
|
|
68
32
|
```bash
|
|
69
|
-
|
|
70
|
-
bun
|
|
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
|
-
});
|
|
33
|
+
bun install
|
|
34
|
+
bun run build # compile the Rust addon (release)
|
|
253
35
|
```
|
|
254
36
|
|
|
255
|
-
|
|
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:
|
|
37
|
+
### 2. Start a server
|
|
260
38
|
|
|
261
39
|
```ts
|
|
262
40
|
import { createIngressHandler, createIngressServer } from "castrum";
|
|
@@ -268,165 +46,173 @@ const ingress = createIngressHandler({
|
|
|
268
46
|
rateLimit: { limit: 1000, windowMs: 60_000 },
|
|
269
47
|
});
|
|
270
48
|
|
|
271
|
-
createIngressServer({
|
|
49
|
+
const server = createIngressServer({
|
|
272
50
|
port: 3000,
|
|
273
51
|
routes: {
|
|
274
|
-
"/health": { read: ingress },
|
|
275
|
-
"/api/users": { read: ingress, write: ingress },
|
|
276
|
-
"/api/echo": { echo: ingress },
|
|
52
|
+
"/health": { read: ingress }, // GET + HEAD
|
|
53
|
+
"/api/users": { read: ingress, write: ingress }, // + POST/PUT/PATCH (+OPTIONS)
|
|
54
|
+
"/api/echo": { echo: ingress }, // POST echo
|
|
277
55
|
},
|
|
278
56
|
});
|
|
279
|
-
```
|
|
280
57
|
|
|
281
|
-
|
|
282
|
-
|
|
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.
|
|
58
|
+
console.log("listening on", server.port);
|
|
59
|
+
```
|
|
286
60
|
|
|
61
|
+
Run it with `bun`, then `curl http://localhost:3000/health`. A complete
|
|
62
|
+
walkthrough lives in [`examples/basic-server.ts`](examples/basic-server.ts) and
|
|
63
|
+
the tutorial in [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md).
|
|
287
64
|
|
|
288
|
-
|
|
65
|
+
### 3. Call native primitives
|
|
289
66
|
|
|
290
|
-
|
|
67
|
+
```ts
|
|
68
|
+
import { rust, encoder } from "castrum";
|
|
291
69
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
70
|
+
rust.crc32(encoder.encode("hello")); // number
|
|
71
|
+
rust.jsonValid(encoder.encode('{"a":1}')); // boolean (zero-DOM)
|
|
72
|
+
rust.validateEmail(encoder.encode("a@b.co")); // boolean
|
|
73
|
+
rust.text.mimeFromExtension(".js"); // "text/javascript"
|
|
74
|
+
rust.batch.jsonValid([docA, docB]); // Uint8Array bitset (one packed call)
|
|
357
75
|
```
|
|
358
76
|
|
|
359
|
-
|
|
77
|
+
Everything is synchronous. For repeated calls, the higher-order
|
|
78
|
+
[`loader`](#api-at-a-glance) batches and caches for you automatically.
|
|
360
79
|
|
|
361
|
-
##
|
|
80
|
+
## Node.js support
|
|
362
81
|
|
|
363
|
-
|
|
82
|
+
Bun is the primary target — it resolves the `bun` export condition to raw
|
|
83
|
+
`index.ts` (zero startup cost). Node.js ≥ 20.3 resolves the `node`/`default`
|
|
84
|
+
condition to a compiled ESM bundle (`dist/`, built with `bun run build:js`). It's
|
|
85
|
+
the same cdylib either way: Bun uses `bun:ffi`, Node uses the NAPI fallback, so
|
|
86
|
+
`rust.*` calls behave identically on both runtimes.
|
|
364
87
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
88
|
+
```ts
|
|
89
|
+
// Node (ESM) — the same pre-baked handlers over node:http
|
|
90
|
+
import { createIngressServerNode } from "castrum";
|
|
91
|
+
const srv = createIngressServerNode({ port: 3000, routes: { "/health": { read: ingress } } });
|
|
92
|
+
await srv.ready; // resolves once listening
|
|
93
|
+
```
|
|
370
94
|
|
|
371
|
-
|
|
95
|
+
The public `createIngressServer` is runtime-adaptive — it picks `Bun.serve` on
|
|
96
|
+
Bun and `node:http` on Node; `createIngressServerNode` stays available for pinned
|
|
97
|
+
Node users with the same route handlers. `rust.transport()` /
|
|
98
|
+
`rust.ffiActive()` report which transport is live, and `CASTRUM_FFI_MODE` forces
|
|
99
|
+
one (see [`docs/ENVIRONMENT.md`](./docs/ENVIRONMENT.md)).
|
|
372
100
|
|
|
373
|
-
##
|
|
101
|
+
## How it works
|
|
374
102
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
103
|
+
TypeScript is the API; a Rust cdylib (`rust/`, one crate) is the engine. Bun
|
|
104
|
+
`dlopen`s the addon and JIT-calls its `extern "C"` exports — that's `bun:ffi`,
|
|
105
|
+
the primary transport (~10–20 ns per crossing). On Node, or if the bind-time
|
|
106
|
+
ffi self-test fails, the same addon loads through napi-rs (NAPI). Request data is
|
|
107
|
+
packed into shared, length-prefixed buffers so hot paths allocate nothing per
|
|
108
|
+
request. See [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for the full deep
|
|
109
|
+
dive.
|
|
378
110
|
|
|
379
|
-
|
|
380
|
-
bun test test/unit/ingress/fast.test.ts
|
|
111
|
+
## Benchmarks
|
|
381
112
|
|
|
382
|
-
|
|
383
|
-
bun test --coverage
|
|
384
|
-
```
|
|
113
|
+
Two suites ship with the repo:
|
|
385
114
|
|
|
386
|
-
|
|
115
|
+
- **CPU benchmark** — `bun run check` races every `rust.*` op against a pure-JS
|
|
116
|
+
baseline (and Bun's built-ins) and writes a machine-readable report to
|
|
117
|
+
`bench/results/cpu/`.
|
|
118
|
+
- **HTTP benchmark** — `bun run bench:http` compares raw `Bun.serve`, Elysia,
|
|
119
|
+
and the castrum ingress server across ~20 load scenarios (smoke, stress,
|
|
120
|
+
soak, spike, heavy JSON, slowloris, …).
|
|
387
121
|
|
|
388
|
-
|
|
122
|
+
```bash
|
|
123
|
+
bun run check # CPU benchmark + correctness (writes latest.json)
|
|
124
|
+
bun run bench:http # HTTP benchmark, all servers
|
|
125
|
+
bun run bench:http:smoke # fast HTTP sanity — the CI wire-format guard
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For scenario details, how to read the reports, and the benchmark-specific env
|
|
129
|
+
controls, see [`docs/BENCHMARKS.md`](./docs/BENCHMARKS.md). The keep/delegate
|
|
130
|
+
decisions for ops where Bun's built-ins win (gzip, crc32, xxh3, `randomUUIDv7`,
|
|
131
|
+
…) live in [`docs/bun-builtins-decision-matrix.md`](./docs/bun-builtins-decision-matrix.md).
|
|
132
|
+
|
|
133
|
+
## API at a glance
|
|
134
|
+
|
|
135
|
+
The complete, verified reference lives in [`docs/API.md`](./docs/API.md). This
|
|
136
|
+
is the map:
|
|
137
|
+
|
|
138
|
+
| Export | What it is |
|
|
139
|
+
|--------|-----------|
|
|
140
|
+
| `rust` | The flat native surface: hashing, JSON, validation, URL/cookie/query/form parsing, MIME, WebSocket/SSE, crypto (HMAC, JWT, AEAD, argon2, bcrypt, PBKDF2), signed cookies, CSRF, gzip/brotli, and more. |
|
|
141
|
+
| `rust.text.*` | String-in / string-out ergonomics over `rust.*`. |
|
|
142
|
+
| `rust.batch.*` | Array-of-bytes → native results (bitsets, typed arrays, byte arrays) in one packed call. |
|
|
143
|
+
| `rust.packed.*` | Raw packed-wire low-level variants + metadata. |
|
|
144
|
+
| `loader` / `createLoader` | Higher-order loader: pre-bound ops, automatic scalar-vs-bulk dispatch, DataLoader-style coalescing, LRU cache. |
|
|
145
|
+
| `opImpl` / `isNativeOp` / `opDecision` | Benchmark-driven native-vs-JS selection hints for framework consumers. |
|
|
146
|
+
| `createIngressHandler` + route factories | Pre-baked HTTP pipeline: CORS, rate limit, body guard, schema validation, security headers. |
|
|
147
|
+
| `createIngressServer` / `createIngressServerNode` | Server builders — the runtime adapter picks `Bun.serve` on Bun and `node:http` on Node. |
|
|
148
|
+
| `createIngress` / `createIngressFast` / `createIngressSync` | Lower-level ingress entry points. |
|
|
149
|
+
| `createPipeline` / `createWebSocketUpgrade` / `sseResponse` | Framework-agnostic integration helpers. |
|
|
150
|
+
| `createMetrics` / `createIngressMetrics` / `livenessHandler` / `readinessHandler` / trace helpers | Zero-dependency observability. |
|
|
151
|
+
| `uuidv7` / `AdaptiveEstimate` / packing & parsing utilities | Shared utilities (`encoder`, `decoder`, `packBatch`, `parseQueryString`, …). |
|
|
389
152
|
|
|
390
|
-
|
|
391
|
-
|
|
153
|
+
```ts
|
|
154
|
+
import { rust, loader } from "castrum";
|
|
392
155
|
|
|
393
|
-
|
|
156
|
+
rust.jsonValid(bytes); // scalar boolean
|
|
157
|
+
const isEmail = loader("validateEmail");
|
|
158
|
+
isEmail([a, b, c]); // one packed batch call
|
|
159
|
+
```
|
|
394
160
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
161
|
+
> **The selection surface** — framework consumers that want to bind each op to
|
|
162
|
+
> a fixed implementation at load time use `opImpl(op)` / `isNativeOp(op)` /
|
|
163
|
+
> `opDecision(op)` (source of truth: `rust/selection.rs` + `src/selection.json`,
|
|
164
|
+
> audited by CI). They read it once at startup — they don't swap native↔js per
|
|
165
|
+
> call. See [`docs/API.md`](./docs/API.md) and
|
|
166
|
+
> [`docs/bun-builtins-decision-matrix.md`](./docs/bun-builtins-decision-matrix.md).
|
|
167
|
+
|
|
168
|
+
## Testing
|
|
398
169
|
|
|
399
170
|
```bash
|
|
400
|
-
|
|
171
|
+
bun test # TypeScript unit tests (~540)
|
|
172
|
+
bun run test:rust # Rust unit tests (cargo test)
|
|
173
|
+
bun run typecheck # TypeScript typecheck
|
|
174
|
+
bun run test:node # Node.js integration tests (run `bun run build:js` first)
|
|
175
|
+
bun run bench:http:smoke # HTTP smoke — the CI wire-format guard
|
|
401
176
|
```
|
|
402
177
|
|
|
403
|
-
|
|
178
|
+
For coverage floors, the typecheck/test configs, and the full test matrix, see
|
|
179
|
+
[`docs/REPO_MAP.md`](./docs/REPO_MAP.md).
|
|
180
|
+
|
|
181
|
+
## Publishing
|
|
404
182
|
|
|
405
|
-
|
|
406
|
-
|
|
183
|
+
The package ships **all platform addons in a single tarball**. Push a `v*` tag
|
|
184
|
+
and CI builds + publishes every platform to npm; or use `bun run publish:manual`
|
|
185
|
+
for a quick single-platform release:
|
|
407
186
|
|
|
408
187
|
```bash
|
|
409
|
-
bun run publish:manual
|
|
410
|
-
bun run publish:manual
|
|
411
|
-
bun run publish:manual:dry # print the plan, change nothing
|
|
188
|
+
bun run publish:manual -- --increment minor # bump + tag + build + publish
|
|
189
|
+
bun run publish:manual:dry # print the plan, change nothing
|
|
412
190
|
```
|
|
413
191
|
|
|
414
|
-
`
|
|
415
|
-
|
|
416
|
-
|
|
192
|
+
Run `bun run check:version` before tagging (it verifies `package.json`,
|
|
193
|
+
`Cargo.toml`, and `CHANGELOG.md` agree). See [`docs/REPO_MAP.md`](./docs/REPO_MAP.md)
|
|
194
|
+
§6 for the full flow.
|
|
417
195
|
|
|
418
|
-
|
|
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.
|
|
196
|
+
## Documentation
|
|
423
197
|
|
|
424
|
-
|
|
198
|
+
| Doc | Read it for |
|
|
199
|
+
|-----|-------------|
|
|
200
|
+
| [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md) | First-time walkthrough: install → server → bench → test. |
|
|
201
|
+
| [`docs/API.md`](./docs/API.md) | The complete public API reference. |
|
|
202
|
+
| [`docs/INGRESS.md`](./docs/INGRESS.md) | The ingress pipeline: options, route factories, servers, framework integration. |
|
|
203
|
+
| [`docs/CASE_STUDY.md`](./docs/CASE_STUDY.md) | Data-driven case study: how castrum was built and measured. |
|
|
204
|
+
| [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) | Internals: modules, data flow, wire layout, memory, concurrency. |
|
|
205
|
+
| [`docs/BENCHMARKS.md`](./docs/BENCHMARKS.md) | Benchmark scenarios + how to interpret reports. |
|
|
206
|
+
| [`docs/ENVIRONMENT.md`](./docs/ENVIRONMENT.md) | Every `CASTRUM_*` / `INGRESS_*` env var. |
|
|
207
|
+
| [`docs/REPO_MAP.md`](./docs/REPO_MAP.md) | Where everything lives + how to build/test/publish. |
|
|
208
|
+
| [`docs/bun-builtins-decision-matrix.md`](./docs/bun-builtins-decision-matrix.md) | Bun built-in vs castrum keep/delegate decisions. |
|
|
209
|
+
| [`examples/`](examples/README.md) | Runnable examples. |
|
|
425
210
|
|
|
426
211
|
## License
|
|
427
212
|
|
|
428
|
-
MIT
|
|
213
|
+
MIT — see [LICENSE](./LICENSE).
|
|
429
214
|
|
|
430
215
|
## Contributing
|
|
431
216
|
|
|
432
|
-
|
|
217
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) and [`AGENTS.md`](./AGENTS.md) (AI-agent
|
|
218
|
+
guidance) for contribution and testing requirements.
|
|
Binary file
|