castrum 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -59
  3. package/castrum.linux-x64-gnu.node +0 -0
  4. package/dist/index.d.ts +3161 -0
  5. package/dist/index.js +7306 -0
  6. package/docs/ARCHITECTURE.md +415 -0
  7. package/docs/BENCHMARKS.md +307 -0
  8. package/docs/ENVIRONMENT.md +79 -0
  9. package/docs/GETTING_STARTED.md +209 -0
  10. package/docs/INGRESS.md +745 -0
  11. package/docs/REPO_MAP.md +259 -0
  12. package/docs/adr/0001-two-wire-formats.md +39 -0
  13. package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
  14. package/docs/adr/0003-bun-builtins-delegation.md +42 -0
  15. package/docs/bun-builtins-decision-matrix.md +108 -0
  16. package/index.ts +51 -8
  17. package/package.json +73 -20
  18. package/src/data/json-rows.ts +13 -13
  19. package/src/ingress/body.ts +149 -0
  20. package/src/ingress/constants.ts +220 -48
  21. package/src/ingress/context.ts +136 -0
  22. package/src/ingress/decode/baked-result.ts +121 -0
  23. package/src/ingress/decode/fast-result.ts +160 -0
  24. package/src/ingress/decode/packed-sections.ts +56 -0
  25. package/src/ingress/decode/result-base.ts +172 -0
  26. package/src/ingress/errors.ts +72 -0
  27. package/src/ingress/fast.ts +152 -1021
  28. package/src/ingress/handlers.ts +563 -1001
  29. package/src/ingress/headers/baked-templates.ts +102 -0
  30. package/src/ingress/headers/cors.ts +44 -0
  31. package/src/ingress/headers/fast-templates.ts +238 -0
  32. package/src/ingress/headers/hsts.ts +83 -0
  33. package/src/ingress/health.ts +67 -0
  34. package/src/ingress/index.ts +130 -405
  35. package/src/ingress/metrics.ts +132 -0
  36. package/src/ingress/options.ts +197 -0
  37. package/src/ingress/packing/gather-raw-headers.ts +75 -0
  38. package/src/ingress/packing/header-packing.ts +75 -0
  39. package/src/ingress/packing/input-packer.ts +150 -0
  40. package/src/ingress/packing/scratch.ts +50 -0
  41. package/src/ingress/packing/select-headers.ts +96 -0
  42. package/src/ingress/response/error-bodies.ts +75 -0
  43. package/src/ingress/response/terminal.ts +59 -0
  44. package/src/ingress/routes/common.ts +38 -0
  45. package/src/ingress/routes/delete.ts +14 -0
  46. package/src/ingress/routes/echo.ts +107 -0
  47. package/src/ingress/routes/fallback.ts +23 -0
  48. package/src/ingress/routes/head.ts +37 -0
  49. package/src/ingress/routes/index.ts +10 -0
  50. package/src/ingress/routes/json-write.ts +153 -0
  51. package/src/ingress/routes/options.ts +36 -0
  52. package/src/ingress/routes/read.ts +45 -0
  53. package/src/ingress/server-node.ts +376 -0
  54. package/src/ingress/server.ts +443 -0
  55. package/src/ingress/shared.ts +132 -0
  56. package/src/ingress/status.ts +97 -0
  57. package/src/ingress/types.ts +157 -0
  58. package/src/integration/batch.ts +58 -0
  59. package/src/integration/index.ts +29 -0
  60. package/src/integration/pipeline.ts +217 -0
  61. package/src/integration/streaming.ts +79 -0
  62. package/src/integration/websocket.ts +92 -0
  63. package/src/loader/batch.ts +133 -0
  64. package/src/loader/cost.ts +104 -0
  65. package/src/loader/index.ts +930 -0
  66. package/src/loader/ops.ts +657 -0
  67. package/src/native/ffi.ts +1541 -0
  68. package/src/native/index.ts +8 -207
  69. package/src/native/loader.ts +194 -0
  70. package/src/native/types.ts +598 -0
  71. package/src/rust-ffi/addon.ts +10 -0
  72. package/src/rust-ffi/batch.ts +431 -0
  73. package/src/rust-ffi/client.ts +88 -0
  74. package/src/rust-ffi/context.ts +164 -0
  75. package/src/rust-ffi/index.ts +40 -541
  76. package/src/rust-ffi/options.ts +113 -0
  77. package/src/rust-ffi/packed.ts +253 -0
  78. package/src/rust-ffi/proven.ts +42 -0
  79. package/src/rust-ffi/scalar/crypto.ts +265 -0
  80. package/src/rust-ffi/scalar/factories.ts +233 -0
  81. package/src/rust-ffi/scalar/hashing.ts +72 -0
  82. package/src/rust-ffi/scalar/http.ts +206 -0
  83. package/src/rust-ffi/scalar/index.ts +31 -0
  84. package/src/rust-ffi/scalar/interface.ts +450 -0
  85. package/src/rust-ffi/scalar/json.ts +45 -0
  86. package/src/rust-ffi/scalar/payload.ts +124 -0
  87. package/src/rust-ffi/text.ts +95 -0
  88. package/src/selection.json +194 -0
  89. package/src/selection.ts +77 -0
  90. package/src/shared/adaptive.ts +68 -0
  91. package/src/shared/bench-classify.ts +209 -0
  92. package/src/shared/buffer-pool.ts +209 -0
  93. package/src/shared/bytes.ts +35 -5
  94. package/src/shared/env.ts +20 -0
  95. package/src/shared/log.ts +74 -0
  96. package/src/shared/metrics.ts +247 -0
  97. package/src/shared/packed.ts +350 -154
  98. package/src/shared/proven.ts +596 -0
  99. package/src/shared/request-id.ts +67 -0
  100. package/src/shared/response.ts +104 -0
  101. package/src/shared/runtime.ts +34 -0
  102. package/src/shared/trace.ts +81 -0
  103. package/src/shared/uuid.ts +31 -0
  104. package/Cargo.toml +0 -73
  105. package/bench.ts +0 -3
  106. package/rust/batch.rs +0 -195
  107. package/rust/cookie_parser.rs +0 -70
  108. package/rust/cors.rs +0 -263
  109. package/rust/hashing.rs +0 -164
  110. package/rust/headers.rs +0 -234
  111. package/rust/hmac_sha256.rs +0 -113
  112. package/rust/http_parser.rs +0 -132
  113. package/rust/ingress.rs +0 -616
  114. package/rust/ingress_constants.rs +0 -89
  115. package/rust/ip_trust.rs +0 -185
  116. package/rust/json_ops.rs +0 -66
  117. package/rust/json_patch_ops.rs +0 -25
  118. package/rust/json_schema.rs +0 -151
  119. package/rust/json_ser.rs +0 -429
  120. package/rust/lib.rs +0 -48
  121. package/rust/method.rs +0 -141
  122. package/rust/mime_lookup.rs +0 -76
  123. package/rust/output.rs +0 -238
  124. package/rust/proxy.rs +0 -90
  125. package/rust/query_parser.rs +0 -141
  126. package/rust/random_token.rs +0 -52
  127. package/rust/rate_limit.rs +0 -203
  128. package/rust/terminal.rs +0 -95
  129. package/rust/unit_tests.rs +0 -843
  130. package/rust/url_codec.rs +0 -133
  131. package/rust/util.rs +0 -537
  132. package/rust/validation.rs +0 -107
  133. package/rust/websocket.rs +0 -23
  134. package/src/baseline/index.ts +0 -12
  135. package/src/baseline/tasks/cookie.ts +0 -22
  136. package/src/baseline/tasks/hashing.ts +0 -18
  137. package/src/baseline/tasks/hmac.ts +0 -38
  138. package/src/baseline/tasks/http.ts +0 -62
  139. package/src/baseline/tasks/json-patch.ts +0 -12
  140. package/src/baseline/tasks/json.ts +0 -50
  141. package/src/baseline/tasks/mime.ts +0 -5
  142. package/src/baseline/tasks/query.ts +0 -14
  143. package/src/baseline/tasks/token.ts +0 -15
  144. package/src/baseline/tasks/url.ts +0 -11
  145. package/src/baseline/tasks/validation.ts +0 -19
  146. package/src/baseline/tasks/websocket.ts +0 -14
  147. package/src/bench/assert.ts +0 -70
  148. package/src/bench/checks.ts +0 -227
  149. package/src/bench/checksum.ts +0 -27
  150. package/src/bench/comparisons.ts +0 -70
  151. package/src/bench/concurrent-worker.ts +0 -135
  152. package/src/bench/fixtures.ts +0 -233
  153. package/src/bench/index.ts +0 -2
  154. package/src/bench/measure.ts +0 -177
  155. package/src/bench/now.ts +0 -3
  156. package/src/bench/report.ts +0 -79
  157. package/src/bench/run.ts +0 -57
  158. package/src/bench/tasks/complex.ts +0 -232
  159. package/src/bench/tasks/concurrent.ts +0 -213
  160. package/src/bench/tasks/cookie.ts +0 -44
  161. package/src/bench/tasks/hashing.ts +0 -33
  162. package/src/bench/tasks/hmac.ts +0 -36
  163. package/src/bench/tasks/http.ts +0 -50
  164. package/src/bench/tasks/index.ts +0 -52
  165. package/src/bench/tasks/json-patch.ts +0 -21
  166. package/src/bench/tasks/json.ts +0 -33
  167. package/src/bench/tasks/mime.ts +0 -21
  168. package/src/bench/tasks/query.ts +0 -44
  169. package/src/bench/tasks/stress.ts +0 -142
  170. package/src/bench/tasks/token.ts +0 -20
  171. package/src/bench/tasks/url.ts +0 -34
  172. package/src/bench/tasks/validation.ts +0 -57
  173. package/src/bench/tasks/websocket.ts +0 -21
  174. package/src/bench/types.ts +0 -60
  175. package/src/ingress/packed-input.ts +0 -59
  176. package/src/shared/json.ts +0 -18
