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/docs/API.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# castrum — API Reference
|
|
2
|
+
|
|
3
|
+
The complete public API, verified against the current source. For the friendly
|
|
4
|
+
overview and quick start, see the [README](../README.md); for the HTTP pipeline
|
|
5
|
+
deep-dive, see [`INGRESS.md`](./INGRESS.md).
|
|
6
|
+
|
|
7
|
+
Everything is **synchronous**. All byte-level functions accept `Uint8Array`;
|
|
8
|
+
`rust.text.*` provides string-in/string-out ergonomics.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Runtime return-type divergence (Bun vs Node)
|
|
13
|
+
|
|
14
|
+
On **Bun** the `bun:ffi` C-ABI transport returns text results as **JS strings**
|
|
15
|
+
via JSC's native transfer (cstring → `new CString(ptr, 0, len)`), with **zero
|
|
16
|
+
`TextEncoder`/`TextDecoder`** on the hot path. So `rust.urlEncode`,
|
|
17
|
+
`rust.base64Encode`, `rust.hexEncode`, `rust.hmacSha256`, `rust.etag`,
|
|
18
|
+
`rust.wsAcceptKey`, `rust.randomToken`, `rust.signCookie`, `rust.verifyCookie`
|
|
19
|
+
(`string | null`), `rust.csrfToken`, `rust.passwordHash`,
|
|
20
|
+
`rust.passwordHashBcrypt`, `rust.jwtSignBytes`, `rust.jwtVerify`
|
|
21
|
+
(`string | null`), `rust.jsonPatch`, `rust.httpDate`, `rust.mimeFromExtension`,
|
|
22
|
+
`rust.urlResolve`, `rust.urlEncodeQuery`, and `rust.acceptNegotiatorNegotiate`
|
|
23
|
+
(`string | null` — the `null` is the C `NULL` sentinel) return **`string`** on Bun. On **Node** (napi fallback) the same
|
|
24
|
+
functions return **`Uint8Array`**. The TypeScript surface types these as
|
|
25
|
+
`Uint8Array | string`; normalize with `toBytes(value)` / `toText(value)` (from
|
|
26
|
+
`src/shared/bytes`). The `*Into`/pooled variants always return bytes on both
|
|
27
|
+
runtimes, and binary ops (`gzip*`, `brotli*`, `aead*`, `pbkdf2Sha256`,
|
|
28
|
+
`wsFrame*`, packed parsers, `batch.*`/`packed.*`) return `Uint8Array` on both.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Core exports
|
|
33
|
+
|
|
34
|
+
| Export | Description |
|
|
35
|
+
|--------|-------------|
|
|
36
|
+
| `rust` | The default, shared native client — every Rust utility, flat and complete. |
|
|
37
|
+
| `createRust(options?)` | Build an isolated client (own caches / rayon settings). |
|
|
38
|
+
| `proven`, `PROVEN_SELECTION`, `provenImpl`, `provenStatus`, `isProven`, `provenSurface`, `provenSummary` | The full `rust.*` surface + the baked (pure-data) proven-selection registry of benchmark winners (`native` / `js` / `bun`). |
|
|
39
|
+
| `opImpl`, `isNativeOp`, `opDecision` | Native-vs-JS selection hints (benchmark-driven). |
|
|
40
|
+
| `loader`, `createLoader` | Higher-order loader over the batch surface. |
|
|
41
|
+
| `encoder`, `decoder` | Codec-backed UTF-8 helpers (Bun: `Bun.ArrayBufferSink` / `CString`; Node: `TextEncoder`/`TextDecoder`). `encode`/`decode` accept the `Uint8Array | string` union and normalize. |
|
|
42
|
+
| `uuidv7()` | UUIDv7 — `Bun.randomUUIDv7` on Bun, `crypto.randomUUID` on Node. |
|
|
43
|
+
| `AdaptiveEstimate`, `AdaptiveEstimateOptions` | Bounded EWMA adaptive-estimate utility. |
|
|
44
|
+
| `createMetrics`, `DEFAULT_BUCKETS` | Zero-dependency metrics registry (counters/gauges/histograms + Prometheus text). |
|
|
45
|
+
| `packBatch`, `packPairs`, `readPairsPacked`, `readHttpPacked`, `pairsToObject`, `parseQueryString`, `parseCookieHeader`, `parseFormBody`, `unpackBitset`, `unpackByteResults`, `unpackI64ArrayAsBigInt`, `unpackU32Array` | Packed-wire + high-level parsing utilities. |
|
|
46
|
+
| Ingress: `createIngress`, `createIngressSync`, `createIngressFast`, `createIngressHandler`, `createIngressServer`, `createIngressServerNode`, `createIngressRouter`, route factories, metrics/health/trace helpers | See [`INGRESS.md`](./INGRESS.md) and [`INGRESS-ROUTER.md`](./INGRESS-ROUTER.md). |
|
|
47
|
+
| Integration: `createPipeline`, `createWebSocketUpgrade`, `sseResponse` | Framework-agnostic helpers — see [`INGRESS.md`](./INGRESS.md) §Framework Integration. |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## The `rust` client
|
|
52
|
+
|
|
53
|
+
`rust` is a single flat object exposing **every** native utility. Text-oriented
|
|
54
|
+
operations also have ergonomic string variants under `rust.text`.
|
|
55
|
+
|
|
56
|
+
| Namespace | Description |
|
|
57
|
+
|-----------|-------------|
|
|
58
|
+
| `rust.crc32(...)`, `rust.jsonValid(...)`, … | Scalar utilities (bytes in → typed out) |
|
|
59
|
+
| `rust.text.mimeFromExtension(".js")`, … | String ergonomics (string in → string/bool out) |
|
|
60
|
+
| `rust.batch.jsonValid(docs)`, … | High-throughput batch (arrays → unpacked results) |
|
|
61
|
+
| `rust.packed.jsonValidBatchPacked(p)`, … | Raw packed low-level variants + metadata |
|
|
62
|
+
| `rust.configure({ ... })` | Override defaults on the shared instance |
|
|
63
|
+
|
|
64
|
+
`rust.transport()` returns `"ffi"` / `"napi"` (the resolved transport);
|
|
65
|
+
`rust.ffiActive()` reports whether the bun:ffi transport is live.
|
|
66
|
+
|
|
67
|
+
### Scalar utilities
|
|
68
|
+
|
|
69
|
+
| Function | Returns |
|
|
70
|
+
|----------|---------|
|
|
71
|
+
| `crc32(input)` | `number` — CRC32 checksum |
|
|
72
|
+
| `fnv1a64(input)` | `bigint` — FNV-1a 64-bit hash |
|
|
73
|
+
| `xxh3(input)` | `bigint` — XXH3-64 (non-crypto hash) |
|
|
74
|
+
| `hmacSha256(key, data)` | `Uint8Array` — HMAC-SHA256 signature |
|
|
75
|
+
| `hmacSha256Verify(key, data, sig)` | `boolean` |
|
|
76
|
+
| `jsonValid(input)` | `boolean` — JSON validity check (zero-DOM) |
|
|
77
|
+
| `jsonParse(input)` | `unknown` — JSON → JS value (native sonic-rs DOM; throws on invalid) |
|
|
78
|
+
| `jsonSumIds(input)` | `bigint` — sum of numeric `id` fields |
|
|
79
|
+
| `jsonPatch(doc, patch)` | `Uint8Array` — RFC 6902 result |
|
|
80
|
+
| `createSchemaValidator(schema)` | `SchemaValidator` — compile a JSON Schema (draft-07); `.validate(doc): boolean`, `.validateDetailed(doc): SchemaError[]`, `.validateFirstError(doc): SchemaError | null`, `.validateBatchPackedCount(packed): number`, … |
|
|
81
|
+
| `mimeFromExtension(ext)` | `Uint8Array` — MIME type |
|
|
82
|
+
| `randomToken(byteLen)` | `Uint8Array` — CSPRNG token |
|
|
83
|
+
| `urlEncode(input)` / `urlDecode(input)` | `Uint8Array` — percent encode/decode |
|
|
84
|
+
| `urlDecodeBytes(input)` | `Uint8Array` — strict %-decode (no UTF-8 check) |
|
|
85
|
+
| `validateEmail/Uuid/Ipv4/Ipv6(input)` | `boolean` |
|
|
86
|
+
| `wsAcceptKey(key)` | `Uint8Array` — WebSocket accept key |
|
|
87
|
+
| `passwordHashBcrypt(password, salt?, cost?)` | `Uint8Array` — bcrypt `$2b$` PHC string |
|
|
88
|
+
| `passwordVerifyBcrypt(password, phc)` | `boolean` |
|
|
89
|
+
| `pbkdf2Sha256(password, salt, iterations, dkLen?)` | `Uint8Array` — PBKDF2-HMAC-SHA256 |
|
|
90
|
+
| `gzipCompress(data)` / `gzipDecompress(data, maxDecompressed?)` | `Uint8Array` — gzip (decompress capped at 64 MiB by default) |
|
|
91
|
+
| `brotliCompress(data)` / `brotliDecompress(data, maxDecompressed?)` | `Uint8Array` — brotli |
|
|
92
|
+
|
|
93
|
+
### Framework actions (HTTP semantics + security)
|
|
94
|
+
|
|
95
|
+
| Function | Returns |
|
|
96
|
+
|----------|---------|
|
|
97
|
+
| `formParsePacked(body)` | `Uint8Array` — packed x-www-form-urlencoded pairs |
|
|
98
|
+
| `createFormParser(capacity?)` | `FormParser` — reusable-buffer form parser (`.parse`, `.parseInto`) |
|
|
99
|
+
| `parseMediaType(header)` | `{ mediaType, charset, boundary, params }` — Content-Type parse |
|
|
100
|
+
| `createMediaTypeParser()` | `MediaTypeParser` — `.parse`, `.matches(actual, expected)` (wildcards) |
|
|
101
|
+
| `createMediaTypeMatcher(expected)` | `MediaTypeMatcher` — `.matches(actual)` (expected precompiled once) |
|
|
102
|
+
| `etag(data, weak?)` | `Uint8Array` — strong/weak ETag (crc32-based) |
|
|
103
|
+
| `httpDate(secs?)` / `parseHttpDate(input)` | `Uint8Array` / `bigint | null` — IMF-fixdate format/parse |
|
|
104
|
+
| `createConditionalRequest(etag, lastModifiedSecs?)` | `ConditionalRequest` — `.isNotModified(ifNoneMatch, ifModifiedSince)` → 304 |
|
|
105
|
+
| `parseAcceptEncoding(header)` | `{ encoding, q, order }[]` |
|
|
106
|
+
| `createAcceptNegotiator(supported[])` | `AcceptNegotiator` — `.negotiate(header)` → best encoding |
|
|
107
|
+
| `base64Encode/Decode(input, urlSafe?, padding?)`, `base64UrlEncode/Decode(input)` | `Uint8Array` |
|
|
108
|
+
| `createBase64Codec(urlSafe?, padding?)` | `Base64Codec` — `.encode`, `.decode` |
|
|
109
|
+
| `hexEncode(input)` / `hexDecode(input)` | `Uint8Array` — lowercase hex |
|
|
110
|
+
| `signCookie(value, secret)` / `verifyCookie(signed, secret)` | `Uint8Array` / `Uint8Array | null` — signed cookies |
|
|
111
|
+
| `createCookieSigner(secret)` | `CookieSigner` — `.sign`, `.verify` (HMAC key compiled once) |
|
|
112
|
+
| `csrfToken(secret)` / `csrfVerify(token, secret)` | `Uint8Array` / `boolean` |
|
|
113
|
+
| `createCsrfProtector(secret)` | `CsrfProtector` — `.create`, `.verify` |
|
|
114
|
+
| `urlResolve(base, reference)` | `Uint8Array` — RFC 3986 resolution |
|
|
115
|
+
| `urlEncodeQuery(params)` | `Uint8Array` — percent-encoded query (sorted keys) |
|
|
116
|
+
| `createUrlBuilder(base)` | `UrlBuilder` — `.resolve(reference)` (base parsed once) |
|
|
117
|
+
| `createJwtSigner(secret, ttlSeconds?)` | `JwtSigner` — `.sign(claims, now)`, `.verify(token, now)` (HS256 key + ttl compiled once) |
|
|
118
|
+
| `createAeadCipher(key, algorithm?)` | `AeadCipher` — `.encrypt(nonce, pt)`, `.decrypt(nonce, ct)` (key compiled once) |
|
|
119
|
+
| `createArgon2Hasher(options?)` | `Argon2Hasher` — `.hash(password, salt)`, `.verify(password, phc)` (params compiled once) |
|
|
120
|
+
| `httpParseRequestPacked(bytes)` | packed HTTP request parse (low-level) |
|
|
121
|
+
| `queryParsePacked(query)` / `cookieParsePacked(header)` / `formParsePacked(body)` | packed pair parsing (low-level) |
|
|
122
|
+
|
|
123
|
+
> `passwordHash` / `passwordVerify` (argon2id) and `passwordHashBcrypt` /
|
|
124
|
+
> `passwordVerifyBcrypt` are also exposed as scalar helpers; the compiled-once
|
|
125
|
+
> `createArgon2Hasher` is preferred when hashing repeatedly.
|
|
126
|
+
|
|
127
|
+
### String ergonomics (`rust.text`)
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
rust.text.mimeFromExtension(".js"); // "text/javascript"
|
|
131
|
+
rust.text.mimeFromExtension("html"); // "text/html"
|
|
132
|
+
rust.text.urlEncode("a b"); // "a%20b"
|
|
133
|
+
rust.text.urlDecode("a%20b"); // "a b"
|
|
134
|
+
rust.text.wsAcceptKey(keyBase64); // base64 accept key
|
|
135
|
+
rust.text.validateEmail("a@b.co"); // true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Batch operations (`rust.batch`)
|
|
139
|
+
|
|
140
|
+
All batch helpers accept `Uint8Array[]` and return unpacked, ready-to-use results:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const docs = [encoder.encode('{"id":1}'), encoder.encode('{"id":2}')];
|
|
144
|
+
|
|
145
|
+
rust.batch.jsonValid(docs); // Uint8Array bitset (1 per doc)
|
|
146
|
+
rust.batch.crc32(docs); // Uint32Array
|
|
147
|
+
rust.batch.jsonSumIds(docs); // BigInt64Array
|
|
148
|
+
rust.batch.queryParse(docs); // Uint8Array[]
|
|
149
|
+
rust.batch.formParse(docs); // Uint8Array[] (packed pairs per doc)
|
|
150
|
+
rust.batch.signCookie(docs, secret); // Uint8Array[] (signed)
|
|
151
|
+
rust.batch.verifyCookie(docs, secret); // Uint8Array bitset (valid?)
|
|
152
|
+
rust.batch.csrfVerify(tokens, secret); // Uint8Array bitset (valid?)
|
|
153
|
+
rust.batch.schemaValidate(validator, docs); // bitset
|
|
154
|
+
|
|
155
|
+
// Hashing / url / mime / ws / base64url
|
|
156
|
+
rust.batch.fnv1a64(docs); // BigUint64Array (unsigned hashes)
|
|
157
|
+
rust.batch.etag(docs); // Uint8Array[]
|
|
158
|
+
rust.batch.urlEncode(docs); // Uint8Array[]
|
|
159
|
+
rust.batch.urlDecode(docs); // Uint8Array[]
|
|
160
|
+
rust.batch.base64UrlEncode(docs); // Uint8Array[]
|
|
161
|
+
rust.batch.wsAcceptKey(keys); // Uint8Array[]
|
|
162
|
+
rust.batch.mimeFromExtension(exts); // Uint8Array[]
|
|
163
|
+
|
|
164
|
+
// Backend-framework batches (zipped lists / shared args)
|
|
165
|
+
rust.batch.passwordVerify(pwds, phcs); // Uint8Array bitset (zipped)
|
|
166
|
+
rust.batch.urlResolve(bases, refs); // Uint8Array[] (zipped)
|
|
167
|
+
rust.batch.jwtSign(claimDocs, secret); // Uint8Array[] (sign N JSON claim docs)
|
|
168
|
+
rust.batch.jwtVerify(tokens, secret, now); // Uint8Array bitset
|
|
169
|
+
rust.batch.sseEncode(items, "event"); // Uint8Array[]
|
|
170
|
+
rust.batch.wsFrameEncode(items, 1, false, true); // Uint8Array[]
|
|
171
|
+
rust.batch.wsFrameDecode(frames); // Uint8Array[] (payloads)
|
|
172
|
+
rust.batch.multipartParse(bodies, boundary); // MultipartPart[][]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Raw packed + metadata variants (advanced): `rust.packed.*` mirrors the native
|
|
176
|
+
functions 1:1 (`jsonValidBatchPacked`, `jsonValidBatchCountPacked`,
|
|
177
|
+
`queryParseBatchTotalLenPacked`, …).
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Higher-order data loader (`loader` / `createLoader`)
|
|
182
|
+
|
|
183
|
+
`loader` is a callable higher-order function over the whole batch surface. It
|
|
184
|
+
pre-binds an op so repeated calls skip registry dispatch, and it routes small
|
|
185
|
+
vs. large workloads automatically: a single item → one scalar native call, a
|
|
186
|
+
bulk → ONE packed batch call, and `load()` coalesces N same-tick calls into one
|
|
187
|
+
packed call (DataLoader-style) with a bounded LRU cache.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { loader } from "castrum";
|
|
191
|
+
|
|
192
|
+
const isEmail = loader("validateEmail");
|
|
193
|
+
isEmail(emailBytes); // scalar → boolean
|
|
194
|
+
isEmail([a, b, c]); // bulk → Uint8Array bitset (one packed call)
|
|
195
|
+
await isEmail.load(a); // coalesced + cached (auto batch/strategy)
|
|
196
|
+
|
|
197
|
+
// Covered op families (44 ops):
|
|
198
|
+
// hash crc32, fnv1a64
|
|
199
|
+
// json jsonValid, jsonSumIds, jsonPatch*, schemaValidate
|
|
200
|
+
// validate email, uuid, ipv4, ipv6
|
|
201
|
+
// parse query, cookie, form, httpParseRequest, urlResolve*
|
|
202
|
+
// encode hex, base64, base64Url, urlEncode, urlDecode, urlDecodeBytes, etag
|
|
203
|
+
// compress gzip, brotli
|
|
204
|
+
// crypto hmacSha256, hmacSha256Verify*, signCookie, verifyCookie, csrfVerify,
|
|
205
|
+
// passwordHash, passwordVerify*, aeadEncrypt, aeadDecrypt, jwtSign, jwtVerify
|
|
206
|
+
// web wsAcceptKey, wsFrameEncode, sseEncode, mimeFromExtension
|
|
207
|
+
// (* = paired: per-item companion array, e.g. loader.run("jsonPatch", docs, patches))
|
|
208
|
+
|
|
209
|
+
// Paired ops take a companion array:
|
|
210
|
+
loader.run("jsonPatch", docs, patches); // bulk
|
|
211
|
+
loader.run("jsonPatch", doc, patch); // single
|
|
212
|
+
|
|
213
|
+
// verifyCookie / jwtVerify are boolean-validity ops (valid/invalid);
|
|
214
|
+
// use rust.verifyCookie / rust.jwtVerify for the decoded value.
|
|
215
|
+
|
|
216
|
+
// Bind a schema validator for repeated single/bulk validation + count:
|
|
217
|
+
const schema = loader.schema(rust.createSchemaValidator(schemaBytes));
|
|
218
|
+
schema(doc); // boolean
|
|
219
|
+
schema(docs); // Uint8Array bitset
|
|
220
|
+
schema.count(docs); // number of valid docs
|
|
221
|
+
|
|
222
|
+
// Fine-grained control: createLoader({ adaptive, batchMin, maxCacheKeys, ... })
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## The `proven` selection surface
|
|
228
|
+
|
|
229
|
+
`proven` is literally the same object as `rust` (`export const proven = rust`) —
|
|
230
|
+
nothing is filtered. Alongside it, the pure-data registry `PROVEN_SELECTION`
|
|
231
|
+
(`src/shared/proven.ts`) states which implementation is the benchmark-proven
|
|
232
|
+
winner for every op: `native` (the addon), `js` (pure TS), or `bun` (a Bun
|
|
233
|
+
built-in that is delegated under Bun). The winners are **baked** from
|
|
234
|
+
measurements (`src/selection.json` + `docs/bun-builtins-decision-matrix.md`),
|
|
235
|
+
and `test/unit/shared/proven.test.ts` verifies each winner is actually wired
|
|
236
|
+
(`opImpl` agrees, `builtins.has` matches, native/js entries match the addon's
|
|
237
|
+
embedded `selection.json`) — so the registry cannot silently drift. There is no
|
|
238
|
+
live benchmark gate (the old `check:proven` / `check:annotate` friction is gone).
|
|
239
|
+
|
|
240
|
+
- Statuses: `proven` (decisive win) / `parity` (borderline) / `unmeasured` (no
|
|
241
|
+
direct ratio — pinned by policy). `provenImpl(op)` returns the baked winner.
|
|
242
|
+
- The full surface is unchanged — `rust.jsonParse`, `rust.createSchemaValidator`,
|
|
243
|
+
etc. remain available for completeness; they just aren't advertised as wins.
|
|
244
|
+
- Classifications are based on the **release build on the shipped baseline CPU**
|
|
245
|
+
(published artifacts are baseline — not the local SIMD `build:perf`).
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { proven, PROVEN_SELECTION, provenImpl } from "castrum";
|
|
249
|
+
|
|
250
|
+
proven.fnv1a64(bytes); // native — Rust wins ~12x vs the JS baseline
|
|
251
|
+
provenImpl("crc32"); // "bun" — Bun.hash.crc32 wins under Bun
|
|
252
|
+
provenImpl("validateEmail"); // "js" — pure-TS wins
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
bun test test/unit/shared/proven.test.ts # proves every baked winner is wired
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Native-vs-JS selection surface
|
|
262
|
+
|
|
263
|
+
For framework consumers that want to bind each operation to a fixed
|
|
264
|
+
implementation at load time (instead of calling `rust.*` directly):
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { opImpl, isNativeOp, opDecision } from "castrum";
|
|
268
|
+
|
|
269
|
+
opImpl("gzipCompress"); // "native" | "js" | null
|
|
270
|
+
isNativeOp("crc32"); // boolean
|
|
271
|
+
opDecision("fnv1a64"); // { impl, note? } | null
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- Source of truth: `rust/selection.rs`, which embeds the benchmark-generated
|
|
275
|
+
`src/selection.json` (produced by `scripts/select-native.ts --write`, audited
|
|
276
|
+
by `--check` in CI).
|
|
277
|
+
- Under Bun, ops where the Bun built-in beats the Rust addon (gzip, crc32, xxh3,
|
|
278
|
+
HMAC, random tokens) are selected as `"js"` (a JS path that delegates to the
|
|
279
|
+
Bun built-in); under Node the base benchmark decision stands.
|
|
280
|
+
- Consumers read `opImpl(op)` **once at startup** and bind each op to a fixed
|
|
281
|
+
implementation — they do not swap native↔js per call.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Configuration
|
|
286
|
+
|
|
287
|
+
Defaults are selected automatically, and can be overridden:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
// Defaults:
|
|
291
|
+
// rayonThreads = max(1, hardwareConcurrency - 1)
|
|
292
|
+
// (env override: CASTRUM_RAYON_THREADS / RUST_RAYON_THREADS)
|
|
293
|
+
// mimeCache = true
|
|
294
|
+
// hmacCache = true
|
|
295
|
+
|
|
296
|
+
// Override per-instance options on the shared instance:
|
|
297
|
+
rust.configure({ mimeCache: false, hmacCache: false });
|
|
298
|
+
|
|
299
|
+
// Or create an isolated instance with custom defaults:
|
|
300
|
+
import { createRust } from "castrum";
|
|
301
|
+
const myRust = createRust({ mimeCache: false });
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
> **Thread pool note**: the rayon pool is process-wide and initialized **once**
|
|
305
|
+
> (native `OnceLock`) — the first initialization wins. Because importing the
|
|
306
|
+
> module initializes it with the default, set
|
|
307
|
+
> `CASTRUM_RAYON_THREADS` / `RUST_RAYON_THREADS` (or call
|
|
308
|
+
> `rust.configure({ rayonThreads })` / `createRust({ rayonThreads })` before any
|
|
309
|
+
> other pool use) to tune it. `mimeCache` / `hmacCache` are per-instance and can
|
|
310
|
+
> be toggled any time.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Shared utilities
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import {
|
|
318
|
+
encoder, decoder, // TextEncoder/TextDecoder singletons
|
|
319
|
+
packBatch, packPairs, // batch/pair packing
|
|
320
|
+
unpackBitset, unpackByteResults, unpackI64ArrayAsBigInt, unpackU32Array,
|
|
321
|
+
readPairsPacked, pairsToObject, readHttpPacked,
|
|
322
|
+
parseQueryString, parseCookieHeader, parseFormBody,
|
|
323
|
+
uuidv7, createMetrics,
|
|
324
|
+
} from "castrum";
|
|
325
|
+
|
|
326
|
+
parseQueryString("a=1&b=2&tag=a&tag=b"); // { a: "1", b: "2", tag: ["a", "b"] }
|
|
327
|
+
parseCookieHeader("session=abc; theme=dark"); // { session: "abc", theme: "dark" }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
> **Environment variables**: see [`ENVIRONMENT.md`](./ENVIRONMENT.md) for the
|
|
331
|
+
> full reference of `INGRESS_*`, `CASTRUM_*`, and `RUST_*` runtime variables.
|
|
332
|
+
|
|
333
|
+
## Ingress & integration
|
|
334
|
+
|
|
335
|
+
The HTTP pipeline (options, route factories, `BakedIngressResult`, server
|
|
336
|
+
builders, observability, and the framework integration helpers) has its own
|
|
337
|
+
dedicated reference: [`INGRESS.md`](./INGRESS.md).
|