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,657 @@
1
+ // src/loader/ops.ts — Curated loader-op registry.
2
+ //
3
+ // Each op pairs a scalar FFI call (single item) with the packed batch call
4
+ // (bulk items → ONE native crossing). Result kinds mirror `rust.batch.*`:
5
+ //
6
+ // - "boolean" → single `boolean`, bulk bitset `Uint8Array` (0/1 per item)
7
+ // - "number" → single `number`, bulk `Uint32Array`
8
+ // - "bigint" → single `bigint`, bulk `BigInt64Array | BigUint64Array`
9
+ // - "bytes" → single `Uint8Array`, bulk `Uint8Array[]`
10
+ //
11
+ // Ops marked "paired" (see `LoaderOpSpec.pairedRest`) take a per-item
12
+ // companion in bulk form (e.g. `jsonPatch` = doc + patch). Ops whose scalar
13
+ // returns something richer than the batch bitset (`verifyCookie` returning the
14
+ // value, `jwtVerify` returning the decoded payload) are exposed as
15
+ // BOOLEAN-validity ops so single and bulk semantics stay identical — use
16
+ // `rust.<op>` directly for the decoded value.
17
+ //
18
+ // The bulk result shapes are intentionally IDENTICAL to `rust.batch.<op>` so
19
+ // the loader never pays a conversion cost on the hot path. This module is the
20
+ // single source of truth for what the loader can dispatch.
21
+
22
+ import { rust } from '../rust-ffi'
23
+ import { addon } from '../rust-ffi/addon'
24
+ import { decoder } from '../shared/bytes'
25
+ import type { PasswordHashOptions } from '../native'
26
+ import type { SchemaValidator } from '../shared/packed'
27
+
28
+ /** Empty bytes sentinel for skip-on-error scalar parity (e.g. AEAD decrypt). */
29
+ export const EMPTY_BYTES = new Uint8Array(0)
30
+
31
+ /** Result kinds supported by the loader (mirrors the packed batch wire shapes). */
32
+ export type LoaderResultKind = 'boolean' | 'number' | 'bigint' | 'bytes'
33
+
34
+ /** Single-item (scalar) result type for a kind. */
35
+ export type ScalarResult<K extends LoaderResultKind> = K extends 'boolean'
36
+ ? boolean
37
+ : K extends 'number'
38
+ ? number
39
+ : K extends 'bigint'
40
+ ? bigint
41
+ : Uint8Array
42
+
43
+ /** Bulk (array-input) result type for a kind — same shape as `rust.batch.*`. */
44
+ export type BulkResult<K extends LoaderResultKind> = K extends 'boolean'
45
+ ? Uint8Array
46
+ : K extends 'number'
47
+ ? Uint32Array
48
+ : K extends 'bigint'
49
+ ? BigInt64Array
50
+ : Uint8Array[]
51
+
52
+ /**
53
+ * Bulk-result type overrides per op. Bigint ops whose hashes can set the high
54
+ * bit (e.g. `fnv1a64`) return an UNSIGNED `BigUint64Array` to match the scalar
55
+ * `rust.<op>`; the default signed `BigInt64Array` stays for sum-style ops.
56
+ */
57
+ export interface LoaderOpBulkOverride {
58
+ fnv1a64: BigUint64Array
59
+ }
60
+
61
+ /** Bulk result type of a specific op (per-op override wins). */
62
+ export type LoaderBulk<K extends LoaderOpName> = K extends keyof LoaderOpBulkOverride
63
+ ? LoaderOpBulkOverride[K]
64
+ : BulkResult<LoaderOpKind<K>>
65
+
66
+ /** Every op the loader can dispatch. */
67
+ export type LoaderOpName =
68
+ | 'crc32'
69
+ | 'fnv1a64'
70
+ | 'jsonValid'
71
+ | 'jsonSumIds'
72
+ | 'validateEmail'
73
+ | 'validateUuid'
74
+ | 'validateIpv4'
75
+ | 'validateIpv6'
76
+ | 'queryParse'
77
+ | 'cookieParse'
78
+ | 'formParse'
79
+ | 'httpParseRequest'
80
+ | 'hexEncode'
81
+ | 'hexDecode'
82
+ | 'base64Encode'
83
+ | 'base64Decode'
84
+ | 'base64UrlEncode'
85
+ | 'base64UrlDecode'
86
+ | 'gzipCompress'
87
+ | 'gzipDecompress'
88
+ | 'brotliCompress'
89
+ | 'brotliDecompress'
90
+ | 'etag'
91
+ | 'urlEncode'
92
+ | 'urlDecode'
93
+ | 'urlDecodeBytes'
94
+ | 'wsAcceptKey'
95
+ | 'mimeFromExtension'
96
+ | 'signCookie'
97
+ | 'verifyCookie'
98
+ | 'csrfVerify'
99
+ | 'passwordHash'
100
+ | 'passwordVerify'
101
+ | 'hmacSha256'
102
+ | 'hmacSha256Verify'
103
+ | 'aeadEncrypt'
104
+ | 'aeadDecrypt'
105
+ | 'jwtSign'
106
+ | 'jwtVerify'
107
+ | 'jsonPatch'
108
+ | 'urlResolve'
109
+ | 'wsFrameEncode'
110
+ | 'sseEncode'
111
+ | 'schemaValidate'
112
+
113
+ /** Extra arguments (after the item/items) for each op — the SINGLE call shape. */
114
+ export interface LoaderOpArgs {
115
+ crc32: []
116
+ fnv1a64: []
117
+ jsonValid: []
118
+ jsonSumIds: []
119
+ validateEmail: []
120
+ validateUuid: []
121
+ validateIpv4: []
122
+ validateIpv6: []
123
+ queryParse: []
124
+ cookieParse: []
125
+ formParse: []
126
+ httpParseRequest: []
127
+ hexEncode: []
128
+ hexDecode: []
129
+ base64Encode: [urlSafe?: boolean, padding?: boolean]
130
+ base64Decode: [urlSafe?: boolean, padding?: boolean]
131
+ base64UrlEncode: []
132
+ base64UrlDecode: []
133
+ gzipCompress: [level?: number | null]
134
+ gzipDecompress: []
135
+ brotliCompress: [quality?: number | null]
136
+ brotliDecompress: []
137
+ etag: [weak?: boolean | null]
138
+ urlEncode: []
139
+ urlDecode: []
140
+ urlDecodeBytes: []
141
+ wsAcceptKey: []
142
+ mimeFromExtension: []
143
+ signCookie: [secret: Uint8Array]
144
+ /** Boolean-validity op: returns valid/invalid (use `rust.verifyCookie` for the value). */
145
+ verifyCookie: [secret: Uint8Array]
146
+ csrfVerify: [secret: Uint8Array]
147
+ passwordHash: [salt: Uint8Array, options?: PasswordHashOptions | null]
148
+ passwordVerify: [phc: Uint8Array]
149
+ hmacSha256: [key: Uint8Array]
150
+ hmacSha256Verify: [key: Uint8Array, sig: Uint8Array]
151
+ aeadEncrypt: [key: Uint8Array, nonce: Uint8Array, algorithm?: string | null]
152
+ aeadDecrypt: [key: Uint8Array, nonce: Uint8Array, algorithm?: string | null]
153
+ jwtSign: [secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number]
154
+ /** Boolean-validity op: returns valid/invalid (use `rust.jwtVerify` for the payload). */
155
+ jwtVerify: [secret: Uint8Array, nowSeconds?: number]
156
+ jsonPatch: [patch: Uint8Array]
157
+ urlResolve: [base: Uint8Array]
158
+ wsFrameEncode: [opcode: number, mask: boolean, fin: boolean]
159
+ sseEncode: [event?: string | null, id?: string | null, retry?: number | null]
160
+ schemaValidate: [validator: SchemaValidator]
161
+ }
162
+
163
+ /**
164
+ * Extra arguments for the BULK call shape. Paired ops take a per-item
165
+ * companion ARRAY here; every other op inherits the scalar `LoaderOpArgs`
166
+ * shape (companions are shared across the batch).
167
+ */
168
+ export interface LoaderOpBulkArgs {
169
+ jsonPatch: [patches: Uint8Array[]]
170
+ hmacSha256Verify: [key: Uint8Array, sigs: Uint8Array[]]
171
+ passwordVerify: [phcs: Uint8Array[]]
172
+ urlResolve: [bases: Uint8Array[]]
173
+ }
174
+
175
+ /** Bulk call shape for any op (defaults to the scalar shape). */
176
+ export type LoaderBulkArgs<K extends LoaderOpName> = K extends keyof LoaderOpBulkArgs
177
+ ? LoaderOpBulkArgs[K]
178
+ : LoaderOpArgs[K]
179
+
180
+ /**
181
+ * Zero-alloc fast-bulk wiring used by the coalesced `load()` flush for
182
+ * boolean/number/bigint ops. The loader packs the inputs into a reusable
183
+ * scratch, calls `batch` (a native `_into` packed variant) to write the packed
184
+ * result straight into a reusable output buffer, then reads each element out
185
+ * of that buffer — no intermediate unpack array, no per-call native Vec.
186
+ */
187
+ export interface LoaderFastSpec {
188
+ /** Write the packed batch result for `packed` into `output`; returns bytes written. */
189
+ batch(packed: Uint8Array, output: Uint8Array): number
190
+ /** Read the i-th scalar result out of the packed `output` written by `batch`. */
191
+ element(view: DataView, output: Uint8Array, index: number): boolean | number | bigint
192
+ /** Exact number of bytes `output` needs for `count` items. */
193
+ outSize(count: number): number
194
+ }
195
+
196
+ /** Per-op runtime spec: single + bulk execution, plus element access. */
197
+ export interface LoaderOpSpec {
198
+ kind: LoaderResultKind
199
+ /** Single-item execution: `(input, ...rest) → scalar result`. */
200
+ scalar(input: Uint8Array, ...rest: unknown[]): unknown
201
+ /** Bulk execution: `(items, ...rest) → rust.batch-shaped result`. */
202
+ batch(items: Uint8Array[], ...rest: unknown[]): unknown
203
+ /** Read the i-th scalar result out of a bulk result. */
204
+ element(bulk: unknown, index: number): unknown
205
+ /**
206
+ * Zero-alloc fast bulk path for the coalesced `load()` flush. Ops with this
207
+ * MUST keep `element` returning the same per-item value as `fast.element`.
208
+ */
209
+ fast?: LoaderFastSpec
210
+ /**
211
+ * Indices into the bulk `rest` args that are PER-ITEM companion arrays
212
+ * (paired ops). Used only by the adaptive scalar-loop fallback to split
213
+ * companions per item; the packed batch path passes them through unchanged.
214
+ */
215
+ pairedRest?: number[]
216
+ /**
217
+ * Bigint ops whose bulk result is UNSIGNED (`BigUint64Array`). The adaptive
218
+ * scalar-loop fallback builds the same unsigned array so single/bulk parity
219
+ * holds for high-bit hashes.
220
+ */
221
+ unsigned?: boolean
222
+ }
223
+
224
+ function boolElem(bulk: unknown, index: number): boolean {
225
+ return ((bulk as Uint8Array)[index] ?? 0) === 1
226
+ }
227
+ function numElem(bulk: unknown, index: number): number {
228
+ return (bulk as Uint32Array)[index] as number
229
+ }
230
+ function bigElem(bulk: unknown, index: number): bigint {
231
+ return (bulk as BigInt64Array)[index] as bigint
232
+ }
233
+ function byteElem(bulk: unknown, index: number): Uint8Array {
234
+ return (bulk as Uint8Array[])[index] as Uint8Array
235
+ }
236
+
237
+ // ── Zero-alloc fast-bulk readers (packed output → scalar) ──────────
238
+
239
+ function boolFast(_view: DataView, output: Uint8Array, index: number): boolean {
240
+ return (((output[4 + (index >> 3)] ?? 0) >> (index & 7)) & 1) === 1
241
+ }
242
+ function bitsetOutSize(count: number): number {
243
+ return 4 + Math.ceil(count / 8)
244
+ }
245
+ function u32Fast(view: DataView, _output: Uint8Array, index: number): number {
246
+ return view.getUint32(4 + index * 4, true)
247
+ }
248
+ function u32OutSize(count: number): number {
249
+ return 4 + count * 4
250
+ }
251
+ function i64Fast(view: DataView, _output: Uint8Array, index: number): bigint {
252
+ return view.getBigInt64(4 + index * 8, true)
253
+ }
254
+ function u64Fast(view: DataView, _output: Uint8Array, index: number): bigint {
255
+ return view.getBigUint64(4 + index * 8, true)
256
+ }
257
+ function i64OutSize(count: number): number {
258
+ return 4 + count * 8
259
+ }
260
+
261
+ // Native `_into` packed batch fns (lazy addon; resolved + cached on first use
262
+ // so the flush path skips the lazy-addon Proxy get after warm-up).
263
+ const fastNative = new Map<string, (packed: Uint8Array, output: Uint8Array) => number>()
264
+ function fastInto(name: string): (packed: Uint8Array, output: Uint8Array) => number {
265
+ let f = fastNative.get(name)
266
+ if (f === undefined) {
267
+ f = (addon as unknown as Record<string, (packed: Uint8Array, output: Uint8Array) => number>)[
268
+ name
269
+ ] as (packed: Uint8Array, output: Uint8Array) => number
270
+ fastNative.set(name, f)
271
+ }
272
+ return f
273
+ }
274
+
275
+ /**
276
+ * Packed FNV-1a 64 batch into a caller-owned buffer. The coalesced `load()`
277
+ * flush uses this to compute every deferred default cache key in ONE native
278
+ * crossing (`[u32 count][u64 LE × n]`, u64 bit pattern — matches the scalar
279
+ * `rust.fnv1a64` key). Returns bytes written.
280
+ */
281
+ export const fnvBatchKeysInto = (packed: Uint8Array, output: Uint8Array): number =>
282
+ fastInto('fnv1A64BatchPackedInto')(packed, output)
283
+
284
+ const BOOLEAN = 'boolean'
285
+ const NUMBER = 'number'
286
+ const BIGINT = 'bigint'
287
+ const BYTES = 'bytes'
288
+
289
+ /**
290
+ * Registry — single source of truth for loader-op wiring. Every op must have
291
+ * a scalar variant and a packed batch variant; ops whose scalar returns
292
+ * something other than the bulk element (e.g. `jwtVerify` returning the
293
+ * decoded payload, `verifyCookie` returning the value) are deliberately
294
+ * EXCLUDED so single and bulk semantics stay identical.
295
+ */
296
+ export const LOADER_OPS = {
297
+ crc32: {
298
+ kind: NUMBER,
299
+ scalar: (i: Uint8Array) => rust.crc32(i),
300
+ batch: (items: Uint8Array[]) => rust.batch.crc32(items),
301
+ element: numElem,
302
+ fast: {
303
+ batch: (packed, output) => fastInto('crc32BatchPackedInto')(packed, output),
304
+ element: u32Fast,
305
+ outSize: u32OutSize,
306
+ },
307
+ },
308
+ fnv1a64: {
309
+ kind: BIGINT,
310
+ unsigned: true,
311
+ scalar: (i: Uint8Array) => rust.fnv1a64(i),
312
+ batch: (items: Uint8Array[]) => rust.batch.fnv1a64(items),
313
+ element: bigElem,
314
+ fast: {
315
+ batch: (packed, output) => fastInto('fnv1A64BatchPackedInto')(packed, output),
316
+ element: u64Fast,
317
+ outSize: i64OutSize,
318
+ },
319
+ },
320
+ jsonValid: {
321
+ kind: BOOLEAN,
322
+ scalar: (i: Uint8Array) => rust.jsonValid(i),
323
+ batch: (items: Uint8Array[]) => rust.batch.jsonValid(items),
324
+ element: boolElem,
325
+ fast: {
326
+ batch: (packed, output) => fastInto('jsonValidBatchPackedInto')(packed, output),
327
+ element: boolFast,
328
+ outSize: bitsetOutSize,
329
+ },
330
+ },
331
+ jsonSumIds: {
332
+ kind: BIGINT,
333
+ scalar: (i: Uint8Array) => rust.jsonSumIds(i),
334
+ batch: (items: Uint8Array[]) => rust.batch.jsonSumIds(items),
335
+ element: bigElem,
336
+ fast: {
337
+ batch: (packed, output) => fastInto('jsonSumBatchPackedInto')(packed, output),
338
+ element: i64Fast,
339
+ outSize: i64OutSize,
340
+ },
341
+ },
342
+ validateEmail: {
343
+ kind: BOOLEAN,
344
+ scalar: (i: Uint8Array) => rust.validateEmail(i),
345
+ batch: (items: Uint8Array[]) => rust.batch.validateEmail(items),
346
+ element: boolElem,
347
+ fast: {
348
+ batch: (packed, output) => fastInto('validateEmailBatchPackedInto')(packed, output),
349
+ element: boolFast,
350
+ outSize: bitsetOutSize,
351
+ },
352
+ },
353
+ validateUuid: {
354
+ kind: BOOLEAN,
355
+ scalar: (i: Uint8Array) => rust.validateUuid(i),
356
+ batch: (items: Uint8Array[]) => rust.batch.validateUuid(items),
357
+ element: boolElem,
358
+ fast: {
359
+ batch: (packed, output) => fastInto('validateUuidBatchPackedInto')(packed, output),
360
+ element: boolFast,
361
+ outSize: bitsetOutSize,
362
+ },
363
+ },
364
+ validateIpv4: {
365
+ kind: BOOLEAN,
366
+ scalar: (i: Uint8Array) => rust.validateIpv4(i),
367
+ batch: (items: Uint8Array[]) => rust.batch.validateIpv4(items),
368
+ element: boolElem,
369
+ fast: {
370
+ batch: (packed, output) => fastInto('validateIpv4BatchPackedInto')(packed, output),
371
+ element: boolFast,
372
+ outSize: bitsetOutSize,
373
+ },
374
+ },
375
+ validateIpv6: {
376
+ kind: BOOLEAN,
377
+ scalar: (i: Uint8Array) => rust.validateIpv6(i),
378
+ batch: (items: Uint8Array[]) => rust.batch.validateIpv6(items),
379
+ element: boolElem,
380
+ fast: {
381
+ batch: (packed, output) => fastInto('validateIpv6BatchPackedInto')(packed, output),
382
+ element: boolFast,
383
+ outSize: bitsetOutSize,
384
+ },
385
+ },
386
+ queryParse: {
387
+ kind: BYTES,
388
+ scalar: (i: Uint8Array) => rust.queryParsePacked(i),
389
+ batch: (items: Uint8Array[]) => rust.batch.queryParse(items),
390
+ element: byteElem,
391
+ },
392
+ cookieParse: {
393
+ kind: BYTES,
394
+ scalar: (i: Uint8Array) => rust.cookieParsePacked(i),
395
+ batch: (items: Uint8Array[]) => rust.batch.cookieParse(items),
396
+ element: byteElem,
397
+ },
398
+ formParse: {
399
+ kind: BYTES,
400
+ scalar: (i: Uint8Array) => rust.formParsePacked(i),
401
+ batch: (items: Uint8Array[]) => rust.batch.formParse(items),
402
+ element: byteElem,
403
+ },
404
+ httpParseRequest: {
405
+ kind: BYTES,
406
+ scalar: (i: Uint8Array) => rust.httpParseRequestPacked(i),
407
+ batch: (items: Uint8Array[]) => rust.batch.httpParseRequest(items),
408
+ element: byteElem,
409
+ },
410
+ hexEncode: {
411
+ kind: BYTES,
412
+ scalar: (i: Uint8Array) => rust.hexEncode(i),
413
+ batch: (items: Uint8Array[]) => rust.batch.hexEncode(items),
414
+ element: byteElem,
415
+ },
416
+ hexDecode: {
417
+ kind: BYTES,
418
+ scalar: (i: Uint8Array) => rust.hexDecode(i),
419
+ batch: (items: Uint8Array[]) => rust.batch.hexDecode(items),
420
+ element: byteElem,
421
+ },
422
+ base64Encode: {
423
+ kind: BYTES,
424
+ scalar: (i: Uint8Array, urlSafe?: boolean, padding?: boolean) =>
425
+ rust.base64Encode(i, urlSafe, padding),
426
+ batch: (items: Uint8Array[], urlSafe?: boolean, padding?: boolean) =>
427
+ rust.batch.base64Encode(items, urlSafe, padding),
428
+ element: byteElem,
429
+ },
430
+ base64Decode: {
431
+ kind: BYTES,
432
+ scalar: (i: Uint8Array, urlSafe?: boolean, padding?: boolean) =>
433
+ rust.base64Decode(i, urlSafe, padding),
434
+ batch: (items: Uint8Array[], urlSafe?: boolean, padding?: boolean) =>
435
+ rust.batch.base64Decode(items, urlSafe, padding),
436
+ element: byteElem,
437
+ },
438
+ base64UrlEncode: {
439
+ kind: BYTES,
440
+ scalar: (i: Uint8Array) => rust.base64UrlEncode(i),
441
+ batch: (items: Uint8Array[]) => rust.batch.base64UrlEncode(items),
442
+ element: byteElem,
443
+ },
444
+ base64UrlDecode: {
445
+ kind: BYTES,
446
+ scalar: (i: Uint8Array) => rust.base64UrlDecode(i),
447
+ batch: (items: Uint8Array[]) => rust.batch.base64UrlDecode(items),
448
+ element: byteElem,
449
+ },
450
+ gzipCompress: {
451
+ kind: BYTES,
452
+ scalar: (i: Uint8Array, level?: number | null) => rust.gzipCompress(i, level),
453
+ batch: (items: Uint8Array[], level?: number | null) => rust.batch.gzipCompress(items, level),
454
+ element: byteElem,
455
+ },
456
+ gzipDecompress: {
457
+ kind: BYTES,
458
+ scalar: (i: Uint8Array) => rust.gzipDecompress(i),
459
+ batch: (items: Uint8Array[]) => rust.batch.gzipDecompress(items),
460
+ element: byteElem,
461
+ },
462
+ brotliCompress: {
463
+ kind: BYTES,
464
+ scalar: (i: Uint8Array, quality?: number | null) => rust.brotliCompress(i, quality),
465
+ batch: (items: Uint8Array[], quality?: number | null) =>
466
+ rust.batch.brotliCompress(items, quality),
467
+ element: byteElem,
468
+ },
469
+ brotliDecompress: {
470
+ kind: BYTES,
471
+ scalar: (i: Uint8Array) => rust.brotliDecompress(i),
472
+ batch: (items: Uint8Array[]) => rust.batch.brotliDecompress(items),
473
+ element: byteElem,
474
+ },
475
+ etag: {
476
+ kind: BYTES,
477
+ scalar: (i: Uint8Array, weak?: boolean | null) => rust.etag(i, weak ?? undefined),
478
+ batch: (items: Uint8Array[], weak?: boolean | null) => rust.batch.etag(items, weak),
479
+ element: byteElem,
480
+ },
481
+ urlEncode: {
482
+ kind: BYTES,
483
+ scalar: (i: Uint8Array) => rust.urlEncode(i),
484
+ batch: (items: Uint8Array[]) => rust.batch.urlEncode(items),
485
+ element: byteElem,
486
+ },
487
+ urlDecode: {
488
+ kind: BYTES,
489
+ scalar: (i: Uint8Array) => rust.urlDecode(i),
490
+ batch: (items: Uint8Array[]) => rust.batch.urlDecode(items),
491
+ element: byteElem,
492
+ },
493
+ urlDecodeBytes: {
494
+ kind: BYTES,
495
+ scalar: (i: Uint8Array) => rust.urlDecodeBytes(i),
496
+ batch: (items: Uint8Array[]) => rust.batch.urlDecodeBytes(items),
497
+ element: byteElem,
498
+ },
499
+ wsAcceptKey: {
500
+ kind: BYTES,
501
+ scalar: (i: Uint8Array) => rust.wsAcceptKey(i),
502
+ batch: (items: Uint8Array[]) => rust.batch.wsAcceptKey(items),
503
+ element: byteElem,
504
+ },
505
+ mimeFromExtension: {
506
+ kind: BYTES,
507
+ scalar: (i: Uint8Array) => rust.mimeFromExtension(i),
508
+ batch: (items: Uint8Array[]) => rust.batch.mimeFromExtension(items),
509
+ element: byteElem,
510
+ },
511
+ signCookie: {
512
+ kind: BYTES,
513
+ scalar: (i: Uint8Array, secret: Uint8Array) => rust.signCookie(i, secret),
514
+ batch: (items: Uint8Array[], secret: Uint8Array) => rust.batch.signCookie(items, secret),
515
+ element: byteElem,
516
+ },
517
+ // Boolean-validity op: single returns valid/invalid (not the decoded value).
518
+ // Use `rust.verifyCookie` when you need the signed value back.
519
+ verifyCookie: {
520
+ kind: BOOLEAN,
521
+ scalar: (i: Uint8Array, secret: Uint8Array) => rust.verifyCookie(i, secret) !== null,
522
+ batch: (items: Uint8Array[], secret: Uint8Array) => rust.batch.verifyCookie(items, secret),
523
+ element: boolElem,
524
+ },
525
+ csrfVerify: {
526
+ kind: BOOLEAN,
527
+ scalar: (i: Uint8Array, secret: Uint8Array) => rust.csrfVerify(i, secret),
528
+ batch: (items: Uint8Array[], secret: Uint8Array) => rust.batch.csrfVerify(items, secret),
529
+ element: boolElem,
530
+ },
531
+ passwordHash: {
532
+ kind: BYTES,
533
+ scalar: (i: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null) =>
534
+ rust.passwordHash(i, salt, options),
535
+ batch: (items: Uint8Array[], salt: Uint8Array, options?: PasswordHashOptions | null) =>
536
+ rust.batch.passwordHash(items, salt, options),
537
+ element: byteElem,
538
+ },
539
+ passwordVerify: {
540
+ kind: BOOLEAN,
541
+ pairedRest: [0],
542
+ scalar: (i: Uint8Array, phc: Uint8Array) => rust.passwordVerify(i, phc),
543
+ batch: (items: Uint8Array[], phcs: Uint8Array[]) => rust.batch.passwordVerify(items, phcs),
544
+ element: boolElem,
545
+ },
546
+ aeadEncrypt: {
547
+ kind: BYTES,
548
+ scalar: (i: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null) =>
549
+ rust.aeadEncrypt(key, nonce, i, algorithm),
550
+ batch: (items: Uint8Array[], key: Uint8Array, nonce: Uint8Array, algorithm?: string | null) =>
551
+ rust.batch.aeadEncrypt(key, nonce, items, algorithm),
552
+ element: byteElem,
553
+ },
554
+ aeadDecrypt: {
555
+ kind: BYTES,
556
+ scalar: (i: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null) =>
557
+ rust.aeadDecrypt(key, nonce, i, algorithm) ?? EMPTY_BYTES,
558
+ batch: (items: Uint8Array[], key: Uint8Array, nonce: Uint8Array, algorithm?: string | null) =>
559
+ rust.batch.aeadDecrypt(key, nonce, items, algorithm),
560
+ element: byteElem,
561
+ },
562
+ jwtSign: {
563
+ kind: BYTES,
564
+ // Input is a JSON claim document (bytes); the scalar parses it to the
565
+ // claims object the native signer expects.
566
+ scalar: (i: Uint8Array, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number) =>
567
+ rust.jwtSign(
568
+ JSON.parse(decoder.decode(i)) as Record<string, unknown>,
569
+ secret,
570
+ ttlSeconds,
571
+ nowSeconds,
572
+ ),
573
+ batch: (
574
+ items: Uint8Array[],
575
+ secret: Uint8Array,
576
+ ttlSeconds?: number | null,
577
+ nowSeconds?: number,
578
+ ) => rust.batch.jwtSign(items, secret, ttlSeconds, nowSeconds),
579
+ element: byteElem,
580
+ },
581
+ // Boolean-validity op: single returns valid/invalid (not the payload).
582
+ // Use `rust.jwtVerify` when you need the decoded claims back.
583
+ jwtVerify: {
584
+ kind: BOOLEAN,
585
+ scalar: (i: Uint8Array, secret: Uint8Array, nowSeconds?: number) =>
586
+ rust.jwtVerify(i, secret, nowSeconds) !== null,
587
+ batch: (items: Uint8Array[], secret: Uint8Array, nowSeconds?: number) =>
588
+ rust.batch.jwtVerify(items, secret, nowSeconds ?? Math.floor(Date.now() / 1000)),
589
+ element: boolElem,
590
+ },
591
+ jsonPatch: {
592
+ kind: BYTES,
593
+ pairedRest: [0],
594
+ scalar: (i: Uint8Array, patch: Uint8Array) => rust.jsonPatch(i, patch),
595
+ batch: (items: Uint8Array[], patches: Uint8Array[]) => rust.batch.jsonPatch(items, patches),
596
+ element: byteElem,
597
+ },
598
+ urlResolve: {
599
+ kind: BYTES,
600
+ pairedRest: [0],
601
+ scalar: (i: Uint8Array, base: Uint8Array) => rust.urlResolve(base, i),
602
+ batch: (items: Uint8Array[], bases: Uint8Array[]) => rust.batch.urlResolve(bases, items),
603
+ element: byteElem,
604
+ },
605
+ wsFrameEncode: {
606
+ kind: BYTES,
607
+ scalar: (i: Uint8Array, opcode: number, mask: boolean, fin: boolean) =>
608
+ rust.wsFrameEncode(opcode, i, mask, fin),
609
+ batch: (items: Uint8Array[], opcode: number, mask: boolean, fin: boolean) =>
610
+ rust.batch.wsFrameEncode(items, opcode, mask, fin),
611
+ element: byteElem,
612
+ },
613
+ sseEncode: {
614
+ kind: BYTES,
615
+ scalar: (i: Uint8Array, event?: string | null, id?: string | null, retry?: number | null) =>
616
+ rust.sseEncodeEvent(event ?? null, i, id ?? null, retry ?? null),
617
+ batch: (
618
+ items: Uint8Array[],
619
+ event?: string | null,
620
+ id?: string | null,
621
+ retry?: number | null,
622
+ ) => rust.batch.sseEncode(items, event, id, retry),
623
+ element: byteElem,
624
+ },
625
+ schemaValidate: {
626
+ kind: BOOLEAN,
627
+ scalar: (i: Uint8Array, validator: SchemaValidator) => validator.validate(i),
628
+ batch: (items: Uint8Array[], validator: SchemaValidator) =>
629
+ rust.batch.schemaValidate(validator, items),
630
+ element: boolElem,
631
+ },
632
+ hmacSha256: {
633
+ kind: BYTES,
634
+ // NOTE: scalar arg order is (key, data); the loader normalizes to
635
+ // (input, ...rest) so callers pass the key as the trailing argument.
636
+ scalar: (i: Uint8Array, key: Uint8Array) => rust.hmacSha256(key, i),
637
+ batch: (items: Uint8Array[], key: Uint8Array) => rust.batch.hmacSha256(items, key),
638
+ element: byteElem,
639
+ },
640
+ hmacSha256Verify: {
641
+ kind: BOOLEAN,
642
+ pairedRest: [1],
643
+ // scalar arg order is (key, data, sig); normalized to (input=data, key, sig).
644
+ scalar: (i: Uint8Array, key: Uint8Array, sig: Uint8Array) => rust.hmacSha256Verify(key, i, sig),
645
+ batch: (items: Uint8Array[], key: Uint8Array, sigs: Uint8Array[]) =>
646
+ rust.batch.hmacSha256Verify(items, sigs, key),
647
+ element: boolElem,
648
+ },
649
+ } as const satisfies Record<LoaderOpName, LoaderOpSpec>
650
+
651
+ /** Kind of a specific op. */
652
+ export type LoaderOpKind<K extends LoaderOpName> = (typeof LOADER_OPS)[K]['kind']
653
+ /** Single-item result type of a specific op. */
654
+ export type LoaderScalar<K extends LoaderOpName> = ScalarResult<LoaderOpKind<K>>
655
+
656
+ /** All op names, as a readonly array (for iteration). */
657
+ export const LOADER_OP_NAMES = Object.keys(LOADER_OPS) as LoaderOpName[]