castrum 0.9.0 → 0.9.2
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/README.md +153 -630
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1653 -1817
- package/dist/index.js +6172 -4294
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +111 -17
- package/docs/BENCHMARKS.md +117 -49
- package/docs/CASE_STUDY.md +243 -0
- package/docs/ENVIRONMENT.md +24 -7
- package/docs/FFI_BUN_GUIDE.md +335 -0
- package/docs/GETTING_STARTED.md +18 -34
- package/docs/HOW_TO_ADD_AN_OP.md +180 -0
- package/docs/INGRESS-ROUTER.md +82 -0
- package/docs/INGRESS.md +11 -7
- package/docs/NATIVE-ROUTE.md +77 -0
- package/docs/REPO_MAP.md +103 -53
- package/docs/adr/0001-two-wire-formats.md +1 -1
- package/docs/adr/0003-bun-builtins-delegation.md +6 -5
- package/docs/ai/LOCAL_DEV.md +81 -0
- package/docs/ai/TREE.md +1070 -0
- package/docs/bun-builtins-decision-matrix.md +25 -18
- package/index.ts +9 -16
- package/package.json +41 -28
- package/src/ingress/body.ts +167 -27
- package/src/ingress/decode/result-base.ts +10 -2
- package/src/ingress/handlers.ts +116 -285
- package/src/ingress/headers/security.ts +41 -0
- package/src/ingress/health.ts +1 -0
- package/src/ingress/index.ts +45 -169
- package/src/ingress/native-route.ts +182 -0
- package/src/ingress/packing/gather-raw-headers.ts +110 -22
- package/src/ingress/packing/header-packing.ts +5 -5
- package/src/ingress/packing/input-packer.ts +17 -3
- package/src/ingress/packing/route-wire.ts +197 -0
- package/src/ingress/packing/select-headers.ts +20 -7
- package/src/ingress/path-matcher.ts +115 -0
- package/src/ingress/response/baked-response.ts +302 -0
- package/src/ingress/router.ts +277 -0
- package/src/ingress/routes/common.ts +50 -1
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +3 -13
- package/src/ingress/routes/index.ts +5 -4
- package/src/ingress/routes/json-write.ts +26 -47
- package/src/ingress/routes/native.ts +130 -0
- package/src/ingress/routes/options.ts +2 -12
- package/src/ingress/routes/read.ts +3 -13
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +7 -7
- package/src/ingress/server.ts +83 -112
- package/src/ingress/sync.ts +151 -0
- package/src/ingress/types.ts +44 -1
- package/src/integration/pipeline.ts +3 -0
- package/src/integration/streaming.ts +2 -3
- package/src/integration/websocket.ts +4 -3
- package/src/loader/batch.ts +4 -0
- package/src/loader/cost.ts +1 -0
- package/src/loader/index.ts +43 -228
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/build/codecs.ts +736 -0
- package/src/native/ffi/build/compress.ts +248 -0
- package/src/native/ffi/build/instances.ts +598 -0
- package/src/native/ffi/build/parse.ts +539 -0
- package/src/native/ffi/build/util.ts +140 -0
- package/src/native/ffi/build.ts +94 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +22 -0
- package/src/native/ffi/types.ts +499 -0
- package/src/native/ffi.ts +307 -1357
- package/src/native/loader.ts +52 -6
- package/src/native/types/instances.ts +279 -0
- package/src/native/types.ts +41 -238
- 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 +1 -1
- 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 +5 -6
- package/src/rust-ffi/context.ts +39 -10
- package/src/rust-ffi/index.ts +28 -17
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +1 -1
- package/src/rust-ffi/packed.ts +40 -2
- package/src/rust-ffi/proven.ts +16 -30
- package/src/rust-ffi/scalar/crypto.ts +188 -107
- package/src/rust-ffi/scalar/factories.ts +257 -27
- package/src/rust-ffi/scalar/hashing.ts +43 -41
- package/src/rust-ffi/scalar/http.ts +208 -77
- package/src/rust-ffi/scalar/index.ts +3 -3
- package/src/rust-ffi/scalar/interface.ts +88 -213
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +28 -12
- package/src/rust-ffi/scalar/payload.ts +159 -42
- package/src/rust-ffi/text.ts +31 -5
- package/src/selection.ts +27 -31
- package/src/shared/adaptive.ts +1 -2
- package/src/shared/buffer-pool.ts +72 -17
- package/src/shared/bytes.ts +75 -6
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +4 -4
- package/src/shared/log.ts +1 -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.ts → packed/wire.ts} +31 -39
- package/src/shared/proven.ts +189 -496
- package/src/shared/runtime.ts +20 -25
- package/src/shared/uuid.ts +12 -28
- package/src/data/json-rows.ts +0 -33
- package/src/rust-ffi/batch.ts +0 -431
- package/src/shared/bench-classify.ts +0 -209
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).
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -31,14 +31,22 @@ The bridge between TypeScript and Rust has **two transports**, both loading the
|
|
|
31
31
|
cdylib (built with napi-rs):
|
|
32
32
|
|
|
33
33
|
- **`bun:ffi` (PRIMARY on Bun)** — `src/native/ffi.ts` `dlopen`s the addon's
|
|
34
|
-
`extern "C"` exports (`rust/ffi
|
|
34
|
+
`extern "C"` exports (`rust/ffi/`, 85 `castrum_*` symbols — 77 direct + 4
|
|
35
|
+
`validator_c_abi!` + 4 `compress_to_out!`, parity guarded by
|
|
36
|
+
`test/unit/native/ffi-symbol-parity.test.ts`) and Bun JIT-calls them
|
|
35
37
|
directly (~10-20ns crossing vs ~100-350ns N-API). A bind-time self-test guards
|
|
36
38
|
correctness: on ANY failure the layer is disabled and calls fall back to napi.
|
|
37
39
|
`CASTRUM_FFI_MODE=auto|ffi|napi` selects the transport (default `auto`).
|
|
38
40
|
- **NAPI (napi-rs v3) — the fallback** — used under Node.js, when `bun:ffi` is
|
|
39
41
|
unavailable (self-test failure, `CASTRUM_FFI_MODE=napi`), and for surfaces with no
|
|
40
|
-
C-ABI export
|
|
41
|
-
|
|
42
|
+
C-ABI export. Since Phase 6 the compiled-once instances are NOT napi-only — their
|
|
43
|
+
hot per-call ops route through the C-ABI via an opaque-`usize` handle
|
|
44
|
+
(`conditional_is_not_modified`, `media_type_matcher_matches`,
|
|
45
|
+
`accept_negotiator_negotiate`, `jwt_signer_sign`/`_verify`, `template_render`,
|
|
46
|
+
`schema_validator_validate`, `rate_limiter_check*`, `url_builder_resolve`), and
|
|
47
|
+
`jwtVerify` has `castrum_jwt_verify`. Still napi-only: `rust.batch.*` /
|
|
48
|
+
`rust.packed.*`, `text.*`, and the DOM `jsonParse` (only `castrum_json_parse_packed`
|
|
49
|
+
exists).
|
|
42
50
|
|
|
43
51
|
Shared characteristics:
|
|
44
52
|
|
|
@@ -46,10 +54,25 @@ Shared characteristics:
|
|
|
46
54
|
- **Synchronous calls**: All functions are synchronous, running on the main JS thread
|
|
47
55
|
- **Thread pool**: Rayon is available for parallel CPU-bound work
|
|
48
56
|
|
|
57
|
+
### Dual-binary CPU-detect (x86-64-v3 SIMD variant)
|
|
58
|
+
|
|
59
|
+
On `linux-x64` the package ships a second artifact, `castrum.linux-x64-v3-gnu.node`,
|
|
60
|
+
built with `-C target-cpu=x86-64-v3 -C target-feature=+avx2,+bmi2,+fma,+sse4.2`
|
|
61
|
+
(`scripts/build-v3.sh`, package script `build:v3`; `build:all` builds both). The
|
|
62
|
+
loader (`src/native/loader.ts`) picks the v3 variant at runtime when the host
|
|
63
|
+
CPU exposes AVX2+BMI2+FMA+SSE4.2 (read from `/proc/cpuinfo`), falling back to the
|
|
64
|
+
baseline artifact otherwise — a v3 binary dlopened on an unsupported CPU would
|
|
65
|
+
SIGILL (not catchable from JS), so detection is conservative and requires ALL
|
|
66
|
+
four flags. The v3 variant is OPTIONAL (an absent v3 file resolves to baseline),
|
|
67
|
+
so `build:v3` is never a hard publish requirement.
|
|
68
|
+
|
|
49
69
|
---
|
|
50
70
|
|
|
51
71
|
## Module Architecture
|
|
52
72
|
|
|
73
|
+
> **Canonical tree**: [`REPO_MAP.md`](./REPO_MAP.md) is the single-source module map
|
|
74
|
+
> (src/ + rust/ trees, kept in sync). The trees below are the deep-dive annotations.
|
|
75
|
+
|
|
53
76
|
### Rust Modules
|
|
54
77
|
|
|
55
78
|
One cdylib crate (`Cargo [lib] → rust/lib.rs`) decomposed into domain folders.
|
|
@@ -60,7 +83,8 @@ shared-infra names so legacy `crate::util::*` call sites keep working.
|
|
|
60
83
|
rust/
|
|
61
84
|
├── lib.rs ── Crate root: folder declarations + module map, global allocator
|
|
62
85
|
├── test_support.rs ── shared #[cfg(test)] helpers (pack_headers, decode_packed_pairs, Rng)
|
|
63
|
-
├──
|
|
86
|
+
├── panic_safety.rs ── #[cfg(test)] cross-parser fuzz tests (malformed input never panics)
|
|
87
|
+
├── proptest_suite.rs ── #[cfg(test)] property-based adversarial-parser tests
|
|
64
88
|
│
|
|
65
89
|
├── util/ ── Shared infrastructure (mod.rs re-exports crate::util::*)
|
|
66
90
|
│ ├── bytes.rs ── byte primitives (word-compare, hex, %XX decode, cookie_pairs)
|
|
@@ -97,9 +121,16 @@ rust/
|
|
|
97
121
|
│ └── random_token.rs ── random hex tokens
|
|
98
122
|
│
|
|
99
123
|
├── json/ ── JSON & schema
|
|
100
|
-
│ ├── json_ops.rs ── zero-DOM validate/sum + DOM parse
|
|
124
|
+
│ ├── json_ops.rs ── zero-DOM validate/sum + DOM parse (sonic Value → JS)
|
|
125
|
+
│ ├── napi_marshal.rs ── sonic_rs::Value → napi walker (sonic_value_to_js, napi
|
|
126
|
+
│ │ serde-json Number parity) + sonic_values_equal
|
|
127
|
+
│ │ (jsonschema cmp::equal; shared by jwt_verify, json_parse,
|
|
128
|
+
│ │ fast_schema enum/const/uniqueItems)
|
|
101
129
|
│ ├── json_ser.rs ── zero-alloc JSON escaping + cookie/query → JSON writers
|
|
102
|
-
│ ├──
|
|
130
|
+
│ ├── patch/ ── CUSTOM sonic RFC 6902 engine (pointer.rs + ops.rs + engine.rs +
|
|
131
|
+
│ │ api.rs + tests.rs; Pointer ~0/~1 unescape + add/remove/replace/
|
|
132
|
+
│ │ move/copy/test incl. array `-`/bounds + 1==1.0; json-patch/jsonptr
|
|
133
|
+
│ │ deps REMOVED)
|
|
103
134
|
│ ├── json_schema.rs ── SchemaValidator napi class (fast + jsonschema fallback)
|
|
104
135
|
│ └── fast_schema/ ── zero-DOM draft-07 JSON Schema fast path
|
|
105
136
|
│ └── mod.rs + types.rs / cursor.rs / compile.rs / validate.rs / errors.rs / email.rs (format:"email" replica) / tests.rs
|
|
@@ -137,7 +168,7 @@ src/
|
|
|
137
168
|
│ ├── fast.ts ── Thin: createIngressFast (packed input) + re-exports
|
|
138
169
|
│ ├── handlers.ts ── Thin: createIngressHandler (packs frame in JS → handleRequestPacked) + response builders
|
|
139
170
|
│ ├── server.ts ── createIngressServer (Bun.serve builder) + buildRouteHandlers + gracefulShutdown
|
|
140
|
-
│ ├── server-node.ts ── createIngressServerNode (node:http adapter, same route handlers)
|
|
171
|
+
│ ├── server-node.ts ── createIngressServerNode (node:http adapter, same route handlers; the runtime adapter picks the backend)
|
|
141
172
|
│ ├── constants.ts ── Layout constants (from Rust NAPI)
|
|
142
173
|
│ ├── shared.ts ── JS constants shared by both paths
|
|
143
174
|
│ ├── status.ts ── Status normalization helpers (both paths)
|
|
@@ -153,14 +184,15 @@ src/
|
|
|
153
184
|
│ └── routes/ ── read/head/json-write/echo/fallback factories
|
|
154
185
|
│
|
|
155
186
|
├── shared/
|
|
156
|
-
│ ├── runtime.ts ── isBun/isNode
|
|
187
|
+
│ ├── runtime.ts ── facade over src/runtime/detect.ts (isBun/isNode — cached)
|
|
188
|
+
│ ├── codec.ts ── facade over src/runtime/codec.ts (Bun-native UTF-8 codec)
|
|
189
|
+
│ ├── uuid.ts ── facade over src/runtime/uuid.ts
|
|
157
190
|
│ ├── log.ts ── createStructuredLogger (CASTRUM_LOG_LEVEL-gated JSON lines)
|
|
158
191
|
│ ├── request-id.ts ── zero-alloc request ID generator
|
|
159
192
|
│ ├── buffer-pool.ts ── reusable output-buffer pool
|
|
160
193
|
│ ├── response.ts ── pooledBodyResponse (zero-copy response)
|
|
161
|
-
│ ├── bytes.ts ──
|
|
162
|
-
│
|
|
163
|
-
│ └── proven.ts ── PROVEN_SURFACE registry (single source for `proven`)
|
|
194
|
+
│ ├── bytes.ts ── codec-backed encoder/decoder + toBytes/toText normalizers
|
|
195
|
+
│ └── packed.ts ── packed-wire unpackers + packers
|
|
164
196
|
│
|
|
165
197
|
├── native/ ── Native transport: bun:ffi (primary on Bun) + NAPI fallback
|
|
166
198
|
│ ├── types.ts ── NativeAddon interface + instance types
|
|
@@ -169,6 +201,18 @@ src/
|
|
|
169
201
|
│ │ CASTRUM_FFI_MODE gating; castrum_ingress_layout blob)
|
|
170
202
|
│ └── index.ts ── Barrel
|
|
171
203
|
│
|
|
204
|
+
├── runtime/ ── RUNTIME ADAPTER seam (Bun/Node), selected once at load
|
|
205
|
+
│ ├── detect.ts ── cached runtime detection (the ONLY place `typeof Bun` is checked)
|
|
206
|
+
│ ├── types.ts ── RuntimeAdapter contract (codec/uuid/env/builtins/transport/server/websocket)
|
|
207
|
+
│ ├── codec.ts ── runtime-native UTF-8 codec (Bun transfer vs TextEncoder/Decoder)
|
|
208
|
+
│ ├── uuid.ts ── uuidv7 (Bun.randomUUIDv7 vs crypto.randomUUID)
|
|
209
|
+
│ ├── builtins.ts ── BUN_WINS delegation registry (single source, `has(op)`)
|
|
210
|
+
│ ├── transport.ts ── bun:ffi-first `resolve(op)` + ffi/napi surfaces (lazy bind)
|
|
211
|
+
│ ├── native.ts ── FFI-facing seam (codec/uuid/env/builtins/transport) — imported by rust-ffi
|
|
212
|
+
│ ├── server.ts ── createIngressServer: Bun.serve on Bun, node:http on Node
|
|
213
|
+
│ ├── websocket.ts ── 101 upgrade on Bun; clear Bun-only error on Node
|
|
214
|
+
│ └── index.ts ── FULL adapter `runtime` (native + server + websocket) — for the public API
|
|
215
|
+
│
|
|
172
216
|
├── rust-ffi/ ── Rust FFI bindings
|
|
173
217
|
│ ├── options.ts ── RustOptions + input-normalization helpers
|
|
174
218
|
│ ├── addon.ts ── shared lazy addon proxy
|
|
@@ -223,7 +267,7 @@ src/
|
|
|
223
267
|
> (`handle_request_packed`). Different wire format: success
|
|
224
268
|
> `{"ok":true,...,"requestId":...}`, errors
|
|
225
269
|
> `{"ok":false,"error":{code,message}}`, `ratelimit-*` headers. The
|
|
226
|
-
> benchmark server (`bench/servers/ingress-server.ts`) uses this path and
|
|
270
|
+
> benchmark server (`bench/http/servers/ingress-server.ts`) uses this path and
|
|
227
271
|
> re-exports the pre-baked functions.
|
|
228
272
|
|
|
229
273
|
## Node.js Compatibility
|
|
@@ -240,10 +284,12 @@ package ships a compiled ESM bundle built by `bun run build:js`:
|
|
|
240
284
|
from multiple candidate roots so the same loader works from `src/native/…`
|
|
241
285
|
(Bun, source layout) and `dist/…` (Node, bundled layout), plus the
|
|
242
286
|
`CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH` override.
|
|
243
|
-
- `src/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
287
|
+
- `src/runtime/detect.ts` is the single runtime-detection seam (the ONLY place
|
|
288
|
+
`typeof Bun` is checked); `src/shared/runtime.ts` is a thin facade over it.
|
|
289
|
+
- The public `createIngressServer` is runtime-adaptive: it delegates to the
|
|
290
|
+
Bun.serve builder (`src/ingress/server.ts`) on Bun and to `createIngressServerNode`
|
|
291
|
+
(`src/ingress/server-node.ts`, same route handlers over `node:http`) on Node,
|
|
292
|
+
via `src/runtime/server.ts`. The `BakedServer.server` type is a
|
|
247
293
|
runtime-agnostic `ServerHandle` so Node TS consumers don't need `@types/bun`.
|
|
248
294
|
- The benchmark tooling (`bench/`, `bun:test` suites) remains Bun-only by design.
|
|
249
295
|
|
|
@@ -385,6 +431,54 @@ Offset (bytes)
|
|
|
385
431
|
|
|
386
432
|
---
|
|
387
433
|
|
|
434
|
+
## JSON & zero-DOM architecture
|
|
435
|
+
|
|
436
|
+
- **`sonic_rs::Value` is the primary DOM** on every crate boundary that allows
|
|
437
|
+
it (compact 16-byte tagged nodes, borrowed/inline strings, zero per-key heap
|
|
438
|
+
`String`). `serde_json::Value` REMAINS only where a crate is hardwired to it
|
|
439
|
+
and cannot be swapped: the jsonschema 0.48 DOM fallback
|
|
440
|
+
(`json_schema.rs` + `pipeline.rs::IngressSchema`), `selection.rs`, and the
|
|
441
|
+
napi JS-object input of `jwt_sign` / `JwtSigner::sign`.
|
|
442
|
+
- **`rust/json/napi_marshal.rs`** is the single sonic→JS bridge:
|
|
443
|
+
`sonic_value_to_js` walks a `sonic_rs::Value` into a napi value with
|
|
444
|
+
**byte-parity** to napi's `serde-json` `ToNapiValue for serde_json::Number`
|
|
445
|
+
(PosInt u64 ≤ u32::MAX → Number, larger → BigInt; NegInt within ±2^53-1 →
|
|
446
|
+
Number, outside → BigInt; Float → Number). `sonic_values_equal` replicates
|
|
447
|
+
jsonschema `cmp::equal` (exact cross-type numeric equality + insertion-order
|
|
448
|
+
object keys) for fast_schema enum/const/uniqueItems and is unit-pinned against
|
|
449
|
+
a serde_json reference over a value corpus.
|
|
450
|
+
- **Zero-DOM hot paths** (no `serde_json::Value` on the per-request path):
|
|
451
|
+
- `castrum_json_parse_packed` (FFI) parses ONCE with sonic-rs and emits a
|
|
452
|
+
typed token stream (deduped string table) that the JS decoder assembles
|
|
453
|
+
with NO second JSON text parse.
|
|
454
|
+
- JWT verify (`verify_token_with_key`) decodes the payload ONCE, checks
|
|
455
|
+
`exp`/`nbf`/`iat` over `sonic_rs::Value`, and returns the payload BYTES
|
|
456
|
+
(no re-serialize); `jwt_verify` / `JwtSigner::verify` marshal via the
|
|
457
|
+
walker. JWT sign (`jwtSignBytes`, `JwtSigner::signBytes`,
|
|
458
|
+
`castrum_jwt_sign_bytes*`) injects `iat`/`exp` into a sonic object
|
|
459
|
+
(`inject_and_payload_b64_sonic`) and serializes with `sonic_rs::to_vec`.
|
|
460
|
+
- fast_schema enum/const/uniqueItems re-parse the doc into `sonic_rs::Value`
|
|
461
|
+
and compare with `sonic_values_equal` (no `Vec<serde_json::Value>`).
|
|
462
|
+
- minijinja contexts are `sonic_rs::Value` (implements `Serialize`).
|
|
463
|
+
- **Custom RFC 6902 patch engine** (`rust/json/patch/`): no longer round-trips
|
|
464
|
+
through the serde_json-DOM `json-patch` crate. A `Pointer` type (`~0`/`~1`
|
|
465
|
+
unescape, RFC 6901) + `PatchOp` parser + applicator (add incl. array `-` and
|
|
466
|
+
bounds, remove, replace, move, copy, test incl. `1 == 1.0`) runs directly on
|
|
467
|
+
`sonic_rs::Value`, keeping `JsonPatchError` semantics, the input/output size
|
|
468
|
+
guards, and the packed batch entry points. `json-patch` and `jsonptr` deps
|
|
469
|
+
are removed.
|
|
470
|
+
- **Serialization determinism (`sonic sort_keys`)**: sonic's owned `Value`
|
|
471
|
+
backs MUTABLE objects with `AHashMap` (non-deterministic iteration order), so
|
|
472
|
+
the crate enables `sort_keys` (`Cargo.toml`), swapping the owned map to
|
|
473
|
+
`BTreeMap` → deterministic sorted serialization. This is a hard wire-format
|
|
474
|
+
requirement: JWT sign and the patch engine must emit byte-identical output
|
|
475
|
+
across the FFI/napi/scalar/bulk transports. Parsed (unmutated) values keep
|
|
476
|
+
the shared Vec (insertion order) — only `as_object_mut()` + `insert` switches
|
|
477
|
+
to the map — so `sonic_values_equal` / `sonic_value_to_js` (which iterate the
|
|
478
|
+
shared representation) keep insertion-order semantics.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
388
482
|
## Concurrency Model
|
|
389
483
|
|
|
390
484
|
- **Synchronous calls**: Run on the main JS thread (Bun's event loop)
|
|
@@ -405,7 +499,7 @@ Offset (bytes)
|
|
|
405
499
|
└─── [cookie_parser]
|
|
406
500
|
[hashing] ──── [hmac_sha256]
|
|
407
501
|
[validation]
|
|
408
|
-
[json_ops] ──── [
|
|
502
|
+
[json_ops] ──── [json/patch]
|
|
409
503
|
[json_schema]
|
|
410
504
|
[url_codec]
|
|
411
505
|
[mime_lookup]
|