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.
- package/LICENSE +21 -0
- package/README.md +322 -59
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +3161 -0
- package/dist/index.js +7306 -0
- package/docs/ARCHITECTURE.md +415 -0
- package/docs/BENCHMARKS.md +307 -0
- package/docs/ENVIRONMENT.md +79 -0
- package/docs/GETTING_STARTED.md +209 -0
- package/docs/INGRESS.md +745 -0
- package/docs/REPO_MAP.md +259 -0
- package/docs/adr/0001-two-wire-formats.md +39 -0
- package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
- package/docs/adr/0003-bun-builtins-delegation.md +42 -0
- package/docs/bun-builtins-decision-matrix.md +108 -0
- package/index.ts +51 -8
- package/package.json +73 -20
- package/src/data/json-rows.ts +13 -13
- package/src/ingress/body.ts +149 -0
- package/src/ingress/constants.ts +220 -48
- package/src/ingress/context.ts +136 -0
- package/src/ingress/decode/baked-result.ts +121 -0
- package/src/ingress/decode/fast-result.ts +160 -0
- package/src/ingress/decode/packed-sections.ts +56 -0
- package/src/ingress/decode/result-base.ts +172 -0
- package/src/ingress/errors.ts +72 -0
- package/src/ingress/fast.ts +152 -1021
- package/src/ingress/handlers.ts +563 -1001
- package/src/ingress/headers/baked-templates.ts +102 -0
- package/src/ingress/headers/cors.ts +44 -0
- package/src/ingress/headers/fast-templates.ts +238 -0
- package/src/ingress/headers/hsts.ts +83 -0
- package/src/ingress/health.ts +67 -0
- package/src/ingress/index.ts +130 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +75 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +150 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +96 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/routes/common.ts +38 -0
- package/src/ingress/routes/delete.ts +14 -0
- package/src/ingress/routes/echo.ts +107 -0
- package/src/ingress/routes/fallback.ts +23 -0
- package/src/ingress/routes/head.ts +37 -0
- package/src/ingress/routes/index.ts +10 -0
- package/src/ingress/routes/json-write.ts +153 -0
- package/src/ingress/routes/options.ts +36 -0
- package/src/ingress/routes/read.ts +45 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +443 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +157 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +217 -0
- package/src/integration/streaming.ts +79 -0
- package/src/integration/websocket.ts +92 -0
- package/src/loader/batch.ts +133 -0
- package/src/loader/cost.ts +104 -0
- package/src/loader/index.ts +930 -0
- package/src/loader/ops.ts +657 -0
- package/src/native/ffi.ts +1541 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +194 -0
- package/src/native/types.ts +598 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch.ts +431 -0
- package/src/rust-ffi/client.ts +88 -0
- package/src/rust-ffi/context.ts +164 -0
- package/src/rust-ffi/index.ts +40 -541
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +42 -0
- package/src/rust-ffi/scalar/crypto.ts +265 -0
- package/src/rust-ffi/scalar/factories.ts +233 -0
- package/src/rust-ffi/scalar/hashing.ts +72 -0
- package/src/rust-ffi/scalar/http.ts +206 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +450 -0
- package/src/rust-ffi/scalar/json.ts +45 -0
- package/src/rust-ffi/scalar/payload.ts +124 -0
- package/src/rust-ffi/text.ts +95 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +77 -0
- package/src/shared/adaptive.ts +68 -0
- package/src/shared/bench-classify.ts +209 -0
- package/src/shared/buffer-pool.ts +209 -0
- package/src/shared/bytes.ts +35 -5
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +74 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed.ts +350 -154
- package/src/shared/proven.ts +596 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +34 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +31 -0
- package/Cargo.toml +0 -73
- package/bench.ts +0 -3
- package/rust/batch.rs +0 -195
- package/rust/cookie_parser.rs +0 -70
- package/rust/cors.rs +0 -263
- package/rust/hashing.rs +0 -164
- package/rust/headers.rs +0 -234
- package/rust/hmac_sha256.rs +0 -113
- package/rust/http_parser.rs +0 -132
- package/rust/ingress.rs +0 -616
- package/rust/ingress_constants.rs +0 -89
- package/rust/ip_trust.rs +0 -185
- package/rust/json_ops.rs +0 -66
- package/rust/json_patch_ops.rs +0 -25
- package/rust/json_schema.rs +0 -151
- package/rust/json_ser.rs +0 -429
- package/rust/lib.rs +0 -48
- package/rust/method.rs +0 -141
- package/rust/mime_lookup.rs +0 -76
- package/rust/output.rs +0 -238
- package/rust/proxy.rs +0 -90
- package/rust/query_parser.rs +0 -141
- package/rust/random_token.rs +0 -52
- package/rust/rate_limit.rs +0 -203
- package/rust/terminal.rs +0 -95
- package/rust/unit_tests.rs +0 -843
- package/rust/url_codec.rs +0 -133
- package/rust/util.rs +0 -537
- package/rust/validation.rs +0 -107
- package/rust/websocket.rs +0 -23
- package/src/baseline/index.ts +0 -12
- package/src/baseline/tasks/cookie.ts +0 -22
- package/src/baseline/tasks/hashing.ts +0 -18
- package/src/baseline/tasks/hmac.ts +0 -38
- package/src/baseline/tasks/http.ts +0 -62
- package/src/baseline/tasks/json-patch.ts +0 -12
- package/src/baseline/tasks/json.ts +0 -50
- package/src/baseline/tasks/mime.ts +0 -5
- package/src/baseline/tasks/query.ts +0 -14
- package/src/baseline/tasks/token.ts +0 -15
- package/src/baseline/tasks/url.ts +0 -11
- package/src/baseline/tasks/validation.ts +0 -19
- package/src/baseline/tasks/websocket.ts +0 -14
- package/src/bench/assert.ts +0 -70
- package/src/bench/checks.ts +0 -227
- package/src/bench/checksum.ts +0 -27
- package/src/bench/comparisons.ts +0 -70
- package/src/bench/concurrent-worker.ts +0 -135
- package/src/bench/fixtures.ts +0 -233
- package/src/bench/index.ts +0 -2
- package/src/bench/measure.ts +0 -177
- package/src/bench/now.ts +0 -3
- package/src/bench/report.ts +0 -79
- package/src/bench/run.ts +0 -57
- package/src/bench/tasks/complex.ts +0 -232
- package/src/bench/tasks/concurrent.ts +0 -213
- package/src/bench/tasks/cookie.ts +0 -44
- package/src/bench/tasks/hashing.ts +0 -33
- package/src/bench/tasks/hmac.ts +0 -36
- package/src/bench/tasks/http.ts +0 -50
- package/src/bench/tasks/index.ts +0 -52
- package/src/bench/tasks/json-patch.ts +0 -21
- package/src/bench/tasks/json.ts +0 -33
- package/src/bench/tasks/mime.ts +0 -21
- package/src/bench/tasks/query.ts +0 -44
- package/src/bench/tasks/stress.ts +0 -142
- package/src/bench/tasks/token.ts +0 -20
- package/src/bench/tasks/url.ts +0 -34
- package/src/bench/tasks/validation.ts +0 -57
- package/src/bench/tasks/websocket.ts +0 -21
- package/src/bench/types.ts +0 -60
- package/src/ingress/packed-input.ts +0 -59
- package/src/shared/json.ts +0 -18
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/rust-ffi/packed.ts — Raw packed-wire FFI namespace.
|
|
2
|
+
//
|
|
3
|
+
// `rust.packed.*` is the zero-copy API: callers pass already-packed buffers and
|
|
4
|
+
// receive the raw packed output, plus the batch metadata/count helpers.
|
|
5
|
+
|
|
6
|
+
import { asNumber } from './options'
|
|
7
|
+
import { resolvePoolNative, type RustClientContext } from './context'
|
|
8
|
+
import type { PasswordHashOptions } from '../native'
|
|
9
|
+
|
|
10
|
+
/** Raw packed-wire FFI namespace. */
|
|
11
|
+
export interface RustPacked {
|
|
12
|
+
// ── Sync batch (packed in → packed out) ──
|
|
13
|
+
crc32BatchPacked(input: Uint8Array): Uint8Array
|
|
14
|
+
jsonValidBatchPacked(input: Uint8Array): Uint8Array
|
|
15
|
+
validateEmailBatchPacked(input: Uint8Array): Uint8Array
|
|
16
|
+
validateUuidBatchPacked(input: Uint8Array): Uint8Array
|
|
17
|
+
validateIpv4BatchPacked(input: Uint8Array): Uint8Array
|
|
18
|
+
validateIpv6BatchPacked(input: Uint8Array): Uint8Array
|
|
19
|
+
jsonSumBatchPacked(input: Uint8Array): Uint8Array
|
|
20
|
+
queryParseBatchPacked(input: Uint8Array): Uint8Array
|
|
21
|
+
cookieParseBatchPacked(input: Uint8Array): Uint8Array
|
|
22
|
+
httpParseRequestBatchPacked(input: Uint8Array): Uint8Array
|
|
23
|
+
/** Batch RFC 6902 JSON Patch (two packed lists, zipped) → packed results. */
|
|
24
|
+
jsonPatchBatchPacked(docs: Uint8Array, patches: Uint8Array): Uint8Array
|
|
25
|
+
/** Packed FNV-1a 64 batch (i64 per item). */
|
|
26
|
+
fnv1A64BatchPacked(input: Uint8Array): Uint8Array
|
|
27
|
+
/** Packed ETag batch (10 strong / 12 weak bytes per item). */
|
|
28
|
+
etagBatchPacked(input: Uint8Array, weak?: boolean | null): Uint8Array
|
|
29
|
+
urlEncodeBatchPacked(input: Uint8Array): Uint8Array
|
|
30
|
+
urlDecodeBatchPacked(input: Uint8Array): Uint8Array
|
|
31
|
+
urlDecodeBytesBatchPacked(input: Uint8Array): Uint8Array
|
|
32
|
+
mimeFromExtensionBatchPacked(input: Uint8Array): Uint8Array
|
|
33
|
+
wsAcceptKeyBatchPacked(input: Uint8Array): Uint8Array
|
|
34
|
+
|
|
35
|
+
// ── Batch metadata / counts ──
|
|
36
|
+
jsonValidBatchCountPacked(input: Uint8Array): number
|
|
37
|
+
validateEmailBatchCountPacked(input: Uint8Array): number
|
|
38
|
+
validateUuidBatchCountPacked(input: Uint8Array): number
|
|
39
|
+
validateIpv4BatchCountPacked(input: Uint8Array): number
|
|
40
|
+
validateIpv6BatchCountPacked(input: Uint8Array): number
|
|
41
|
+
jsonSumBatchTotalPacked(input: Uint8Array): number
|
|
42
|
+
queryParseBatchTotalLenPacked(input: Uint8Array): number
|
|
43
|
+
cookieParseBatchTotalLenPacked(input: Uint8Array): number
|
|
44
|
+
httpParseRequestBatchTotalLenPacked(input: Uint8Array): number
|
|
45
|
+
|
|
46
|
+
// ── Backend-framework features ──
|
|
47
|
+
passwordHashBatchPacked(
|
|
48
|
+
input: Uint8Array,
|
|
49
|
+
salt: Uint8Array,
|
|
50
|
+
options?: PasswordHashOptions | null,
|
|
51
|
+
): Uint8Array
|
|
52
|
+
/** Packed password-verify batch (two packed lists, zipped) → bitset. */
|
|
53
|
+
passwordVerifyBatchPacked(passwords: Uint8Array, phcs: Uint8Array): Uint8Array
|
|
54
|
+
/** Packed URL-resolve batch (two packed lists, zipped) → packed results. */
|
|
55
|
+
urlResolveBatchPacked(bases: Uint8Array, references: Uint8Array): Uint8Array
|
|
56
|
+
aeadEncryptBatchPacked(
|
|
57
|
+
input: Uint8Array,
|
|
58
|
+
key: Uint8Array,
|
|
59
|
+
nonce: Uint8Array,
|
|
60
|
+
algorithm?: string | null,
|
|
61
|
+
): Uint8Array
|
|
62
|
+
aeadDecryptBatchPacked(
|
|
63
|
+
input: Uint8Array,
|
|
64
|
+
key: Uint8Array,
|
|
65
|
+
nonce: Uint8Array,
|
|
66
|
+
algorithm?: string | null,
|
|
67
|
+
): Uint8Array
|
|
68
|
+
gzipCompressBatchPacked(input: Uint8Array, level?: number | null): Uint8Array
|
|
69
|
+
gzipDecompressBatchPacked(input: Uint8Array): Uint8Array
|
|
70
|
+
brotliCompressBatchPacked(input: Uint8Array, quality?: number | null): Uint8Array
|
|
71
|
+
brotliDecompressBatchPacked(input: Uint8Array): Uint8Array
|
|
72
|
+
jwtSignBatchPacked(
|
|
73
|
+
input: Uint8Array,
|
|
74
|
+
secret: Uint8Array,
|
|
75
|
+
ttlSeconds: number | null,
|
|
76
|
+
nowSeconds: number,
|
|
77
|
+
): Uint8Array
|
|
78
|
+
jwtVerifyBatchPacked(input: Uint8Array, secret: Uint8Array, nowSeconds: number): Uint8Array
|
|
79
|
+
/** Batch HMAC-SHA256 sign (packed in → hex-signature byte results). */
|
|
80
|
+
hmacSha256BatchPacked(input: Uint8Array, key: Uint8Array): Uint8Array
|
|
81
|
+
/** Batch HMAC-SHA256 verify (two packed lists: data + hex sigs, zipped) → bitset. */
|
|
82
|
+
hmacSha256VerifyBatchPacked(input: Uint8Array, sigs: Uint8Array, key: Uint8Array): Uint8Array
|
|
83
|
+
multipartParseBatchPacked(input: Uint8Array, boundary: Uint8Array): Uint8Array
|
|
84
|
+
templateRenderBatchPacked(input: Uint8Array, source: string): Uint8Array
|
|
85
|
+
sseEncodeBatchPacked(
|
|
86
|
+
input: Uint8Array,
|
|
87
|
+
event: string | null,
|
|
88
|
+
id: string | null,
|
|
89
|
+
retry: number | null,
|
|
90
|
+
): Uint8Array
|
|
91
|
+
wsFrameEncodeBatchPacked(
|
|
92
|
+
input: Uint8Array,
|
|
93
|
+
opcode: number,
|
|
94
|
+
mask: boolean,
|
|
95
|
+
fin: boolean,
|
|
96
|
+
): Uint8Array
|
|
97
|
+
wsFrameDecodeBatchPacked(input: Uint8Array): Uint8Array
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Build the `packed` namespace for a client context. */
|
|
101
|
+
export function buildPacked(ctx: RustClientContext): RustPacked {
|
|
102
|
+
// Packed ops are rayon-backed NAPI calls (no C ABI). Resolve each native fn
|
|
103
|
+
// on first use — ensuring the process-wide pool initializes at that point so
|
|
104
|
+
// `rust.configure({ rayonThreads })` still takes effect — and cache it via
|
|
105
|
+
// `resolvePoolNative` (shared with `resolveNative`/batch, context.ts). This
|
|
106
|
+
// removes BOTH the lazy-addon Proxy `get` and the old `withPoolInit`
|
|
107
|
+
// per-access Proxy from the packed hot path. The object keeps the
|
|
108
|
+
// `NativeAddon` type so the method bodies below typecheck unchanged.
|
|
109
|
+
const addon = new Proxy(ctx.addon, {
|
|
110
|
+
get: (_target, name) => resolvePoolNative(ctx, String(name)),
|
|
111
|
+
}) as typeof ctx.addon
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
crc32BatchPacked(input) {
|
|
115
|
+
return addon.crc32BatchPacked(input)
|
|
116
|
+
},
|
|
117
|
+
jsonValidBatchPacked(input) {
|
|
118
|
+
return addon.jsonValidBatchPacked(input)
|
|
119
|
+
},
|
|
120
|
+
validateEmailBatchPacked(input) {
|
|
121
|
+
return addon.validateEmailBatchPacked(input)
|
|
122
|
+
},
|
|
123
|
+
validateUuidBatchPacked(input) {
|
|
124
|
+
return addon.validateUuidBatchPacked(input)
|
|
125
|
+
},
|
|
126
|
+
validateIpv4BatchPacked(input) {
|
|
127
|
+
return addon.validateIpv4BatchPacked(input)
|
|
128
|
+
},
|
|
129
|
+
validateIpv6BatchPacked(input) {
|
|
130
|
+
return addon.validateIpv6BatchPacked(input)
|
|
131
|
+
},
|
|
132
|
+
jsonSumBatchPacked(input) {
|
|
133
|
+
return addon.jsonSumBatchPacked(input)
|
|
134
|
+
},
|
|
135
|
+
queryParseBatchPacked(input) {
|
|
136
|
+
return addon.queryParseBatchPacked(input)
|
|
137
|
+
},
|
|
138
|
+
cookieParseBatchPacked(input) {
|
|
139
|
+
return addon.cookieParseBatchPacked(input)
|
|
140
|
+
},
|
|
141
|
+
httpParseRequestBatchPacked(input) {
|
|
142
|
+
return addon.httpParseRequestBatchPacked(input)
|
|
143
|
+
},
|
|
144
|
+
jsonPatchBatchPacked(docs, patches) {
|
|
145
|
+
return addon.jsonPatchBatchPacked(docs, patches)
|
|
146
|
+
},
|
|
147
|
+
fnv1A64BatchPacked(input) {
|
|
148
|
+
return addon.fnv1A64BatchPacked(input)
|
|
149
|
+
},
|
|
150
|
+
etagBatchPacked(input, weak) {
|
|
151
|
+
return addon.etagBatchPacked(input, weak ?? null)
|
|
152
|
+
},
|
|
153
|
+
urlEncodeBatchPacked(input) {
|
|
154
|
+
return addon.urlEncodeBatchPacked(input)
|
|
155
|
+
},
|
|
156
|
+
urlDecodeBatchPacked(input) {
|
|
157
|
+
return addon.urlDecodeBatchPacked(input)
|
|
158
|
+
},
|
|
159
|
+
urlDecodeBytesBatchPacked(input) {
|
|
160
|
+
return addon.urlDecodeBytesBatchPacked(input)
|
|
161
|
+
},
|
|
162
|
+
mimeFromExtensionBatchPacked(input) {
|
|
163
|
+
return addon.mimeFromExtensionBatchPacked(input)
|
|
164
|
+
},
|
|
165
|
+
wsAcceptKeyBatchPacked(input) {
|
|
166
|
+
return addon.wsAcceptKeyBatchPacked(input)
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
jsonValidBatchCountPacked(input) {
|
|
170
|
+
return asNumber(addon.jsonValidBatchCountPacked(input))
|
|
171
|
+
},
|
|
172
|
+
validateEmailBatchCountPacked(input) {
|
|
173
|
+
return asNumber(addon.validateEmailBatchCountPacked(input))
|
|
174
|
+
},
|
|
175
|
+
validateUuidBatchCountPacked(input) {
|
|
176
|
+
return asNumber(addon.validateUuidBatchCountPacked(input))
|
|
177
|
+
},
|
|
178
|
+
validateIpv4BatchCountPacked(input) {
|
|
179
|
+
return asNumber(addon.validateIpv4BatchCountPacked(input))
|
|
180
|
+
},
|
|
181
|
+
validateIpv6BatchCountPacked(input) {
|
|
182
|
+
return asNumber(addon.validateIpv6BatchCountPacked(input))
|
|
183
|
+
},
|
|
184
|
+
jsonSumBatchTotalPacked(input) {
|
|
185
|
+
return asNumber(addon.jsonSumBatchTotalPacked(input))
|
|
186
|
+
},
|
|
187
|
+
queryParseBatchTotalLenPacked(input) {
|
|
188
|
+
return asNumber(addon.queryParseBatchTotalLenPacked(input))
|
|
189
|
+
},
|
|
190
|
+
cookieParseBatchTotalLenPacked(input) {
|
|
191
|
+
return asNumber(addon.cookieParseBatchTotalLenPacked(input))
|
|
192
|
+
},
|
|
193
|
+
httpParseRequestBatchTotalLenPacked(input) {
|
|
194
|
+
return asNumber(addon.httpParseRequestBatchTotalLenPacked(input))
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// ── Backend-framework features ──
|
|
198
|
+
passwordHashBatchPacked(input, salt, options) {
|
|
199
|
+
return addon.passwordHashBatchPacked(input, salt, options ?? null)
|
|
200
|
+
},
|
|
201
|
+
passwordVerifyBatchPacked(passwords, phcs) {
|
|
202
|
+
return addon.passwordVerifyBatchPacked(passwords, phcs)
|
|
203
|
+
},
|
|
204
|
+
urlResolveBatchPacked(bases, references) {
|
|
205
|
+
return addon.urlResolveBatchPacked(bases, references)
|
|
206
|
+
},
|
|
207
|
+
aeadEncryptBatchPacked(input, key, nonce, algorithm) {
|
|
208
|
+
return addon.aeadEncryptBatchPacked(input, key, nonce, algorithm ?? null)
|
|
209
|
+
},
|
|
210
|
+
aeadDecryptBatchPacked(input, key, nonce, algorithm) {
|
|
211
|
+
return addon.aeadDecryptBatchPacked(input, key, nonce, algorithm ?? null)
|
|
212
|
+
},
|
|
213
|
+
gzipCompressBatchPacked(input, level) {
|
|
214
|
+
return addon.gzipCompressBatchPacked(input, level ?? null)
|
|
215
|
+
},
|
|
216
|
+
gzipDecompressBatchPacked(input) {
|
|
217
|
+
return addon.gzipDecompressBatchPacked(input)
|
|
218
|
+
},
|
|
219
|
+
brotliCompressBatchPacked(input, quality) {
|
|
220
|
+
return addon.brotliCompressBatchPacked(input, quality ?? null)
|
|
221
|
+
},
|
|
222
|
+
brotliDecompressBatchPacked(input) {
|
|
223
|
+
return addon.brotliDecompressBatchPacked(input)
|
|
224
|
+
},
|
|
225
|
+
jwtSignBatchPacked(input, secret, ttlSeconds, nowSeconds) {
|
|
226
|
+
return addon.jwtSignBatchPacked(input, secret, ttlSeconds ?? null, nowSeconds)
|
|
227
|
+
},
|
|
228
|
+
jwtVerifyBatchPacked(input, secret, nowSeconds) {
|
|
229
|
+
return addon.jwtVerifyBatchPacked(input, secret, nowSeconds)
|
|
230
|
+
},
|
|
231
|
+
hmacSha256BatchPacked(input, key) {
|
|
232
|
+
return addon.hmacSha256BatchPacked(input, key)
|
|
233
|
+
},
|
|
234
|
+
hmacSha256VerifyBatchPacked(input, sigs, key) {
|
|
235
|
+
return addon.hmacSha256VerifyBatchPacked(input, sigs, key)
|
|
236
|
+
},
|
|
237
|
+
multipartParseBatchPacked(input, boundary) {
|
|
238
|
+
return addon.multipartParseBatchPacked(input, boundary)
|
|
239
|
+
},
|
|
240
|
+
templateRenderBatchPacked(input, source) {
|
|
241
|
+
return addon.templateRenderBatchPacked(input, source)
|
|
242
|
+
},
|
|
243
|
+
sseEncodeBatchPacked(input, event, id, retry) {
|
|
244
|
+
return addon.sseEncodeBatchPacked(input, event ?? null, id ?? null, retry ?? null)
|
|
245
|
+
},
|
|
246
|
+
wsFrameEncodeBatchPacked(input, opcode, mask, fin) {
|
|
247
|
+
return addon.wsFrameEncodeBatchPacked(input, opcode, mask, fin)
|
|
248
|
+
},
|
|
249
|
+
wsFrameDecodeBatchPacked(input) {
|
|
250
|
+
return addon.wsFrameDecodeBatchPacked(input)
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/rust-ffi/proven.ts — The performance-annotated export surface.
|
|
2
|
+
//
|
|
3
|
+
// `proven` exposes the FULL `rust.*` surface. Each exported function's JSDoc
|
|
4
|
+
// carries its measured performance vs the native JS baseline, annotated from
|
|
5
|
+
// the CPU benchmark report by `scripts/annotate-performance.ts` (run via
|
|
6
|
+
// `bun run check:annotate`). Functions that are SLOWER than the JS baseline
|
|
7
|
+
// (e.g. `jsonParse` vs Bun's JSON.parse, or schema validation vs ajv) are
|
|
8
|
+
// marked `@deprecated`.
|
|
9
|
+
//
|
|
10
|
+
// The registry (`src/shared/proven.ts`) is the single source of truth for the
|
|
11
|
+
// annotations; `bun run check:proven` audits it against the latest benchmark
|
|
12
|
+
// report (with `--fail` to gate CI).
|
|
13
|
+
|
|
14
|
+
import { rust, type RustClient } from './client'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The performance-annotated client: the full {@link RustClient} surface.
|
|
18
|
+
* Each exported function's JSDoc notes its measured performance vs the JS
|
|
19
|
+
* baseline; functions slower than the baseline carry `@deprecated`.
|
|
20
|
+
*/
|
|
21
|
+
export type ProvenClient = RustClient
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The full `rust.*` surface with benchmark-annotated JSDoc. Identical to
|
|
25
|
+
* `rust` — the annotations and `@deprecated` markers are what communicate
|
|
26
|
+
* performance now, not a curated subset.
|
|
27
|
+
*/
|
|
28
|
+
export const proven: ProvenClient = rust
|
|
29
|
+
|
|
30
|
+
// Re-export the registry + helpers so consumers can audit the surface.
|
|
31
|
+
export {
|
|
32
|
+
PROVEN_SURFACE,
|
|
33
|
+
provenStatus,
|
|
34
|
+
isProven,
|
|
35
|
+
provenSurface,
|
|
36
|
+
provenSummary,
|
|
37
|
+
} from '../shared/proven'
|
|
38
|
+
export type {
|
|
39
|
+
PerformanceStatus,
|
|
40
|
+
ProvenEntry,
|
|
41
|
+
ProvenKey,
|
|
42
|
+
} from '../shared/proven'
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// src/rust-ffi/scalar/crypto.ts — Auth / crypto scalar methods.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors rust/crypto/*: base64/hex codecs, signed cookies, CSRF, random
|
|
4
|
+
// tokens, JWT, password hashing and AEAD.
|
|
5
|
+
|
|
6
|
+
import type { PasswordHashOptions } from '../../native'
|
|
7
|
+
import { getBunFFI } from '../../native/ffi'
|
|
8
|
+
import { isBun } from '../../shared/runtime'
|
|
9
|
+
import type { RustClientContext } from '../context'
|
|
10
|
+
|
|
11
|
+
const encoder = new TextEncoder()
|
|
12
|
+
const decoder = new TextDecoder()
|
|
13
|
+
|
|
14
|
+
/** Auth / crypto scalar methods (`Pick<RustScalar, ...>`). */
|
|
15
|
+
export function buildCrypto(ctx: RustClientContext) {
|
|
16
|
+
const { addon } = ctx
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
randomToken(byteLen: number): Uint8Array {
|
|
20
|
+
// Optimal by default under Bun: `crypto.getRandomValues` + native hex
|
|
21
|
+
// beats the rust+FFI crossing for token-sized draws (decision matrix).
|
|
22
|
+
// Preserves the native 16 MiB allocation guard and the hex format
|
|
23
|
+
// (2n bytes). Node keeps the addon.
|
|
24
|
+
if (isBun()) {
|
|
25
|
+
if (byteLen > 16 * 1024 * 1024) {
|
|
26
|
+
throw new RangeError('randomToken: byteLen exceeds 16 MiB limit')
|
|
27
|
+
}
|
|
28
|
+
return encoder.encode(
|
|
29
|
+
Buffer.from(crypto.getRandomValues(new Uint8Array(byteLen))).toString('hex'),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
const ffi = getBunFFI()
|
|
33
|
+
if (ffi) return ffi.randomToken(byteLen)
|
|
34
|
+
return addon.randomToken(byteLen)
|
|
35
|
+
},
|
|
36
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array {
|
|
37
|
+
// Bun's Buffer base64 (SIMD) beats the rust+FFI crossing (~2x, measured)
|
|
38
|
+
// for the standard (url-safe=false, padded) case; byte-parity pinned by
|
|
39
|
+
// test/unit/features/encoding.test.ts. url-safe/unpadded falls through to
|
|
40
|
+
// the native path (Buffer can't express base64url without string surgery).
|
|
41
|
+
if (isBun() && !urlSafe && padding !== false) {
|
|
42
|
+
return encoder.encode(Buffer.from(input).toString('base64'))
|
|
43
|
+
}
|
|
44
|
+
const ffi = getBunFFI()
|
|
45
|
+
if (ffi) return ffi.base64Encode(input, urlSafe, padding)
|
|
46
|
+
return addon.base64Encode(input, urlSafe ?? undefined, padding ?? undefined)
|
|
47
|
+
},
|
|
48
|
+
base64EncodeInto(
|
|
49
|
+
input: Uint8Array,
|
|
50
|
+
output: Uint8Array,
|
|
51
|
+
urlSafe?: boolean,
|
|
52
|
+
padding?: boolean,
|
|
53
|
+
): number {
|
|
54
|
+
const ffi = getBunFFI()
|
|
55
|
+
if (ffi) return ffi.base64EncodeInto(input, output, urlSafe, padding)
|
|
56
|
+
return addon.base64EncodeInto(input, output, urlSafe ?? undefined, padding ?? undefined)
|
|
57
|
+
},
|
|
58
|
+
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array {
|
|
59
|
+
const ffi = getBunFFI()
|
|
60
|
+
if (ffi) return ffi.base64Decode(input, urlSafe, padding)
|
|
61
|
+
return addon.base64Decode(input, urlSafe ?? undefined, padding ?? undefined)
|
|
62
|
+
},
|
|
63
|
+
base64DecodeInto(
|
|
64
|
+
input: Uint8Array,
|
|
65
|
+
output: Uint8Array,
|
|
66
|
+
urlSafe?: boolean,
|
|
67
|
+
padding?: boolean,
|
|
68
|
+
): number {
|
|
69
|
+
const ffi = getBunFFI()
|
|
70
|
+
if (ffi) return ffi.base64DecodeInto(input, output, urlSafe, padding)
|
|
71
|
+
return addon.base64DecodeInto(input, output, urlSafe ?? undefined, padding ?? undefined)
|
|
72
|
+
},
|
|
73
|
+
base64UrlEncode(input: Uint8Array): Uint8Array {
|
|
74
|
+
const ffi = getBunFFI()
|
|
75
|
+
if (ffi) return ffi.base64Encode(input, true, false)
|
|
76
|
+
return addon.base64UrlEncode(input)
|
|
77
|
+
},
|
|
78
|
+
base64UrlDecode(input: Uint8Array): Uint8Array {
|
|
79
|
+
const ffi = getBunFFI()
|
|
80
|
+
if (ffi) return ffi.base64Decode(input, true, false)
|
|
81
|
+
return addon.base64UrlDecode(input)
|
|
82
|
+
},
|
|
83
|
+
hexEncode(input: Uint8Array): Uint8Array {
|
|
84
|
+
// Bun fast path writes straight into a fresh buffer — no napi Buffer
|
|
85
|
+
// alloc + copy (~13x cheaper crossing for small inputs).
|
|
86
|
+
const ffi = getBunFFI()
|
|
87
|
+
if (ffi) return ffi.hexEncode(input)
|
|
88
|
+
return addon.hexEncode(input)
|
|
89
|
+
},
|
|
90
|
+
hexEncodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
91
|
+
const ffi = getBunFFI()
|
|
92
|
+
if (ffi) return ffi.hexEncodeInto(input, output)
|
|
93
|
+
return addon.hexEncodeInto(input, output)
|
|
94
|
+
},
|
|
95
|
+
hexDecode(input: Uint8Array): Uint8Array {
|
|
96
|
+
const ffi = getBunFFI()
|
|
97
|
+
if (ffi) return ffi.hexDecode(input)
|
|
98
|
+
return addon.hexDecode(input)
|
|
99
|
+
},
|
|
100
|
+
hexDecodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
101
|
+
const ffi = getBunFFI()
|
|
102
|
+
if (ffi) return ffi.hexDecodeInto(input, output)
|
|
103
|
+
return addon.hexDecodeInto(input, output)
|
|
104
|
+
},
|
|
105
|
+
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array {
|
|
106
|
+
const ffi = getBunFFI()
|
|
107
|
+
if (ffi) return ffi.signCookie(value, secret)
|
|
108
|
+
return addon.signCookie(value, secret)
|
|
109
|
+
},
|
|
110
|
+
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number {
|
|
111
|
+
const ffi = getBunFFI()
|
|
112
|
+
if (ffi) return ffi.signCookieInto(value, secret, output)
|
|
113
|
+
const bytes = addon.signCookie(value, secret)
|
|
114
|
+
if (output.length < bytes.length) throw new Error('sign cookie: output buffer too small')
|
|
115
|
+
output.set(bytes)
|
|
116
|
+
return bytes.length
|
|
117
|
+
},
|
|
118
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | null {
|
|
119
|
+
const ffi = getBunFFI()
|
|
120
|
+
if (ffi) return ffi.verifyCookie(signed, secret)
|
|
121
|
+
return addon.verifyCookie(signed, secret)
|
|
122
|
+
},
|
|
123
|
+
csrfToken(secret: Uint8Array): Uint8Array {
|
|
124
|
+
const ffi = getBunFFI()
|
|
125
|
+
if (ffi) return ffi.csrfToken(secret)
|
|
126
|
+
return addon.csrfToken(secret)
|
|
127
|
+
},
|
|
128
|
+
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean {
|
|
129
|
+
const ffi = getBunFFI()
|
|
130
|
+
if (ffi) return ffi.csrfVerify(token, secret)
|
|
131
|
+
return addon.csrfVerify(token, secret)
|
|
132
|
+
},
|
|
133
|
+
jwtSign(
|
|
134
|
+
claims: Record<string, unknown>,
|
|
135
|
+
secret: Uint8Array,
|
|
136
|
+
ttlSeconds?: number | null,
|
|
137
|
+
nowSeconds?: number,
|
|
138
|
+
): Uint8Array {
|
|
139
|
+
return addon.jwtSign(
|
|
140
|
+
claims,
|
|
141
|
+
secret,
|
|
142
|
+
ttlSeconds ?? null,
|
|
143
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
144
|
+
)
|
|
145
|
+
},
|
|
146
|
+
jwtSignBytes(
|
|
147
|
+
claimsJson: Uint8Array,
|
|
148
|
+
secret: Uint8Array,
|
|
149
|
+
ttlSeconds?: number | null,
|
|
150
|
+
nowSeconds?: number,
|
|
151
|
+
): Uint8Array {
|
|
152
|
+
const ffi = getBunFFI()
|
|
153
|
+
if (ffi) {
|
|
154
|
+
// The C-ABI `ttl <= 0` sentinel matches napi's Option<i64> (inject only
|
|
155
|
+
// when positive), and nowSeconds defaults identically.
|
|
156
|
+
return ffi.jwtSignBytes(
|
|
157
|
+
claimsJson,
|
|
158
|
+
secret,
|
|
159
|
+
ttlSeconds ?? 0,
|
|
160
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
return addon.jwtSignBytes(
|
|
164
|
+
claimsJson,
|
|
165
|
+
secret,
|
|
166
|
+
ttlSeconds ?? null,
|
|
167
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown {
|
|
171
|
+
return addon.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000))
|
|
172
|
+
},
|
|
173
|
+
passwordHash(
|
|
174
|
+
password: Uint8Array,
|
|
175
|
+
salt: Uint8Array,
|
|
176
|
+
options?: PasswordHashOptions | null,
|
|
177
|
+
): Uint8Array {
|
|
178
|
+
const ffi = getBunFFI()
|
|
179
|
+
if (ffi) {
|
|
180
|
+
// Resolve the napi defaults (rust/crypto/argon2.rs resolve_opts).
|
|
181
|
+
const o = options ?? {}
|
|
182
|
+
return ffi.passwordHash(
|
|
183
|
+
password,
|
|
184
|
+
salt,
|
|
185
|
+
o.mCost ?? 19456,
|
|
186
|
+
o.tCost ?? 2,
|
|
187
|
+
o.pCost ?? 1,
|
|
188
|
+
o.outLen ?? 32,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
return addon.passwordHash(password, salt, options ?? null)
|
|
192
|
+
},
|
|
193
|
+
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean {
|
|
194
|
+
const ffi = getBunFFI()
|
|
195
|
+
if (ffi) return ffi.passwordVerify(password, phc)
|
|
196
|
+
return addon.passwordVerify(password, phc)
|
|
197
|
+
},
|
|
198
|
+
passwordHashBcrypt(password: Uint8Array, cost: number): string {
|
|
199
|
+
const ffi = getBunFFI()
|
|
200
|
+
if (ffi) return decoder.decode(ffi.passwordHashBcrypt(password, cost))
|
|
201
|
+
return addon.passwordHashBcrypt(password, cost)
|
|
202
|
+
},
|
|
203
|
+
passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean {
|
|
204
|
+
const ffi = getBunFFI()
|
|
205
|
+
if (ffi) return ffi.passwordVerifyBcrypt(password, encoder.encode(hash))
|
|
206
|
+
return addon.passwordVerifyBcrypt(password, hash)
|
|
207
|
+
},
|
|
208
|
+
pbkdf2Sha256(
|
|
209
|
+
password: Uint8Array,
|
|
210
|
+
salt: Uint8Array,
|
|
211
|
+
rounds: number,
|
|
212
|
+
dkLen: number,
|
|
213
|
+
): Uint8Array {
|
|
214
|
+
const ffi = getBunFFI()
|
|
215
|
+
if (ffi) return ffi.pbkdf2Sha256(password, salt, rounds, dkLen)
|
|
216
|
+
return addon.pbkdf2Sha256(password, salt, rounds, dkLen)
|
|
217
|
+
},
|
|
218
|
+
aeadEncrypt(
|
|
219
|
+
key: Uint8Array,
|
|
220
|
+
nonce: Uint8Array,
|
|
221
|
+
plaintext: Uint8Array,
|
|
222
|
+
algorithm?: string | null,
|
|
223
|
+
): Uint8Array {
|
|
224
|
+
const ffi = getBunFFI()
|
|
225
|
+
if (ffi) {
|
|
226
|
+
return ffi.aeadEncrypt(key, nonce, plaintext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
227
|
+
}
|
|
228
|
+
return addon.aeadEncrypt(key, nonce, plaintext, algorithm ?? null)
|
|
229
|
+
},
|
|
230
|
+
aeadEncryptInto(
|
|
231
|
+
key: Uint8Array,
|
|
232
|
+
nonce: Uint8Array,
|
|
233
|
+
plaintext: Uint8Array,
|
|
234
|
+
output: Uint8Array,
|
|
235
|
+
algorithm?: string | null,
|
|
236
|
+
): number {
|
|
237
|
+
const ffi = getBunFFI()
|
|
238
|
+
if (ffi) {
|
|
239
|
+
return ffi.aeadEncryptInto(
|
|
240
|
+
key,
|
|
241
|
+
nonce,
|
|
242
|
+
plaintext,
|
|
243
|
+
output,
|
|
244
|
+
algorithm === 'chacha20-poly1305' ? 1 : 0,
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
const bytes = addon.aeadEncrypt(key, nonce, plaintext, algorithm ?? null)
|
|
248
|
+
if (output.length < bytes.length) throw new Error('aead encrypt: output buffer too small')
|
|
249
|
+
output.set(bytes)
|
|
250
|
+
return bytes.length
|
|
251
|
+
},
|
|
252
|
+
aeadDecrypt(
|
|
253
|
+
key: Uint8Array,
|
|
254
|
+
nonce: Uint8Array,
|
|
255
|
+
ciphertext: Uint8Array,
|
|
256
|
+
algorithm?: string | null,
|
|
257
|
+
): Uint8Array | null {
|
|
258
|
+
const ffi = getBunFFI()
|
|
259
|
+
if (ffi) {
|
|
260
|
+
return ffi.aeadDecrypt(key, nonce, ciphertext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
261
|
+
}
|
|
262
|
+
return addon.aeadDecrypt(key, nonce, ciphertext, algorithm ?? null)
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
}
|