@@ -0,0 +1,450 @@
1
+ // src/rust-ffi/scalar/interface.ts — The `RustScalar` method surface.
2
+ //
3
+ // One file per public method (with its perf JSDoc, written by
4
+ // `scripts/annotate-performance.ts`). The implementation is split into domain
5
+ // builders (hashing, json, http, crypto, payload, factories) composed by
6
+ // `buildScalar` in index.ts.
7
+
8
+ import type {
9
+ HmacSignerInstance,
10
+ SchemaValidatorInstance,
11
+ TemplateRendererInstance,
12
+ FormParserInstance,
13
+ MediaTypeParserInstance,
14
+ MediaTypeResult,
15
+ MediaTypeMatcherInstance,
16
+ ConditionalRequestInstance,
17
+ EncodingPrefResult,
18
+ AcceptNegotiatorInstance,
19
+ Base64CodecInstance,
20
+ CookieSignerInstance,
21
+ CsrfProtectorInstance,
22
+ UrlBuilderInstance,
23
+ JwtSignerInstance,
24
+ AeadCipherInstance,
25
+ Argon2HasherInstance,
26
+ RateLimiterInstance,
27
+ MultipartPart,
28
+ WsFrame,
29
+ PasswordHashOptions,
30
+ } from '../../native'
31
+
32
+ /** Scalar + factory + runtime methods of the Rust client. */
33
+ export interface RustScalar {
34
+ // ── Scalar utilities (bytes in → normalized out) ──
35
+ /**
36
+ * @performance CRC32: ~3.7x slower than the JS baseline (2/3 tasks failed) [check:annotate]
37
+ * @deprecated Slower than the native JS baseline (~3.7x) — prefer the JS/Bun baseline. [check:annotate]
38
+ * @remarks Sub-µs FFI-crossing bound vs the same-engine JS crc-32 loop (~0.23-0.44x across runs); use the packed crc32BatchPacked path which wins on larger inputs. [check:annotate]
39
+ */
40
+ crc32(input: Uint8Array): number
41
+ /**
42
+ * @performance FNV-1a 64: ~6.6x faster than the JS baseline [check:annotate]
43
+ */
44
+ fnv1a64(input: Uint8Array): bigint
45
+ /**
46
+ * XXH3-64 over raw bytes (high-throughput non-cryptographic hash).
47
+ * Compare against `Bun.hash.xxHash3` — see docs/bun-builtins-decision-matrix.md.
48
+ */
49
+ xxh3(input: Uint8Array): bigint
50
+ /**
51
+ * @performance HMAC sign: ~1.1x faster than the JS baseline (2/3 tasks won) [check:annotate]
52
+ */
53
+ hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array
54
+ /**
55
+ * @performance HMAC verify: ~2.5x faster than the JS baseline [check:annotate]
56
+ */
57
+ hmacSha256Verify(key: Uint8Array, data: Uint8Array, sig: Uint8Array): boolean
58
+ /**
59
+ * @performance JSON valid: ~3.2x faster than the JS baseline (8/8 tasks won) [check:annotate]
60
+ */
61
+ jsonValid(input: Uint8Array): boolean
62
+ /**
63
+ * Parse JSON to a JS value (native sonic-rs DOM → napi). Throws on invalid JSON.
64
+ * @performance JSON parse: ~5.0x slower than the JS baseline [check:annotate]
65
+ * @deprecated Slower than the native JS baseline (~5.0x) — prefer the JS/Bun baseline. [check:annotate]
66
+ * @remarks Bun's JSON.parse beats the native DOM + napi marshaling path (~5x). [check:annotate]
67
+ */
68
+ jsonParse(input: Uint8Array): unknown
69
+ /**
70
+ * @performance JSON sum: ~2.9x faster than the JS baseline (4/4 tasks won) [check:annotate]
71
+ */
72
+ jsonSumIds(input: Uint8Array): bigint
73
+ /**
74
+ * @performance JSON Patch: ~1.2x faster than the JS baseline [check:annotate]
75
+ * @remarks Modest but real win; swings ~0.9-1.5x run-to-run (DOM marshal dominated). [check:annotate]
76
+ */
77
+ jsonPatch(doc: Uint8Array, patch: Uint8Array): Uint8Array
78
+ /**
79
+ * @performance MIME lookup: ≈ parity with the JS baseline [check:annotate]
80
+ * @remarks Noisy sub-µs op: wins ~1.6x on good runs, loses on bad runs; phf table lookup. [check:annotate]
81
+ */
82
+ mimeFromExtension(ext: Uint8Array): Uint8Array
83
+ /**
84
+ * @performance Random token: ≈ parity with the JS baseline [check:annotate]
85
+ */
86
+ randomToken(byteLen: number): Uint8Array
87
+ /**
88
+ * @performance URL encode: ~2.2x slower than the JS baseline (2/3 tasks failed) [check:annotate]
89
+ * @deprecated Slower than the native JS baseline (~2.2x) — prefer the JS/Bun baseline. [check:annotate]
90
+ * @remarks Loses ~2.8x on the shipped baseline release build (only wins in the LOCAL SIMD perf build). [check:annotate]
91
+ */
92
+ urlEncode(input: Uint8Array): Uint8Array
93
+ /**
94
+ * @performance URL decode: ~2.2x slower than the JS baseline (2/3 tasks failed) [check:annotate]
95
+ * @deprecated Slower than the native JS baseline (~2.2x) — prefer the JS/Bun baseline. [check:annotate]
96
+ * @remarks Loses ~2.8x on the shipped baseline release build (only wins in the LOCAL SIMD perf build). [check:annotate]
97
+ */
98
+ urlDecode(input: Uint8Array): Uint8Array
99
+ /** Strict percent-decode without UTF-8 validation. */
100
+ urlDecodeBytes(input: Uint8Array): Uint8Array
101
+ /** Reusable-output percent-encode; returns bytes written. */
102
+ urlEncodeInto(input: Uint8Array, output: Uint8Array): number
103
+ /** Reusable-output percent-decode; returns bytes written. */
104
+ urlDecodeInto(input: Uint8Array, output: Uint8Array): number
105
+ /**
106
+ * @performance Email validation: ~3.1x faster than the JS baseline (4/4 tasks won) [check:annotate]
107
+ */
108
+ validateEmail(input: Uint8Array): boolean
109
+ /**
110
+ * @performance UUID validation: ≈ parity with the JS baseline (2/4 tasks won) [check:annotate]
111
+ */
112
+ validateUuid(input: Uint8Array): boolean
113
+ /**
114
+ * @performance IPv4 validation: ≈ parity with the JS baseline (2/2 tasks won) [check:annotate]
115
+ */
116
+ validateIpv4(input: Uint8Array): boolean
117
+ /**
118
+ * @performance IPv6 validation: ~1.9x faster than the JS baseline [check:annotate]
119
+ */
120
+ validateIpv6(input: Uint8Array): boolean
121
+ /**
122
+ * @performance WebSocket accept: ~1.9x faster than the JS baseline (1/2 tasks won) [check:annotate]
123
+ */
124
+ wsAcceptKey(key: Uint8Array): Uint8Array
125
+
126
+ // ── Low-level / packed scalar ──
127
+ /**
128
+ * @performance HTTP parse: ~5.3x faster than the JS baseline (6/6 tasks won) [check:annotate]
129
+ */
130
+ httpParseRequestPacked(input: Uint8Array): Uint8Array
131
+ /**
132
+ * @performance Query parse: ~3.8x faster than the JS baseline (6/6 tasks won) [check:annotate]
133
+ */
134
+ queryParsePacked(input: Uint8Array): Uint8Array
135
+ /**
136
+ * @performance Cookie parse: ~2.6x faster than the JS baseline (5/5 tasks won) [check:annotate]
137
+ */
138
+ cookieParsePacked(input: Uint8Array): Uint8Array
139
+ /** Reusable-output packed HTTP parse; returns bytes written. */
140
+ httpParseRequestPackedInto(input: Uint8Array, output: Uint8Array): number
141
+ /** Reusable-output packed query parse; returns bytes written. */
142
+ queryParsePackedInto(input: Uint8Array, output: Uint8Array): number
143
+ /** Reusable-output packed cookie parse; returns bytes written. */
144
+ cookieParsePackedInto(input: Uint8Array, output: Uint8Array): number
145
+ /**
146
+ * Parse an application/x-www-form-urlencoded body into packed pairs.
147
+ * @performance Form parse: ~4.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
148
+ * @remarks Benchmarked via the FormParser instance (same parser core). [check:annotate]
149
+ */
150
+ formParsePacked(input: Uint8Array): Uint8Array
151
+ /**
152
+ * Parse a Content-Type header into a structured media type.
153
+ * @performance Media type parse: ~1.3x slower than the JS baseline [check:annotate]
154
+ * @deprecated Slower than the native JS baseline (~1.3x) — prefer the JS/Bun baseline. [check:annotate]
155
+ * @remarks Consistent ~0.71-0.81x (sub-µs FFI crossing vs the JS baseline); use MediaTypeMatcher (precompiled expected) for the zero-alloc match path. [check:annotate]
156
+ */
157
+ parseMediaType(input: Uint8Array): MediaTypeResult
158
+ /**
159
+ * Generate a strong/weak ETag (crc32-based).
160
+ * @performance ETag: ≈ parity with the JS baseline [check:annotate]
161
+ */
162
+ etag(data: Uint8Array, weak?: boolean): Uint8Array
163
+ /** Reusable-output etag; returns bytes written (10 strong / 12 weak). */
164
+ etagInto(data: Uint8Array, output: Uint8Array, weak?: boolean): number
165
+ /**
166
+ * Format a unix timestamp as an IMF-fixdate HTTP-date.
167
+ * @performance HTTP date: ~1.5x slower than the JS baseline (2/2 tasks failed) [check:annotate]
168
+ * @deprecated Slower than the native JS baseline (~1.5x) — prefer the JS/Bun baseline. [check:annotate]
169
+ * @remarks Date.toUTCString() is native (~1.5-1.7x loss); pooled httpDateInto cuts it to ~1.3x. [check:annotate]
170
+ */
171
+ httpDate(secs?: number): Uint8Array
172
+ /** Reusable-output http-date; returns bytes written (29). Throws if `output` is too small or the year is out of the fixed-width range. */
173
+ httpDateInto(secs: number | undefined, output: Uint8Array): number
174
+ /** Parse an IMF-fixdate HTTP-date back to unix seconds. */
175
+ parseHttpDate(input: Uint8Array): bigint | null
176
+ /** Parse an Accept-Encoding header into ordered preferences. */
177
+ parseAcceptEncoding(input: Uint8Array): EncodingPrefResult[]
178
+ /**
179
+ * Base64 encode (standard by default; url-safe/padding configurable).
180
+ * @performance Base64 encode: ~1.6x slower than the JS baseline [check:annotate]
181
+ * @deprecated Slower than the native JS baseline (~1.6x) — prefer the JS/Bun baseline. [check:annotate]
182
+ * @remarks Bun Buffer base64 is SIMD; loses ~1.5x. [check:annotate]
183
+ */
184
+ base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array
185
+ /**
186
+ * Base64 decode (standard); throws on invalid input.
187
+ * @performance Base64 decode: ≈ parity with the JS baseline [check:annotate]
188
+ */
189
+ base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array
190
+ /** Reusable-output base64 encode; returns bytes written. */
191
+ base64EncodeInto(
192
+ input: Uint8Array,
193
+ output: Uint8Array,
194
+ urlSafe?: boolean,
195
+ padding?: boolean,
196
+ ): number
197
+ /** Reusable-output base64 decode; returns bytes written. Throws on invalid input. */
198
+ base64DecodeInto(
199
+ input: Uint8Array,
200
+ output: Uint8Array,
201
+ urlSafe?: boolean,
202
+ padding?: boolean,
203
+ ): number
204
+ base64UrlEncode(input: Uint8Array): Uint8Array
205
+ base64UrlDecode(input: Uint8Array): Uint8Array
206
+ /**
207
+ * Lowercase hex encode.
208
+ * @performance Hex encode: ~1.4x slower than the JS baseline (1/2 tasks failed) [check:annotate]
209
+ * @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
210
+ * @remarks Buffer.toString('hex') is native SIMD; pooled hexEncodeInto cuts the loss from ~2.2x to ~1.35x. [check:annotate]
211
+ */
212
+ hexEncode(input: Uint8Array): Uint8Array
213
+ /**
214
+ * Hex decode; throws on odd length or invalid digits.
215
+ * @performance Hex decode: ≈ parity with the JS baseline [check:annotate]
216
+ * @remarks Slightly slower than Buffer hex decode (~1.3x). [check:annotate]
217
+ */
218
+ hexDecode(input: Uint8Array): Uint8Array
219
+ /** Reusable-output lowercase-hex encode; returns bytes written. */
220
+ hexEncodeInto(input: Uint8Array, output: Uint8Array): number
221
+ /** Reusable-output hex decode; returns bytes written. Throws on bad input. */
222
+ hexDecodeInto(input: Uint8Array, output: Uint8Array): number
223
+ /** Reusable-output HMAC-SHA256 hex (64 bytes); returns bytes written. Throws on too-small buffer. */
224
+ hmacSha256Into(key: Uint8Array, data: Uint8Array, output: Uint8Array): number
225
+ /** Reusable-output cookie sign (`value.<64-hex>`); returns bytes written. Throws on too-small buffer. */
226
+ signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number
227
+ /** Reusable-output AEAD encrypt (ct + 16-byte tag); returns bytes written. Throws on too-small buffer. */
228
+ aeadEncryptInto(
229
+ key: Uint8Array,
230
+ nonce: Uint8Array,
231
+ plaintext: Uint8Array,
232
+ output: Uint8Array,
233
+ algorithm?: string | null,
234
+ ): number
235
+ /** Reusable-output WS frame encode; returns bytes written. Throws on too-small buffer. */
236
+ wsFrameEncodeInto(
237
+ opcode: number,
238
+ payload: Uint8Array,
239
+ mask: boolean,
240
+ fin: boolean,
241
+ output: Uint8Array,
242
+ ): number
243
+ /** Reusable-output gzip compress; returns bytes written. Throws on too-small buffer. */
244
+ gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number | null): number
245
+ /** Reusable-output brotli compress; returns bytes written. Throws on too-small buffer. */
246
+ brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number | null): number
247
+ /**
248
+ * Sign a cookie value as `value.signature` (HMAC-SHA256 hex).
249
+ * @performance Cookie sign: ~1.0x faster than the JS baseline [check:annotate]
250
+ */
251
+ signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array
252
+ /**
253
+ * Verify a signed cookie; returns the value or null.
254
+ * @performance Cookie verify: ~9.5x faster than the JS baseline [check:annotate]
255
+ */
256
+ verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | null
257
+ /**
258
+ * Create a CSRF token (32-byte random hex + HMAC signature).
259
+ * @performance CSRF create: ~13.7x faster than the JS baseline [check:annotate]
260
+ */
261
+ csrfToken(secret: Uint8Array): Uint8Array
262
+ /**
263
+ * Constant-time verify a CSRF token.
264
+ * @performance CSRF verify: ~3.3x faster than the JS baseline [check:annotate]
265
+ */
266
+ csrfVerify(token: Uint8Array, secret: Uint8Array): boolean
267
+ /**
268
+ * Resolve a URL reference against a base (RFC 3986).
269
+ * @performance URL resolve: ~1.8x slower than the JS baseline [check:annotate]
270
+ * @deprecated Slower than the native JS baseline (~1.8x) — prefer the JS/Bun baseline. [check:annotate]
271
+ * @remarks Auto-deprecated: 1/1 benchmarks failed in the latest run (static classification was parity). [check:annotate]
272
+ * @remarks Noisy ~1.5µs op; swings 0.74-1.03x run-to-run. UrlBuilder reuses the parsed base. [check:annotate]
273
+ */
274
+ urlResolve(base: Uint8Array, reference: Uint8Array): Uint8Array
275
+ /**
276
+ * Build a percent-encoded query string from params (sorted keys).
277
+ * @performance URL query build: ~1.4x slower than the JS baseline [check:annotate]
278
+ * @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
279
+ * @remarks encodeURIComponent baseline wins (~1.2-1.35x). [check:annotate]
280
+ */
281
+ urlEncodeQuery(params: Record<string, string>): Uint8Array
282
+
283
+ // ── Factories / runtime ──
284
+ /**
285
+ * @performance JSON schema validate: ~1.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
286
+ * @remarks Zero-DOM fast path validates raw bytes for the common keyword subset (scalar ~1.2-2.4x, batch ~1.0-1.56x vs ajv); unsupported keywords fall back to the jsonschema crate. [check:annotate]
287
+ */
288
+ createSchemaValidator(schema: Uint8Array): SchemaValidatorInstance
289
+ createHmacSigner(key: Uint8Array): HmacSignerInstance
290
+ createTemplateRenderer(source: string): TemplateRendererInstance
291
+ /** Higher-order form parser: owns a reusable output buffer (setup once). */
292
+ createFormParser(capacity?: number): FormParserInstance
293
+ /** Higher-order media-type parser: reusable wildcard matcher. */
294
+ createMediaTypeParser(): MediaTypeParserInstance
295
+ /**
296
+ * Higher-order conditional-request check: per-resource 304 decision (setup once).
297
+ * @performance Conditional request: ~1.7x faster than the JS baseline [check:annotate]
298
+ */
299
+ createConditionalRequest(
300
+ etagValue: Uint8Array,
301
+ lastModifiedSecs?: number,
302
+ ): ConditionalRequestInstance
303
+ /**
304
+ * Higher-order negotiator: precompiles supported encodings once.
305
+ * @performance Accept-Encoding negotiate: ~4.2x faster than the JS baseline [check:annotate]
306
+ */
307
+ createAcceptNegotiator(supported: string[]): AcceptNegotiatorInstance
308
+ /** Higher-order codec: base64 config compiled once. */
309
+ createBase64Codec(urlSafe?: boolean, padding?: boolean): Base64CodecInstance
310
+ /** Higher-order cookie signer: HMAC key compiled once. */
311
+ createCookieSigner(secret: Uint8Array): CookieSignerInstance
312
+ /** Higher-order CSRF protector: HMAC key compiled once. */
313
+ createCsrfProtector(secret: Uint8Array): CsrfProtectorInstance
314
+ /** Higher-order URL builder: base parsed once, reused. */
315
+ createUrlBuilder(base: Uint8Array): UrlBuilderInstance
316
+ /** Higher-order JWT signer: HS256 key + ttl compiled once. */
317
+ createJwtSigner(secret: Uint8Array, ttlSeconds?: number): JwtSignerInstance
318
+ /** Higher-order AEAD cipher: algorithm + key compiled once. */
319
+ createAeadCipher(key: Uint8Array, algorithm?: string): AeadCipherInstance
320
+ /** Higher-order argon2id hasher: params compiled once. */
321
+ createArgon2Hasher(options?: PasswordHashOptions | null): Argon2HasherInstance
322
+ /** Higher-order media-type matcher: expected precompiled once. */
323
+ createMediaTypeMatcher(expected: Uint8Array): MediaTypeMatcherInstance
324
+ /**
325
+ * Sharded fixed-window per-key rate limiter. `maxEntries` clamps internally
326
+ * (default 1,048,576). Each instance owns an independent budget.
327
+ * @remarks Scalar per-check cost: ~3-14x SLOWER than the JS Map baseline —
328
+ * prefer the ingress pipeline (one FFI for all stages) for native rate
329
+ * limiting in a request path. [measured 2026-08]
330
+ */
331
+ createRateLimiter(
332
+ limit: number,
333
+ windowMs: number,
334
+ maxEntries?: number | null,
335
+ ): RateLimiterInstance
336
+ initThreadPool(rayonThreads?: number): void
337
+ rayonNumThreads(): number
338
+
339
+ // ── Backend-framework scalar features ──
340
+ /**
341
+ * @performance JWT sign: ~1.1x slower than the JS baseline [check:annotate]
342
+ * @deprecated Slower than the native JS baseline (~1.1x) — prefer the JS/Bun baseline. [check:annotate]
343
+ * @remarks Base64 + claims DOM marshal overhead. Use jwtSignBytes (claims as JSON bytes) which skips the napi Value marshal and flips to a ~1.07x win. [check:annotate]
344
+ */
345
+ jwtSign(
346
+ claims: Record<string, unknown>,
347
+ secret: Uint8Array,
348
+ ttlSeconds?: number | null,
349
+ nowSeconds?: number,
350
+ ): Uint8Array
351
+ /**
352
+ * Sign a JWT from pre-serialized claim JSON bytes — skips the napi
353
+ * `serde_json::Value` marshal of `jwtSign` for callers that already hold the
354
+ * bytes (same injection semantics).
355
+ * @performance JWT sign (bytes): ≈ parity with the JS baseline [check:annotate]
356
+ * @remarks Byte-JSON claims overload avoids the napi serde_json::Value DOM marshal; flips jwtSign from a ~1.3x loss to ~1.07x (parity). [check:annotate]
357
+ */
358
+ jwtSignBytes(
359
+ claimsJson: Uint8Array,
360
+ secret: Uint8Array,
361
+ ttlSeconds?: number | null,
362
+ nowSeconds?: number,
363
+ ): Uint8Array
364
+ /**
365
+ * @performance JWT verify: ~1.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
366
+ */
367
+ jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown
368
+ /**
369
+ * @performance Password hash: ~18.2x faster than the JS baseline (2/2 tasks won) [check:annotate]
370
+ * @remarks argon2id vs node:crypto scrypt. [check:annotate]
371
+ */
372
+ passwordHash(
373
+ password: Uint8Array,
374
+ salt: Uint8Array,
375
+ options?: PasswordHashOptions | null,
376
+ ): Uint8Array
377
+ passwordVerify(password: Uint8Array, phc: Uint8Array): boolean
378
+ /** bcrypt password hash → `$2b$` PHC string (cost clamped 4..=31). */
379
+ passwordHashBcrypt(password: Uint8Array, cost: number): string
380
+ /** Verify a password against a bcrypt `$2b$` PHC string. */
381
+ passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean
382
+ /** PBKDF2-HMAC-SHA256 key derivation (dkLen clamped to 1 MiB). */
383
+ pbkdf2Sha256(password: Uint8Array, salt: Uint8Array, rounds: number, dkLen: number): Uint8Array
384
+ /**
385
+ * @performance AEAD encrypt: ~1.3x faster than the JS baseline [check:annotate]
386
+ */
387
+ aeadEncrypt(
388
+ key: Uint8Array,
389
+ nonce: Uint8Array,
390
+ plaintext: Uint8Array,
391
+ algorithm?: string | null,
392
+ ): Uint8Array
393
+ /**
394
+ * @performance AEAD decrypt: ~1.4x faster than the JS baseline [check:annotate]
395
+ */
396
+ aeadDecrypt(
397
+ key: Uint8Array,
398
+ nonce: Uint8Array,
399
+ ciphertext: Uint8Array,
400
+ algorithm?: string | null,
401
+ ): Uint8Array | null
402
+ /**
403
+ * @performance Gzip compress: ≈ parity with the JS baseline (1/2 tasks won) [check:annotate]
404
+ */
405
+ gzipCompress(data: Uint8Array, level?: number | null): Uint8Array
406
+ /**
407
+ * @performance Gzip decompress: ~1.0x faster than the JS baseline [check:annotate]
408
+ * @remarks zlib-rs vs node zlib; swings 1.0-1.4x run-to-run. [check:annotate]
409
+ * @param maxDecompressed Caps decompressed output (default 64 MiB) as a
410
+ * decompression-bomb guard; a larger value errors.
411
+ */
412
+ gzipDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array
413
+ /**
414
+ * @performance Brotli compress: ~1.7x slower than the JS baseline [check:annotate]
415
+ * @deprecated Slower than the native JS baseline (~1.7x) — prefer the JS/Bun baseline. [check:annotate]
416
+ * @remarks The native brotli baseline wins for small inputs. [check:annotate]
417
+ */
418
+ brotliCompress(data: Uint8Array, quality?: number | null): Uint8Array
419
+ /**
420
+ * @performance Brotli decompress: ~1.4x slower than the JS baseline [check:annotate]
421
+ * @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
422
+ * @remarks Auto-deprecated: 1/1 benchmarks failed in the latest run (static classification was proven). [check:annotate]
423
+ * @param maxDecompressed Caps decompressed output (default 64 MiB) as a
424
+ * decompression-bomb guard; a larger value errors.
425
+ */
426
+ brotliDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array
427
+ /**
428
+ * @performance Multipart parse: ~1.9x faster than the JS baseline [check:annotate]
429
+ */
430
+ multipartParse(body: Uint8Array, boundary: Uint8Array): MultipartPart[]
431
+ /** Zero-copy packed sibling of `multipartParse` (packed parts layout). */
432
+ multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array
433
+ /**
434
+ * @performance WS frame encode: ~2.0x faster than the JS baseline [check:annotate]
435
+ */
436
+ wsFrameEncode(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean): Uint8Array
437
+ /**
438
+ * @performance WS frame decode: ~2.3x faster than the JS baseline [check:annotate]
439
+ */
440
+ wsFrameDecode(data: Uint8Array): WsFrame | null
441
+ /**
442
+ * @performance SSE encode: ≈ parity with the JS baseline [check:annotate]
443
+ */
444
+ sseEncodeEvent(
445
+ event: string | null,
446
+ data: Uint8Array,
447
+ id: string | null,
448
+ retry: number | null,
449
+ ): Uint8Array
450
+ }
@@ -0,0 +1,45 @@
1
+ // src/rust-ffi/scalar/json.ts — JSON scalar methods.
2
+ //
3
+ // Mirrors rust/json/*: validate, DOM parse, id-sum and RFC 6902 patch.
4
+
5
+ import { getBunFFI } from '../../native/ffi'
6
+ import type { RustClientContext } from '../context'
7
+ import { asBigInt } from '../options'
8
+
9
+ /** JSON scalar methods (`Pick<RustScalar, ...>`). */
10
+ export function buildJson(ctx: RustClientContext) {
11
+ const { addon } = ctx
12
+
13
+ return {
14
+ jsonValid(input: Uint8Array): boolean {
15
+ const ffi = getBunFFI()
16
+ if (ffi) return ffi.jsonValid(input)
17
+ return addon.jsonValid(input)
18
+ },
19
+ jsonParse(input: Uint8Array): unknown {
20
+ return addon.jsonParse(input)
21
+ },
22
+ jsonSumIds(input: Uint8Array): bigint {
23
+ // The C ABI returns a packed [u8 ok][i64 sum LE] output, so a legit
24
+ // zero-sum and invalid input are unambiguous — no napi re-dispatch.
25
+ const ffi = getBunFFI()
26
+ if (ffi) return ffi.jsonSumIds(input)
27
+ return asBigInt(addon.jsonSumIds(input) as unknown)
28
+ },
29
+ jsonPatch(doc: Uint8Array, patch: Uint8Array): Uint8Array {
30
+ const ffi = getBunFFI()
31
+ if (ffi) {
32
+ try {
33
+ return ffi.jsonPatch(doc, patch)
34
+ } catch {
35
+ // The C ABI's 0 is ambiguous (too-small buffer vs a real error) and
36
+ // carries no message. Re-dispatch to napi for exact error semantics:
37
+ // it either succeeds (too-small case) or throws the contextual
38
+ // "invalid document"/"invalid patch"/"apply failed" message.
39
+ return addon.jsonPatch(doc, patch)
40
+ }
41
+ }
42
+ return addon.jsonPatch(doc, patch)
43
+ },
44
+ }
45
+ }
@@ -0,0 +1,124 @@
1
+ // src/rust-ffi/scalar/payload.ts — Output / streaming scalar methods.
2
+ //
3
+ // Mirrors rust/payload/*: gzip/brotli compress, multipart parse, WebSocket
4
+ // frame codec and SSE event framing.
5
+
6
+ import type { MultipartPart, WsFrame } from '../../native'
7
+ import { getBunFFI } from '../../native/ffi'
8
+ import { isBun } from '../../shared/runtime'
9
+ import type { RustClientContext } from '../context'
10
+
11
+ /** Output / streaming scalar methods (`Pick<RustScalar, ...>`). */
12
+ export function buildPayload(ctx: RustClientContext) {
13
+ const { addon } = ctx
14
+
15
+ return {
16
+ gzipCompress(data: Uint8Array, level?: number | null): Uint8Array {
17
+ // Optimal by default under Bun: `Bun.gzipSync` (native zlib) is ~2x
18
+ // faster than the rust+FFI crossing (decision matrix). Emits valid gzip
19
+ // (decompression-parity with the addon — only the header OS byte
20
+ // differs: 0xFF vs 0x03). `gzipCompressInto` (pooled) stays native.
21
+ if (isBun()) {
22
+ const opts = level != null ? ({ level } as Parameters<typeof Bun.gzipSync>[1]) : undefined
23
+ return Bun.gzipSync(data as unknown as Uint8Array<ArrayBuffer>, opts)
24
+ }
25
+ const ffi = getBunFFI()
26
+ if (ffi) return ffi.gzipCompress(data, level ?? undefined)
27
+ return addon.gzipCompress(data, level ?? null)
28
+ },
29
+ gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number | null): number {
30
+ const ffi = getBunFFI()
31
+ if (ffi) return ffi.gzipCompressInto(data, output, level ?? undefined)
32
+ const bytes = addon.gzipCompress(data, level ?? null)
33
+ if (output.length < bytes.length) throw new Error('gzip compress: output buffer too small')
34
+ output.set(bytes)
35
+ return bytes.length
36
+ },
37
+ gzipDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array {
38
+ const ffi = getBunFFI()
39
+ if (ffi) return ffi.gzipDecompress(data, maxDecompressed ?? undefined)
40
+ return addon.gzipDecompress(data, maxDecompressed ?? null)
41
+ },
42
+ brotliCompress(data: Uint8Array, quality?: number | null): Uint8Array {
43
+ const ffi = getBunFFI()
44
+ if (ffi) return ffi.brotliCompress(data, quality ?? undefined)
45
+ return addon.brotliCompress(data, quality ?? null)
46
+ },
47
+ brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number | null): number {
48
+ const ffi = getBunFFI()
49
+ if (ffi) return ffi.brotliCompressInto(data, output, quality ?? undefined)
50
+ const bytes = addon.brotliCompress(data, quality ?? null)
51
+ if (output.length < bytes.length) throw new Error('brotli compress: output buffer too small')
52
+ output.set(bytes)
53
+ return bytes.length
54
+ },
55
+ brotliDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array {
56
+ const ffi = getBunFFI()
57
+ if (ffi) return ffi.brotliDecompress(data, maxDecompressed ?? undefined)
58
+ return addon.brotliDecompress(data, maxDecompressed ?? null)
59
+ },
60
+ multipartParse(body: Uint8Array, boundary: Uint8Array): MultipartPart[] {
61
+ // Normalize napi `Option<String>` (undefined) → null and expose the
62
+ // camelCase `contentType` key (napi renames `content_type` to camelCase).
63
+ return addon.multipartParse(body, boundary).map((p) => ({
64
+ name: p.name,
65
+ filename: p.filename ?? null,
66
+ contentType: p.contentType ?? null,
67
+ data: p.data,
68
+ }))
69
+ },
70
+ multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array {
71
+ const ffi = getBunFFI()
72
+ if (ffi) return ffi.multipartParsePacked(body, boundary)
73
+ // Zero-copy packed sibling — no JS objects per part (see native types).
74
+ return addon.multipartParsePacked(body, boundary)
75
+ },
76
+ wsFrameEncode(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean): Uint8Array {
77
+ const ffi = getBunFFI()
78
+ if (ffi) return ffi.wsFrameEncode(opcode, payload, mask, fin)
79
+ return addon.wsFrameEncode(opcode, payload, mask, fin)
80
+ },
81
+ wsFrameEncodeInto(
82
+ opcode: number,
83
+ payload: Uint8Array,
84
+ mask: boolean,
85
+ fin: boolean,
86
+ output: Uint8Array,
87
+ ): number {
88
+ const ffi = getBunFFI()
89
+ if (ffi) return ffi.wsFrameEncodeInto(opcode, payload, mask, fin, output)
90
+ const bytes = addon.wsFrameEncode(opcode, payload, mask, fin)
91
+ if (output.length < bytes.length) throw new Error('ws frame encode: output buffer too small')
92
+ output.set(bytes)
93
+ return bytes.length
94
+ },
95
+ wsFrameDecode(data: Uint8Array): WsFrame | null {
96
+ const ffi = getBunFFI()
97
+ if (ffi) {
98
+ // C ABI returns packed [flags][opcode][u32 len][payload]; decode here.
99
+ const packed = ffi.wsFrameDecodePacked(data)
100
+ if (packed === null) return null
101
+ const payloadLen =
102
+ ((packed[2] ?? 0) |
103
+ ((packed[3] ?? 0) << 8) |
104
+ ((packed[4] ?? 0) << 16) |
105
+ ((packed[5] ?? 0) << 24)) >>>
106
+ 0
107
+ return {
108
+ fin: (packed[0] ?? 0) === 1,
109
+ opcode: packed[1] ?? 0,
110
+ payload: packed.subarray(6, 6 + payloadLen),
111
+ }
112
+ }
113
+ return addon.wsFrameDecode(data)
114
+ },
115
+ sseEncodeEvent(
116
+ event: string | null,
117
+ data: Uint8Array,
118
+ id: string | null,
119
+ retry: number | null,
120
+ ): Uint8Array {
121
+ return addon.sseEncodeEvent(event ?? null, data, id ?? null, retry ?? null)
122
+ },
123
+ }
124
+ }