castrum 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -630
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1653 -1817
- package/dist/index.js +6172 -4294
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +111 -17
- package/docs/BENCHMARKS.md +117 -49
- package/docs/CASE_STUDY.md +243 -0
- package/docs/ENVIRONMENT.md +24 -7
- package/docs/FFI_BUN_GUIDE.md +335 -0
- package/docs/GETTING_STARTED.md +18 -34
- package/docs/HOW_TO_ADD_AN_OP.md +180 -0
- package/docs/INGRESS-ROUTER.md +82 -0
- package/docs/INGRESS.md +11 -7
- package/docs/NATIVE-ROUTE.md +77 -0
- package/docs/REPO_MAP.md +103 -53
- package/docs/adr/0001-two-wire-formats.md +1 -1
- package/docs/adr/0003-bun-builtins-delegation.md +6 -5
- package/docs/ai/LOCAL_DEV.md +81 -0
- package/docs/ai/TREE.md +1070 -0
- package/docs/bun-builtins-decision-matrix.md +25 -18
- package/index.ts +9 -16
- package/package.json +41 -28
- package/src/ingress/body.ts +167 -27
- package/src/ingress/decode/result-base.ts +10 -2
- package/src/ingress/handlers.ts +116 -285
- package/src/ingress/headers/security.ts +41 -0
- package/src/ingress/health.ts +1 -0
- package/src/ingress/index.ts +45 -169
- package/src/ingress/native-route.ts +182 -0
- package/src/ingress/packing/gather-raw-headers.ts +110 -22
- package/src/ingress/packing/header-packing.ts +5 -5
- package/src/ingress/packing/input-packer.ts +17 -3
- package/src/ingress/packing/route-wire.ts +197 -0
- package/src/ingress/packing/select-headers.ts +20 -7
- package/src/ingress/path-matcher.ts +115 -0
- package/src/ingress/response/baked-response.ts +302 -0
- package/src/ingress/router.ts +277 -0
- package/src/ingress/routes/common.ts +50 -1
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +3 -13
- package/src/ingress/routes/index.ts +5 -4
- package/src/ingress/routes/json-write.ts +26 -47
- package/src/ingress/routes/native.ts +130 -0
- package/src/ingress/routes/options.ts +2 -12
- package/src/ingress/routes/read.ts +3 -13
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +7 -7
- package/src/ingress/server.ts +83 -112
- package/src/ingress/sync.ts +151 -0
- package/src/ingress/types.ts +44 -1
- package/src/integration/pipeline.ts +3 -0
- package/src/integration/streaming.ts +2 -3
- package/src/integration/websocket.ts +4 -3
- package/src/loader/batch.ts +4 -0
- package/src/loader/cost.ts +1 -0
- package/src/loader/index.ts +43 -228
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/build/codecs.ts +736 -0
- package/src/native/ffi/build/compress.ts +248 -0
- package/src/native/ffi/build/instances.ts +598 -0
- package/src/native/ffi/build/parse.ts +539 -0
- package/src/native/ffi/build/util.ts +140 -0
- package/src/native/ffi/build.ts +94 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +22 -0
- package/src/native/ffi/types.ts +499 -0
- package/src/native/ffi.ts +307 -1357
- package/src/native/loader.ts +52 -6
- package/src/native/types/instances.ts +279 -0
- package/src/native/types.ts +41 -238
- package/src/runtime/builtins.ts +131 -0
- package/src/runtime/codec.ts +147 -0
- package/src/runtime/detect.ts +36 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/native.ts +35 -0
- package/src/runtime/server.ts +31 -0
- package/src/runtime/transport.ts +64 -0
- package/src/runtime/types.ts +173 -0
- package/src/runtime/uuid.ts +45 -0
- package/src/runtime/websocket.ts +30 -0
- package/src/rust-ffi/addon.ts +1 -1
- package/src/rust-ffi/batch/build.ts +155 -0
- package/src/rust-ffi/batch/index.ts +8 -0
- package/src/rust-ffi/batch/types.ts +92 -0
- package/src/rust-ffi/client.ts +5 -6
- package/src/rust-ffi/context.ts +39 -10
- package/src/rust-ffi/index.ts +28 -17
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +1 -1
- package/src/rust-ffi/packed.ts +40 -2
- package/src/rust-ffi/proven.ts +16 -30
- package/src/rust-ffi/scalar/crypto.ts +188 -107
- package/src/rust-ffi/scalar/factories.ts +257 -27
- package/src/rust-ffi/scalar/hashing.ts +43 -41
- package/src/rust-ffi/scalar/http.ts +208 -77
- package/src/rust-ffi/scalar/index.ts +3 -3
- package/src/rust-ffi/scalar/interface.ts +88 -213
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +28 -12
- package/src/rust-ffi/scalar/payload.ts +159 -42
- package/src/rust-ffi/text.ts +31 -5
- package/src/selection.ts +27 -31
- package/src/shared/adaptive.ts +1 -2
- package/src/shared/buffer-pool.ts +72 -17
- package/src/shared/bytes.ts +75 -6
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +4 -4
- package/src/shared/log.ts +1 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/{packed.ts → packed/wire.ts} +31 -39
- package/src/shared/proven.ts +189 -496
- package/src/shared/runtime.ts +20 -25
- package/src/shared/uuid.ts +12 -28
- package/src/data/json-rows.ts +0 -33
- package/src/rust-ffi/batch.ts +0 -431
- package/src/shared/bench-classify.ts +0 -209
package/dist/index.d.ts
CHANGED
|
@@ -15,13 +15,14 @@ export interface SchemaError {
|
|
|
15
15
|
export interface SchemaValidatorInstance {
|
|
16
16
|
/** Validate a single JSON document. */
|
|
17
17
|
validate(input: Uint8Array): boolean;
|
|
18
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_schema_validator_validate`. */
|
|
19
|
+
innerPtr?(): bigint;
|
|
18
20
|
/** Validate a single JSON document, returning detailed errors (empty = valid). */
|
|
19
21
|
validateDetailed(input: Uint8Array): SchemaError[];
|
|
20
22
|
/** Validate a single JSON document, returning only the first error (null = valid). */
|
|
21
23
|
validateFirstError(input: Uint8Array): SchemaError | null;
|
|
22
24
|
validateBatchPackedCount(packed: Uint8Array): number;
|
|
23
25
|
validateBatchPackedBitset(packed: Uint8Array): Uint8Array;
|
|
24
|
-
validateBatchStreaming(batchBytes: Uint8Array): number;
|
|
25
26
|
/**
|
|
26
27
|
* One-pass validate + extract: validate `input` against the schema and
|
|
27
28
|
* capture scalar values / array lengths at `paths` during the same native
|
|
@@ -74,12 +75,13 @@ export interface MediaTypeResult {
|
|
|
74
75
|
/** The native `MediaTypeParser` class instance. */
|
|
75
76
|
export interface MediaTypeParserInstance {
|
|
76
77
|
parse(input: Uint8Array): MediaTypeResult;
|
|
77
|
-
/** Wildcard match: any/any, type/any, or exact type/subtype. */
|
|
78
|
-
matches(actual: Uint8Array, expected: Uint8Array): boolean;
|
|
79
78
|
}
|
|
80
79
|
/** The native `ConditionalRequest` class instance (per-resource 304 checks). */
|
|
81
80
|
export interface ConditionalRequestInstance {
|
|
82
81
|
isNotModified(ifNoneMatch: Uint8Array | null, ifModifiedSince: Uint8Array | null): boolean;
|
|
82
|
+
/** Opaque handle to the precompiled state (present when the bun:ffi fast path
|
|
83
|
+
* is active) — `castrum_conditional_is_not_modified` in rust/ffi.rs. */
|
|
84
|
+
innerPtr?(): bigint;
|
|
83
85
|
}
|
|
84
86
|
/** One parsed Accept-Encoding preference. */
|
|
85
87
|
export interface EncodingPrefResult {
|
|
@@ -87,10 +89,20 @@ export interface EncodingPrefResult {
|
|
|
87
89
|
q: number;
|
|
88
90
|
order: number;
|
|
89
91
|
}
|
|
90
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* The native `AcceptNegotiator` class instance (supported encodings compiled
|
|
94
|
+
* once). Best supported encoding for a header, or `null` for identity.
|
|
95
|
+
*/
|
|
91
96
|
export interface AcceptNegotiatorInstance {
|
|
92
97
|
/** Best supported encoding for `header`, or null for identity. */
|
|
93
98
|
negotiate(header: Uint8Array): string | null;
|
|
99
|
+
/** Best supported encoding for `header` with SERVER-preference tie-breaking
|
|
100
|
+
* (RFC 7231 server semantics — the supported list's order breaks q-value
|
|
101
|
+
* ties), or null for identity. On the napi fallback this is the projected
|
|
102
|
+
* `negotiate_server_preference` method. */
|
|
103
|
+
negotiateServerPreference(header: Uint8Array): string | null;
|
|
104
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_accept_negotiator_negotiate`. */
|
|
105
|
+
innerPtr?(): bigint;
|
|
94
106
|
}
|
|
95
107
|
/** The native `Base64Codec` class instance (config compiled once). */
|
|
96
108
|
export interface Base64CodecInstance {
|
|
@@ -107,6 +119,8 @@ export interface RateCheckResult {
|
|
|
107
119
|
export interface RateLimiterInstance {
|
|
108
120
|
check(key: string, nowMs: number): RateCheckResult;
|
|
109
121
|
checkKey(key: number, nowMs: number): RateCheckResult;
|
|
122
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_rate_limiter_check*`. */
|
|
123
|
+
innerPtr?(): bigint;
|
|
110
124
|
}
|
|
111
125
|
/** The native `CookieSigner` class instance (HMAC key compiled once). */
|
|
112
126
|
export interface CookieSignerInstance {
|
|
@@ -122,6 +136,9 @@ export interface CsrfProtectorInstance {
|
|
|
122
136
|
/** The native `UrlBuilder` class instance (base parsed once). */
|
|
123
137
|
export interface UrlBuilderInstance {
|
|
124
138
|
resolve(reference: Uint8Array): Uint8Array;
|
|
139
|
+
/** Opaque handle to the precompiled base (present when the bun:ffi fast path
|
|
140
|
+
* is active) — `castrum_url_builder_resolve` in rust/ffi.rs. */
|
|
141
|
+
innerPtr?(): bigint;
|
|
125
142
|
}
|
|
126
143
|
/** Options for `passwordHash` (argon2id). */
|
|
127
144
|
export interface PasswordHashOptions {
|
|
@@ -150,11 +167,15 @@ export interface TemplateRendererInstance {
|
|
|
150
167
|
renderBytes(contextJson: Uint8Array): Uint8Array;
|
|
151
168
|
/** Parallel batch render: packed contexts in → packed rendered out. */
|
|
152
169
|
renderBatchPacked(data: Uint8Array): Uint8Array;
|
|
170
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_template_render`. */
|
|
171
|
+
innerPtr?(): bigint;
|
|
153
172
|
}
|
|
154
173
|
/** The native `MediaTypeMatcher` class instance (expected precompiled once). */
|
|
155
174
|
export interface MediaTypeMatcherInstance {
|
|
156
175
|
/** Wildcard match against the precompiled expected type. */
|
|
157
176
|
matches(actual: Uint8Array): boolean;
|
|
177
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_media_type_matcher_matches`. */
|
|
178
|
+
innerPtr?(): bigint;
|
|
158
179
|
}
|
|
159
180
|
/** The native `JwtSigner` class instance (HS256 key + ttl compiled once). */
|
|
160
181
|
export interface JwtSignerInstance {
|
|
@@ -162,6 +183,8 @@ export interface JwtSignerInstance {
|
|
|
162
183
|
/** Sign from pre-serialized claim JSON bytes (no napi Value marshal). */
|
|
163
184
|
signBytes(claimsJson: Uint8Array, nowSeconds: number): Uint8Array;
|
|
164
185
|
verify(token: Uint8Array, nowSeconds: number): unknown;
|
|
186
|
+
/** Opaque handle (bun:ffi fast path) — `castrum_jwt_signer_*`. */
|
|
187
|
+
innerPtr?(): bigint;
|
|
165
188
|
}
|
|
166
189
|
/** The native `AeadCipher` class instance (algorithm + key compiled once). */
|
|
167
190
|
export interface AeadCipherInstance {
|
|
@@ -173,63 +196,13 @@ export interface Argon2HasherInstance {
|
|
|
173
196
|
hash(password: Uint8Array, salt: Uint8Array): Uint8Array;
|
|
174
197
|
verify(password: Uint8Array, phc: Uint8Array): boolean;
|
|
175
198
|
}
|
|
176
|
-
/**
|
|
177
|
-
export
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* initializes the pool with defaults, so this option only takes effect when
|
|
184
|
-
* the pool has not yet been created. For reliable control set
|
|
185
|
-
* `CASTRUM_RAYON_THREADS` / `RUST_RAYON_THREADS` before import.
|
|
186
|
-
* Default: max(1, hardwareConcurrency - 1).
|
|
187
|
-
*/
|
|
188
|
-
rayonThreads?: number;
|
|
189
|
-
/** Cache MIME lookups keyed by normalized extension. Default: true. */
|
|
190
|
-
mimeCache?: boolean;
|
|
191
|
-
/** Reuse HMAC signers keyed by key-buffer identity. Default: true. */
|
|
192
|
-
hmacCache?: boolean;
|
|
193
|
-
}
|
|
194
|
-
/** String-oriented FFI namespace. */
|
|
195
|
-
export interface RustText {
|
|
196
|
-
/**
|
|
197
|
-
* Resolve a lowercase file extension to a MIME type string (e.g. `"html"` →
|
|
198
|
-
* `"text/html"`). Unknown extensions return `"application/octet-stream"`.
|
|
199
|
-
* @param ext the file extension WITHOUT a leading dot, case-insensitive.
|
|
200
|
-
* @returns the MIME type for `ext`.
|
|
201
|
-
*/
|
|
202
|
-
mimeFromExtension(ext: string): string;
|
|
203
|
-
/**
|
|
204
|
-
* RFC 3986 percent-encode a string (the `encodeURIComponent` unreserved
|
|
205
|
-
* set). Under Bun this delegates to the native `encodeURIComponent` for
|
|
206
|
-
* ~4x speed with byte-for-byte parity.
|
|
207
|
-
* @param input the string to encode.
|
|
208
|
-
* @returns the percent-encoded string.
|
|
209
|
-
*/
|
|
210
|
-
urlEncode(input: string): string;
|
|
211
|
-
/**
|
|
212
|
-
* Percent-decode a string. Throws on malformed `%XX` or invalid UTF-8
|
|
213
|
-
* output (parity with `decodeURIComponent`, which is used under Bun).
|
|
214
|
-
* @param input the percent-encoded string to decode.
|
|
215
|
-
* @returns the decoded string.
|
|
216
|
-
*/
|
|
217
|
-
urlDecode(input: string): string;
|
|
218
|
-
/**
|
|
219
|
-
* Compute the RFC 6455 `Sec-WebSocket-Accept` value for a client key.
|
|
220
|
-
* @param key the base64 `Sec-WebSocket-Key` header value.
|
|
221
|
-
* @returns the 28-byte base64 accept value.
|
|
222
|
-
*/
|
|
223
|
-
wsAcceptKey(key: string): string;
|
|
224
|
-
/** @param input an email address. @returns whether it is RFC-valid. */
|
|
225
|
-
validateEmail(input: string): boolean;
|
|
226
|
-
/** @param input a UUID string. @returns whether it is a valid (v1-v8) UUID. */
|
|
227
|
-
validateUuid(input: string): boolean;
|
|
228
|
-
/** @param input an IPv4 address. @returns whether it is valid. */
|
|
229
|
-
validateIpv4(input: string): boolean;
|
|
230
|
-
/** @param input an IPv6 address. @returns whether it is valid. */
|
|
231
|
-
validateIpv6(input: string): boolean;
|
|
232
|
-
}
|
|
199
|
+
/** Parse a query string (`a=1&b=2`) into an object via the native parser. */
|
|
200
|
+
export declare function parseQueryString(query: string): Record<string, string | string[]>;
|
|
201
|
+
/** Parse a cookie header (`a=1; b=2`) into an object via the native parser. */
|
|
202
|
+
export declare function parseCookieHeader(header: string): Record<string, string | string[]>;
|
|
203
|
+
/** Parse an application/x-www-form-urlencoded body into a key/value object. */
|
|
204
|
+
export declare function parseFormBody(body: Uint8Array): Record<string, string | string[]>;
|
|
205
|
+
/** Native JSON-Schema validator handle (see `createSchemaValidator`). */
|
|
233
206
|
export type SchemaValidator = SchemaValidatorInstance;
|
|
234
207
|
/**
|
|
235
208
|
* Unpack a packed u32 array: `[u32 count]{[u32 value]}` → `Uint32Array`.
|
|
@@ -252,18 +225,32 @@ export declare function unpackI64ArrayAsBigInt(bytes: Uint8Array): BigInt64Array
|
|
|
252
225
|
* (zero-copy) — copy them if held beyond the buffer's lifetime.
|
|
253
226
|
*/
|
|
254
227
|
export declare function unpackByteResults(bytes: Uint8Array): Uint8Array[];
|
|
228
|
+
/** A decoded `[name, value]` pair from a packed buffer. */
|
|
255
229
|
export type Pair = [
|
|
256
230
|
string,
|
|
257
231
|
string
|
|
258
232
|
];
|
|
233
|
+
/**
|
|
234
|
+
* Decode a packed `[u32 count][u32 keyLen][key][u32 valLen][val]...` buffer
|
|
235
|
+
* into `[name, value]` string pairs (see `packPairs` for the layout).
|
|
236
|
+
*/
|
|
259
237
|
export declare function readPairsPacked(bytes: Uint8Array): Pair[];
|
|
238
|
+
/**
|
|
239
|
+
* Fold `[name, value]` pairs into an object; repeated names collect into a
|
|
240
|
+
* string array (query-string/cookie semantics).
|
|
241
|
+
*/
|
|
260
242
|
export declare function pairsToObject(pairs: Pair[]): Record<string, string | string[]>;
|
|
243
|
+
/** A decoded HTTP request from the native packed parser. */
|
|
261
244
|
export interface ParsedHttpRequestPacked {
|
|
262
245
|
method: string;
|
|
263
246
|
path: string;
|
|
264
247
|
version: string;
|
|
265
248
|
headers: Record<string, string>;
|
|
266
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Decode a native `httpParseRequestPacked` buffer into a plain request object
|
|
252
|
+
* (zero-alloc on the Rust side; this builds the JS strings once).
|
|
253
|
+
*/
|
|
267
254
|
export declare function readHttpPacked(bytes: Uint8Array): ParsedHttpRequestPacked;
|
|
268
255
|
/**
|
|
269
256
|
* Build a packed batch buffer. Returns an OWNED buffer (safe to hold across
|
|
@@ -271,1521 +258,1411 @@ export declare function readHttpPacked(bytes: Uint8Array): ParsedHttpRequestPack
|
|
|
271
258
|
* guarantee synchronous consumption should use `withPackScratch` instead.
|
|
272
259
|
*/
|
|
273
260
|
export declare function packBatch(items: Uint8Array[]): Uint8Array;
|
|
261
|
+
/**
|
|
262
|
+
* Pack `[name, value]` string pairs into the native `[u32 count]...` layout
|
|
263
|
+
* that `readPairsPacked` / the batch validators consume.
|
|
264
|
+
*/
|
|
274
265
|
export declare function packPairs(pairs: Array<[
|
|
275
266
|
string,
|
|
276
267
|
string
|
|
277
268
|
]>): Uint8Array;
|
|
278
|
-
|
|
279
|
-
export
|
|
280
|
-
/**
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
cookieParse(items: Uint8Array[]): Uint8Array[];
|
|
293
|
-
httpParseRequest(items: Uint8Array[]): Uint8Array[];
|
|
294
|
-
/** Apply N RFC 6902 patches (docs and patches zipped). Fail-fast on error. */
|
|
295
|
-
jsonPatch(docs: Uint8Array[], patches: Uint8Array[]): Uint8Array[];
|
|
296
|
-
/** Encode N items to lowercase hex (packed in → byte results). */
|
|
297
|
-
hexEncode(items: Uint8Array[]): Uint8Array[];
|
|
298
|
-
/** Decode N lowercase/uppercase hex strings to bytes. */
|
|
299
|
-
hexDecode(items: Uint8Array[]): Uint8Array[];
|
|
300
|
-
/** Base64-encode N items (standard by default; url-safe/padding configurable). */
|
|
301
|
-
base64Encode(items: Uint8Array[], urlSafe?: boolean, padding?: boolean): Uint8Array[];
|
|
302
|
-
/** Base64-decode N items (standard by default; url-safe/padding configurable). */
|
|
303
|
-
base64Decode(items: Uint8Array[], urlSafe?: boolean, padding?: boolean): Uint8Array[];
|
|
304
|
-
formParse(items: Uint8Array[]): Uint8Array[];
|
|
305
|
-
/** Sign N cookie values (packed → byte results). */
|
|
306
|
-
signCookie(items: Uint8Array[], secret: Uint8Array): Uint8Array[];
|
|
307
|
-
/** Verify N signed cookies (bitset of valid). */
|
|
308
|
-
verifyCookie(items: Uint8Array[], secret: Uint8Array): Uint8Array;
|
|
309
|
-
/** Verify N CSRF tokens (bitset of valid). */
|
|
310
|
-
csrfVerify(items: Uint8Array[], secret: Uint8Array): Uint8Array;
|
|
311
|
-
schemaValidate(validator: SchemaValidator, items: Uint8Array[]): Uint8Array;
|
|
312
|
-
schemaValidateCount(validator: SchemaValidator, items: Uint8Array[]): number;
|
|
313
|
-
/** Hash N items with FNV-1a 64 (packed → unsigned u64 array). */
|
|
314
|
-
fnv1a64(items: Uint8Array[]): BigUint64Array;
|
|
315
|
-
/** Generate N ETags (strong by default; weak flag applies to all items). */
|
|
316
|
-
etag(items: Uint8Array[], weak?: boolean | null): Uint8Array[];
|
|
317
|
-
urlEncode(items: Uint8Array[]): Uint8Array[];
|
|
318
|
-
/** Percent-decode N strings (UTF-8 validated; malformed items → empty). */
|
|
319
|
-
urlDecode(items: Uint8Array[]): Uint8Array[];
|
|
320
|
-
/** Strict percent-decode N strings (no UTF-8 validation). */
|
|
321
|
-
urlDecodeBytes(items: Uint8Array[]): Uint8Array[];
|
|
322
|
-
mimeFromExtension(items: Uint8Array[]): Uint8Array[];
|
|
323
|
-
wsAcceptKey(items: Uint8Array[]): Uint8Array[];
|
|
324
|
-
/** Base64url-encode N items (URL-safe, no padding). */
|
|
325
|
-
base64UrlEncode(items: Uint8Array[]): Uint8Array[];
|
|
326
|
-
/** Base64url-decode N items (URL-safe, no padding). */
|
|
327
|
-
base64UrlDecode(items: Uint8Array[]): Uint8Array[];
|
|
328
|
-
passwordHash(passwords: Uint8Array[], salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array[];
|
|
329
|
-
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
330
|
-
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
331
|
-
gzipCompress(items: Uint8Array[], level?: number | null): Uint8Array[];
|
|
332
|
-
gzipDecompress(items: Uint8Array[], maxDecompressed?: number | null): Uint8Array[];
|
|
333
|
-
brotliCompress(items: Uint8Array[], quality?: number | null): Uint8Array[];
|
|
334
|
-
brotliDecompress(items: Uint8Array[], maxDecompressed?: number | null): Uint8Array[];
|
|
335
|
-
jwtVerify(tokens: Uint8Array[], secret: Uint8Array, nowSeconds: number): Uint8Array;
|
|
336
|
-
/** Sign N messages to lowercase-hex HMAC-SHA256 signatures (packed → byte results). */
|
|
337
|
-
hmacSha256(items: Uint8Array[], key: Uint8Array): Uint8Array[];
|
|
338
|
-
/** Verify N message + hex-signature pairs (two packed lists, zipped) → bitset of valid. */
|
|
339
|
-
hmacSha256Verify(items: Uint8Array[], sigs: Uint8Array[], key: Uint8Array): Uint8Array;
|
|
340
|
-
/** Verify N password+PHC pairs (two packed lists, zipped) → bitset of valid. */
|
|
341
|
-
passwordVerify(items: Uint8Array[], phcs: Uint8Array[]): Uint8Array;
|
|
342
|
-
/** Resolve N references against N bases (two packed lists, zipped). */
|
|
343
|
-
urlResolve(bases: Uint8Array[], references: Uint8Array[]): Uint8Array[];
|
|
344
|
-
/** Sign N JSON claim documents (packed → signed tokens). */
|
|
345
|
-
jwtSign(items: Uint8Array[], secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array[];
|
|
346
|
-
/** Encode N SSE events with shared event/id/retry. */
|
|
347
|
-
sseEncode(items: Uint8Array[], event?: string | null, id?: string | null, retry?: number | null): Uint8Array[];
|
|
348
|
-
/** Encode N frames (same opcode/mask/fin) from payloads. */
|
|
349
|
-
wsFrameEncode(items: Uint8Array[], opcode: number, mask: boolean, fin: boolean): Uint8Array[];
|
|
350
|
-
/** Decode N frames → payloads (opcodes/flags dropped; failed items → empty). */
|
|
351
|
-
wsFrameDecode(items: Uint8Array[]): Uint8Array[];
|
|
352
|
-
/** Parse N multipart bodies → array of parts per item (same boundary). */
|
|
353
|
-
multipartParse(items: Uint8Array[], boundary: Uint8Array): MultipartPart[][];
|
|
354
|
-
/**
|
|
355
|
-
* @performance Template render: ~1.7x slower than the JS baseline (1/3 tasks failed) [check:annotate]
|
|
356
|
-
* @deprecated Slower than the native JS baseline (~1.7x) — prefer the JS/Bun baseline. [check:annotate]
|
|
357
|
-
* @remarks Promotion candidate: 2/3 benchmarks won in the latest run (static: not-competitive). [check:annotate]
|
|
358
|
-
* @remarks The allocating render marshals the context via napi DOM (~1.5x loss). Use TemplateRenderer.renderBytes (JSON-bytes context) which avoids it and wins ~1.37x. [check:annotate]
|
|
359
|
-
*/
|
|
360
|
-
templateRender(source: string, contexts: Uint8Array[]): Uint8Array[];
|
|
361
|
-
}
|
|
362
|
-
/** Raw packed-wire FFI namespace. */
|
|
363
|
-
export interface RustPacked {
|
|
364
|
-
crc32BatchPacked(input: Uint8Array): Uint8Array;
|
|
365
|
-
jsonValidBatchPacked(input: Uint8Array): Uint8Array;
|
|
366
|
-
validateEmailBatchPacked(input: Uint8Array): Uint8Array;
|
|
367
|
-
validateUuidBatchPacked(input: Uint8Array): Uint8Array;
|
|
368
|
-
validateIpv4BatchPacked(input: Uint8Array): Uint8Array;
|
|
369
|
-
validateIpv6BatchPacked(input: Uint8Array): Uint8Array;
|
|
370
|
-
jsonSumBatchPacked(input: Uint8Array): Uint8Array;
|
|
371
|
-
queryParseBatchPacked(input: Uint8Array): Uint8Array;
|
|
372
|
-
cookieParseBatchPacked(input: Uint8Array): Uint8Array;
|
|
373
|
-
httpParseRequestBatchPacked(input: Uint8Array): Uint8Array;
|
|
374
|
-
/** Batch RFC 6902 JSON Patch (two packed lists, zipped) → packed results. */
|
|
375
|
-
jsonPatchBatchPacked(docs: Uint8Array, patches: Uint8Array): Uint8Array;
|
|
376
|
-
/** Packed FNV-1a 64 batch (i64 per item). */
|
|
377
|
-
fnv1A64BatchPacked(input: Uint8Array): Uint8Array;
|
|
378
|
-
/** Packed ETag batch (10 strong / 12 weak bytes per item). */
|
|
379
|
-
etagBatchPacked(input: Uint8Array, weak?: boolean | null): Uint8Array;
|
|
380
|
-
urlEncodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
381
|
-
urlDecodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
382
|
-
urlDecodeBytesBatchPacked(input: Uint8Array): Uint8Array;
|
|
383
|
-
mimeFromExtensionBatchPacked(input: Uint8Array): Uint8Array;
|
|
384
|
-
wsAcceptKeyBatchPacked(input: Uint8Array): Uint8Array;
|
|
385
|
-
jsonValidBatchCountPacked(input: Uint8Array): number;
|
|
386
|
-
validateEmailBatchCountPacked(input: Uint8Array): number;
|
|
387
|
-
validateUuidBatchCountPacked(input: Uint8Array): number;
|
|
388
|
-
validateIpv4BatchCountPacked(input: Uint8Array): number;
|
|
389
|
-
validateIpv6BatchCountPacked(input: Uint8Array): number;
|
|
390
|
-
jsonSumBatchTotalPacked(input: Uint8Array): number;
|
|
391
|
-
queryParseBatchTotalLenPacked(input: Uint8Array): number;
|
|
392
|
-
cookieParseBatchTotalLenPacked(input: Uint8Array): number;
|
|
393
|
-
httpParseRequestBatchTotalLenPacked(input: Uint8Array): number;
|
|
394
|
-
passwordHashBatchPacked(input: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array;
|
|
395
|
-
/** Packed password-verify batch (two packed lists, zipped) → bitset. */
|
|
396
|
-
passwordVerifyBatchPacked(passwords: Uint8Array, phcs: Uint8Array): Uint8Array;
|
|
397
|
-
/** Packed URL-resolve batch (two packed lists, zipped) → packed results. */
|
|
398
|
-
urlResolveBatchPacked(bases: Uint8Array, references: Uint8Array): Uint8Array;
|
|
399
|
-
aeadEncryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
400
|
-
aeadDecryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
401
|
-
gzipCompressBatchPacked(input: Uint8Array, level?: number | null): Uint8Array;
|
|
402
|
-
gzipDecompressBatchPacked(input: Uint8Array): Uint8Array;
|
|
403
|
-
brotliCompressBatchPacked(input: Uint8Array, quality?: number | null): Uint8Array;
|
|
404
|
-
brotliDecompressBatchPacked(input: Uint8Array): Uint8Array;
|
|
405
|
-
jwtSignBatchPacked(input: Uint8Array, secret: Uint8Array, ttlSeconds: number | null, nowSeconds: number): Uint8Array;
|
|
406
|
-
jwtVerifyBatchPacked(input: Uint8Array, secret: Uint8Array, nowSeconds: number): Uint8Array;
|
|
407
|
-
/** Batch HMAC-SHA256 sign (packed in → hex-signature byte results). */
|
|
408
|
-
hmacSha256BatchPacked(input: Uint8Array, key: Uint8Array): Uint8Array;
|
|
409
|
-
/** Batch HMAC-SHA256 verify (two packed lists: data + hex sigs, zipped) → bitset. */
|
|
410
|
-
hmacSha256VerifyBatchPacked(input: Uint8Array, sigs: Uint8Array, key: Uint8Array): Uint8Array;
|
|
411
|
-
multipartParseBatchPacked(input: Uint8Array, boundary: Uint8Array): Uint8Array;
|
|
412
|
-
templateRenderBatchPacked(input: Uint8Array, source: string): Uint8Array;
|
|
413
|
-
sseEncodeBatchPacked(input: Uint8Array, event: string | null, id: string | null, retry: number | null): Uint8Array;
|
|
414
|
-
wsFrameEncodeBatchPacked(input: Uint8Array, opcode: number, mask: boolean, fin: boolean): Uint8Array;
|
|
415
|
-
wsFrameDecodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
269
|
+
/** Bounds-checked section offsets for one ingress output buffer. */
|
|
270
|
+
export interface PackedSectionLayout {
|
|
271
|
+
/** Cookie-JSON length clamped to the buffer (0 if none / out of range). */
|
|
272
|
+
safeCookiesLen: number;
|
|
273
|
+
/** Byte offset where the query-JSON section begins. */
|
|
274
|
+
queryStart: number;
|
|
275
|
+
/** Query-JSON length clamped to the buffer (0 if none / out of range). */
|
|
276
|
+
safeQueryLen: number;
|
|
277
|
+
/** Byte offset where the body-JSON section begins. */
|
|
278
|
+
bodyJsonStart: number;
|
|
279
|
+
/** Body-JSON length clamped to the buffer (0 if none / out of range). */
|
|
280
|
+
safeBodyJsonLen: number;
|
|
281
|
+
/** Whether any declared section length overran the buffer. */
|
|
282
|
+
truncated: boolean;
|
|
416
283
|
}
|
|
417
|
-
|
|
418
|
-
|
|
284
|
+
declare abstract class IngressResultBase {
|
|
285
|
+
/** Raw native verdict byte (0 = accepted). */
|
|
286
|
+
verdict: number;
|
|
287
|
+
/** Raw native error-code byte. */
|
|
288
|
+
errorCode: number;
|
|
289
|
+
/** Normalized HTTP status. */
|
|
290
|
+
status: number;
|
|
291
|
+
/** Header template variant byte emitted by the native core. */
|
|
292
|
+
headerVariant: number;
|
|
293
|
+
/** Remaining rate-limit budget (count). */
|
|
294
|
+
rateRemaining: number;
|
|
295
|
+
/** Rate-limit window reset (ms epoch). */
|
|
296
|
+
rateResetMs: number;
|
|
297
|
+
/** Retry-After value (ms). */
|
|
298
|
+
retryAfterMs: number;
|
|
299
|
+
/** True when the pipeline returned a terminal (error) verdict. */
|
|
300
|
+
terminal: boolean;
|
|
301
|
+
/** True when the pipeline accepted the request (verdict 0 + 2xx/3xx). */
|
|
302
|
+
ok: boolean;
|
|
303
|
+
/** True when the request was a CORS preflight. */
|
|
304
|
+
isPreflight: boolean;
|
|
305
|
+
/** True when the CORS engine allowed the request origin. */
|
|
306
|
+
corsAllowed: boolean;
|
|
307
|
+
/** True when the request was rate-limited. */
|
|
308
|
+
rateLimited: boolean;
|
|
309
|
+
/** True when the request came from a trusted proxy. */
|
|
310
|
+
trustedProxy: boolean;
|
|
311
|
+
/** True when the request body parsed as valid JSON. */
|
|
312
|
+
bodyValidJson: boolean;
|
|
313
|
+
/** True when the request body validated against the ingress schema. */
|
|
314
|
+
schemaValid: boolean;
|
|
315
|
+
/** True when a declared output section overran the buffer. */
|
|
316
|
+
bodyTruncated: boolean;
|
|
317
|
+
/** Request body bytes (may be an empty view). */
|
|
318
|
+
body: Uint8Array;
|
|
319
|
+
protected _buf: Uint8Array;
|
|
320
|
+
protected _bodyJsonStart: number;
|
|
321
|
+
protected _bodyJsonLen: number;
|
|
322
|
+
private _cookiesBuf;
|
|
323
|
+
private _queryBuf;
|
|
324
|
+
private _cookiesDecoded;
|
|
325
|
+
private _queryDecoded;
|
|
326
|
+
/** Reset the shared fields to their empty state (call from subclass `invalidate`). */
|
|
327
|
+
protected resetShared(): void;
|
|
328
|
+
/** Recompute `terminal`/`ok` from the (subclass-set) verdict + status. */
|
|
329
|
+
protected updateOkTerminal(): void;
|
|
419
330
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
331
|
+
* Store the rate-limit window fields. Both decoders read the same OUT_*
|
|
332
|
+
* slots and gate them on the same conditions (a positive budget OR the
|
|
333
|
+
* rate-limited flag), so the read cannot drift between paths.
|
|
423
334
|
*/
|
|
424
|
-
|
|
335
|
+
protected setRateWindow(rateLimit: number, rateLimited: boolean, view: DataView): void;
|
|
425
336
|
/**
|
|
426
|
-
*
|
|
337
|
+
* Store the bounds-checked cookie/query/body JSON sections from `layout`.
|
|
338
|
+
*
|
|
339
|
+
* `bodyJsonLen` is the body-JSON length to trust for `bodyJson()`: the fast
|
|
340
|
+
* path passes the RAW declared length (it re-checks bounds on read), the
|
|
341
|
+
* baked path passes the already-clamped `layout.safeBodyJsonLen`. Sections
|
|
342
|
+
* are stored as slices and decoded lazily by `cookiesJson()`/`queryJson()`.
|
|
427
343
|
*/
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
344
|
+
protected setSections(buf: Uint8Array, layout: PackedSectionLayout, bodyJsonLen: number): void;
|
|
345
|
+
/** Decoded cookies-JSON section, or `"{}"` when absent. */
|
|
346
|
+
cookiesJson(): string;
|
|
347
|
+
/** Decoded query-JSON section, or `"{}"` when absent. */
|
|
348
|
+
queryJson(): string;
|
|
349
|
+
/** Bounds-checked body-JSON slice (empty when absent / out of range). */
|
|
350
|
+
protected bodyJsonSlice(): Uint8Array;
|
|
351
|
+
}
|
|
352
|
+
/** Zero-alloc, reusable result decoder for the pre-baked handler path. */
|
|
353
|
+
export declare class BakedIngressResult extends IngressResultBase {
|
|
354
|
+
refresh(buf: Uint8Array, body: Uint8Array, view: DataView): void;
|
|
355
|
+
invalidate(): void;
|
|
356
|
+
setInternalError(): void;
|
|
357
|
+
bodyJson(copy: boolean): Uint8Array;
|
|
358
|
+
}
|
|
359
|
+
/** User-facing CORS configuration. */
|
|
360
|
+
export interface CorsOptions {
|
|
361
|
+
allowOrigin?: string[];
|
|
362
|
+
allowMethods?: string[];
|
|
363
|
+
allowHeaders?: string[];
|
|
364
|
+
exposeHeaders?: string[];
|
|
365
|
+
allowCredentials?: boolean;
|
|
366
|
+
maxAge?: number;
|
|
367
|
+
}
|
|
368
|
+
/** Precomputed, immutable CORS policy strings. */
|
|
369
|
+
export interface CorsStaticStrings {
|
|
370
|
+
readonly allowMethodsJoined: string;
|
|
371
|
+
readonly allowHeadersJoined: string;
|
|
372
|
+
readonly exposeHeadersJoined: string;
|
|
373
|
+
readonly maxAgeString: string | null;
|
|
374
|
+
readonly isWildcard: boolean;
|
|
375
|
+
readonly credentials: boolean;
|
|
376
|
+
}
|
|
377
|
+
/** User-facing security-headers configuration. */
|
|
378
|
+
export interface SecurityHeadersOptions {
|
|
379
|
+
contentSecurityPolicy?: string;
|
|
380
|
+
hsts?: boolean;
|
|
381
|
+
hstsMaxAge?: number;
|
|
382
|
+
hstsIncludeSubdomains?: boolean;
|
|
383
|
+
hstsPreload?: boolean;
|
|
384
|
+
frameOptions?: string;
|
|
385
|
+
nosniff?: boolean;
|
|
386
|
+
referrerPolicy?: string;
|
|
387
|
+
coep?: string;
|
|
388
|
+
coop?: string;
|
|
389
|
+
corp?: string;
|
|
390
|
+
xssProtection?: string;
|
|
391
|
+
}
|
|
392
|
+
/** Zero-alloc, lazy-decoding view over a native Ingress output buffer. */
|
|
393
|
+
export declare class FastIngressResult extends IngressResultBase {
|
|
394
|
+
status: number;
|
|
395
|
+
verdict: number;
|
|
396
|
+
errorCode: number;
|
|
397
|
+
flags: number;
|
|
398
|
+
https: boolean;
|
|
399
|
+
hasCookies: boolean;
|
|
400
|
+
hasQuery: boolean;
|
|
401
|
+
rateLimit: number;
|
|
402
|
+
requestId: string;
|
|
403
|
+
refresh(buf: Uint8Array, body: Uint8Array, requestId: string): void;
|
|
404
|
+
invalidate(): void;
|
|
405
|
+
setInternalError(requestId: string): void;
|
|
406
|
+
/** Bounds-checked body-JSON section, or an empty view when absent. */
|
|
407
|
+
bodyJson(): Uint8Array;
|
|
408
|
+
}
|
|
409
|
+
/** Options accepted by `createIngressFast` / `createIngress`. */
|
|
410
|
+
export interface IngressFastOptions {
|
|
411
|
+
trustProxy?: boolean;
|
|
412
|
+
trustedProxies?: {
|
|
413
|
+
enabled?: boolean;
|
|
414
|
+
networks?: string[];
|
|
415
|
+
};
|
|
416
|
+
parseCookies?: boolean;
|
|
417
|
+
parseQuery?: boolean;
|
|
418
|
+
requireJsonBody?: boolean;
|
|
419
|
+
schema?: Uint8Array;
|
|
420
|
+
cors?: CorsOptions;
|
|
421
|
+
rateLimit?: {
|
|
422
|
+
limit?: number;
|
|
423
|
+
windowMs?: number;
|
|
424
|
+
maxEntries?: number;
|
|
425
|
+
};
|
|
438
426
|
/**
|
|
439
|
-
*
|
|
427
|
+
* Structured security-header options (CSP/HSTS) — honored by the fast/async
|
|
428
|
+
* paths (`createIngressFast` throws on it; `createIngress` applies it via
|
|
429
|
+
* `buildResponseContext`). The PRE-BAKED path (`createIngressHandler`)
|
|
430
|
+
* instead takes raw `securityHeaders: [name, value][]` pairs. The two paths
|
|
431
|
+
* intentionally use different option shapes (see docs/INGRESS.md).
|
|
440
432
|
*/
|
|
441
|
-
|
|
433
|
+
security?: SecurityHeadersOptions;
|
|
434
|
+
https?: boolean;
|
|
435
|
+
maxBodyBytes?: number;
|
|
436
|
+
enableSecurityHeaders?: boolean;
|
|
437
|
+
enableRequestIds?: boolean;
|
|
438
|
+
enableBodySizeGuard?: boolean;
|
|
439
|
+
emitMetadataJson?: boolean;
|
|
440
|
+
readBody?: boolean;
|
|
441
|
+
outputBufferSize?: number;
|
|
442
442
|
/**
|
|
443
|
-
*
|
|
443
|
+
* Overall deadline (ms) for reading a request body in the async
|
|
444
|
+
* `createIngress` path. 0 disables. Default: `DEFAULT_BODY_TIMEOUT_MS` (30s).
|
|
445
|
+
* Ignored by the sync `createIngressFast` (it does not read bodies).
|
|
444
446
|
*/
|
|
445
|
-
|
|
447
|
+
bodyTimeoutMs?: number;
|
|
446
448
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* @deprecated Slower than the native JS baseline (~5.0x) — prefer the JS/Bun baseline. [check:annotate]
|
|
450
|
-
* @remarks Bun's JSON.parse beats the native DOM + napi marshaling path (~5x). [check:annotate]
|
|
449
|
+
* Invoked when the native pipeline throws (the request becomes a 500).
|
|
450
|
+
* Native failures are otherwise silent in the fast path. Never throws.
|
|
451
451
|
*/
|
|
452
|
-
|
|
452
|
+
onError?: (error: Error) => void;
|
|
453
453
|
/**
|
|
454
|
-
*
|
|
454
|
+
* When true, run one probe GET at construction so the `run()` closure,
|
|
455
|
+
* packed pipeline and FFI ingress call are JIT-warmed before the first real
|
|
456
|
+
* request (cuts cold-invocation tail latency in serverless-style
|
|
457
|
+
* deployments). Opt-in; default false. Not forwarded to the native addon.
|
|
455
458
|
*/
|
|
456
|
-
|
|
459
|
+
warmOnCreate?: boolean;
|
|
460
|
+
limits?: {
|
|
461
|
+
maxUrlBytes?: number;
|
|
462
|
+
maxQueryBytes?: number;
|
|
463
|
+
maxCookieBytes?: number;
|
|
464
|
+
maxHeadersBytes?: number;
|
|
465
|
+
maxHeaders?: number;
|
|
466
|
+
maxPairs?: number;
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** The zero-alloc sync handler returned by `createIngressFast`. */
|
|
470
|
+
export interface IngressFastHandler {
|
|
471
|
+
run<T>(req: Request, ip: string | undefined, body: Uint8Array | null, requestId: string, fn: (result: FastIngressResult) => T): T;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Options accepted by `createIngressHandler` (path 2).
|
|
475
|
+
*
|
|
476
|
+
* Same native option surface as the fast path, minus `onError` — path 2
|
|
477
|
+
* reports native failures through `BakedIngressRuntime.onError` instead of the
|
|
478
|
+
* options bag.
|
|
479
|
+
*/
|
|
480
|
+
export type IngressHandlerOptions = Omit<IngressFastOptions, "onError">;
|
|
481
|
+
/** Public ingress options (extends the fast-path options). */
|
|
482
|
+
export interface IngressOptions extends IngressFastOptions {
|
|
483
|
+
enableRequestIds?: boolean;
|
|
457
484
|
/**
|
|
458
|
-
*
|
|
459
|
-
*
|
|
485
|
+
* Invoked before a request is processed (for tracing/context hooks).
|
|
486
|
+
* Only used by the async `createIngress` path.
|
|
460
487
|
*/
|
|
461
|
-
|
|
488
|
+
onRequest?: (req: Request, requestId: string, ip: string | undefined) => void;
|
|
462
489
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
490
|
+
* Invoked after a terminal context (and its `Response`) is produced, for
|
|
491
|
+
* metrics/logging hooks. Only used by the async `createIngress` path.
|
|
465
492
|
*/
|
|
466
|
-
|
|
493
|
+
onResponse?: (req: Request, result: IngressContext, status: number, requestId: string) => void;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Readonly structural view of a decoded ingress result.
|
|
497
|
+
*
|
|
498
|
+
* Note: the exported `FastIngressResult` class is the zero-alloc implementation;
|
|
499
|
+
* this interface is the readonly view used by the public sync/async handlers.
|
|
500
|
+
*/
|
|
501
|
+
export interface IngressResult {
|
|
502
|
+
readonly status: number;
|
|
503
|
+
readonly verdict: number;
|
|
504
|
+
readonly flags: number;
|
|
505
|
+
readonly errorCode: number;
|
|
506
|
+
readonly terminal: boolean;
|
|
507
|
+
readonly ok: boolean;
|
|
508
|
+
readonly https: boolean;
|
|
509
|
+
readonly trustedProxy: boolean;
|
|
510
|
+
readonly hasCookies: boolean;
|
|
511
|
+
readonly hasQuery: boolean;
|
|
512
|
+
readonly bodyValidJson: boolean;
|
|
513
|
+
readonly schemaValid: boolean;
|
|
514
|
+
readonly corsAllowed: boolean;
|
|
515
|
+
readonly isPreflight: boolean;
|
|
516
|
+
readonly rateLimited: boolean;
|
|
517
|
+
readonly rateLimit: number;
|
|
518
|
+
readonly rateRemaining: number;
|
|
519
|
+
readonly rateResetMs: number;
|
|
520
|
+
readonly retryAfterMs: number;
|
|
521
|
+
readonly body: Uint8Array;
|
|
522
|
+
readonly headerVariant: number;
|
|
523
|
+
readonly requestId: string;
|
|
524
|
+
readonly bodyTruncated: boolean;
|
|
525
|
+
cookiesJson(): string;
|
|
526
|
+
queryJson(): string;
|
|
527
|
+
bodyJson(): Uint8Array;
|
|
528
|
+
}
|
|
529
|
+
/** A result plus the terminal Response computed for it. */
|
|
530
|
+
export interface IngressContext extends IngressResult {
|
|
531
|
+
response: Response | null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Synchronous ingress handler.
|
|
535
|
+
*
|
|
536
|
+
* The callback runs on the zero-alloc result object and MUST be synchronous:
|
|
537
|
+
* returning a Promise (or any thenable) throws. The result is invalidated after
|
|
538
|
+
* `run()` returns, so capture the fields you need inside the callback (or use
|
|
539
|
+
* the async {@link IngressHandler} which snapshots for you).
|
|
540
|
+
*/
|
|
541
|
+
export interface SyncIngressHandler {
|
|
542
|
+
run<T>(req: Request, ip: string | undefined, body: Uint8Array | null, requestId: string, fn: (result: IngressResult) => T): T;
|
|
543
|
+
}
|
|
544
|
+
/** Async ingress handler (wraps {@link SyncIngressHandler} with body reading). */
|
|
545
|
+
export type IngressHandler = (req: Request, ip?: string) => Promise<IngressContext>;
|
|
546
|
+
/** Mutable per-request context threaded through the pre-baked handler run. */
|
|
547
|
+
export interface BakedContext {
|
|
548
|
+
/** The request-id string (null when `emitRequestIdHeader` is off). */
|
|
549
|
+
requestIdHeader: string | null;
|
|
550
|
+
/** The `Origin` request header when CORS extraction is enabled. */
|
|
551
|
+
origin: string | null;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* An optimized ingress handler. `run()` is the zero-alloc pipeline entry
|
|
555
|
+
* point; the response-builder methods are pre-baked and bound to this
|
|
556
|
+
* handler's configuration, so consumers can build custom routes without
|
|
557
|
+
* touching header templates or error bodies.
|
|
558
|
+
*/
|
|
559
|
+
export interface OptimizedIngressHandler {
|
|
560
|
+
run<T>(req: Request, ip: string | undefined, body: Uint8Array | null, fn: (result: BakedIngressResult, ctx: BakedContext) => T): T;
|
|
561
|
+
responseHeaders(variant: number, requestIdHeader: string | null, origin: string | null, rateRemaining?: number, rateResetSecs?: number, retryAfterSecs?: number): [
|
|
562
|
+
string,
|
|
563
|
+
string
|
|
564
|
+
][];
|
|
565
|
+
terminalHeaders(variant: number, ctx: BakedContext, result: BakedIngressResult | null): [
|
|
566
|
+
string,
|
|
567
|
+
string
|
|
568
|
+
][];
|
|
569
|
+
/** `req` is accepted for back-compat but unused by the baked terminal path. */
|
|
570
|
+
terminalResponse(req: Request | undefined, result: BakedIngressResult, ctx: BakedContext): Response | null;
|
|
571
|
+
/** `req` is accepted for back-compat but unused by the baked error path. */
|
|
572
|
+
errorResponse(req: Request | undefined, result: BakedIngressResult | null, status: number, code: string, message: string, ctx: BakedContext): Response;
|
|
573
|
+
internalErrorResponse(ctx: BakedContext, result?: BakedIngressResult): Response;
|
|
574
|
+
withContentType(headers: ReadonlyArray<[
|
|
575
|
+
string,
|
|
576
|
+
string
|
|
577
|
+
]>, contentType: string): [
|
|
578
|
+
string,
|
|
579
|
+
string
|
|
580
|
+
][];
|
|
467
581
|
/**
|
|
468
|
-
*
|
|
582
|
+
* Zero-DOM JSON-validity check bound to this handler's native transport
|
|
583
|
+
* (bun:ffi primary, napi fallback). Provided so the pure route factories
|
|
584
|
+
* (`routes/*`) can validate request bodies via dependency injection without
|
|
585
|
+
* importing the native layer. Always present on handlers from
|
|
586
|
+
* `createIngressHandler`; optional so hand-rolled mocks need not provide it.
|
|
469
587
|
*/
|
|
470
|
-
|
|
588
|
+
jsonValid?: (bytes: Uint8Array) => boolean;
|
|
471
589
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
590
|
+
* Build a zero-copy `Response` whose body is the request's pooled output
|
|
591
|
+
* slice. The pooled buffer is returned to its pool once the body has been
|
|
592
|
+
* consumed (stream closed) or the request aborted. Use when `copyBody` is
|
|
593
|
+
* `false`; otherwise a copied body is released eagerly at the end of `run()`.
|
|
475
594
|
*/
|
|
476
|
-
|
|
595
|
+
zeroCopyResponse(result: BakedIngressResult, ctx: BakedContext, init: ResponseInit): Response;
|
|
596
|
+
}
|
|
597
|
+
/** Terminal (rejection) response envelope style for responder routes.
|
|
598
|
+
* `"castrum"` (default) emits the pre-baked `ok:false`/`ratelimit-*` wire;
|
|
599
|
+
* `"ignex"` emits the ignex framework envelope (`{error,status,code}` +
|
|
600
|
+
* `x-frame-options`/`nosniff`/`referrer-policy` security headers). Additive —
|
|
601
|
+
* castrum's own wire formats are untouched. */
|
|
602
|
+
export type TerminalStyle = "castrum" | "ignex";
|
|
603
|
+
/**
|
|
604
|
+
* Decoded request context handed to a {@link NativeResponder} after the native
|
|
605
|
+
* pipeline succeeded (non-terminal). The native pipeline owns parse/validate/
|
|
606
|
+
* CORS/rate-limit + terminal rejections; the responder builds the 2xx body
|
|
607
|
+
* (async OK). Fields are captured INSIDE the sync `run()` callback, so the
|
|
608
|
+
* pooled output buffer is never aliased after it is released.
|
|
609
|
+
*/
|
|
610
|
+
export interface NativeRequestContext {
|
|
611
|
+
readonly requestId: string;
|
|
612
|
+
readonly status: number;
|
|
613
|
+
readonly rateLimited: boolean;
|
|
614
|
+
readonly retryAfterMs: number;
|
|
615
|
+
/** Parsed query object (last-wins per key) — `{}` when `parseQuery` is off. */
|
|
616
|
+
readonly query: Record<string, string | string[]>;
|
|
617
|
+
/** Parsed cookie object (last-wins per key) — `{}` when `parseCookies` is off. */
|
|
618
|
+
readonly cookies: Record<string, string>;
|
|
619
|
+
/** Request body bytes when read for native validation, else empty. */
|
|
620
|
+
readonly body: Uint8Array;
|
|
621
|
+
readonly ip: string | undefined;
|
|
622
|
+
readonly req: Request;
|
|
623
|
+
}
|
|
624
|
+
/** A JS 2xx builder invoked after the native pipeline succeeds (async OK). */
|
|
625
|
+
export type NativeResponder = (snap: NativeRequestContext) => Response | Promise<Response>;
|
|
626
|
+
/** Options accepted by the pre-baked route factories. */
|
|
627
|
+
export interface BakedHandlerOptions {
|
|
628
|
+
/** Resolve the client IP from the server object (e.g. `srv.requestIP`). */
|
|
629
|
+
getIp?: (req: Request, srv: unknown) => string | undefined;
|
|
630
|
+
/** Copy body slices instead of sharing the native buffer. Default: true (safe). */
|
|
631
|
+
copyBody?: boolean;
|
|
632
|
+
/** Maximum request body bytes for write/echo handlers. Default: 1 MiB. */
|
|
633
|
+
maxBodyBytes?: number;
|
|
477
634
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*/
|
|
482
|
-
urlDecode(input: Uint8Array): Uint8Array;
|
|
483
|
-
/** Strict percent-decode without UTF-8 validation. */
|
|
484
|
-
urlDecodeBytes(input: Uint8Array): Uint8Array;
|
|
485
|
-
/** Reusable-output percent-encode; returns bytes written. */
|
|
486
|
-
urlEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
487
|
-
/** Reusable-output percent-decode; returns bytes written. */
|
|
488
|
-
urlDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
489
|
-
/**
|
|
490
|
-
* @performance Email validation: ~3.1x faster than the JS baseline (4/4 tasks won) [check:annotate]
|
|
491
|
-
*/
|
|
492
|
-
validateEmail(input: Uint8Array): boolean;
|
|
493
|
-
/**
|
|
494
|
-
* @performance UUID validation: ≈ parity with the JS baseline (2/4 tasks won) [check:annotate]
|
|
495
|
-
*/
|
|
496
|
-
validateUuid(input: Uint8Array): boolean;
|
|
497
|
-
/**
|
|
498
|
-
* @performance IPv4 validation: ≈ parity with the JS baseline (2/2 tasks won) [check:annotate]
|
|
499
|
-
*/
|
|
500
|
-
validateIpv4(input: Uint8Array): boolean;
|
|
501
|
-
/**
|
|
502
|
-
* @performance IPv6 validation: ~1.9x faster than the JS baseline [check:annotate]
|
|
503
|
-
*/
|
|
504
|
-
validateIpv6(input: Uint8Array): boolean;
|
|
505
|
-
/**
|
|
506
|
-
* @performance WebSocket accept: ~1.9x faster than the JS baseline (1/2 tasks won) [check:annotate]
|
|
507
|
-
*/
|
|
508
|
-
wsAcceptKey(key: Uint8Array): Uint8Array;
|
|
509
|
-
/**
|
|
510
|
-
* @performance HTTP parse: ~5.3x faster than the JS baseline (6/6 tasks won) [check:annotate]
|
|
511
|
-
*/
|
|
512
|
-
httpParseRequestPacked(input: Uint8Array): Uint8Array;
|
|
513
|
-
/**
|
|
514
|
-
* @performance Query parse: ~3.8x faster than the JS baseline (6/6 tasks won) [check:annotate]
|
|
515
|
-
*/
|
|
516
|
-
queryParsePacked(input: Uint8Array): Uint8Array;
|
|
517
|
-
/**
|
|
518
|
-
* @performance Cookie parse: ~2.6x faster than the JS baseline (5/5 tasks won) [check:annotate]
|
|
519
|
-
*/
|
|
520
|
-
cookieParsePacked(input: Uint8Array): Uint8Array;
|
|
521
|
-
/** Reusable-output packed HTTP parse; returns bytes written. */
|
|
522
|
-
httpParseRequestPackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
523
|
-
/** Reusable-output packed query parse; returns bytes written. */
|
|
524
|
-
queryParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
525
|
-
/** Reusable-output packed cookie parse; returns bytes written. */
|
|
526
|
-
cookieParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
527
|
-
/**
|
|
528
|
-
* Parse an application/x-www-form-urlencoded body into packed pairs.
|
|
529
|
-
* @performance Form parse: ~4.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
|
|
530
|
-
* @remarks Benchmarked via the FormParser instance (same parser core). [check:annotate]
|
|
531
|
-
*/
|
|
532
|
-
formParsePacked(input: Uint8Array): Uint8Array;
|
|
533
|
-
/**
|
|
534
|
-
* Parse a Content-Type header into a structured media type.
|
|
535
|
-
* @performance Media type parse: ~1.3x slower than the JS baseline [check:annotate]
|
|
536
|
-
* @deprecated Slower than the native JS baseline (~1.3x) — prefer the JS/Bun baseline. [check:annotate]
|
|
537
|
-
* @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]
|
|
538
|
-
*/
|
|
539
|
-
parseMediaType(input: Uint8Array): MediaTypeResult;
|
|
540
|
-
/**
|
|
541
|
-
* Generate a strong/weak ETag (crc32-based).
|
|
542
|
-
* @performance ETag: ≈ parity with the JS baseline [check:annotate]
|
|
543
|
-
*/
|
|
544
|
-
etag(data: Uint8Array, weak?: boolean): Uint8Array;
|
|
545
|
-
/** Reusable-output etag; returns bytes written (10 strong / 12 weak). */
|
|
546
|
-
etagInto(data: Uint8Array, output: Uint8Array, weak?: boolean): number;
|
|
547
|
-
/**
|
|
548
|
-
* Format a unix timestamp as an IMF-fixdate HTTP-date.
|
|
549
|
-
* @performance HTTP date: ~1.5x slower than the JS baseline (2/2 tasks failed) [check:annotate]
|
|
550
|
-
* @deprecated Slower than the native JS baseline (~1.5x) — prefer the JS/Bun baseline. [check:annotate]
|
|
551
|
-
* @remarks Date.toUTCString() is native (~1.5-1.7x loss); pooled httpDateInto cuts it to ~1.3x. [check:annotate]
|
|
552
|
-
*/
|
|
553
|
-
httpDate(secs?: number): Uint8Array;
|
|
554
|
-
/** Reusable-output http-date; returns bytes written (29). Throws if `output` is too small or the year is out of the fixed-width range. */
|
|
555
|
-
httpDateInto(secs: number | undefined, output: Uint8Array): number;
|
|
556
|
-
/** Parse an IMF-fixdate HTTP-date back to unix seconds. */
|
|
557
|
-
parseHttpDate(input: Uint8Array): bigint | null;
|
|
558
|
-
/** Parse an Accept-Encoding header into ordered preferences. */
|
|
559
|
-
parseAcceptEncoding(input: Uint8Array): EncodingPrefResult[];
|
|
560
|
-
/**
|
|
561
|
-
* Base64 encode (standard by default; url-safe/padding configurable).
|
|
562
|
-
* @performance Base64 encode: ~1.6x slower than the JS baseline [check:annotate]
|
|
563
|
-
* @deprecated Slower than the native JS baseline (~1.6x) — prefer the JS/Bun baseline. [check:annotate]
|
|
564
|
-
* @remarks Bun Buffer base64 is SIMD; loses ~1.5x. [check:annotate]
|
|
565
|
-
*/
|
|
566
|
-
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array;
|
|
567
|
-
/**
|
|
568
|
-
* Base64 decode (standard); throws on invalid input.
|
|
569
|
-
* @performance Base64 decode: ≈ parity with the JS baseline [check:annotate]
|
|
570
|
-
*/
|
|
571
|
-
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array;
|
|
572
|
-
/** Reusable-output base64 encode; returns bytes written. */
|
|
573
|
-
base64EncodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
574
|
-
/** Reusable-output base64 decode; returns bytes written. Throws on invalid input. */
|
|
575
|
-
base64DecodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
576
|
-
base64UrlEncode(input: Uint8Array): Uint8Array;
|
|
577
|
-
base64UrlDecode(input: Uint8Array): Uint8Array;
|
|
578
|
-
/**
|
|
579
|
-
* Lowercase hex encode.
|
|
580
|
-
* @performance Hex encode: ~1.4x slower than the JS baseline (1/2 tasks failed) [check:annotate]
|
|
581
|
-
* @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
|
|
582
|
-
* @remarks Buffer.toString('hex') is native SIMD; pooled hexEncodeInto cuts the loss from ~2.2x to ~1.35x. [check:annotate]
|
|
583
|
-
*/
|
|
584
|
-
hexEncode(input: Uint8Array): Uint8Array;
|
|
585
|
-
/**
|
|
586
|
-
* Hex decode; throws on odd length or invalid digits.
|
|
587
|
-
* @performance Hex decode: ≈ parity with the JS baseline [check:annotate]
|
|
588
|
-
* @remarks Slightly slower than Buffer hex decode (~1.3x). [check:annotate]
|
|
589
|
-
*/
|
|
590
|
-
hexDecode(input: Uint8Array): Uint8Array;
|
|
591
|
-
/** Reusable-output lowercase-hex encode; returns bytes written. */
|
|
592
|
-
hexEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
593
|
-
/** Reusable-output hex decode; returns bytes written. Throws on bad input. */
|
|
594
|
-
hexDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
595
|
-
/** Reusable-output HMAC-SHA256 hex (64 bytes); returns bytes written. Throws on too-small buffer. */
|
|
596
|
-
hmacSha256Into(key: Uint8Array, data: Uint8Array, output: Uint8Array): number;
|
|
597
|
-
/** Reusable-output cookie sign (`value.<64-hex>`); returns bytes written. Throws on too-small buffer. */
|
|
598
|
-
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number;
|
|
599
|
-
/** Reusable-output AEAD encrypt (ct + 16-byte tag); returns bytes written. Throws on too-small buffer. */
|
|
600
|
-
aeadEncryptInto(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, output: Uint8Array, algorithm?: string | null): number;
|
|
601
|
-
/** Reusable-output WS frame encode; returns bytes written. Throws on too-small buffer. */
|
|
602
|
-
wsFrameEncodeInto(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean, output: Uint8Array): number;
|
|
603
|
-
/** Reusable-output gzip compress; returns bytes written. Throws on too-small buffer. */
|
|
604
|
-
gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number | null): number;
|
|
605
|
-
/** Reusable-output brotli compress; returns bytes written. Throws on too-small buffer. */
|
|
606
|
-
brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number | null): number;
|
|
607
|
-
/**
|
|
608
|
-
* Sign a cookie value as `value.signature` (HMAC-SHA256 hex).
|
|
609
|
-
* @performance Cookie sign: ~1.0x faster than the JS baseline [check:annotate]
|
|
610
|
-
*/
|
|
611
|
-
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array;
|
|
612
|
-
/**
|
|
613
|
-
* Verify a signed cookie; returns the value or null.
|
|
614
|
-
* @performance Cookie verify: ~9.5x faster than the JS baseline [check:annotate]
|
|
615
|
-
*/
|
|
616
|
-
verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | null;
|
|
617
|
-
/**
|
|
618
|
-
* Create a CSRF token (32-byte random hex + HMAC signature).
|
|
619
|
-
* @performance CSRF create: ~13.7x faster than the JS baseline [check:annotate]
|
|
620
|
-
*/
|
|
621
|
-
csrfToken(secret: Uint8Array): Uint8Array;
|
|
622
|
-
/**
|
|
623
|
-
* Constant-time verify a CSRF token.
|
|
624
|
-
* @performance CSRF verify: ~3.3x faster than the JS baseline [check:annotate]
|
|
625
|
-
*/
|
|
626
|
-
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean;
|
|
627
|
-
/**
|
|
628
|
-
* Resolve a URL reference against a base (RFC 3986).
|
|
629
|
-
* @performance URL resolve: ~1.8x slower than the JS baseline [check:annotate]
|
|
630
|
-
* @deprecated Slower than the native JS baseline (~1.8x) — prefer the JS/Bun baseline. [check:annotate]
|
|
631
|
-
* @remarks Auto-deprecated: 1/1 benchmarks failed in the latest run (static classification was parity). [check:annotate]
|
|
632
|
-
* @remarks Noisy ~1.5µs op; swings 0.74-1.03x run-to-run. UrlBuilder reuses the parsed base. [check:annotate]
|
|
633
|
-
*/
|
|
634
|
-
urlResolve(base: Uint8Array, reference: Uint8Array): Uint8Array;
|
|
635
|
-
/**
|
|
636
|
-
* Build a percent-encoded query string from params (sorted keys).
|
|
637
|
-
* @performance URL query build: ~1.4x slower than the JS baseline [check:annotate]
|
|
638
|
-
* @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
|
|
639
|
-
* @remarks encodeURIComponent baseline wins (~1.2-1.35x). [check:annotate]
|
|
640
|
-
*/
|
|
641
|
-
urlEncodeQuery(params: Record<string, string>): Uint8Array;
|
|
642
|
-
/**
|
|
643
|
-
* @performance JSON schema validate: ~1.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
|
|
644
|
-
* @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]
|
|
645
|
-
*/
|
|
646
|
-
createSchemaValidator(schema: Uint8Array): SchemaValidatorInstance;
|
|
647
|
-
createHmacSigner(key: Uint8Array): HmacSignerInstance;
|
|
648
|
-
createTemplateRenderer(source: string): TemplateRendererInstance;
|
|
649
|
-
/** Higher-order form parser: owns a reusable output buffer (setup once). */
|
|
650
|
-
createFormParser(capacity?: number): FormParserInstance;
|
|
651
|
-
/** Higher-order media-type parser: reusable wildcard matcher. */
|
|
652
|
-
createMediaTypeParser(): MediaTypeParserInstance;
|
|
653
|
-
/**
|
|
654
|
-
* Higher-order conditional-request check: per-resource 304 decision (setup once).
|
|
655
|
-
* @performance Conditional request: ~1.7x faster than the JS baseline [check:annotate]
|
|
656
|
-
*/
|
|
657
|
-
createConditionalRequest(etagValue: Uint8Array, lastModifiedSecs?: number): ConditionalRequestInstance;
|
|
658
|
-
/**
|
|
659
|
-
* Higher-order negotiator: precompiles supported encodings once.
|
|
660
|
-
* @performance Accept-Encoding negotiate: ~4.2x faster than the JS baseline [check:annotate]
|
|
661
|
-
*/
|
|
662
|
-
createAcceptNegotiator(supported: string[]): AcceptNegotiatorInstance;
|
|
663
|
-
/** Higher-order codec: base64 config compiled once. */
|
|
664
|
-
createBase64Codec(urlSafe?: boolean, padding?: boolean): Base64CodecInstance;
|
|
665
|
-
/** Higher-order cookie signer: HMAC key compiled once. */
|
|
666
|
-
createCookieSigner(secret: Uint8Array): CookieSignerInstance;
|
|
667
|
-
/** Higher-order CSRF protector: HMAC key compiled once. */
|
|
668
|
-
createCsrfProtector(secret: Uint8Array): CsrfProtectorInstance;
|
|
669
|
-
/** Higher-order URL builder: base parsed once, reused. */
|
|
670
|
-
createUrlBuilder(base: Uint8Array): UrlBuilderInstance;
|
|
671
|
-
/** Higher-order JWT signer: HS256 key + ttl compiled once. */
|
|
672
|
-
createJwtSigner(secret: Uint8Array, ttlSeconds?: number): JwtSignerInstance;
|
|
673
|
-
/** Higher-order AEAD cipher: algorithm + key compiled once. */
|
|
674
|
-
createAeadCipher(key: Uint8Array, algorithm?: string): AeadCipherInstance;
|
|
675
|
-
/** Higher-order argon2id hasher: params compiled once. */
|
|
676
|
-
createArgon2Hasher(options?: PasswordHashOptions | null): Argon2HasherInstance;
|
|
677
|
-
/** Higher-order media-type matcher: expected precompiled once. */
|
|
678
|
-
createMediaTypeMatcher(expected: Uint8Array): MediaTypeMatcherInstance;
|
|
679
|
-
/**
|
|
680
|
-
* Sharded fixed-window per-key rate limiter. `maxEntries` clamps internally
|
|
681
|
-
* (default 1,048,576). Each instance owns an independent budget.
|
|
682
|
-
* @remarks Scalar per-check cost: ~3-14x SLOWER than the JS Map baseline —
|
|
683
|
-
* prefer the ingress pipeline (one FFI for all stages) for native rate
|
|
684
|
-
* limiting in a request path. [measured 2026-08]
|
|
685
|
-
*/
|
|
686
|
-
createRateLimiter(limit: number, windowMs: number, maxEntries?: number | null): RateLimiterInstance;
|
|
687
|
-
initThreadPool(rayonThreads?: number): void;
|
|
688
|
-
rayonNumThreads(): number;
|
|
689
|
-
/**
|
|
690
|
-
* @performance JWT sign: ~1.1x slower than the JS baseline [check:annotate]
|
|
691
|
-
* @deprecated Slower than the native JS baseline (~1.1x) — prefer the JS/Bun baseline. [check:annotate]
|
|
692
|
-
* @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]
|
|
693
|
-
*/
|
|
694
|
-
jwtSign(claims: Record<string, unknown>, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array;
|
|
695
|
-
/**
|
|
696
|
-
* Sign a JWT from pre-serialized claim JSON bytes — skips the napi
|
|
697
|
-
* `serde_json::Value` marshal of `jwtSign` for callers that already hold the
|
|
698
|
-
* bytes (same injection semantics).
|
|
699
|
-
* @performance JWT sign (bytes): ≈ parity with the JS baseline [check:annotate]
|
|
700
|
-
* @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]
|
|
701
|
-
*/
|
|
702
|
-
jwtSignBytes(claimsJson: Uint8Array, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array;
|
|
703
|
-
/**
|
|
704
|
-
* @performance JWT verify: ~1.7x faster than the JS baseline (2/2 tasks won) [check:annotate]
|
|
705
|
-
*/
|
|
706
|
-
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown;
|
|
707
|
-
/**
|
|
708
|
-
* @performance Password hash: ~18.2x faster than the JS baseline (2/2 tasks won) [check:annotate]
|
|
709
|
-
* @remarks argon2id vs node:crypto scrypt. [check:annotate]
|
|
710
|
-
*/
|
|
711
|
-
passwordHash(password: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array;
|
|
712
|
-
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean;
|
|
713
|
-
/** bcrypt password hash → `$2b$` PHC string (cost clamped 4..=31). */
|
|
714
|
-
passwordHashBcrypt(password: Uint8Array, cost: number): string;
|
|
715
|
-
/** Verify a password against a bcrypt `$2b$` PHC string. */
|
|
716
|
-
passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean;
|
|
717
|
-
/** PBKDF2-HMAC-SHA256 key derivation (dkLen clamped to 1 MiB). */
|
|
718
|
-
pbkdf2Sha256(password: Uint8Array, salt: Uint8Array, rounds: number, dkLen: number): Uint8Array;
|
|
719
|
-
/**
|
|
720
|
-
* @performance AEAD encrypt: ~1.3x faster than the JS baseline [check:annotate]
|
|
721
|
-
*/
|
|
722
|
-
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
723
|
-
/**
|
|
724
|
-
* @performance AEAD decrypt: ~1.4x faster than the JS baseline [check:annotate]
|
|
725
|
-
*/
|
|
726
|
-
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, algorithm?: string | null): Uint8Array | null;
|
|
727
|
-
/**
|
|
728
|
-
* @performance Gzip compress: ≈ parity with the JS baseline (1/2 tasks won) [check:annotate]
|
|
729
|
-
*/
|
|
730
|
-
gzipCompress(data: Uint8Array, level?: number | null): Uint8Array;
|
|
731
|
-
/**
|
|
732
|
-
* @performance Gzip decompress: ~1.0x faster than the JS baseline [check:annotate]
|
|
733
|
-
* @remarks zlib-rs vs node zlib; swings 1.0-1.4x run-to-run. [check:annotate]
|
|
734
|
-
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
735
|
-
* decompression-bomb guard; a larger value errors.
|
|
736
|
-
*/
|
|
737
|
-
gzipDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array;
|
|
738
|
-
/**
|
|
739
|
-
* @performance Brotli compress: ~1.7x slower than the JS baseline [check:annotate]
|
|
740
|
-
* @deprecated Slower than the native JS baseline (~1.7x) — prefer the JS/Bun baseline. [check:annotate]
|
|
741
|
-
* @remarks The native brotli baseline wins for small inputs. [check:annotate]
|
|
742
|
-
*/
|
|
743
|
-
brotliCompress(data: Uint8Array, quality?: number | null): Uint8Array;
|
|
744
|
-
/**
|
|
745
|
-
* @performance Brotli decompress: ~1.4x slower than the JS baseline [check:annotate]
|
|
746
|
-
* @deprecated Slower than the native JS baseline (~1.4x) — prefer the JS/Bun baseline. [check:annotate]
|
|
747
|
-
* @remarks Auto-deprecated: 1/1 benchmarks failed in the latest run (static classification was proven). [check:annotate]
|
|
748
|
-
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
749
|
-
* decompression-bomb guard; a larger value errors.
|
|
750
|
-
*/
|
|
751
|
-
brotliDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array;
|
|
752
|
-
/**
|
|
753
|
-
* @performance Multipart parse: ~1.9x faster than the JS baseline [check:annotate]
|
|
754
|
-
*/
|
|
755
|
-
multipartParse(body: Uint8Array, boundary: Uint8Array): MultipartPart[];
|
|
756
|
-
/** Zero-copy packed sibling of `multipartParse` (packed parts layout). */
|
|
757
|
-
multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array;
|
|
758
|
-
/**
|
|
759
|
-
* @performance WS frame encode: ~2.0x faster than the JS baseline [check:annotate]
|
|
635
|
+
* Overall deadline (ms) for reading the request body. Default: 30s
|
|
636
|
+
* (`DEFAULT_BODY_TIMEOUT_MS`). Set `0` to disable. Protects against
|
|
637
|
+
* slowloris / trickling bodies.
|
|
760
638
|
*/
|
|
761
|
-
|
|
639
|
+
bodyTimeoutMs?: number;
|
|
640
|
+
/** Fallback handler used for write error paths. Defaults to the write ingress. */
|
|
641
|
+
fallback?: OptimizedIngressHandler;
|
|
762
642
|
/**
|
|
763
|
-
*
|
|
643
|
+
* Zero-DOM JSON-validity check for the write-route fallback validation
|
|
644
|
+
* (`jsonWriteHandler` when neither `requireJsonBody` nor a `schema` is
|
|
645
|
+
* configured). Injected so the pure route factories never import the native
|
|
646
|
+
* layer (dependency injection across the purity boundary). Defaults to the
|
|
647
|
+
* `ingress.jsonValid` capability that `createIngressHandler` provides.
|
|
764
648
|
*/
|
|
765
|
-
|
|
649
|
+
jsonValid?: (bytes: Uint8Array) => boolean;
|
|
766
650
|
/**
|
|
767
|
-
*
|
|
651
|
+
* When true, route the JSON-validity fallback check through the higher-order
|
|
652
|
+
* loader (`loader("jsonValid")`) instead of the direct native call —
|
|
653
|
+
* exercising the loader's dispatch + counters on the real request path.
|
|
654
|
+
* Default: false (the direct call is marginally faster for a single request;
|
|
655
|
+
* the loader pays off for same-tick bursts / bulk work, see src/integration/batch.ts).
|
|
768
656
|
*/
|
|
769
|
-
|
|
657
|
+
enableLoader?: boolean;
|
|
770
658
|
}
|
|
771
|
-
/**
|
|
772
|
-
export
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
659
|
+
/** Route descriptor magic (`"ROUT"` LE). Must match `ROUTE_DESC_MAGIC` in Rust. */
|
|
660
|
+
export declare const ROUTE_DESC_MAGIC = 1380930900;
|
|
661
|
+
/** Wire version — bump on ANY descriptor/frame/result layout change. */
|
|
662
|
+
export declare const ROUTE_DESC_VERSION = 3;
|
|
663
|
+
/** Descriptor stage tags (the ordered pipeline a route instance runs). */
|
|
664
|
+
export declare const ROUTE_STAGE: {
|
|
665
|
+
readonly parseQuery: 0;
|
|
666
|
+
readonly parseCookies: 1;
|
|
667
|
+
readonly validateQuery: 2;
|
|
668
|
+
readonly validateCookies: 3;
|
|
669
|
+
readonly validateBody: 4;
|
|
670
|
+
readonly requireJsonBody: 5;
|
|
671
|
+
};
|
|
672
|
+
/** A descriptor stage tag (`ROUTE_STAGE` values, must match Rust). */
|
|
673
|
+
export type RouteStageTag = (typeof ROUTE_STAGE)[keyof typeof ROUTE_STAGE];
|
|
674
|
+
/** Descriptor part tags (`RoutePartKind`): the schema-bearing request parts. */
|
|
675
|
+
export declare const ROUTE_PART: {
|
|
676
|
+
readonly body: 3;
|
|
677
|
+
};
|
|
678
|
+
/** Result flag bits (`ROUTE_RESULT_FLAG_*` in Rust). */
|
|
679
|
+
export declare const ROUTE_FLAG: {
|
|
680
|
+
readonly OK: number;
|
|
681
|
+
readonly BODY_VALID_JSON: number;
|
|
682
|
+
readonly QUERY_VALID: number;
|
|
683
|
+
readonly COOKIE_VALID: number;
|
|
684
|
+
readonly BODY_VALID: number;
|
|
685
|
+
};
|
|
686
|
+
/** Frame flag: the body section is present (bit 0 of the frame flags word). */
|
|
687
|
+
export declare const ROUTE_FRAME_FLAG_HAS_BODY: number;
|
|
688
|
+
/** Size limits a route descriptor carries. */
|
|
689
|
+
export interface RouteWireLimits {
|
|
690
|
+
maxBodyBytes: number;
|
|
691
|
+
maxQueryBytes: number;
|
|
692
|
+
maxCookieBytes: number;
|
|
693
|
+
maxPairs: number;
|
|
694
|
+
}
|
|
695
|
+
/** A schema part carried by the descriptor (part tag + draft-07 JSON bytes). */
|
|
696
|
+
export interface RouteWireSchema {
|
|
697
|
+
part: number;
|
|
698
|
+
bytes: Uint8Array;
|
|
790
699
|
}
|
|
791
700
|
/**
|
|
792
|
-
*
|
|
701
|
+
* Encode a route plan into the descriptor wire.
|
|
793
702
|
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
* {
|
|
797
|
-
* `max(1, hardwareConcurrency - 1)` otherwise.
|
|
703
|
+
* Layout: `[magic u32][version u32][maxBody u32][maxQuery u32][maxCookie u32]
|
|
704
|
+
* [maxPairs u32][stageCount u32][stages u8…][schemaCount u32]
|
|
705
|
+
* { [part u8][len u32][schema] }…`
|
|
798
706
|
*/
|
|
799
|
-
export declare function
|
|
707
|
+
export declare function encodeRouteDescriptor(pipeline: readonly RouteStageTag[], schemas: readonly RouteWireSchema[], limits: RouteWireLimits): Uint8Array;
|
|
800
708
|
/**
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
* `import { rust } from "castrum"` and call `rust.<fn>(...)` directly.
|
|
709
|
+
* Encode a request frame: `[flags u32][qLen][query][cLen][cookie]([bLen][body])`.
|
|
710
|
+
* The `flags` word currently only carries `ROUTE_FRAME_FLAG_HAS_BODY`.
|
|
804
711
|
*/
|
|
805
|
-
export declare
|
|
806
|
-
/**
|
|
807
|
-
export type
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
712
|
+
export declare function packRouteFrame(query: string, cookie: string, body: Uint8Array | null): Uint8Array;
|
|
713
|
+
/** A decoded `[name, value]` pair from a result section. */
|
|
714
|
+
export type RouteWirePair = [
|
|
715
|
+
string,
|
|
716
|
+
string
|
|
717
|
+
];
|
|
718
|
+
/** The decoded route result: the verdict header + optional pair sections. */
|
|
719
|
+
export interface RouteWireResult {
|
|
720
|
+
/** `ROUTE_FLAG_*` bits from the result header. */
|
|
721
|
+
flags: number;
|
|
722
|
+
/** `0` = ok; `400` = not-JSON under requireJsonBody; `422` = schema fail. */
|
|
723
|
+
errorCode: number;
|
|
724
|
+
/** Decoded query pairs (present iff the plan compiled `parseQuery`). */
|
|
725
|
+
query: RouteWirePair[];
|
|
726
|
+
/** Decoded cookie pairs (present iff the plan compiled `parseCookies`). */
|
|
727
|
+
cookie: RouteWirePair[];
|
|
813
728
|
}
|
|
814
729
|
/**
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* `"js"` when a pure-JS (or Bun built-in) implementation wins, or `null` for
|
|
819
|
-
* unknown ops (or when the addon is unavailable — with no addon there is no
|
|
820
|
-
* "native" choice). Under Bun, ops in {@link BUN_WINS} resolve to `"js"` so the
|
|
821
|
-
* consumer's JS path delegates to the faster Bun built-in.
|
|
730
|
+
* Decode the result wire: `[flags u32][errorCode u32]` + a query pair section
|
|
731
|
+
* iff `query` and a cookie pair section iff `cookie` (the caller knows its own
|
|
732
|
+
* plan). Sections are `[count u32] { [nameLen u32][name][valueLen u32][value] }`.
|
|
822
733
|
*/
|
|
823
|
-
export declare
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
export
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
|
|
734
|
+
export declare function decodeRouteResult(buf: Uint8Array, opts: {
|
|
735
|
+
query: boolean;
|
|
736
|
+
cookie: boolean;
|
|
737
|
+
}): RouteWireResult;
|
|
738
|
+
/** Plan for a compiled native route (the stage/limit/schema surface). */
|
|
739
|
+
export interface NativeRoutePlan {
|
|
740
|
+
/** Parse + lenient-decode the query string into pairs (stage 0). */
|
|
741
|
+
parseQuery?: boolean;
|
|
742
|
+
/** Parse + lenient-decode the Cookie header into pairs (stage 1). */
|
|
743
|
+
parseCookies?: boolean;
|
|
744
|
+
/** Reject a missing / non-JSON body with 400 (stage 5, first-failure-wins). */
|
|
745
|
+
requireJsonBody?: boolean;
|
|
746
|
+
/** Validate the body against `schema` with 422 on failure (stage 4). */
|
|
747
|
+
validateBody?: boolean;
|
|
748
|
+
/** Draft-07 JSON schema bytes for the body (`validateBody`). */
|
|
749
|
+
schema?: Uint8Array;
|
|
750
|
+
/** Max body bytes before `requireJsonBody`/`validateBody` fail (default 2 MiB). */
|
|
751
|
+
maxBodyBytes?: number;
|
|
752
|
+
/** Max query bytes before the parse VALID bit clears (default 8192). */
|
|
753
|
+
maxQueryBytes?: number;
|
|
754
|
+
/** Max cookie bytes before the parse VALID bit clears (default 8192). */
|
|
755
|
+
maxCookieBytes?: number;
|
|
756
|
+
/** Max pairs per section (0 = unlimited; default 0). */
|
|
757
|
+
maxPairs?: number;
|
|
758
|
+
}
|
|
759
|
+
/** A compiled per-route native stack (route-wire v3). */
|
|
760
|
+
export interface NativeRoute {
|
|
761
|
+
/** Whether the plan compiled `parseQuery` (the result carries a query section). */
|
|
762
|
+
readonly parseQuery: boolean;
|
|
763
|
+
/** Whether the plan compiled `parseCookies` (the result carries a cookie section). */
|
|
764
|
+
readonly parseCookies: boolean;
|
|
765
|
+
/**
|
|
766
|
+
* Run one request frame through the compiled stack and return the decoded
|
|
767
|
+
* verdict. The frame is `[flags u32][qLen][query][cLen][cookie]([bLen][body])`
|
|
768
|
+
* — build it with {@link packRouteFrame}. Reuses one growable output buffer
|
|
769
|
+
* (the needed-size convention: `0` = real error → throws; `> out.length` =
|
|
770
|
+
* exact required size → retry once).
|
|
771
|
+
*/
|
|
772
|
+
runFrame(frame: Uint8Array): RouteWireResult;
|
|
773
|
+
/** Convenience: pack a `(query, cookie, body)` frame then {@link runFrame}. */
|
|
774
|
+
run(query: string, cookie: string, body: Uint8Array | null): RouteWireResult;
|
|
775
|
+
/** Free the native handle (idempotent; safe to call once at shutdown). */
|
|
776
|
+
destroy(): void;
|
|
843
777
|
}
|
|
844
778
|
/**
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
* report. Borderline wins are kept as "parity" so the `proven` surface stays
|
|
848
|
-
* honest.
|
|
779
|
+
* Compile a per-route native stack from a plan. Returns a compiled route whose
|
|
780
|
+
* `run`/`runFrame` perform the parse + verdict in ONE native call.
|
|
849
781
|
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
readonly label: "FNV-1a 64";
|
|
857
|
-
readonly nativeTask: "native:fnv1a64";
|
|
858
|
-
readonly rustTask: "rust:fnv1a64";
|
|
859
|
-
readonly status: "proven";
|
|
860
|
-
readonly typicalRatio: 11;
|
|
861
|
-
},
|
|
862
|
-
{
|
|
863
|
-
readonly name: "jsonValid";
|
|
864
|
-
readonly label: "JSON valid";
|
|
865
|
-
readonly nativeTask: "native:json_valid";
|
|
866
|
-
readonly rustTask: "rust:json_valid";
|
|
867
|
-
readonly status: "proven";
|
|
868
|
-
readonly typicalRatio: 3;
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
readonly name: "jsonSumIds";
|
|
872
|
-
readonly label: "JSON sum";
|
|
873
|
-
readonly nativeTask: "native:json_sum";
|
|
874
|
-
readonly rustTask: "rust:json_sum";
|
|
875
|
-
readonly status: "proven";
|
|
876
|
-
readonly typicalRatio: 3;
|
|
877
|
-
},
|
|
878
|
-
{
|
|
879
|
-
readonly name: "httpParseRequestPacked";
|
|
880
|
-
readonly label: "HTTP parse";
|
|
881
|
-
readonly nativeTask: "native:http_parse";
|
|
882
|
-
readonly rustTask: "rust:http_parse";
|
|
883
|
-
readonly status: "proven";
|
|
884
|
-
readonly typicalRatio: 4;
|
|
885
|
-
},
|
|
886
|
-
{
|
|
887
|
-
readonly name: "queryParsePacked";
|
|
888
|
-
readonly label: "Query parse";
|
|
889
|
-
readonly nativeTask: "native:query_parse";
|
|
890
|
-
readonly rustTask: "rust:query_parse";
|
|
891
|
-
readonly status: "proven";
|
|
892
|
-
readonly typicalRatio: 4;
|
|
893
|
-
},
|
|
894
|
-
{
|
|
895
|
-
readonly name: "cookieParsePacked";
|
|
896
|
-
readonly label: "Cookie parse";
|
|
897
|
-
readonly nativeTask: "native:cookie_parse";
|
|
898
|
-
readonly rustTask: "rust:cookie_parse";
|
|
899
|
-
readonly status: "proven";
|
|
900
|
-
readonly typicalRatio: 2.5;
|
|
901
|
-
},
|
|
902
|
-
{
|
|
903
|
-
readonly name: "wsAcceptKey";
|
|
904
|
-
readonly label: "WebSocket accept";
|
|
905
|
-
readonly nativeTask: "native:ws_accept_key";
|
|
906
|
-
readonly rustTask: "rust:ws_accept_key";
|
|
907
|
-
readonly status: "proven";
|
|
908
|
-
readonly typicalRatio: 1.6;
|
|
909
|
-
},
|
|
910
|
-
{
|
|
911
|
-
readonly name: "jsonPatch";
|
|
912
|
-
readonly label: "JSON Patch";
|
|
913
|
-
readonly nativeTask: "native:json_patch";
|
|
914
|
-
readonly rustTask: "rust:json_patch";
|
|
915
|
-
readonly status: "proven";
|
|
916
|
-
readonly typicalRatio: 1.2;
|
|
917
|
-
readonly note: "Modest but real win; swings ~0.9-1.5x run-to-run (DOM marshal dominated).";
|
|
918
|
-
},
|
|
919
|
-
{
|
|
920
|
-
readonly name: "hmacSha256";
|
|
921
|
-
readonly label: "HMAC sign";
|
|
922
|
-
readonly nativeTask: "native:hmac_sha256";
|
|
923
|
-
readonly rustTask: "rust:hmac_sha256";
|
|
924
|
-
readonly status: "proven";
|
|
925
|
-
readonly typicalRatio: 1.5;
|
|
926
|
-
readonly note: "Classification vs the node:crypto baseline. Under Bun the public rust.hmacSha256 delegates to Bun.CryptoHasher (BUN_WINS \u2014 hex re-encoded to the same contract); this row measures the raw addon (see src/bench/raw-native.ts).";
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
readonly name: "hmacSha256Verify";
|
|
930
|
-
readonly label: "HMAC verify";
|
|
931
|
-
readonly nativeTask: "native:hmac_verify";
|
|
932
|
-
readonly rustTask: "rust:hmac_verify";
|
|
933
|
-
readonly status: "proven";
|
|
934
|
-
readonly typicalRatio: 2.4;
|
|
935
|
-
},
|
|
936
|
-
{
|
|
937
|
-
readonly name: "validateEmail";
|
|
938
|
-
readonly label: "Email validation";
|
|
939
|
-
readonly nativeTask: "native:validate_email";
|
|
940
|
-
readonly rustTask: "rust:validate_email";
|
|
941
|
-
readonly status: "proven";
|
|
942
|
-
readonly typicalRatio: 3;
|
|
943
|
-
},
|
|
944
|
-
{
|
|
945
|
-
readonly name: "validateIpv6";
|
|
946
|
-
readonly label: "IPv6 validation";
|
|
947
|
-
readonly nativeTask: "native:validate_ipv6";
|
|
948
|
-
readonly rustTask: "rust:validate_ipv6";
|
|
949
|
-
readonly status: "proven";
|
|
950
|
-
readonly typicalRatio: 1.3;
|
|
951
|
-
},
|
|
952
|
-
{
|
|
953
|
-
readonly name: "urlEncode";
|
|
954
|
-
readonly label: "URL encode";
|
|
955
|
-
readonly nativeTask: "native:url_encode";
|
|
956
|
-
readonly rustTask: "rust:url_encode";
|
|
957
|
-
readonly status: "not-competitive";
|
|
958
|
-
readonly note: "Public rust.urlEncode delegates to encodeURIComponent under Bun (BUN_WINS \u2014 ~3-4x faster than the FFI crossing; skips the C-ABI call). The rust: column measures the RAW addon (src/bench/raw-native.ts): the small-input FFI crossing loses (~0.58x), but LARGE inputs win (rust:url_encode_large ~1.45x) \u2014 the honest report no longer self-compares the built-in. Rust addon remains the Node path. Byte parity pinned by test/unit/features/url.test.ts + test/unit/features/wiring.test.ts.";
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
readonly name: "urlDecode";
|
|
962
|
-
readonly label: "URL decode";
|
|
963
|
-
readonly nativeTask: "native:url_decode";
|
|
964
|
-
readonly rustTask: "rust:url_decode";
|
|
965
|
-
readonly status: "not-competitive";
|
|
966
|
-
readonly note: "Public rust.urlDecode delegates to decodeURIComponent under Bun (BUN_WINS \u2014 ~4-8x faster than the FFI crossing; strict UTF-8 semantics match: both throw on malformed/invalid). The rust: column measures the RAW addon (src/bench/raw-native.ts): the small-input FFI crossing loses (~0.55x), but LARGE inputs win (rust:url_decode_large ~3.45x) \u2014 the honest report no longer self-compares the built-in. Rust addon remains the Node path and the raw-bytes urlDecodeBytes stays native. Parity pinned by test/unit/features/url.test.ts + test/unit/features/wiring.test.ts.";
|
|
967
|
-
},
|
|
968
|
-
{
|
|
969
|
-
readonly name: "jwtVerify";
|
|
970
|
-
readonly label: "JWT verify";
|
|
971
|
-
readonly nativeTask: "native:jwt_verify";
|
|
972
|
-
readonly rustTask: "rust:jwt_verify";
|
|
973
|
-
readonly status: "proven";
|
|
974
|
-
readonly typicalRatio: 1.4;
|
|
975
|
-
},
|
|
976
|
-
{
|
|
977
|
-
readonly name: "passwordHash";
|
|
978
|
-
readonly label: "Password hash";
|
|
979
|
-
readonly nativeTask: "native:password_hash";
|
|
980
|
-
readonly rustTask: "rust:password_hash";
|
|
981
|
-
readonly status: "proven";
|
|
982
|
-
readonly typicalRatio: 19;
|
|
983
|
-
readonly note: "argon2id vs node:crypto scrypt.";
|
|
984
|
-
},
|
|
985
|
-
{
|
|
986
|
-
readonly name: "aeadEncrypt";
|
|
987
|
-
readonly label: "AEAD encrypt";
|
|
988
|
-
readonly nativeTask: "native:aead_encrypt";
|
|
989
|
-
readonly rustTask: "rust:aead_encrypt";
|
|
990
|
-
readonly status: "proven";
|
|
991
|
-
readonly typicalRatio: 1.6;
|
|
992
|
-
},
|
|
993
|
-
{
|
|
994
|
-
readonly name: "aeadDecrypt";
|
|
995
|
-
readonly label: "AEAD decrypt";
|
|
996
|
-
readonly nativeTask: "native:aead_decrypt";
|
|
997
|
-
readonly rustTask: "rust:aead_decrypt";
|
|
998
|
-
readonly status: "proven";
|
|
999
|
-
readonly typicalRatio: 2;
|
|
1000
|
-
},
|
|
1001
|
-
{
|
|
1002
|
-
readonly name: "gzipDecompress";
|
|
1003
|
-
readonly label: "Gzip decompress";
|
|
1004
|
-
readonly nativeTask: "native:gzip_decompress";
|
|
1005
|
-
readonly rustTask: "rust:gzip_decompress";
|
|
1006
|
-
readonly status: "proven";
|
|
1007
|
-
readonly typicalRatio: 1.4;
|
|
1008
|
-
readonly note: "zlib-rs vs node zlib; swings 1.0-1.4x run-to-run. Deliberately NOT delegated to Bun.gunzipSync under Bun (NOT in BUN_WINS): Bun's decompressor has no output-size bound, and the native path keeps the 64 MiB decompression-bomb cap.";
|
|
1009
|
-
},
|
|
1010
|
-
{
|
|
1011
|
-
readonly name: "brotliDecompress";
|
|
1012
|
-
readonly label: "Brotli decompress";
|
|
1013
|
-
readonly nativeTask: "native:brotli_decompress";
|
|
1014
|
-
readonly rustTask: "rust:brotli_decompress";
|
|
1015
|
-
readonly status: "proven";
|
|
1016
|
-
readonly typicalRatio: 1.9;
|
|
1017
|
-
},
|
|
1018
|
-
{
|
|
1019
|
-
readonly name: "multipartParse";
|
|
1020
|
-
readonly label: "Multipart parse";
|
|
1021
|
-
readonly nativeTask: "native:multipart_parse";
|
|
1022
|
-
readonly rustTask: "rust:multipart_parse";
|
|
1023
|
-
readonly status: "proven";
|
|
1024
|
-
readonly typicalRatio: 2.3;
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
readonly name: "wsFrameEncode";
|
|
1028
|
-
readonly label: "WS frame encode";
|
|
1029
|
-
readonly nativeTask: "native:ws_frame_encode";
|
|
1030
|
-
readonly rustTask: "rust:ws_frame_encode";
|
|
1031
|
-
readonly status: "proven";
|
|
1032
|
-
readonly typicalRatio: 1.8;
|
|
1033
|
-
},
|
|
1034
|
-
{
|
|
1035
|
-
readonly name: "wsFrameDecode";
|
|
1036
|
-
readonly label: "WS frame decode";
|
|
1037
|
-
readonly nativeTask: "native:ws_frame_decode";
|
|
1038
|
-
readonly rustTask: "rust:ws_frame_decode";
|
|
1039
|
-
readonly status: "proven";
|
|
1040
|
-
readonly typicalRatio: 2;
|
|
1041
|
-
},
|
|
1042
|
-
{
|
|
1043
|
-
readonly name: "createSchemaValidator";
|
|
1044
|
-
readonly label: "JSON schema validate";
|
|
1045
|
-
readonly nativeTask: "native:json_schema_validate";
|
|
1046
|
-
readonly rustTask: "rust:json_schema_validate";
|
|
1047
|
-
readonly status: "proven";
|
|
1048
|
-
readonly note: "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.";
|
|
1049
|
-
},
|
|
1050
|
-
{
|
|
1051
|
-
readonly name: "crc32";
|
|
1052
|
-
readonly label: "CRC32";
|
|
1053
|
-
readonly nativeTask: "native:crc32";
|
|
1054
|
-
readonly rustTask: "rust:crc32";
|
|
1055
|
-
readonly status: "not-competitive";
|
|
1056
|
-
readonly note: "Raw addon vs the same-engine JS crc-32 loop (~0.23-0.44x across runs). Under Bun the public rust.crc32 delegates to Bun.hash.crc32 (BUN_WINS, ~2.8-8.4x) \u2014 this row measures the raw addon (src/bench/raw-native.ts).";
|
|
1057
|
-
},
|
|
1058
|
-
{
|
|
1059
|
-
readonly name: "xxh3";
|
|
1060
|
-
readonly label: "XXH3-64";
|
|
1061
|
-
readonly nativeTask: "diag:bun_hash_xxh3";
|
|
1062
|
-
readonly rustTask: "diag:xxh3";
|
|
1063
|
-
readonly status: "not-competitive";
|
|
1064
|
-
readonly note: "Bun.hash.xxHash3 wins ~4x (in-process C++ beats the FFI crossing). The public rust.xxh3 now DELEGATES to Bun.hash.xxHash3 under Bun (BUN_WINS); the addon remains the Node/non-Bun fast path. See docs/bun-builtins-decision-matrix.md.";
|
|
1065
|
-
},
|
|
1066
|
-
{
|
|
1067
|
-
readonly name: "randomToken";
|
|
1068
|
-
readonly label: "Random token";
|
|
1069
|
-
readonly nativeTask: "native:random_token";
|
|
1070
|
-
readonly rustTask: "rust:random_token";
|
|
1071
|
-
readonly status: "parity";
|
|
1072
|
-
readonly typicalRatio: 1.1;
|
|
1073
|
-
readonly note: "Raw addon vs crypto.getRandomValues baseline. Under Bun the public rust.randomToken delegates to crypto.getRandomValues + native hex (BUN_WINS, same 2n-hex format + 16 MiB guard) \u2014 this row measures the raw addon (src/bench/raw-native.ts).";
|
|
1074
|
-
},
|
|
1075
|
-
{
|
|
1076
|
-
readonly name: "validateUuid";
|
|
1077
|
-
readonly label: "UUID validation";
|
|
1078
|
-
readonly nativeTask: "native:validate_uuid";
|
|
1079
|
-
readonly rustTask: "rust:validate_uuid";
|
|
1080
|
-
readonly status: "parity";
|
|
1081
|
-
readonly typicalRatio: 1;
|
|
1082
|
-
},
|
|
1083
|
-
{
|
|
1084
|
-
readonly name: "validateIpv4";
|
|
1085
|
-
readonly label: "IPv4 validation";
|
|
1086
|
-
readonly nativeTask: "native:validate_ipv4";
|
|
1087
|
-
readonly rustTask: "rust:validate_ipv4";
|
|
1088
|
-
readonly status: "parity";
|
|
1089
|
-
readonly typicalRatio: 1.05;
|
|
1090
|
-
},
|
|
1091
|
-
{
|
|
1092
|
-
readonly name: "mimeFromExtension";
|
|
1093
|
-
readonly label: "MIME lookup";
|
|
1094
|
-
readonly nativeTask: "native:mime";
|
|
1095
|
-
readonly rustTask: "rust:mime";
|
|
1096
|
-
readonly status: "parity";
|
|
1097
|
-
readonly typicalRatio: 1.4;
|
|
1098
|
-
readonly note: "Consistently ~1.38-1.44x rust after the 2026-08-12 marshal reduction (cachedMime no longer per-call slices; text.mimeFromExtension memoizes the decoded string). phf table lookup.";
|
|
1099
|
-
},
|
|
1100
|
-
{
|
|
1101
|
-
readonly name: "gzipCompress";
|
|
1102
|
-
readonly label: "Gzip compress";
|
|
1103
|
-
readonly nativeTask: "native:gzip_compress";
|
|
1104
|
-
readonly rustTask: "rust:gzip_compress";
|
|
1105
|
-
readonly status: "parity";
|
|
1106
|
-
readonly typicalRatio: 1;
|
|
1107
|
-
readonly note: "Raw addon vs node zlib baseline. Under Bun the public rust.gzipCompress delegates to Bun.gzipSync (BUN_WINS, ~2x; decompression-parity with the addon \u2014 only the header OS byte differs) \u2014 this row measures the raw addon (src/bench/raw-native.ts).";
|
|
1108
|
-
},
|
|
1109
|
-
{
|
|
1110
|
-
readonly name: "sseEncodeEvent";
|
|
1111
|
-
readonly label: "SSE encode";
|
|
1112
|
-
readonly nativeTask: "native:sse_encode";
|
|
1113
|
-
readonly rustTask: "rust:sse_encode";
|
|
1114
|
-
readonly status: "parity";
|
|
1115
|
-
readonly typicalRatio: 1.1;
|
|
1116
|
-
},
|
|
1117
|
-
{
|
|
1118
|
-
readonly name: "passwordHashBcrypt";
|
|
1119
|
-
readonly label: "Password hash (bcrypt)";
|
|
1120
|
-
readonly nativeTask: "diag:bun_password_bcrypt_hash";
|
|
1121
|
-
readonly rustTask: "diag:bcrypt_hash";
|
|
1122
|
-
readonly status: "parity";
|
|
1123
|
-
readonly note: "Bun.password.bcrypt ~1.24x faster at cost 10 (noisy KDF); rust is self-contained and non-Bun. See docs/bun-builtins-decision-matrix.md.";
|
|
1124
|
-
},
|
|
1125
|
-
{
|
|
1126
|
-
readonly name: "passwordVerifyBcrypt";
|
|
1127
|
-
readonly label: "Password verify (bcrypt)";
|
|
1128
|
-
readonly nativeTask: "diag:bun_password_bcrypt_verify";
|
|
1129
|
-
readonly rustTask: "diag:bcrypt_verify";
|
|
1130
|
-
readonly status: "parity";
|
|
1131
|
-
readonly note: "Rust ~1.49x vs Bun.password.verify at cost 10; KDF timings noisy \u2014 parity for gate stability.";
|
|
1132
|
-
},
|
|
1133
|
-
{
|
|
1134
|
-
readonly name: "pbkdf2Sha256";
|
|
1135
|
-
readonly label: "PBKDF2-HMAC-SHA256";
|
|
1136
|
-
readonly nativeTask: "diag:pbkdf2_sha256";
|
|
1137
|
-
readonly rustTask: "diag:pbkdf2_sha256_rust";
|
|
1138
|
-
readonly status: "parity";
|
|
1139
|
-
readonly note: "~1.08x vs node:crypto pbkdf2Sync at 100k rounds (parity); Bun has no synchronous PBKDF2.";
|
|
1140
|
-
},
|
|
1141
|
-
{
|
|
1142
|
-
readonly name: "jsonParse";
|
|
1143
|
-
readonly label: "JSON parse";
|
|
1144
|
-
readonly nativeTask: "native:json_parse";
|
|
1145
|
-
readonly rustTask: "rust:json_parse";
|
|
1146
|
-
readonly status: "not-competitive";
|
|
1147
|
-
readonly note: "Bun's JSON.parse beats the native DOM + napi marshaling path (~5x).";
|
|
1148
|
-
},
|
|
1149
|
-
{
|
|
1150
|
-
readonly name: "jwtSign";
|
|
1151
|
-
readonly label: "JWT sign";
|
|
1152
|
-
readonly nativeTask: "native:jwt_sign";
|
|
1153
|
-
readonly rustTask: "rust:jwt_sign";
|
|
1154
|
-
readonly status: "not-competitive";
|
|
1155
|
-
readonly note: "Base64 + claims DOM marshal overhead. Use jwtSignBytes (claims as JSON bytes) which skips the napi Value marshal and flips to a ~1.07x win.";
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
readonly name: "jwtSignBytes";
|
|
1159
|
-
readonly label: "JWT sign (bytes)";
|
|
1160
|
-
readonly nativeTask: "native:jwt_sign";
|
|
1161
|
-
readonly rustTask: "rust:jwt_sign_bytes";
|
|
1162
|
-
readonly status: "parity";
|
|
1163
|
-
readonly note: "Byte-JSON claims overload avoids the napi serde_json::Value DOM marshal; flips jwtSign from a ~1.3x loss to ~1.07x (parity).";
|
|
1164
|
-
},
|
|
1165
|
-
{
|
|
1166
|
-
readonly name: "brotliCompress";
|
|
1167
|
-
readonly label: "Brotli compress";
|
|
1168
|
-
readonly nativeTask: "native:brotli_compress";
|
|
1169
|
-
readonly rustTask: "rust:brotli_compress";
|
|
1170
|
-
readonly status: "not-competitive";
|
|
1171
|
-
readonly note: "The native brotli baseline wins for small inputs.";
|
|
1172
|
-
},
|
|
1173
|
-
{
|
|
1174
|
-
readonly name: "templateRender";
|
|
1175
|
-
readonly label: "Template render";
|
|
1176
|
-
readonly nativeTask: "native:template_render";
|
|
1177
|
-
readonly rustTask: "rust:template_render";
|
|
1178
|
-
readonly status: "not-competitive";
|
|
1179
|
-
readonly note: "The allocating render marshals the context via napi DOM (~1.5x loss). Use TemplateRenderer.renderBytes (JSON-bytes context) which avoids it and wins ~1.37x.";
|
|
1180
|
-
},
|
|
1181
|
-
{
|
|
1182
|
-
readonly name: "formParsePacked";
|
|
1183
|
-
readonly label: "Form parse";
|
|
1184
|
-
readonly nativeTask: "native:form_parse";
|
|
1185
|
-
readonly rustTask: "rust:form_parse";
|
|
1186
|
-
readonly status: "proven";
|
|
1187
|
-
readonly typicalRatio: 5.8;
|
|
1188
|
-
readonly note: "Benchmarked via the FormParser instance (same parser core).";
|
|
1189
|
-
},
|
|
1190
|
-
{
|
|
1191
|
-
readonly name: "parseMediaType";
|
|
1192
|
-
readonly label: "Media type parse";
|
|
1193
|
-
readonly nativeTask: "native:media_type_parse";
|
|
1194
|
-
readonly rustTask: "rust:media_type_parse";
|
|
1195
|
-
readonly status: "not-competitive";
|
|
1196
|
-
readonly note: "Consistent ~0.71-0.81x (sub-\u00B5s FFI crossing vs the JS baseline); use MediaTypeMatcher (precompiled expected) for the zero-alloc match path.";
|
|
1197
|
-
},
|
|
1198
|
-
{
|
|
1199
|
-
readonly name: "etag";
|
|
1200
|
-
readonly label: "ETag";
|
|
1201
|
-
readonly nativeTask: "native:etag";
|
|
1202
|
-
readonly rustTask: "rust:etag";
|
|
1203
|
-
readonly status: "parity";
|
|
1204
|
-
readonly typicalRatio: 1.06;
|
|
1205
|
-
},
|
|
1206
|
-
{
|
|
1207
|
-
readonly name: "httpDate";
|
|
1208
|
-
readonly label: "HTTP date";
|
|
1209
|
-
readonly nativeTask: "native:http_date";
|
|
1210
|
-
readonly rustTask: "rust:http_date";
|
|
1211
|
-
readonly status: "not-competitive";
|
|
1212
|
-
readonly note: "Delegates to Date.toUTCString() under Bun (BUN_WINS \u2014 ~3.7x faster than the FFI crossing; byte-identical RFC 1123 for HTTP timestamps, incl. leap years/epoch). Rust addon remains the Node path; pooled httpDateInto stays native.";
|
|
1213
|
-
},
|
|
1214
|
-
{
|
|
1215
|
-
readonly name: "createConditionalRequest";
|
|
1216
|
-
readonly label: "Conditional request";
|
|
1217
|
-
readonly nativeTask: "native:conditional";
|
|
1218
|
-
readonly rustTask: "rust:conditional";
|
|
1219
|
-
readonly status: "proven";
|
|
1220
|
-
readonly typicalRatio: 1.4;
|
|
1221
|
-
},
|
|
1222
|
-
{
|
|
1223
|
-
readonly name: "createAcceptNegotiator";
|
|
1224
|
-
readonly label: "Accept-Encoding negotiate";
|
|
1225
|
-
readonly nativeTask: "native:accept_negotiate";
|
|
1226
|
-
readonly rustTask: "rust:accept_negotiate";
|
|
1227
|
-
readonly status: "proven";
|
|
1228
|
-
readonly typicalRatio: 4;
|
|
1229
|
-
},
|
|
1230
|
-
{
|
|
1231
|
-
readonly name: "base64Encode";
|
|
1232
|
-
readonly label: "Base64 encode";
|
|
1233
|
-
readonly nativeTask: "native:base64_encode";
|
|
1234
|
-
readonly rustTask: "rust:base64_encode";
|
|
1235
|
-
readonly status: "not-competitive";
|
|
1236
|
-
readonly note: "Delegates to Buffer base64 under Bun for the standard padded, non-url-safe case (BUN_WINS \u2014 ~2x faster than the FFI crossing); url-safe/unpadded falls through to native. Rust addon remains the Node path. Parity pinned by test/unit/features/encoding.test.ts.";
|
|
1237
|
-
},
|
|
1238
|
-
{
|
|
1239
|
-
readonly name: "base64Decode";
|
|
1240
|
-
readonly label: "Base64 decode";
|
|
1241
|
-
readonly nativeTask: "native:base64_decode";
|
|
1242
|
-
readonly rustTask: "rust:base64_decode";
|
|
1243
|
-
readonly status: "parity";
|
|
1244
|
-
readonly typicalRatio: 1.17;
|
|
1245
|
-
},
|
|
1246
|
-
{
|
|
1247
|
-
readonly name: "hexEncode";
|
|
1248
|
-
readonly label: "Hex encode";
|
|
1249
|
-
readonly nativeTask: "native:hex_encode";
|
|
1250
|
-
readonly rustTask: "rust:hex_encode";
|
|
1251
|
-
readonly status: "not-competitive";
|
|
1252
|
-
readonly note: "Buffer.toString('hex') is native SIMD; pooled hexEncodeInto cuts the loss from ~2.2x to ~1.35x.";
|
|
1253
|
-
},
|
|
1254
|
-
{
|
|
1255
|
-
readonly name: "hexDecode";
|
|
1256
|
-
readonly label: "Hex decode";
|
|
1257
|
-
readonly nativeTask: "native:hex_decode";
|
|
1258
|
-
readonly rustTask: "rust:hex_decode";
|
|
1259
|
-
readonly status: "parity";
|
|
1260
|
-
readonly typicalRatio: 0.78;
|
|
1261
|
-
readonly note: "Slightly slower than Buffer hex decode (~1.3x).";
|
|
1262
|
-
},
|
|
1263
|
-
{
|
|
1264
|
-
readonly name: "signCookie";
|
|
1265
|
-
readonly label: "Cookie sign";
|
|
1266
|
-
readonly nativeTask: "native:cookie_sign";
|
|
1267
|
-
readonly rustTask: "rust:cookie_sign";
|
|
1268
|
-
readonly status: "proven";
|
|
1269
|
-
readonly typicalRatio: 9;
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
readonly name: "verifyCookie";
|
|
1273
|
-
readonly label: "Cookie verify";
|
|
1274
|
-
readonly nativeTask: "native:cookie_verify";
|
|
1275
|
-
readonly rustTask: "rust:cookie_verify";
|
|
1276
|
-
readonly status: "proven";
|
|
1277
|
-
readonly typicalRatio: 2.1;
|
|
1278
|
-
},
|
|
1279
|
-
{
|
|
1280
|
-
readonly name: "csrfToken";
|
|
1281
|
-
readonly label: "CSRF create";
|
|
1282
|
-
readonly nativeTask: "native:csrf_create";
|
|
1283
|
-
readonly rustTask: "rust:csrf_create";
|
|
1284
|
-
readonly status: "proven";
|
|
1285
|
-
readonly typicalRatio: 13.8;
|
|
1286
|
-
},
|
|
1287
|
-
{
|
|
1288
|
-
readonly name: "csrfVerify";
|
|
1289
|
-
readonly label: "CSRF verify";
|
|
1290
|
-
readonly nativeTask: "native:csrf_verify";
|
|
1291
|
-
readonly rustTask: "rust:csrf_verify";
|
|
1292
|
-
readonly status: "proven";
|
|
1293
|
-
readonly typicalRatio: 2.7;
|
|
1294
|
-
},
|
|
1295
|
-
{
|
|
1296
|
-
readonly name: "urlResolve";
|
|
1297
|
-
readonly label: "URL resolve";
|
|
1298
|
-
readonly nativeTask: "native:url_resolve";
|
|
1299
|
-
readonly rustTask: "rust:url_resolve";
|
|
1300
|
-
readonly status: "parity";
|
|
1301
|
-
readonly typicalRatio: 1.11;
|
|
1302
|
-
readonly note: "Noisy ~1.5\u00B5s op; swings 0.74-1.03x run-to-run. UrlBuilder reuses the parsed base.";
|
|
1303
|
-
},
|
|
1304
|
-
{
|
|
1305
|
-
readonly name: "urlEncodeQuery";
|
|
1306
|
-
readonly label: "URL query build";
|
|
1307
|
-
readonly nativeTask: "native:url_encode_query";
|
|
1308
|
-
readonly rustTask: "rust:url_encode_query";
|
|
1309
|
-
readonly status: "not-competitive";
|
|
1310
|
-
readonly note: "encodeURIComponent baseline wins (~1.2-1.35x).";
|
|
1311
|
-
},
|
|
1312
|
-
{
|
|
1313
|
-
readonly name: "passwordVerify";
|
|
1314
|
-
readonly label: "Password verify";
|
|
1315
|
-
readonly nativeTask: "";
|
|
1316
|
-
readonly rustTask: "";
|
|
1317
|
-
readonly status: "unmeasured";
|
|
1318
|
-
readonly note: "Depends on the hash cost; no standalone comparison.";
|
|
1319
|
-
},
|
|
1320
|
-
{
|
|
1321
|
-
readonly name: "urlDecodeBytes";
|
|
1322
|
-
readonly label: "URL decode (bytes)";
|
|
1323
|
-
readonly nativeTask: "";
|
|
1324
|
-
readonly rustTask: "";
|
|
1325
|
-
readonly status: "unmeasured";
|
|
1326
|
-
},
|
|
1327
|
-
{
|
|
1328
|
-
readonly name: "configure";
|
|
1329
|
-
readonly label: "configure";
|
|
1330
|
-
readonly nativeTask: "";
|
|
1331
|
-
readonly rustTask: "";
|
|
1332
|
-
readonly status: "unmeasured";
|
|
1333
|
-
readonly note: "Config only.";
|
|
1334
|
-
},
|
|
1335
|
-
{
|
|
1336
|
-
readonly name: "initThreadPool";
|
|
1337
|
-
readonly label: "initThreadPool";
|
|
1338
|
-
readonly nativeTask: "";
|
|
1339
|
-
readonly rustTask: "";
|
|
1340
|
-
readonly status: "unmeasured";
|
|
1341
|
-
readonly note: "Runtime setup only.";
|
|
1342
|
-
},
|
|
1343
|
-
{
|
|
1344
|
-
readonly name: "rayonNumThreads";
|
|
1345
|
-
readonly label: "rayonNumThreads";
|
|
1346
|
-
readonly nativeTask: "";
|
|
1347
|
-
readonly rustTask: "";
|
|
1348
|
-
readonly status: "unmeasured";
|
|
1349
|
-
readonly note: "Introspection only.";
|
|
1350
|
-
}
|
|
1351
|
-
];
|
|
1352
|
-
/** An entry of the registry with a specific status (type-level). */
|
|
1353
|
-
export type ProvenEntryOf<S extends PerformanceStatus> = Extract<(typeof PROVEN_SURFACE)[number], {
|
|
1354
|
-
status: S;
|
|
1355
|
-
}>;
|
|
1356
|
-
/**
|
|
1357
|
-
* The `rust.<name>` keys whose status is "proven" — the only keys exposed on
|
|
1358
|
-
* the `proven` surface. Derived from the registry so the type can never drift
|
|
1359
|
-
* from the data.
|
|
1360
|
-
*/
|
|
1361
|
-
export type ProvenKey = ProvenEntryOf<"proven">["name"];
|
|
1362
|
-
/** Look up the status of a public function by its `rust.<name>` key. */
|
|
1363
|
-
export declare function provenStatus(name: string): PerformanceStatus | undefined;
|
|
1364
|
-
/** Whether a function is part of the performance-proven surface. */
|
|
1365
|
-
export declare function isProven(name: string): boolean;
|
|
1366
|
-
/** The entries that are performance-proven (Rust clearly wins). */
|
|
1367
|
-
export declare function provenSurface(): ProvenEntry[];
|
|
1368
|
-
/** Aggregate summary of the registry (status → count). */
|
|
1369
|
-
export declare function provenSummary(): Record<PerformanceStatus, number>;
|
|
1370
|
-
/**
|
|
1371
|
-
* The performance-annotated client: the full {@link RustClient} surface.
|
|
1372
|
-
* Each exported function's JSDoc notes its measured performance vs the JS
|
|
1373
|
-
* baseline; functions slower than the baseline carry `@deprecated`.
|
|
782
|
+
* @example
|
|
783
|
+
* ```ts
|
|
784
|
+
* const route = createNativeRoute({ parseQuery: true, parseCookies: true })
|
|
785
|
+
* const result = route.run('page=1&limit=20', 'session=abc', null)
|
|
786
|
+
* // result.flags & ROUTE_FLAG.OK → parse succeeded; result.query = [['page','1'],…]
|
|
787
|
+
* ```
|
|
1374
788
|
*/
|
|
1375
|
-
export
|
|
1376
|
-
/**
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
* Reusing a single decoder avoids allocation overhead in hot paths.
|
|
1392
|
-
*/
|
|
1393
|
-
export declare const decoder: TextDecoder;
|
|
1394
|
-
export interface AdaptiveEstimateOptions {
|
|
789
|
+
export declare function createNativeRoute(plan?: NativeRoutePlan): NativeRoute;
|
|
790
|
+
/** A matched dynamic route: the handler map + the extracted path params. */
|
|
791
|
+
export interface PathMatch {
|
|
792
|
+
methods: Record<string, unknown>;
|
|
793
|
+
params: Record<string, string>;
|
|
794
|
+
}
|
|
795
|
+
/** A raw web-standard route handler (`Request` → `Response`), e.g. the
|
|
796
|
+
* health/metrics probes (`livenessHandler`, `readinessHandler`,
|
|
797
|
+
* `healthHandler`, `metricsHandler`). Accepted as `BakedRoute.read` and
|
|
798
|
+
* served for GET directly, outside the ingress pipeline. The optional third
|
|
799
|
+
* arg carries the matched dynamic path params (`:param`/`*`) on the Node
|
|
800
|
+
* adapter (Bun's native router handles matching; the ingress pipeline does
|
|
801
|
+
* not echo params in its response). */
|
|
802
|
+
type RouteHandler = (req: Request, srv?: unknown, params?: Record<string, string>) => Response | Promise<Response>;
|
|
803
|
+
/** Route spec for the Bun.serve builder. */
|
|
804
|
+
export interface BakedRoute {
|
|
1395
805
|
/**
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
806
|
+
* Wires GET + HEAD read handlers. Accepts an optimized ingress handler OR a
|
|
807
|
+
* raw `RouteHandler` function (health/metrics probes are raw — see
|
|
808
|
+
* src/ingress/health.ts). A raw handler is served for GET only.
|
|
1398
809
|
*/
|
|
1399
|
-
|
|
1400
|
-
/**
|
|
1401
|
-
|
|
1402
|
-
/**
|
|
1403
|
-
|
|
1404
|
-
/**
|
|
1405
|
-
|
|
810
|
+
read?: OptimizedIngressHandler | RouteHandler;
|
|
811
|
+
/** Wires POST/PUT/PATCH JSON-write handlers (and OPTIONS fallback). */
|
|
812
|
+
write?: OptimizedIngressHandler;
|
|
813
|
+
/** Wires a POST echo handler. */
|
|
814
|
+
echo?: OptimizedIngressHandler;
|
|
815
|
+
/** Wires a GET read handler (cookies-style route). */
|
|
816
|
+
cookies?: OptimizedIngressHandler;
|
|
817
|
+
/** Wires a DELETE read-style handler. */
|
|
818
|
+
delete?: OptimizedIngressHandler;
|
|
819
|
+
/**
|
|
820
|
+
* A JS responder route: the native pipeline owns parse/validate/CORS/
|
|
821
|
+
* rate-limit + terminal rejections; the responder builds the 2xx body
|
|
822
|
+
* (async OK). Wired for `methods` (default GET). Built by
|
|
823
|
+
* `nativeResponderRoute` (src/ingress/routes/responder.ts).
|
|
824
|
+
*/
|
|
825
|
+
responder?: {
|
|
826
|
+
/** The route's compiled native `IngressInner`. */
|
|
827
|
+
ingress: OptimizedIngressHandler;
|
|
828
|
+
/** The JS 2xx builder. */
|
|
829
|
+
handler: NativeResponder;
|
|
830
|
+
/** HTTP methods to wire (default `['GET']`). */
|
|
831
|
+
methods?: ReadonlyArray<string>;
|
|
832
|
+
/** Terminal envelope style (`'castrum'` default / `'ignex'`). */
|
|
833
|
+
terminalStyle?: TerminalStyle;
|
|
834
|
+
/** Read the body for native validation (default false — framework owns it). */
|
|
835
|
+
readBody?: boolean;
|
|
836
|
+
};
|
|
837
|
+
/**
|
|
838
|
+
* A LEAN native-stack responder route: the route-wire v3 per-route stack
|
|
839
|
+
* (`createNativeRoute`) runs ONLY the plan's stages in ONE native call —
|
|
840
|
+
* no CORS/rate-limit/security/IP/metadata envelope. Wired for `methods`
|
|
841
|
+
* (default GET) by `nativeRouteHandler` (src/ingress/routes/native.ts).
|
|
842
|
+
*/
|
|
843
|
+
native?: {
|
|
844
|
+
/** The route-wire v3 plan (parse/validate stages + limits). */
|
|
845
|
+
plan: NativeRoutePlan;
|
|
846
|
+
/** The JS 2xx builder. */
|
|
847
|
+
handler: NativeResponder;
|
|
848
|
+
/** HTTP methods to wire (default `['GET']`). */
|
|
849
|
+
methods?: ReadonlyArray<string>;
|
|
850
|
+
/** Read the body for `requireJsonBody`/`validateBody` (default false). */
|
|
851
|
+
readBody?: boolean;
|
|
852
|
+
};
|
|
853
|
+
/** Overrides `maxBodyBytes` for this route's write/echo handlers. */
|
|
854
|
+
maxBodyBytes?: number;
|
|
855
|
+
/**
|
|
856
|
+
* Overall deadline (ms) for reading the request body on this route's
|
|
857
|
+
* write/echo handlers. Default: 30_000 (`DEFAULT_BODY_TIMEOUT_MS`).
|
|
858
|
+
* Set 0 to disable.
|
|
859
|
+
*/
|
|
860
|
+
bodyTimeoutMs?: number;
|
|
861
|
+
}
|
|
862
|
+
/** RFC 6455 handshake values returned by `CreateIngressServerOptions.upgrade`. */
|
|
863
|
+
export interface NodeUpgradeHandshake {
|
|
864
|
+
/** `Sec-WebSocket-Accept` value (compute via `rust.text.wsAcceptKey`). */
|
|
865
|
+
accept: string;
|
|
866
|
+
/** Optional negotiated subprotocol (`Sec-WebSocket-Protocol`). */
|
|
867
|
+
protocol?: string;
|
|
868
|
+
}
|
|
869
|
+
/** Options for {@link createIngressServer}. */
|
|
870
|
+
export interface CreateIngressServerOptions {
|
|
871
|
+
port: number;
|
|
872
|
+
hostname?: string;
|
|
873
|
+
idleTimeout?: number;
|
|
874
|
+
maxRequestBodySize?: number;
|
|
875
|
+
reusePort?: boolean;
|
|
876
|
+
copyBody?: boolean;
|
|
877
|
+
getIp?: (req: Request, srv: unknown) => string | undefined;
|
|
878
|
+
routes: Record<string, BakedRoute>;
|
|
879
|
+
fallback?: OptimizedIngressHandler;
|
|
880
|
+
/**
|
|
881
|
+
* Invoked when an unhandled error escapes a request handler (currently wired
|
|
882
|
+
* on the Node adapter; the Bun path uses runtime.onResponse). Never throws —
|
|
883
|
+
* hook failures are swallowed.
|
|
884
|
+
*/
|
|
885
|
+
onError?: (info: {
|
|
886
|
+
error: Error;
|
|
887
|
+
request?: Request;
|
|
888
|
+
}) => void;
|
|
889
|
+
/** Node adapter only: ms to receive the complete request (headers + body).
|
|
890
|
+
* Guards slowloris/trickling requests. Default: 30_000. */
|
|
891
|
+
requestTimeoutMs?: number;
|
|
892
|
+
/** Node adapter only: ms to receive the request headers. Default: 30_000. */
|
|
893
|
+
headersTimeoutMs?: number;
|
|
894
|
+
/** Node adapter only: max requests per keep-alive socket before Node forces
|
|
895
|
+
* a fresh connection (bounds long-lived socket reuse). Default: 1000. */
|
|
896
|
+
maxRequestsPerSocket?: number;
|
|
897
|
+
/** Node adapter only: optional WebSocket upgrade handler. Return the RFC 6455
|
|
898
|
+
* handshake values (see {@link NodeUpgradeHandshake}) to complete the
|
|
899
|
+
* handshake on the hijacked socket; return null/undefined to decline the
|
|
900
|
+
* upgrade. Ignored by the Bun server. (`createWebSocketUpgrade` is Bun-only
|
|
901
|
+
* — Node's Response cannot carry a 101.) */
|
|
902
|
+
upgrade?: (req: Request) => NodeUpgradeHandshake | null | undefined;
|
|
903
|
+
/** Node adapter only: called after a WebSocket upgrade handshake completes
|
|
904
|
+
* (the 101 response was accepted), with the hijacked socket so the caller
|
|
905
|
+
* can attach a frame codec/message loop (e.g. the `ws` library). Ignored by
|
|
906
|
+
* the Bun server. */
|
|
907
|
+
onUpgrade?: (socket: import("node:stream").Duplex, req: unknown, head: Buffer) => void;
|
|
1406
908
|
}
|
|
1407
909
|
/**
|
|
1408
|
-
*
|
|
910
|
+
* Minimal surface of the running server that castrum relies on.
|
|
1409
911
|
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
* observations.
|
|
912
|
+
* This is intentionally NOT `ReturnType<typeof Bun.serve>` so that the public
|
|
913
|
+
* types stay runtime-agnostic — Node.js TypeScript consumers don't need
|
|
914
|
+
* `@types/bun` just to import castrum's types. Bun's server exposes far more
|
|
915
|
+
* fields; treat the handle as opaque.
|
|
1415
916
|
*/
|
|
1416
|
-
export
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
constructor(options?: AdaptiveEstimateOptions);
|
|
1423
|
-
/** Fold a new observation into the running estimate. */
|
|
1424
|
-
sample(x: number): void;
|
|
1425
|
-
/** Current estimate (clamped to `[min, max]`). */
|
|
1426
|
-
get estimate(): number;
|
|
1427
|
-
/** Reset the estimate (back to `initial`). */
|
|
1428
|
-
reset(): void;
|
|
917
|
+
export interface ServerHandle {
|
|
918
|
+
/** The port the server is bound to, when known (Bun exposes it; Node's
|
|
919
|
+
* http.Server does not — use `BakedServer.port` instead). */
|
|
920
|
+
port?: number;
|
|
921
|
+
/** Force-stop the server. Bun semantics: `true` drops active connections. */
|
|
922
|
+
stop(force?: boolean): void;
|
|
1429
923
|
}
|
|
1430
|
-
/**
|
|
1431
|
-
export
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
/** A single labelled counter value. */
|
|
1436
|
-
export interface Counter {
|
|
1437
|
-
/** Increment by 1 (or `by`). `labels` must match the label names exactly. */
|
|
1438
|
-
inc(labels?: Labels, by?: number): void;
|
|
924
|
+
/** A running server instance plus a stop helper. */
|
|
925
|
+
export interface BakedServer {
|
|
926
|
+
server: ServerHandle;
|
|
927
|
+
stop(): void;
|
|
928
|
+
port: number;
|
|
1439
929
|
}
|
|
1440
|
-
/**
|
|
1441
|
-
export interface
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
930
|
+
/** Options for `gracefulShutdown` (drain grace period + signals). */
|
|
931
|
+
export interface GracefulShutdownOptions {
|
|
932
|
+
/** Grace period (ms) to drain in-flight requests before force-closing. */
|
|
933
|
+
timeoutMs?: number;
|
|
934
|
+
/** Signals to listen for. Default: SIGTERM + SIGINT. */
|
|
935
|
+
signals?: NodeJS.Signals[];
|
|
1445
936
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
937
|
+
/**
|
|
938
|
+
* Wire SIGTERM/SIGINT to a graceful shutdown: soft-stop (drain in-flight
|
|
939
|
+
* requests) then force-close after `timeoutMs`. Works with both the Bun and
|
|
940
|
+
* Node server handles (both expose `stop(force?)`).
|
|
941
|
+
*
|
|
942
|
+
* Returns a cleanup function that removes the signal listeners. Call it (or
|
|
943
|
+
* `process.exit`) to tear down without hanging.
|
|
944
|
+
*/
|
|
945
|
+
export declare function gracefulShutdown(handles: ReadonlyArray<ServerHandle>, options?: GracefulShutdownOptions): () => void;
|
|
946
|
+
/** Options for `createWebSocketUpgrade` (supported subprotocols). */ export interface WebSocketUpgradeOptions {
|
|
947
|
+
/** Subprotocols this server supports; the first client-requested match wins. */
|
|
948
|
+
protocols?: ReadonlyArray<string>;
|
|
1450
949
|
}
|
|
1451
|
-
/** The
|
|
1452
|
-
export interface
|
|
1453
|
-
/**
|
|
1454
|
-
|
|
1455
|
-
/**
|
|
1456
|
-
|
|
950
|
+
/** The WebSocket upgrade handshake result (101 response + negotiation info). */
|
|
951
|
+
export interface WebSocketUpgradeResult {
|
|
952
|
+
/** The 101 Switching Protocols response — return it from your fetch handler. */
|
|
953
|
+
readonly response: Response;
|
|
954
|
+
/** The client's `Sec-WebSocket-Key` (handy for connection logging). */
|
|
955
|
+
readonly key: string;
|
|
956
|
+
/** The negotiated subprotocol, if any. */
|
|
957
|
+
readonly protocol: string | null;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Build a WebSocket 101 upgrade handshake response for a request.
|
|
961
|
+
*
|
|
962
|
+
* Validates `Sec-WebSocket-Key` presence and computes the RFC 6455 accept key
|
|
963
|
+
* via the native `rust.wsAcceptKey`, optionally negotiates a subprotocol, and
|
|
964
|
+
* returns a ready-to-serve `Response`. Returns `null` when the request is not a
|
|
965
|
+
* valid upgrade (missing/empty key).
|
|
966
|
+
*
|
|
967
|
+
* This only produces the handshake — pair it with your framework's own
|
|
968
|
+
* WebSocket server (Bun.serve's `websocket` handler, or a WS library), which
|
|
969
|
+
* owns the subsequent frames:
|
|
970
|
+
*
|
|
971
|
+
* @example
|
|
972
|
+
* ```ts
|
|
973
|
+
* const srv = Bun.serve({
|
|
974
|
+
* port: 3000,
|
|
975
|
+
* fetch(req, server) {
|
|
976
|
+
* const up = createWebSocketUpgrade(req, { protocols: ["chat"] });
|
|
977
|
+
* if (up && server.upgrade(req, { data: { protocol: up.protocol } })) {
|
|
978
|
+
* return up.response;
|
|
979
|
+
* }
|
|
980
|
+
* return new Response("Not a websocket", { status: 400 });
|
|
981
|
+
* },
|
|
982
|
+
* websocket: {
|
|
983
|
+
* message(ws, msg) { ws.send(msg); },
|
|
984
|
+
* },
|
|
985
|
+
* });
|
|
986
|
+
* ```
|
|
987
|
+
*/
|
|
988
|
+
export declare function createWebSocketUpgrade(req: Request, opts?: WebSocketUpgradeOptions): WebSocketUpgradeResult | null;
|
|
989
|
+
/** Options accepted by `createRust` / `rust.configure`. */
|
|
990
|
+
export interface RustOptions {
|
|
1457
991
|
/**
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
992
|
+
* Rayon thread pool size.
|
|
993
|
+
*
|
|
994
|
+
* The rayon pool is process-wide and initialized **once** (native
|
|
995
|
+
* `OnceLock`) — the first initialization wins. Importing this module
|
|
996
|
+
* initializes the pool with defaults, so this option only takes effect when
|
|
997
|
+
* the pool has not yet been created. For reliable control set
|
|
998
|
+
* `CASTRUM_RAYON_THREADS` / `RUST_RAYON_THREADS` before import.
|
|
999
|
+
* Default: max(1, hardwareConcurrency - 1).
|
|
1460
1000
|
*/
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
|
|
1464
|
-
/**
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1467
|
-
/** Create a fresh metrics registry. */
|
|
1468
|
-
export declare function createMetrics(): MetricsRegistry;
|
|
1469
|
-
/** User-facing CORS configuration. */
|
|
1470
|
-
export interface CorsOptions {
|
|
1471
|
-
allowOrigin?: string[];
|
|
1472
|
-
allowMethods?: string[];
|
|
1473
|
-
allowHeaders?: string[];
|
|
1474
|
-
exposeHeaders?: string[];
|
|
1475
|
-
allowCredentials?: boolean;
|
|
1476
|
-
maxAge?: number;
|
|
1477
|
-
}
|
|
1478
|
-
/** Precomputed, immutable CORS policy strings. */
|
|
1479
|
-
export interface CorsStaticStrings {
|
|
1480
|
-
readonly allowMethodsJoined: string;
|
|
1481
|
-
readonly allowHeadersJoined: string;
|
|
1482
|
-
readonly exposeHeadersJoined: string;
|
|
1483
|
-
readonly maxAgeString: string | null;
|
|
1484
|
-
readonly isWildcard: boolean;
|
|
1485
|
-
readonly credentials: boolean;
|
|
1001
|
+
rayonThreads?: number;
|
|
1002
|
+
/** Cache MIME lookups keyed by normalized extension. Default: true. */
|
|
1003
|
+
mimeCache?: boolean;
|
|
1004
|
+
/** Reuse HMAC signers keyed by key-buffer identity. Default: true. */
|
|
1005
|
+
hmacCache?: boolean;
|
|
1486
1006
|
}
|
|
1487
|
-
/**
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* The `rust.batch.*` array-of-bytes batch surface: N items in → one packed
|
|
1009
|
+
* native call → bitsets / typed arrays / byte arrays out. Separated from the
|
|
1010
|
+
* implementation (`./build.ts`) so type-only consumers don't pull the native
|
|
1011
|
+
* resolver.
|
|
1012
|
+
*/
|
|
1013
|
+
export interface RustBatch {
|
|
1014
|
+
jsonValid(items: Uint8Array[]): Uint8Array;
|
|
1015
|
+
crc32(items: Uint8Array[]): Uint32Array;
|
|
1016
|
+
validateEmail(items: Uint8Array[]): Uint8Array;
|
|
1017
|
+
validateUuid(items: Uint8Array[]): Uint8Array;
|
|
1018
|
+
validateIpv4(items: Uint8Array[]): Uint8Array;
|
|
1019
|
+
validateIpv6(items: Uint8Array[]): Uint8Array;
|
|
1020
|
+
jsonSumIds(items: Uint8Array[]): BigInt64Array;
|
|
1021
|
+
queryParse(items: Uint8Array[]): Uint8Array[];
|
|
1022
|
+
cookieParse(items: Uint8Array[]): Uint8Array[];
|
|
1023
|
+
httpParseRequest(items: Uint8Array[]): Uint8Array[];
|
|
1024
|
+
jsonPatch(docs: Uint8Array[], patches: Uint8Array[]): Uint8Array[];
|
|
1025
|
+
hexEncode(items: Uint8Array[]): Uint8Array[];
|
|
1026
|
+
hexDecode(items: Uint8Array[]): Uint8Array[];
|
|
1027
|
+
base64Encode(items: Uint8Array[], urlSafe?: boolean, padding?: boolean): Uint8Array[];
|
|
1028
|
+
base64Decode(items: Uint8Array[], urlSafe?: boolean, padding?: boolean): Uint8Array[];
|
|
1029
|
+
formParse(items: Uint8Array[]): Uint8Array[];
|
|
1030
|
+
signCookie(items: Uint8Array[], secret: Uint8Array): Uint8Array[];
|
|
1031
|
+
verifyCookie(items: Uint8Array[], secret: Uint8Array): Uint8Array;
|
|
1032
|
+
csrfVerify(items: Uint8Array[], secret: Uint8Array): Uint8Array;
|
|
1033
|
+
schemaValidate(validator: SchemaValidator, items: Uint8Array[]): Uint8Array;
|
|
1034
|
+
schemaValidateCount(validator: SchemaValidator, items: Uint8Array[]): number;
|
|
1035
|
+
fnv1a64(items: Uint8Array[]): BigUint64Array;
|
|
1036
|
+
etag(items: Uint8Array[], weak?: boolean | null): Uint8Array[];
|
|
1037
|
+
urlEncode(items: Uint8Array[]): Uint8Array[];
|
|
1038
|
+
urlDecode(items: Uint8Array[]): Uint8Array[];
|
|
1039
|
+
urlDecodeBytes(items: Uint8Array[]): Uint8Array[];
|
|
1040
|
+
mimeFromExtension(items: Uint8Array[]): Uint8Array[];
|
|
1041
|
+
wsAcceptKey(items: Uint8Array[]): Uint8Array[];
|
|
1042
|
+
base64UrlEncode(items: Uint8Array[]): Uint8Array[];
|
|
1043
|
+
base64UrlDecode(items: Uint8Array[]): Uint8Array[];
|
|
1044
|
+
passwordHash(passwords: Uint8Array[], salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array[];
|
|
1045
|
+
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
1046
|
+
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
1047
|
+
gzipCompress(items: Uint8Array[], level?: number | null): Uint8Array[];
|
|
1048
|
+
gzipDecompress(items: Uint8Array[], maxDecompressed?: number | null): Uint8Array[];
|
|
1049
|
+
brotliCompress(items: Uint8Array[], quality?: number | null): Uint8Array[];
|
|
1050
|
+
brotliDecompress(items: Uint8Array[], maxDecompressed?: number | null): Uint8Array[];
|
|
1051
|
+
jwtVerify(tokens: Uint8Array[], secret: Uint8Array, nowSeconds: number): Uint8Array;
|
|
1052
|
+
hmacSha256(items: Uint8Array[], key: Uint8Array): Uint8Array[];
|
|
1053
|
+
hmacSha256Verify(items: Uint8Array[], sigs: Uint8Array[], key: Uint8Array): Uint8Array;
|
|
1054
|
+
passwordVerify(items: Uint8Array[], phcs: Uint8Array[]): Uint8Array;
|
|
1055
|
+
urlResolve(bases: Uint8Array[], references: Uint8Array[]): Uint8Array[];
|
|
1056
|
+
jwtSign(items: Uint8Array[], secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array[];
|
|
1057
|
+
sseEncode(items: Uint8Array[], event?: string | null, id?: string | null, retry?: number | null): Uint8Array[];
|
|
1058
|
+
wsFrameEncode(items: Uint8Array[], opcode: number, mask: boolean, fin: boolean): Uint8Array[];
|
|
1059
|
+
wsFrameDecode(items: Uint8Array[]): Uint8Array[];
|
|
1060
|
+
multipartParse(items: Uint8Array[], boundary: Uint8Array): MultipartPart[][];
|
|
1061
|
+
templateRender(source: string, contexts: Uint8Array[]): Uint8Array[];
|
|
1501
1062
|
}
|
|
1502
|
-
/**
|
|
1503
|
-
export interface
|
|
1504
|
-
/**
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
|
|
1508
|
-
/**
|
|
1509
|
-
|
|
1510
|
-
/**
|
|
1511
|
-
|
|
1512
|
-
/**
|
|
1513
|
-
|
|
1514
|
-
/**
|
|
1515
|
-
|
|
1063
|
+
/** Raw packed-wire FFI namespace. */
|
|
1064
|
+
export interface RustPacked {
|
|
1065
|
+
/** Packed CRC32 batch: packed `[u8[]]` items → packed u32 results. */
|
|
1066
|
+
crc32BatchPacked(input: Uint8Array): Uint8Array;
|
|
1067
|
+
/** Packed JSON-validity batch: packed `[u8[]]` items → packed 0/1 bitset. */
|
|
1068
|
+
jsonValidBatchPacked(input: Uint8Array): Uint8Array;
|
|
1069
|
+
/** Packed email-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
|
|
1070
|
+
validateEmailBatchPacked(input: Uint8Array): Uint8Array;
|
|
1071
|
+
/** Packed UUID-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
|
|
1072
|
+
validateUuidBatchPacked(input: Uint8Array): Uint8Array;
|
|
1073
|
+
/** Packed IPv4-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
|
|
1074
|
+
validateIpv4BatchPacked(input: Uint8Array): Uint8Array;
|
|
1075
|
+
/** Packed IPv6-validator batch: packed `[u8[]]` items → packed 0/1 bitset. */
|
|
1076
|
+
validateIpv6BatchPacked(input: Uint8Array): Uint8Array;
|
|
1077
|
+
/** Packed JSON-sum batch: packed `[{id}]` docs → packed `[ok u8][sum i64 LE]`. */
|
|
1078
|
+
jsonSumBatchPacked(input: Uint8Array): Uint8Array;
|
|
1079
|
+
/** Packed query-string parser batch: packed `[u8[]]` queries → packed pair lists. */
|
|
1080
|
+
queryParseBatchPacked(input: Uint8Array): Uint8Array;
|
|
1081
|
+
/** Packed cookie-header parser batch: packed `[u8[]]` headers → packed pair lists. */
|
|
1082
|
+
cookieParseBatchPacked(input: Uint8Array): Uint8Array;
|
|
1083
|
+
/** Packed HTTP-request parser batch: packed `[u8[]]` requests → packed request sections. */
|
|
1084
|
+
httpParseRequestBatchPacked(input: Uint8Array): Uint8Array;
|
|
1085
|
+
/** Batch RFC 6902 JSON Patch (two packed lists, zipped) → packed results. */
|
|
1086
|
+
jsonPatchBatchPacked(docs: Uint8Array, patches: Uint8Array): Uint8Array;
|
|
1087
|
+
/** Packed FNV-1a 64 batch (i64 per item). */
|
|
1088
|
+
fnv1A64BatchPacked(input: Uint8Array): Uint8Array;
|
|
1089
|
+
/** Packed ETag batch (10 strong / 12 weak bytes per item). */
|
|
1090
|
+
etagBatchPacked(input: Uint8Array, weak?: boolean | null): Uint8Array;
|
|
1091
|
+
/** Packed percent-encoding batch: packed `[u8[]]` items → packed hex-percent bytes. */
|
|
1092
|
+
urlEncodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
1093
|
+
/** Packed percent-decoding batch: packed `[u8[]]` items → packed decoded bytes (string-safe). */
|
|
1094
|
+
urlDecodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
1095
|
+
/** Packed percent-decoding batch returning raw bytes (`%FF` preserved). */
|
|
1096
|
+
urlDecodeBytesBatchPacked(input: Uint8Array): Uint8Array;
|
|
1097
|
+
/** Packed MIME-lookup batch: packed `[u8[]]` extensions → packed media-type strings. */
|
|
1098
|
+
mimeFromExtensionBatchPacked(input: Uint8Array): Uint8Array;
|
|
1099
|
+
/** Packed WebSocket accept-key batch: packed `[u8[]]` keys → packed base64 SHA-1 results. */
|
|
1100
|
+
wsAcceptKeyBatchPacked(input: Uint8Array): Uint8Array;
|
|
1101
|
+
/** Valid JSON count in a packed JSON batch (0..n; invalid items not counted). */
|
|
1102
|
+
jsonValidBatchCountPacked(input: Uint8Array): number;
|
|
1103
|
+
/** Valid-email count in a packed email batch. */
|
|
1104
|
+
validateEmailBatchCountPacked(input: Uint8Array): number;
|
|
1105
|
+
/** Valid-UUID count in a packed UUID batch. */
|
|
1106
|
+
validateUuidBatchCountPacked(input: Uint8Array): number;
|
|
1107
|
+
/** Valid-IPv4 count in a packed IPv4 batch. */
|
|
1108
|
+
validateIpv4BatchCountPacked(input: Uint8Array): number;
|
|
1109
|
+
/** Valid-IPv6 count in a packed IPv6 batch. */
|
|
1110
|
+
validateIpv6BatchCountPacked(input: Uint8Array): number;
|
|
1111
|
+
/** Total `id` sum across a packed JSON batch (valid items only). */
|
|
1112
|
+
jsonSumBatchTotalPacked(input: Uint8Array): number;
|
|
1113
|
+
/** Total packed-output length (bytes) a query batch will produce. */
|
|
1114
|
+
queryParseBatchTotalLenPacked(input: Uint8Array): number;
|
|
1115
|
+
/** Total packed-output length (bytes) a cookie batch will produce. */
|
|
1116
|
+
cookieParseBatchTotalLenPacked(input: Uint8Array): number;
|
|
1117
|
+
/** Total packed-output length (bytes) an HTTP-request batch will produce. */
|
|
1118
|
+
httpParseRequestBatchTotalLenPacked(input: Uint8Array): number;
|
|
1119
|
+
/** Packed Argon2id-hash batch: packed `[u8[]]` passwords + shared salt → packed PHC strings. */
|
|
1120
|
+
passwordHashBatchPacked(input: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array;
|
|
1121
|
+
/** Packed password-verify batch (two packed lists, zipped) → bitset. */
|
|
1122
|
+
passwordVerifyBatchPacked(passwords: Uint8Array, phcs: Uint8Array): Uint8Array;
|
|
1123
|
+
/** Packed URL-resolve batch (two packed lists, zipped) → packed results. */
|
|
1124
|
+
urlResolveBatchPacked(bases: Uint8Array, references: Uint8Array): Uint8Array;
|
|
1125
|
+
/** Packed AEAD-encrypt batch (packed inputs + shared key/nonce) → packed ciphertexts. */
|
|
1126
|
+
aeadEncryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
1127
|
+
/** Packed AEAD-decrypt batch (packed inputs + shared key/nonce) → packed plaintexts. */
|
|
1128
|
+
aeadDecryptBatchPacked(input: Uint8Array, key: Uint8Array, nonce: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
1129
|
+
/** Packed gzip-compress batch: packed `[u8[]]` items → packed gzip streams. */
|
|
1130
|
+
gzipCompressBatchPacked(input: Uint8Array, level?: number | null): Uint8Array;
|
|
1131
|
+
/** Packed gzip-decompress batch (64 MiB cap per item) → packed plaintexts. */
|
|
1132
|
+
gzipDecompressBatchPacked(input: Uint8Array): Uint8Array;
|
|
1133
|
+
/** Packed brotli-compress batch: packed `[u8[]]` items → packed brotli streams. */
|
|
1134
|
+
brotliCompressBatchPacked(input: Uint8Array, quality?: number | null): Uint8Array;
|
|
1135
|
+
/** Packed brotli-decompress batch (64 MiB cap per item) → packed plaintexts. */
|
|
1136
|
+
brotliDecompressBatchPacked(input: Uint8Array): Uint8Array;
|
|
1137
|
+
/** Packed HS256 JWT-sign batch (packed claims + shared secret) → packed tokens. */
|
|
1138
|
+
jwtSignBatchPacked(input: Uint8Array, secret: Uint8Array, ttlSeconds: number | null, nowSeconds: number): Uint8Array;
|
|
1139
|
+
/** Packed HS256 JWT-verify batch (packed tokens + shared secret) → packed claims/validity. */
|
|
1140
|
+
jwtVerifyBatchPacked(input: Uint8Array, secret: Uint8Array, nowSeconds: number): Uint8Array;
|
|
1141
|
+
/** Batch HMAC-SHA256 sign (packed in → hex-signature byte results). */
|
|
1142
|
+
hmacSha256BatchPacked(input: Uint8Array, key: Uint8Array): Uint8Array;
|
|
1143
|
+
/** Batch HMAC-SHA256 verify (two packed lists: data + hex sigs, zipped) → bitset. */
|
|
1144
|
+
hmacSha256VerifyBatchPacked(input: Uint8Array, sigs: Uint8Array, key: Uint8Array): Uint8Array;
|
|
1145
|
+
/** Packed multipart/form-data parser batch (packed bodies + shared boundary) → packed parts. */
|
|
1146
|
+
multipartParseBatchPacked(input: Uint8Array, boundary: Uint8Array): Uint8Array;
|
|
1147
|
+
/** Packed minijinja template batch: packed contexts + shared source → packed renders. */
|
|
1148
|
+
templateRenderBatchPacked(input: Uint8Array, source: string): Uint8Array;
|
|
1149
|
+
/** Packed SSE-frame batch: packed data + shared event/id/retry → packed frames. */
|
|
1150
|
+
sseEncodeBatchPacked(input: Uint8Array, event: string | null, id: string | null, retry: number | null): Uint8Array;
|
|
1151
|
+
/** Packed WebSocket-frame encode batch: packed payloads + shared opcode/mask/fin → packed frames. */
|
|
1152
|
+
wsFrameEncodeBatchPacked(input: Uint8Array, opcode: number, mask: boolean, fin: boolean): Uint8Array;
|
|
1153
|
+
/** Packed WebSocket-frame decode batch: packed frames → packed (fin, opcode, payload) results. */
|
|
1154
|
+
wsFrameDecodeBatchPacked(input: Uint8Array): Uint8Array;
|
|
1516
1155
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
/** Header template variant byte emitted by the native core. */
|
|
1525
|
-
headerVariant: number;
|
|
1526
|
-
/** Remaining rate-limit budget (count). */
|
|
1527
|
-
rateRemaining: number;
|
|
1528
|
-
/** Rate-limit window reset (ms epoch). */
|
|
1529
|
-
rateResetMs: number;
|
|
1530
|
-
/** Retry-After value (ms). */
|
|
1531
|
-
retryAfterMs: number;
|
|
1532
|
-
/** True when the pipeline returned a terminal (error) verdict. */
|
|
1533
|
-
terminal: boolean;
|
|
1534
|
-
/** True when the pipeline accepted the request (verdict 0 + 2xx/3xx). */
|
|
1535
|
-
ok: boolean;
|
|
1536
|
-
/** True when the request was a CORS preflight. */
|
|
1537
|
-
isPreflight: boolean;
|
|
1538
|
-
/** True when the CORS engine allowed the request origin. */
|
|
1539
|
-
corsAllowed: boolean;
|
|
1540
|
-
/** True when the request was rate-limited. */
|
|
1541
|
-
rateLimited: boolean;
|
|
1542
|
-
/** True when the request came from a trusted proxy. */
|
|
1543
|
-
trustedProxy: boolean;
|
|
1544
|
-
/** True when the request body parsed as valid JSON. */
|
|
1545
|
-
bodyValidJson: boolean;
|
|
1546
|
-
/** True when the request body validated against the ingress schema. */
|
|
1547
|
-
schemaValid: boolean;
|
|
1548
|
-
/** True when a declared output section overran the buffer. */
|
|
1549
|
-
bodyTruncated: boolean;
|
|
1550
|
-
/** Request body bytes (may be an empty view). */
|
|
1551
|
-
body: Uint8Array;
|
|
1552
|
-
protected _buf: Uint8Array;
|
|
1553
|
-
protected _bodyJsonStart: number;
|
|
1554
|
-
protected _bodyJsonLen: number;
|
|
1555
|
-
private _cookiesBuf;
|
|
1556
|
-
private _queryBuf;
|
|
1557
|
-
private _cookiesDecoded;
|
|
1558
|
-
private _queryDecoded;
|
|
1559
|
-
/** Reset the shared fields to their empty state (call from subclass `invalidate`). */
|
|
1560
|
-
protected resetShared(): void;
|
|
1561
|
-
/** Recompute `terminal`/`ok` from the (subclass-set) verdict + status. */
|
|
1562
|
-
protected updateOkTerminal(): void;
|
|
1156
|
+
/**
|
|
1157
|
+
* The flat `rust.*` scalar method surface: bytes in → normalized out. Built by
|
|
1158
|
+
* the scalar builders from the native addon + Bun built-in delegations.
|
|
1159
|
+
*/
|
|
1160
|
+
export interface RustScalar {
|
|
1161
|
+
crc32(input: Uint8Array): number;
|
|
1162
|
+
fnv1a64(input: Uint8Array): bigint;
|
|
1563
1163
|
/**
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
* rate-limited flag), so the read cannot drift between paths.
|
|
1164
|
+
* XXH3-64 over raw bytes (high-throughput non-cryptographic hash).
|
|
1165
|
+
* Compare against `Bun.hash.xxHash3` — see docs/bun-builtins-decision-matrix.md.
|
|
1567
1166
|
*/
|
|
1568
|
-
|
|
1167
|
+
xxh3(input: Uint8Array): bigint;
|
|
1168
|
+
hmacSha256(key: Uint8Array, data: Uint8Array): Uint8Array | string;
|
|
1169
|
+
hmacSha256Verify(key: Uint8Array, data: Uint8Array, sig: Uint8Array | string): boolean;
|
|
1170
|
+
jsonValid(input: Uint8Array): boolean;
|
|
1569
1171
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1573
|
-
* path passes the RAW declared length (it re-checks bounds on read), the
|
|
1574
|
-
* baked path passes the already-clamped `layout.safeBodyJsonLen`. Sections
|
|
1575
|
-
* are stored as slices and decoded lazily by `cookiesJson()`/`queryJson()`.
|
|
1172
|
+
* Parse JSON to a JS value (FFI packed structural parse on Bun — sonic-rs
|
|
1173
|
+
* parses ONCE, JS assembles from a token stream with no re-parse; napi
|
|
1174
|
+
* sonic-rs DOM fallback). Throws on invalid JSON.
|
|
1576
1175
|
*/
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
/**
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1176
|
+
jsonParse(input: Uint8Array): unknown;
|
|
1177
|
+
jsonSumIds(input: Uint8Array): bigint;
|
|
1178
|
+
jsonPatch(doc: Uint8Array, patch: Uint8Array): Uint8Array | string;
|
|
1179
|
+
mimeFromExtension(ext: Uint8Array): Uint8Array | string;
|
|
1180
|
+
randomToken(byteLen: number): Uint8Array | string;
|
|
1181
|
+
randomTokenInto(byteLen: number, output: Uint8Array): number;
|
|
1182
|
+
urlEncode(input: Uint8Array): Uint8Array | string;
|
|
1183
|
+
urlDecode(input: Uint8Array): Uint8Array;
|
|
1184
|
+
/**
|
|
1185
|
+
* Strict percent-decode without UTF-8 validation.
|
|
1186
|
+
*/
|
|
1187
|
+
urlDecodeBytes(input: Uint8Array): Uint8Array;
|
|
1188
|
+
urlEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1189
|
+
urlDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1190
|
+
validateEmail(input: Uint8Array): boolean;
|
|
1191
|
+
validateUuid(input: Uint8Array): boolean;
|
|
1192
|
+
validateIpv4(input: Uint8Array): boolean;
|
|
1193
|
+
validateIpv6(input: Uint8Array): boolean;
|
|
1194
|
+
wsAcceptKey(key: Uint8Array): Uint8Array | string;
|
|
1195
|
+
wsAcceptKeyInto(key: Uint8Array, output: Uint8Array): number;
|
|
1196
|
+
httpParseRequestPacked(input: Uint8Array): Uint8Array;
|
|
1197
|
+
queryParsePacked(input: Uint8Array): Uint8Array;
|
|
1198
|
+
cookieParsePacked(input: Uint8Array): Uint8Array;
|
|
1199
|
+
httpParseRequestPackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1200
|
+
queryParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1201
|
+
cookieParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1202
|
+
/**
|
|
1203
|
+
* Parse an application/x-www-form-urlencoded body into packed pairs.
|
|
1204
|
+
*/
|
|
1205
|
+
formParsePacked(input: Uint8Array): Uint8Array;
|
|
1206
|
+
formParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1207
|
+
/**
|
|
1208
|
+
* Parse a Content-Type header into a structured media type.
|
|
1209
|
+
*/
|
|
1210
|
+
parseMediaType(input: Uint8Array): MediaTypeResult;
|
|
1211
|
+
/**
|
|
1212
|
+
* Generate a strong/weak ETag (crc32-based).
|
|
1213
|
+
*/
|
|
1214
|
+
etag(data: Uint8Array, weak?: boolean): Uint8Array | string;
|
|
1215
|
+
etagInto(data: Uint8Array, output: Uint8Array, weak?: boolean): number;
|
|
1216
|
+
/**
|
|
1217
|
+
* Format a unix timestamp as an IMF-fixdate HTTP-date.
|
|
1218
|
+
*/
|
|
1219
|
+
httpDate(secs?: number): Uint8Array | string;
|
|
1220
|
+
httpDateInto(secs: number | undefined, output: Uint8Array): number;
|
|
1221
|
+
parseHttpDate(input: Uint8Array): bigint | null;
|
|
1222
|
+
parseAcceptEncoding(input: Uint8Array): EncodingPrefResult[];
|
|
1223
|
+
/**
|
|
1224
|
+
* Base64 encode (standard by default; url-safe/padding configurable).
|
|
1225
|
+
*/
|
|
1226
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array | string;
|
|
1227
|
+
/**
|
|
1228
|
+
* Base64 decode (standard); throws on invalid input.
|
|
1229
|
+
*/
|
|
1230
|
+
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array;
|
|
1231
|
+
base64EncodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
1232
|
+
base64DecodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
1233
|
+
base64UrlEncode(input: Uint8Array): Uint8Array | string;
|
|
1234
|
+
base64UrlDecode(input: Uint8Array): Uint8Array;
|
|
1235
|
+
/**
|
|
1236
|
+
* Lowercase hex encode.
|
|
1237
|
+
*/
|
|
1238
|
+
hexEncode(input: Uint8Array): Uint8Array | string;
|
|
1239
|
+
/**
|
|
1240
|
+
* Hex decode; throws on odd length or invalid digits.
|
|
1241
|
+
*/
|
|
1242
|
+
hexDecode(input: Uint8Array): Uint8Array;
|
|
1243
|
+
hexEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1244
|
+
hexDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1245
|
+
hmacSha256Into(key: Uint8Array, data: Uint8Array, output: Uint8Array): number;
|
|
1246
|
+
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number;
|
|
1247
|
+
verifyCookieInto(signed: Uint8Array, secret: Uint8Array, output: Uint8Array): number | null;
|
|
1248
|
+
csrfTokenInto(secret: Uint8Array, output: Uint8Array): number;
|
|
1249
|
+
aeadEncryptInto(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, output: Uint8Array, algorithm?: string | null): number;
|
|
1250
|
+
wsFrameEncodeInto(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean, output: Uint8Array): number;
|
|
1251
|
+
gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number | null): number;
|
|
1252
|
+
brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number | null): number;
|
|
1253
|
+
/**
|
|
1254
|
+
* Sign a cookie value as `value.signature` (HMAC-SHA256 hex).
|
|
1255
|
+
*/
|
|
1256
|
+
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array | string;
|
|
1257
|
+
/**
|
|
1258
|
+
* Verify a signed cookie; returns the value or null.
|
|
1259
|
+
*/
|
|
1260
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | string | null;
|
|
1261
|
+
/**
|
|
1262
|
+
* Create a CSRF token (32-byte random hex + HMAC signature).
|
|
1263
|
+
*/
|
|
1264
|
+
csrfToken(secret: Uint8Array): Uint8Array | string;
|
|
1265
|
+
/**
|
|
1266
|
+
* Constant-time verify a CSRF token.
|
|
1267
|
+
*/
|
|
1268
|
+
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean;
|
|
1269
|
+
/**
|
|
1270
|
+
* Resolve a URL reference against a base (RFC 3986).
|
|
1271
|
+
*/
|
|
1272
|
+
urlResolve(base: Uint8Array, reference: Uint8Array): Uint8Array | string;
|
|
1273
|
+
/**
|
|
1274
|
+
* Build a percent-encoded query string from params (sorted keys).
|
|
1275
|
+
*/
|
|
1276
|
+
urlEncodeQuery(params: Record<string, string>): Uint8Array | string;
|
|
1277
|
+
createSchemaValidator(schema: Uint8Array): SchemaValidatorInstance;
|
|
1278
|
+
createHmacSigner(key: Uint8Array): HmacSignerInstance;
|
|
1279
|
+
createTemplateRenderer(source: string): TemplateRendererInstance;
|
|
1280
|
+
createFormParser(capacity?: number): FormParserInstance;
|
|
1281
|
+
createMediaTypeParser(): MediaTypeParserInstance;
|
|
1282
|
+
/**
|
|
1283
|
+
* Higher-order conditional-request check: per-resource 304 decision (setup once).
|
|
1284
|
+
*/
|
|
1285
|
+
createConditionalRequest(etagValue: Uint8Array, lastModifiedSecs?: number): ConditionalRequestInstance;
|
|
1286
|
+
/**
|
|
1287
|
+
* Higher-order negotiator: precompiles supported encodings once.
|
|
1288
|
+
*/
|
|
1289
|
+
createAcceptNegotiator(supported: string[]): AcceptNegotiatorInstance;
|
|
1290
|
+
createBase64Codec(urlSafe?: boolean, padding?: boolean): Base64CodecInstance;
|
|
1291
|
+
createCookieSigner(secret: Uint8Array): CookieSignerInstance;
|
|
1292
|
+
createCsrfProtector(secret: Uint8Array): CsrfProtectorInstance;
|
|
1293
|
+
createUrlBuilder(base: Uint8Array): UrlBuilderInstance;
|
|
1294
|
+
createJwtSigner(secret: Uint8Array, ttlSeconds?: number): JwtSignerInstance;
|
|
1295
|
+
createAeadCipher(key: Uint8Array, algorithm?: string): AeadCipherInstance;
|
|
1296
|
+
createArgon2Hasher(options?: PasswordHashOptions | null): Argon2HasherInstance;
|
|
1297
|
+
createMediaTypeMatcher(expected: Uint8Array): MediaTypeMatcherInstance;
|
|
1298
|
+
/**
|
|
1299
|
+
* Sharded fixed-window per-key rate limiter. `maxEntries` clamps internally
|
|
1300
|
+
* (default 1,048,576). Each instance owns an independent budget.
|
|
1301
|
+
* @remarks Scalar per-check cost: ~3-14x SLOWER than the JS Map baseline —
|
|
1302
|
+
* prefer the ingress pipeline (one FFI for all stages) for native rate
|
|
1303
|
+
* limiting in a request path. [measured 2026-08]
|
|
1304
|
+
*/
|
|
1305
|
+
createRateLimiter(limit: number, windowMs: number, maxEntries?: number | null): RateLimiterInstance;
|
|
1306
|
+
initThreadPool(rayonThreads?: number): void;
|
|
1307
|
+
rayonNumThreads(): number;
|
|
1308
|
+
jwtSign(claims: Record<string, unknown>, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array;
|
|
1309
|
+
/**
|
|
1310
|
+
* Sign a JWT from pre-serialized claim JSON bytes — skips the napi
|
|
1311
|
+
* `serde_json::Value` marshal of `jwtSign` for callers that already hold the
|
|
1312
|
+
* bytes (same injection semantics).
|
|
1313
|
+
*/
|
|
1314
|
+
jwtSignBytes(claimsJson: Uint8Array, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array | string;
|
|
1315
|
+
jwtSignBytesInto(claimsJson: Uint8Array, secret: Uint8Array, output: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): number;
|
|
1316
|
+
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown;
|
|
1317
|
+
passwordHash(password: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array | string;
|
|
1318
|
+
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean;
|
|
1319
|
+
/**
|
|
1320
|
+
* bcrypt password hash → `$2b$` PHC string (cost clamped 4..=31).
|
|
1321
|
+
*/
|
|
1322
|
+
passwordHashBcrypt(password: Uint8Array, cost: number): string;
|
|
1323
|
+
/**
|
|
1324
|
+
* Verify a password against a bcrypt `$2b$` PHC string.
|
|
1325
|
+
*/
|
|
1326
|
+
passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean;
|
|
1327
|
+
/**
|
|
1328
|
+
* PBKDF2-HMAC-SHA256 key derivation (dkLen clamped to 1 MiB).
|
|
1329
|
+
*/
|
|
1330
|
+
pbkdf2Sha256(password: Uint8Array, salt: Uint8Array, rounds: number, dkLen: number): Uint8Array;
|
|
1331
|
+
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, algorithm?: string | null): Uint8Array;
|
|
1332
|
+
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, algorithm?: string | null): Uint8Array | null;
|
|
1333
|
+
gzipCompress(data: Uint8Array, level?: number | null): Uint8Array;
|
|
1334
|
+
/**
|
|
1335
|
+
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
1336
|
+
* decompression-bomb guard; a larger value errors.
|
|
1337
|
+
*/
|
|
1338
|
+
gzipDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array;
|
|
1339
|
+
/**
|
|
1340
|
+
* gzip-decompress into `output` (pooled — caller-owned buffer); returns bytes
|
|
1341
|
+
* written. Throws when `output` is too small (the exact required size is
|
|
1342
|
+
* reported) or the input is invalid / exceeds `maxDecompressed`.
|
|
1343
|
+
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
1344
|
+
* decompression-bomb guard; a larger value errors.
|
|
1345
|
+
*/
|
|
1346
|
+
gzipDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number | null): number;
|
|
1347
|
+
brotliCompress(data: Uint8Array, quality?: number | null): Uint8Array;
|
|
1348
|
+
/**
|
|
1349
|
+
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
1350
|
+
* decompression-bomb guard; a larger value errors.
|
|
1351
|
+
*/
|
|
1352
|
+
brotliDecompress(data: Uint8Array, maxDecompressed?: number | null): Uint8Array;
|
|
1353
|
+
/**
|
|
1354
|
+
* brotli-decompress into `output` (pooled — caller-owned buffer); returns
|
|
1355
|
+
* bytes written. Throws when `output` is too small (the exact required size
|
|
1356
|
+
* is reported) or the input is invalid / exceeds `maxDecompressed`.
|
|
1357
|
+
* @param maxDecompressed Caps decompressed output (default 64 MiB) as a
|
|
1358
|
+
* decompression-bomb guard; a larger value errors.
|
|
1359
|
+
*/
|
|
1360
|
+
brotliDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number | null): number;
|
|
1361
|
+
multipartParse(body: Uint8Array, boundary: Uint8Array): MultipartPart[];
|
|
1362
|
+
multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array;
|
|
1363
|
+
multipartParsePackedInto(body: Uint8Array, boundary: Uint8Array, output: Uint8Array): number;
|
|
1364
|
+
wsFrameEncode(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean): Uint8Array;
|
|
1365
|
+
wsFrameDecode(data: Uint8Array): WsFrame | null;
|
|
1366
|
+
wsFrameDecodePackedInto(data: Uint8Array, output: Uint8Array): number | null;
|
|
1367
|
+
sseEncodeEvent(event: string | null, data: Uint8Array, id: string | null, retry: number | null): Uint8Array;
|
|
1368
|
+
sseEncodeEventInto(event: string | null, data: Uint8Array, id: string | null, retry: number | null, output: Uint8Array): number;
|
|
1601
1369
|
}
|
|
1602
|
-
/**
|
|
1603
|
-
export interface
|
|
1604
|
-
trustProxy?: boolean;
|
|
1605
|
-
trustedProxies?: {
|
|
1606
|
-
enabled?: boolean;
|
|
1607
|
-
networks?: string[];
|
|
1608
|
-
};
|
|
1609
|
-
parseCookies?: boolean;
|
|
1610
|
-
parseQuery?: boolean;
|
|
1611
|
-
requireJsonBody?: boolean;
|
|
1612
|
-
schema?: Uint8Array;
|
|
1613
|
-
cors?: CorsOptions;
|
|
1614
|
-
rateLimit?: {
|
|
1615
|
-
limit?: number;
|
|
1616
|
-
windowMs?: number;
|
|
1617
|
-
maxEntries?: number;
|
|
1618
|
-
};
|
|
1370
|
+
/** String-oriented FFI namespace. */
|
|
1371
|
+
export interface RustText {
|
|
1619
1372
|
/**
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
* intentionally use different option shapes (see docs/INGRESS.md).
|
|
1373
|
+
* Resolve a lowercase file extension to a MIME type string (e.g. `"html"` →
|
|
1374
|
+
* `"text/html"`). Unknown extensions return `"application/octet-stream"`.
|
|
1375
|
+
* @param ext the file extension WITHOUT a leading dot, case-insensitive.
|
|
1376
|
+
* @returns the MIME type for `ext`.
|
|
1625
1377
|
*/
|
|
1626
|
-
|
|
1627
|
-
https?: boolean;
|
|
1628
|
-
maxBodyBytes?: number;
|
|
1629
|
-
enableSecurityHeaders?: boolean;
|
|
1630
|
-
enableRequestIds?: boolean;
|
|
1631
|
-
enableBodySizeGuard?: boolean;
|
|
1632
|
-
emitMetadataJson?: boolean;
|
|
1633
|
-
readBody?: boolean;
|
|
1634
|
-
outputBufferSize?: number;
|
|
1378
|
+
mimeFromExtension(ext: string): string;
|
|
1635
1379
|
/**
|
|
1636
|
-
*
|
|
1637
|
-
*
|
|
1638
|
-
*
|
|
1380
|
+
* RFC 3986 percent-encode a string (the `encodeURIComponent` unreserved
|
|
1381
|
+
* set). Under Bun this delegates to the native `encodeURIComponent` for
|
|
1382
|
+
* ~4x speed with byte-for-byte parity.
|
|
1383
|
+
* @param input the string to encode.
|
|
1384
|
+
* @returns the percent-encoded string.
|
|
1385
|
+
*/
|
|
1386
|
+
urlEncode(input: string): string;
|
|
1387
|
+
/**
|
|
1388
|
+
* Percent-decode a string. Throws on malformed `%XX` or invalid UTF-8
|
|
1389
|
+
* output (parity with `decodeURIComponent`, which is used under Bun).
|
|
1390
|
+
* @param input the percent-encoded string to decode.
|
|
1391
|
+
* @returns the decoded string.
|
|
1392
|
+
*/
|
|
1393
|
+
urlDecode(input: string): string;
|
|
1394
|
+
/**
|
|
1395
|
+
* Compute the RFC 6455 `Sec-WebSocket-Accept` value for a client key.
|
|
1396
|
+
* @param key the base64 `Sec-WebSocket-Key` header value.
|
|
1397
|
+
* @returns the 28-byte base64 accept value.
|
|
1639
1398
|
*/
|
|
1640
|
-
|
|
1399
|
+
wsAcceptKey(key: string): string;
|
|
1400
|
+
/** @param input an email address. @returns whether it is RFC-valid. */
|
|
1401
|
+
validateEmail(input: string): boolean;
|
|
1402
|
+
/** @param input a UUID string. @returns whether it is a valid (v1-v8) UUID. */
|
|
1403
|
+
validateUuid(input: string): boolean;
|
|
1404
|
+
/** @param input an IPv4 address. @returns whether it is valid. */
|
|
1405
|
+
validateIpv4(input: string): boolean;
|
|
1406
|
+
/** @param input an IPv6 address. @returns whether it is valid. */
|
|
1407
|
+
validateIpv6(input: string): boolean;
|
|
1408
|
+
}
|
|
1409
|
+
/** The full Rust FFI client (scalar methods + namespaces + configuration). */
|
|
1410
|
+
export interface RustClient extends RustScalar {
|
|
1411
|
+
text: RustText;
|
|
1412
|
+
batch: RustBatch;
|
|
1413
|
+
packed: RustPacked;
|
|
1641
1414
|
/**
|
|
1642
|
-
*
|
|
1643
|
-
*
|
|
1415
|
+
* Resolve the native transport in use on this runtime: `"ffi"` (bun:ffi —
|
|
1416
|
+
* the PRIMARY transport under Bun) or `"napi"` (Node, forced
|
|
1417
|
+
* `CASTRUM_FFI_MODE=napi`, or a failed ffi bind-time self-test).
|
|
1644
1418
|
*/
|
|
1645
|
-
|
|
1419
|
+
transport(): "ffi" | "napi";
|
|
1420
|
+
/** Whether the bun:ffi transport is live (equivalent to `transport() === "ffi"`). */
|
|
1421
|
+
ffiActive(): boolean;
|
|
1646
1422
|
/**
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
1650
|
-
* deployments). Opt-in; default false. Not forwarded to the native addon.
|
|
1423
|
+
* Update defaults on this instance. `mimeCache` / `hmacCache` apply
|
|
1424
|
+
* immediately (per-instance). `rayonThreads` is applied only if the
|
|
1425
|
+
* process-wide pool is not yet initialized (see {@link RustOptions}).
|
|
1651
1426
|
*/
|
|
1652
|
-
|
|
1653
|
-
limits?: {
|
|
1654
|
-
maxUrlBytes?: number;
|
|
1655
|
-
maxQueryBytes?: number;
|
|
1656
|
-
maxCookieBytes?: number;
|
|
1657
|
-
maxHeadersBytes?: number;
|
|
1658
|
-
maxHeaders?: number;
|
|
1659
|
-
maxPairs?: number;
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
/** The zero-alloc sync handler returned by `createIngressFast`. */
|
|
1663
|
-
export interface IngressFastHandler {
|
|
1664
|
-
run<T>(req: Request, ip: string | undefined, body: Uint8Array | null, requestId: string, fn: (result: FastIngressResult) => T): T;
|
|
1427
|
+
configure(options: RustOptions): void;
|
|
1665
1428
|
}
|
|
1666
1429
|
/**
|
|
1667
|
-
*
|
|
1430
|
+
* Create an isolated Rust client with the given defaults.
|
|
1668
1431
|
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1432
|
+
* `mimeCache` / `hmacCache` are per-instance. `rayonThreads` only applies
|
|
1433
|
+
* before the process-wide rayon pool is initialized (once per process — see
|
|
1434
|
+
* {@link RustOptions.rayonThreads}); the pool defaults to the env var or
|
|
1435
|
+
* `max(1, hardwareConcurrency - 1)` otherwise.
|
|
1672
1436
|
*/
|
|
1673
|
-
export
|
|
1674
|
-
/**
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1437
|
+
export declare function createRust(options?: RustOptions): RustClient;
|
|
1438
|
+
/**
|
|
1439
|
+
* The default, ready-to-use `RustClient` created with no options (resolves
|
|
1440
|
+
* rayon threads from the host automatically). Most consumers just do
|
|
1441
|
+
* `import { rust } from "castrum"` and call `rust.<fn>(...)` directly.
|
|
1442
|
+
*/
|
|
1443
|
+
export declare const rust: RustClient;
|
|
1444
|
+
/** The benchmark-proven default implementation for an op. */
|
|
1445
|
+
export type ProvenImpl = "native" | "js" | "bun";
|
|
1446
|
+
/** How decisive the baked winner is. */
|
|
1447
|
+
export type ProvenStatus = "proven" | "parity" | "unmeasured";
|
|
1448
|
+
/** One entry in the baked proven-selection registry. */
|
|
1449
|
+
export interface ProvenEntry {
|
|
1450
|
+
/** Selection op name (matches `opImpl(op)` / `src/selection.json` keys). */
|
|
1451
|
+
name: string;
|
|
1452
|
+
/** The baked default implementation: the benchmark-proven winner. */
|
|
1453
|
+
impl: ProvenImpl;
|
|
1454
|
+
/** `proven` = decisive win, `parity` = borderline, `unmeasured` = pinned by policy. */
|
|
1455
|
+
status: ProvenStatus;
|
|
1456
|
+
/** Measured ratio behind the decision (see module doc for the axis). */
|
|
1457
|
+
typicalRatio?: number;
|
|
1458
|
+
/** Why this classification. */
|
|
1459
|
+
note?: string;
|
|
1680
1460
|
}
|
|
1681
|
-
/**
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1461
|
+
/**
|
|
1462
|
+
* Baked proven-winner registry. Covering the FULL selection surface
|
|
1463
|
+
* (`src/selection.json` ops + every Bun built-in delegation) so nothing can be
|
|
1464
|
+
* silently dropped. Classifications are based on release/perf-build results;
|
|
1465
|
+
* `test/unit/shared/proven.test.ts` re-checks them against the LIVE wiring.
|
|
1466
|
+
*/
|
|
1467
|
+
export declare const PROVEN_SELECTION: readonly ProvenEntry[];
|
|
1468
|
+
/**
|
|
1469
|
+
* The baked {@link ProvenEntry} for `op`, or `undefined` for unknown ops.
|
|
1470
|
+
* Pure data — never dlopens the addon.
|
|
1471
|
+
*/
|
|
1472
|
+
export declare function provenEntry(op: string): ProvenEntry | undefined;
|
|
1473
|
+
/** The baked default implementation for `op` (`native` / `js` / `bun`), or `null`. */
|
|
1474
|
+
export declare function provenImpl(op: string): ProvenImpl | null;
|
|
1475
|
+
/** The baked status for `op`, or `null` for unknown ops. */
|
|
1476
|
+
export declare function provenStatus(op: string): ProvenStatus | null;
|
|
1477
|
+
/** Whether the op's baked winner is a decisive (`proven`) win. */
|
|
1478
|
+
export declare function isProven(op: string): boolean;
|
|
1479
|
+
/** The full baked registry (same array as {@link PROVEN_SELECTION}). */
|
|
1480
|
+
export declare function provenSurface(): readonly ProvenEntry[];
|
|
1481
|
+
/** A one-line summary of the baked selection (counts per implementation). */
|
|
1482
|
+
export declare function provenSummary(): string;
|
|
1483
|
+
/** The full `rust.*` surface — identical to `rust`; the proven wiring is baked (see {@link ../shared/proven}). */
|
|
1484
|
+
export type ProvenClient = RustClient;
|
|
1485
|
+
/** The full `rust.*` surface, exposed for audit. Same object as `rust`. */
|
|
1486
|
+
export declare const proven: ProvenClient;
|
|
1487
|
+
/** Recommended implementation for an operation. */
|
|
1488
|
+
export type OpImpl = "native" | "js";
|
|
1489
|
+
/** A selection decision: recommended implementation + rationale note. */
|
|
1490
|
+
export interface OpDecision {
|
|
1491
|
+
/** The recommended implementation when the addon is available. */
|
|
1492
|
+
readonly impl: OpImpl;
|
|
1493
|
+
/** Why this decision (measured ratio / rationale). */
|
|
1494
|
+
readonly note?: string;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* The benchmark-driven native-vs-JS decision for `op` — runtime-aware.
|
|
1498
|
+
*
|
|
1499
|
+
* Returns `"native"` when the Rust addon is the recommended implementation,
|
|
1500
|
+
* `"js"` when a pure-JS (or Bun built-in) implementation wins, or `null` for
|
|
1501
|
+
* unknown ops (or when the addon is unavailable — with no addon there is no
|
|
1502
|
+
* "native" choice). Under Bun, ops in the adapter's builtins registry resolve
|
|
1503
|
+
* to `"js"` so the consumer's JS path delegates to the faster Bun built-in.
|
|
1504
|
+
*
|
|
1505
|
+
* The default is the BAKED proven selection (`src/shared/proven.ts`):
|
|
1506
|
+
* `native` / `js` directly, `bun` resolves to `"js"` via the builtins check
|
|
1507
|
+
* above. Unknown ops (and `bun` entries on a non-Bun runtime, where no
|
|
1508
|
+
* built-in exists) fall back to the addon's embedded decision
|
|
1509
|
+
* (`rust/selection.rs` ← `src/selection.json`).
|
|
1510
|
+
*/
|
|
1511
|
+
export declare const opImpl: (op: string) => OpImpl | null;
|
|
1512
|
+
/** `true` when the Rust addon is the recommended impl for `op`. */
|
|
1513
|
+
export declare const isNativeOp: (op: string) => boolean;
|
|
1514
|
+
/** The decision record for `op`, or `null` when unknown. */
|
|
1515
|
+
export declare const opDecision: (op: string) => OpDecision | null;
|
|
1516
|
+
/**
|
|
1517
|
+
* Shared UTF-8 encoder (encode / encodeInto), backed by the runtime-native
|
|
1518
|
+
* codec (`src/shared/codec.ts`): on Bun the work is done by Bun's native
|
|
1519
|
+
* transfer machinery (`Bun.ArrayBufferSink` / a zero-copy `Buffer` view
|
|
1520
|
+
* write); under Node it falls back to `TextEncoder`. Reusing a single object
|
|
1521
|
+
* avoids allocation overhead in hot paths.
|
|
1522
|
+
*
|
|
1523
|
+
* `encode` accepts the string-or-bytes union the text-returning `rust.*` ops
|
|
1524
|
+
* surface (Bun returns strings, Node returns bytes) and normalizes to bytes —
|
|
1525
|
+
* a string is UTF-8 encoded, bytes pass through unchanged.
|
|
1526
|
+
*/
|
|
1527
|
+
export declare const encoder: {
|
|
1528
|
+
encode(s: Uint8Array | string): Uint8Array;
|
|
1529
|
+
encodeInto(s: string, dest: Uint8Array): {
|
|
1530
|
+
read: number;
|
|
1531
|
+
written: number;
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
/**
|
|
1535
|
+
* Shared UTF-8 decoder, backed by the runtime-native codec
|
|
1536
|
+
* (`src/shared/codec.ts`): on Bun it uses `new CString(ptr, 0, len)` from
|
|
1537
|
+
* `bun:ffi` (JSC-native, exact-length clone); under Node it falls back to
|
|
1538
|
+
* `TextDecoder`. Reusing a single object avoids allocation overhead in hot
|
|
1539
|
+
* paths.
|
|
1540
|
+
*
|
|
1541
|
+
* `decode` accepts the string-or-bytes union the text-returning `rust.*` ops
|
|
1542
|
+
* surface and normalizes to a string — bytes are UTF-8 decoded, strings pass
|
|
1543
|
+
* through unchanged.
|
|
1544
|
+
*/
|
|
1545
|
+
export declare const decoder: {
|
|
1546
|
+
decode(bytes: Uint8Array | string): string;
|
|
1547
|
+
};
|
|
1548
|
+
/** Options for an `AdaptiveEstimate`: EWMA smoothing, bounds, starting value. */ export interface AdaptiveEstimateOptions {
|
|
1689
1549
|
/**
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1550
|
+
* EWMA smoothing factor in (0, 1]. Higher reacts faster to new samples;
|
|
1551
|
+
* lower smooths more. Default: 0.25.
|
|
1692
1552
|
*/
|
|
1693
|
-
|
|
1553
|
+
alpha?: number;
|
|
1554
|
+
/** Starting estimate. Default 0 (the first sample wins). */
|
|
1555
|
+
initial?: number;
|
|
1556
|
+
/** Clamp the estimate to at least this. Default 0. */
|
|
1557
|
+
min?: number;
|
|
1558
|
+
/** Clamp the estimate to at most this (bounds memory / resources). Default Infinity. */
|
|
1559
|
+
max?: number;
|
|
1694
1560
|
}
|
|
1695
1561
|
/**
|
|
1696
|
-
*
|
|
1562
|
+
* A bounded exponential-moving-average estimate of an observed quantity.
|
|
1697
1563
|
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1564
|
+
* `sample(x)` folds a new observation into the running estimate; `estimate`
|
|
1565
|
+
* reads the current value. The estimate is clamped to `[min, max]`, so it is
|
|
1566
|
+
* safe to drive resource sizing (buffer pools, batch thresholds) with it —
|
|
1567
|
+
* a single outlier cannot blow up the estimate, and it decays toward recent
|
|
1568
|
+
* observations.
|
|
1700
1569
|
*/
|
|
1701
|
-
export
|
|
1702
|
-
readonly
|
|
1703
|
-
readonly
|
|
1704
|
-
readonly
|
|
1705
|
-
readonly
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
readonly corsAllowed: boolean;
|
|
1715
|
-
readonly isPreflight: boolean;
|
|
1716
|
-
readonly rateLimited: boolean;
|
|
1717
|
-
readonly rateLimit: number;
|
|
1718
|
-
readonly rateRemaining: number;
|
|
1719
|
-
readonly rateResetMs: number;
|
|
1720
|
-
readonly retryAfterMs: number;
|
|
1721
|
-
readonly body: Uint8Array;
|
|
1722
|
-
readonly headerVariant: number;
|
|
1723
|
-
readonly requestId: string;
|
|
1724
|
-
readonly bodyTruncated: boolean;
|
|
1725
|
-
cookiesJson(): string;
|
|
1726
|
-
queryJson(): string;
|
|
1727
|
-
bodyJson(): Uint8Array;
|
|
1728
|
-
}
|
|
1729
|
-
/** A result plus the terminal Response computed for it. */
|
|
1730
|
-
export interface IngressContext extends IngressResult {
|
|
1731
|
-
response: Response | null;
|
|
1570
|
+
export declare class AdaptiveEstimate {
|
|
1571
|
+
private readonly alpha;
|
|
1572
|
+
private readonly min;
|
|
1573
|
+
private readonly max;
|
|
1574
|
+
private readonly initial;
|
|
1575
|
+
private value;
|
|
1576
|
+
constructor(options?: AdaptiveEstimateOptions);
|
|
1577
|
+
/** Fold a new observation into the running estimate. */
|
|
1578
|
+
sample(x: number): void;
|
|
1579
|
+
/** Current estimate (clamped to `[min, max]`). */
|
|
1580
|
+
get estimate(): number;
|
|
1581
|
+
/** Reset the estimate (back to `initial`). */
|
|
1582
|
+
reset(): void;
|
|
1732
1583
|
}
|
|
1733
1584
|
/**
|
|
1734
|
-
*
|
|
1585
|
+
* Generate a UUIDv7 (time-ordered) string, preferring Bun's native built-in.
|
|
1586
|
+
* Under Node this falls back to `crypto.randomUUID` (v4).
|
|
1735
1587
|
*
|
|
1736
|
-
*
|
|
1737
|
-
* returning a Promise (or any thenable) throws. The result is invalidated after
|
|
1738
|
-
* `run()` returns, so capture the fields you need inside the callback (or use
|
|
1739
|
-
* the async {@link IngressHandler} which snapshots for you).
|
|
1588
|
+
* @returns A UUID string.
|
|
1740
1589
|
*/
|
|
1741
|
-
export
|
|
1742
|
-
|
|
1590
|
+
export declare const uuidv7: () => string;
|
|
1591
|
+
/** Default histogram buckets (Prometheus-style, seconds). */
|
|
1592
|
+
export declare const DEFAULT_BUCKETS: readonly number[];
|
|
1593
|
+
export type Labels = Readonly<Record<string, string>>;
|
|
1594
|
+
/** A single labelled counter value. */
|
|
1595
|
+
export interface Counter {
|
|
1596
|
+
/** Increment by 1 (or `by`). `labels` must match the label names exactly. */
|
|
1597
|
+
inc(labels?: Labels, by?: number): void;
|
|
1743
1598
|
}
|
|
1744
|
-
/**
|
|
1745
|
-
export
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1599
|
+
/** A single labelled gauge value (can go up and down). */
|
|
1600
|
+
export interface Gauge {
|
|
1601
|
+
inc(labels?: Labels, by?: number): void;
|
|
1602
|
+
dec(labels?: Labels, by?: number): void;
|
|
1603
|
+
set(labels: Labels | undefined, value: number): void;
|
|
1604
|
+
}
|
|
1605
|
+
/** A single labelled histogram. */
|
|
1606
|
+
export interface Histogram {
|
|
1607
|
+
/** Record one observation. `labels` must match the label names exactly. */
|
|
1608
|
+
observe(value: number, labels?: Labels): void;
|
|
1609
|
+
}
|
|
1610
|
+
/** The metrics registry factory. */
|
|
1611
|
+
export interface MetricsRegistry {
|
|
1612
|
+
/** Create (or fetch) a counter. `help` is only used on first creation. */
|
|
1613
|
+
counter(name: string, help: string, labelNames?: readonly string[]): Counter;
|
|
1614
|
+
/** Create (or fetch) a gauge. */
|
|
1615
|
+
gauge(name: string, help: string, labelNames?: readonly string[]): Gauge;
|
|
1616
|
+
/**
|
|
1617
|
+
* Create (or fetch) a histogram. `buckets`/`help` only apply on first
|
|
1618
|
+
* creation; later calls with the same name return the existing histogram.
|
|
1619
|
+
*/
|
|
1620
|
+
histogram(name: string, help: string, buckets?: readonly number[], labelNames?: readonly string[]): Histogram;
|
|
1621
|
+
/** Render the Prometheus text exposition format. */
|
|
1622
|
+
render(): string;
|
|
1623
|
+
/** Reset all metrics (for tests / interval rotation). */
|
|
1624
|
+
reset(): void;
|
|
1625
|
+
}
|
|
1626
|
+
/** Create a fresh metrics registry. */
|
|
1627
|
+
export declare function createMetrics(): MetricsRegistry;
|
|
1628
|
+
/**
|
|
1629
|
+
* Create the runtime-appropriate ingress server: `Bun.serve` on Bun, the
|
|
1630
|
+
* `node:http` adapter on Node. Selected ONCE at module load — the branch
|
|
1631
|
+
* never runs in the hot path.
|
|
1632
|
+
*/
|
|
1633
|
+
export declare function createIngressServer(options: CreateIngressServerOptions): BakedServer;
|
|
1634
|
+
/**
|
|
1635
|
+
* Generate the next request ID as 16 lowercase hex bytes (zero-alloc).
|
|
1636
|
+
*
|
|
1637
|
+
* @returns A shared buffer aliased across calls — decode/copy before the next
|
|
1638
|
+
* `generateRequestId()` invocation.
|
|
1639
|
+
*/
|
|
1640
|
+
export declare function generateRequestId(): Uint8Array;
|
|
1641
|
+
/** A parsed W3C trace context (valid per the spec's length/hex rules). */
|
|
1642
|
+
export interface TraceContext {
|
|
1643
|
+
/** Version, always `"00"` for accepted headers. */
|
|
1644
|
+
version: string;
|
|
1645
|
+
/** 32 hex chars — the trace id (shared across the whole trace). */
|
|
1646
|
+
traceId: string;
|
|
1647
|
+
/** 16 hex chars — the current span id. */
|
|
1648
|
+
spanId: string;
|
|
1649
|
+
/** The flags byte (bit 0 = sampled). */
|
|
1650
|
+
flags: number;
|
|
1651
|
+
/** True when the `sampled` (bit 0) flag is set. */
|
|
1652
|
+
sampled: boolean;
|
|
1752
1653
|
}
|
|
1753
1654
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* touching header templates or error bodies.
|
|
1655
|
+
* Parse a `traceparent` header value into a {@link TraceContext}, or `null`
|
|
1656
|
+
* when malformed (wrong version / length / non-hex). Invalid contexts are
|
|
1657
|
+
* ignored per the spec — callers should generate a fresh trace then.
|
|
1758
1658
|
*/
|
|
1759
|
-
export
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
string,
|
|
1767
|
-
string
|
|
1768
|
-
][];
|
|
1769
|
-
/** `req` is accepted for back-compat but unused by the baked terminal path. */
|
|
1770
|
-
terminalResponse(req: Request | undefined, result: BakedIngressResult, ctx: BakedContext): Response | null;
|
|
1771
|
-
/** `req` is accepted for back-compat but unused by the baked error path. */
|
|
1772
|
-
errorResponse(req: Request | undefined, result: BakedIngressResult | null, status: number, code: string, message: string, ctx: BakedContext): Response;
|
|
1773
|
-
internalErrorResponse(ctx: BakedContext, result?: BakedIngressResult): Response;
|
|
1774
|
-
withContentType(headers: ReadonlyArray<[
|
|
1775
|
-
string,
|
|
1776
|
-
string
|
|
1777
|
-
]>, contentType: string): [
|
|
1778
|
-
string,
|
|
1779
|
-
string
|
|
1780
|
-
][];
|
|
1781
|
-
/**
|
|
1782
|
-
* Build a zero-copy `Response` whose body is the request's pooled output
|
|
1783
|
-
* slice. The pooled buffer is returned to its pool once the body has been
|
|
1784
|
-
* consumed (stream closed) or the request aborted. Use when `copyBody` is
|
|
1785
|
-
* `false`; otherwise a copied body is released eagerly at the end of `run()`.
|
|
1786
|
-
*/
|
|
1787
|
-
zeroCopyResponse(result: BakedIngressResult, ctx: BakedContext, init: ResponseInit): Response;
|
|
1788
|
-
}
|
|
1659
|
+
export declare function parseTraceParent(header: string | null | undefined): TraceContext | null;
|
|
1660
|
+
/** Random 32-hex trace id (crypto-backed where available). */
|
|
1661
|
+
export declare function createTraceId(): string;
|
|
1662
|
+
/** Random 16-hex span id. */
|
|
1663
|
+
export declare function createSpanId(): string;
|
|
1664
|
+
/** Serialize a trace context back to a `traceparent` header value. */
|
|
1665
|
+
export declare function serializeTraceParent(ctx: TraceContext): string;
|
|
1789
1666
|
/** Offset of the verdict byte (0 = accepted, nonzero = terminal error). */
|
|
1790
1667
|
export declare const OUT_VERDICT: number;
|
|
1791
1668
|
/** Offset of the error-code byte (see ERR_CODE_* below). */
|
|
@@ -1873,6 +1750,86 @@ export declare const ERR_CODE_INTERNAL: number;
|
|
|
1873
1750
|
* numeric layout values come from the addon; this one is a JS-level error).
|
|
1874
1751
|
*/
|
|
1875
1752
|
export declare const ERR_CODE_REQUEST_TIMEOUT: number;
|
|
1753
|
+
/** Map a native error code to its stable string name. */
|
|
1754
|
+
export declare function errorCodeName(code: number): string;
|
|
1755
|
+
/** Map a native error code (plus status context) to a human message. */
|
|
1756
|
+
export declare function errorMessage(status: number, code: number): string;
|
|
1757
|
+
/** A single precomputed header-variant template. */
|
|
1758
|
+
export interface HeaderTemplate {
|
|
1759
|
+
readonly entries: ReadonlyArray<readonly [
|
|
1760
|
+
string,
|
|
1761
|
+
string
|
|
1762
|
+
]>;
|
|
1763
|
+
readonly needsOriginEcho: boolean;
|
|
1764
|
+
readonly needsRateDynamic: boolean;
|
|
1765
|
+
readonly needsRetryAfter: boolean;
|
|
1766
|
+
readonly needsDynamicHsts: boolean;
|
|
1767
|
+
}
|
|
1768
|
+
/** Precomputed per-handler response context. */
|
|
1769
|
+
export interface ResponseBuildContext {
|
|
1770
|
+
readonly templates: HeaderTemplate[];
|
|
1771
|
+
readonly corsStatic: CorsStaticStrings | null;
|
|
1772
|
+
readonly hstsValue: string | null;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Build the full response context (header templates + static CORS strings +
|
|
1776
|
+
* HSTS value) for a handler configuration.
|
|
1777
|
+
*/
|
|
1778
|
+
export declare function buildResponseContext(options: {
|
|
1779
|
+
cors?: CorsOptions;
|
|
1780
|
+
security?: SecurityHeadersOptions;
|
|
1781
|
+
https?: boolean;
|
|
1782
|
+
enableSecurityHeaders?: boolean;
|
|
1783
|
+
rateLimit?: {
|
|
1784
|
+
limit?: number;
|
|
1785
|
+
};
|
|
1786
|
+
}): ResponseBuildContext;
|
|
1787
|
+
/**
|
|
1788
|
+
* Build the response headers for a decoded result, applying the dynamic parts
|
|
1789
|
+
* (request-id, origin echo, rate-limit counters, retry-after, dynamic HSTS).
|
|
1790
|
+
*/
|
|
1791
|
+
export declare function headersForResult(ctx: ResponseBuildContext, r: {
|
|
1792
|
+
readonly headerVariant: number;
|
|
1793
|
+
readonly corsAllowed: boolean;
|
|
1794
|
+
readonly rateRemaining: number;
|
|
1795
|
+
readonly rateResetMs: number;
|
|
1796
|
+
readonly retryAfterMs: number;
|
|
1797
|
+
readonly https: boolean;
|
|
1798
|
+
}, req: Request, requestId: string): Headers;
|
|
1799
|
+
/**
|
|
1800
|
+
* Build a terminal response for a result, or `null` when the result is not
|
|
1801
|
+
* terminal (the caller should serve the body instead).
|
|
1802
|
+
*/
|
|
1803
|
+
export declare function buildTerminalResponse(ctx: ResponseBuildContext, r: {
|
|
1804
|
+
readonly terminal: boolean;
|
|
1805
|
+
readonly isPreflight: boolean;
|
|
1806
|
+
readonly corsAllowed: boolean;
|
|
1807
|
+
readonly errorCode: number;
|
|
1808
|
+
readonly status: number;
|
|
1809
|
+
readonly headerVariant: number;
|
|
1810
|
+
readonly https: boolean;
|
|
1811
|
+
readonly rateLimit: number;
|
|
1812
|
+
readonly rateRemaining: number;
|
|
1813
|
+
readonly rateResetMs: number;
|
|
1814
|
+
readonly retryAfterMs: number;
|
|
1815
|
+
}, req: Request, requestId: string): Response | null;
|
|
1816
|
+
/** Default maximum request body size in bytes. */
|
|
1817
|
+
export declare const DEFAULT_MAX_BODY_BYTES = 1048576;
|
|
1818
|
+
/**
|
|
1819
|
+
* Default overall deadline (ms) for reading a request body. Non-zero so the
|
|
1820
|
+
* async `createIngress` / route handlers are protected against slowloris and
|
|
1821
|
+
* trickling bodies by default. Set `bodyTimeoutMs: 0` to disable.
|
|
1822
|
+
*/
|
|
1823
|
+
export declare const DEFAULT_BODY_TIMEOUT_MS = 30000;
|
|
1824
|
+
/** Maps HTTP methods to the native Ingress method-kind enum. */
|
|
1825
|
+
export declare const METHOD_KIND: Record<string, number>;
|
|
1826
|
+
/** Which request headers to extract into the packed input. */
|
|
1827
|
+
export interface HeaderPlan {
|
|
1828
|
+
cookie: boolean;
|
|
1829
|
+
cors: boolean;
|
|
1830
|
+
proxy: boolean;
|
|
1831
|
+
proto: boolean;
|
|
1832
|
+
}
|
|
1876
1833
|
/**
|
|
1877
1834
|
* Whether a raw status is a legal HTTP response status for the ingress
|
|
1878
1835
|
* pipeline (101 switch-protocol or any 2xx–5xx).
|
|
@@ -1898,10 +1855,18 @@ export declare function safeTerminalStatus(r: {
|
|
|
1898
1855
|
readonly isPreflight: boolean;
|
|
1899
1856
|
readonly corsAllowed: boolean;
|
|
1900
1857
|
}): number;
|
|
1901
|
-
/**
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1858
|
+
/**
|
|
1859
|
+
* Create a fast packed-input ingress handler (path 1).
|
|
1860
|
+
*
|
|
1861
|
+
* JS packs the request headers into a binary buffer (`IngressInputPacker`) and
|
|
1862
|
+
* the native `Ingress.handleRequestPacked` decodes them. Responses use the
|
|
1863
|
+
* `{"error":{...}}` wire format with `x-ratelimit-*` headers.
|
|
1864
|
+
*
|
|
1865
|
+
* Prefer this when you want the lowest-overhead pipeline and will call `run()`
|
|
1866
|
+
* yourself with a **synchronous** callback. For ready-made route handlers and a
|
|
1867
|
+
* server builder, use `createIngressHandler` (path 2).
|
|
1868
|
+
*/
|
|
1869
|
+
export declare function createIngressFast(options?: IngressFastOptions): IngressFastHandler;
|
|
1905
1870
|
declare function createStructuredLogger(stream?: Pick<NodeJS.WritableStream, "write">, env?: NodeJS.ProcessEnv): {
|
|
1906
1871
|
/** Record one completed request. */
|
|
1907
1872
|
request(info: {
|
|
@@ -1922,45 +1887,20 @@ declare function createStructuredLogger(stream?: Pick<NodeJS.WritableStream, "wr
|
|
|
1922
1887
|
};
|
|
1923
1888
|
/** Pre-encoded static error bodies, keyed by error-code string name. */
|
|
1924
1889
|
export declare const ERROR_BODIES: Record<string, Uint8Array>;
|
|
1925
|
-
/** Options accepted by the pre-baked route factories. */
|
|
1926
|
-
export interface BakedHandlerOptions {
|
|
1927
|
-
/** Resolve the client IP from the server object (e.g. `srv.requestIP`). */
|
|
1928
|
-
getIp?: (req: Request, srv: unknown) => string | undefined;
|
|
1929
|
-
/** Copy body slices instead of sharing the native buffer. Default: true (safe). */
|
|
1930
|
-
copyBody?: boolean;
|
|
1931
|
-
/** Maximum request body bytes for write/echo handlers. Default: 1 MiB. */
|
|
1932
|
-
maxBodyBytes?: number;
|
|
1933
|
-
/**
|
|
1934
|
-
* Overall deadline (ms) for reading the request body. Default: 30s
|
|
1935
|
-
* (`DEFAULT_BODY_TIMEOUT_MS`). Set `0` to disable. Protects against
|
|
1936
|
-
* slowloris / trickling bodies.
|
|
1937
|
-
*/
|
|
1938
|
-
bodyTimeoutMs?: number;
|
|
1939
|
-
/** Fallback handler used for write error paths. Defaults to the write ingress. */
|
|
1940
|
-
fallback?: OptimizedIngressHandler;
|
|
1941
|
-
/**
|
|
1942
|
-
* When true, route the JSON-validity fallback check through the higher-order
|
|
1943
|
-
* loader (`loader("jsonValid")`) instead of the direct native call —
|
|
1944
|
-
* exercising the loader's dispatch + counters on the real request path.
|
|
1945
|
-
* Default: false (the direct call is marginally faster for a single request;
|
|
1946
|
-
* the loader pays off for same-tick bursts / bulk work, see src/integration/batch.ts).
|
|
1947
|
-
*/
|
|
1948
|
-
enableLoader?: boolean;
|
|
1949
|
-
}
|
|
1950
1890
|
/**
|
|
1951
1891
|
* Pre-baked GET read handler: returns the ingress body JSON on success.
|
|
1952
1892
|
*/
|
|
1953
1893
|
export declare function readHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Response;
|
|
1954
1894
|
/**
|
|
1955
|
-
* Pre-baked
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
*
|
|
1960
|
-
*
|
|
1961
|
-
*
|
|
1895
|
+
* Pre-baked DELETE handler: DELETE is a read-style method (no request body is
|
|
1896
|
+
* read), so it shares the exact pipeline flow of {@link readHandler} — it
|
|
1897
|
+
* returns the ingress body JSON on success and the terminal/error responses
|
|
1898
|
+
* otherwise. Reusing the read factory keeps the two handlers byte-identical
|
|
1899
|
+
* and avoids a copy-pasted variant.
|
|
1900
|
+
*
|
|
1901
|
+
* Wire it via the `delete` key of a `BakedRoute` (see `createIngressServer`).
|
|
1962
1902
|
*/
|
|
1963
|
-
export declare
|
|
1903
|
+
export declare const deleteHandler: typeof readHandler;
|
|
1964
1904
|
/**
|
|
1965
1905
|
* Pre-baked echo handler: streams the request body back with the client's
|
|
1966
1906
|
* Content-Type, bounded by `maxBodyBytes`.
|
|
@@ -1969,125 +1909,45 @@ export declare function echoHandler(ingress: OptimizedIngressHandler, opts?: Bak
|
|
|
1969
1909
|
/**
|
|
1970
1910
|
* Pre-baked fallback handler: 404 for unmatched routes / OPTIONS.
|
|
1971
1911
|
*/
|
|
1972
|
-
export declare function fallbackHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Response;
|
|
1973
|
-
/** A raw web-standard route handler (`Request` → `Response`), e.g. the
|
|
1974
|
-
* health/metrics probes (`livenessHandler`, `readinessHandler`,
|
|
1975
|
-
* `healthHandler`, `metricsHandler`). Accepted as `BakedRoute.read` and
|
|
1976
|
-
* served for GET directly, outside the ingress pipeline. The optional third
|
|
1977
|
-
* arg carries the matched dynamic path params (`:param`/`*`) on the Node
|
|
1978
|
-
* adapter (Bun's native router handles matching; the ingress pipeline does
|
|
1979
|
-
* not echo params in its response). */
|
|
1980
|
-
type RouteHandler = (req: Request, srv?: unknown, params?: Record<string, string>) => Response | Promise<Response>;
|
|
1981
|
-
/** Route spec for the Bun.serve builder. */
|
|
1982
|
-
export interface BakedRoute {
|
|
1983
|
-
/**
|
|
1984
|
-
* Wires GET + HEAD read handlers. Accepts an optimized ingress handler OR a
|
|
1985
|
-
* raw `RouteHandler` function (health/metrics probes are raw — see
|
|
1986
|
-
* src/ingress/health.ts). A raw handler is served for GET only.
|
|
1987
|
-
*/
|
|
1988
|
-
read?: OptimizedIngressHandler | RouteHandler;
|
|
1989
|
-
/** Wires POST/PUT/PATCH JSON-write handlers (and OPTIONS fallback). */
|
|
1990
|
-
write?: OptimizedIngressHandler;
|
|
1991
|
-
/** Wires a POST echo handler. */
|
|
1992
|
-
echo?: OptimizedIngressHandler;
|
|
1993
|
-
/** Wires a GET read handler (cookies-style route). */
|
|
1994
|
-
cookies?: OptimizedIngressHandler;
|
|
1995
|
-
/** Wires a DELETE read-style handler. */
|
|
1996
|
-
delete?: OptimizedIngressHandler;
|
|
1997
|
-
/** Overrides `maxBodyBytes` for this route's write/echo handlers. */
|
|
1998
|
-
maxBodyBytes?: number;
|
|
1999
|
-
/**
|
|
2000
|
-
* Overall deadline (ms) for reading the request body on this route's
|
|
2001
|
-
* write/echo handlers. Default: 30_000 (`DEFAULT_BODY_TIMEOUT_MS`).
|
|
2002
|
-
* Set 0 to disable.
|
|
2003
|
-
*/
|
|
2004
|
-
bodyTimeoutMs?: number;
|
|
2005
|
-
}
|
|
2006
|
-
/** RFC 6455 handshake values returned by `CreateIngressServerOptions.upgrade`. */
|
|
2007
|
-
export interface NodeUpgradeHandshake {
|
|
2008
|
-
/** `Sec-WebSocket-Accept` value (compute via `rust.text.wsAcceptKey`). */
|
|
2009
|
-
accept: string;
|
|
2010
|
-
/** Optional negotiated subprotocol (`Sec-WebSocket-Protocol`). */
|
|
2011
|
-
protocol?: string;
|
|
2012
|
-
}
|
|
2013
|
-
/** Options for {@link createIngressServer}. */
|
|
2014
|
-
export interface CreateIngressServerOptions {
|
|
2015
|
-
port: number;
|
|
2016
|
-
hostname?: string;
|
|
2017
|
-
idleTimeout?: number;
|
|
2018
|
-
maxRequestBodySize?: number;
|
|
2019
|
-
reusePort?: boolean;
|
|
2020
|
-
copyBody?: boolean;
|
|
2021
|
-
getIp?: (req: Request, srv: unknown) => string | undefined;
|
|
2022
|
-
routes: Record<string, BakedRoute>;
|
|
2023
|
-
fallback?: OptimizedIngressHandler;
|
|
2024
|
-
/**
|
|
2025
|
-
* Invoked when an unhandled error escapes a request handler (currently wired
|
|
2026
|
-
* on the Node adapter; the Bun path uses runtime.onResponse). Never throws —
|
|
2027
|
-
* hook failures are swallowed.
|
|
2028
|
-
*/
|
|
2029
|
-
onError?: (info: {
|
|
2030
|
-
error: Error;
|
|
2031
|
-
request?: Request;
|
|
2032
|
-
}) => void;
|
|
2033
|
-
/** Node adapter only: ms to receive the complete request (headers + body).
|
|
2034
|
-
* Guards slowloris/trickling requests. Default: 30_000. */
|
|
2035
|
-
requestTimeoutMs?: number;
|
|
2036
|
-
/** Node adapter only: ms to receive the request headers. Default: 30_000. */
|
|
2037
|
-
headersTimeoutMs?: number;
|
|
2038
|
-
/** Node adapter only: max requests per keep-alive socket before Node forces
|
|
2039
|
-
* a fresh connection (bounds long-lived socket reuse). Default: 1000. */
|
|
2040
|
-
maxRequestsPerSocket?: number;
|
|
2041
|
-
/** Node adapter only: optional WebSocket upgrade handler. Return the RFC 6455
|
|
2042
|
-
* handshake values (see {@link NodeUpgradeHandshake}) to complete the
|
|
2043
|
-
* handshake on the hijacked socket; return null/undefined to decline the
|
|
2044
|
-
* upgrade. Ignored by the Bun server. (`createWebSocketUpgrade` is Bun-only
|
|
2045
|
-
* — Node's Response cannot carry a 101.) */
|
|
2046
|
-
upgrade?: (req: Request) => NodeUpgradeHandshake | null | undefined;
|
|
2047
|
-
/** Node adapter only: called after a WebSocket upgrade handshake completes
|
|
2048
|
-
* (the 101 response was accepted), with the hijacked socket so the caller
|
|
2049
|
-
* can attach a frame codec/message loop (e.g. the `ws` library). Ignored by
|
|
2050
|
-
* the Bun server. */
|
|
2051
|
-
onUpgrade?: (socket: import("node:stream").Duplex, req: unknown, head: Buffer) => void;
|
|
2052
|
-
}
|
|
1912
|
+
export declare function fallbackHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Response;
|
|
2053
1913
|
/**
|
|
2054
|
-
*
|
|
1914
|
+
* Pre-baked HEAD read handler: headers only, no body.
|
|
1915
|
+
*/
|
|
1916
|
+
export declare function headHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Response;
|
|
1917
|
+
/**
|
|
1918
|
+
* Pre-baked JSON-write handler (POST/PUT/PATCH): enforces Content-Type,
|
|
1919
|
+
* content-length/body-size limits, JSON validity and (optionally) schema
|
|
1920
|
+
* validation, then returns the ingress body JSON on success.
|
|
1921
|
+
*/
|
|
1922
|
+
export declare function jsonWriteHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Promise<Response>;
|
|
1923
|
+
/**
|
|
1924
|
+
* Pre-baked OPTIONS handler: serves the native pipeline's CORS preflight
|
|
1925
|
+
* decision — 204 when the preflight is allowed, 403 (terminal) when denied —
|
|
1926
|
+
* and a plain 204 for non-preflight OPTIONS.
|
|
2055
1927
|
*
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2059
|
-
* fields; treat the handle as opaque.
|
|
1928
|
+
* `createIngressServer` wires this for EVERY route that has a handler, so
|
|
1929
|
+
* read-only routes also answer preflights (previously OPTIONS only reached the
|
|
1930
|
+
* pipeline on write routes that also configured a fallback).
|
|
2060
1931
|
*/
|
|
2061
|
-
export
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
}
|
|
2074
|
-
export interface GracefulShutdownOptions {
|
|
2075
|
-
/** Grace period (ms) to drain in-flight requests before force-closing. */
|
|
2076
|
-
timeoutMs?: number;
|
|
2077
|
-
/** Signals to listen for. Default: SIGTERM + SIGINT. */
|
|
2078
|
-
signals?: NodeJS.Signals[];
|
|
1932
|
+
export declare function optionsHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Response;
|
|
1933
|
+
/** Options for {@link nativeResponderRoute}. */
|
|
1934
|
+
export interface ResponderRouteOptions extends BakedHandlerOptions {
|
|
1935
|
+
/** Terminal envelope style. Default: `'castrum'` (the pre-baked wire). */
|
|
1936
|
+
terminalStyle?: TerminalStyle;
|
|
1937
|
+
/**
|
|
1938
|
+
* Read the request body and pass it to the native pipeline for validation
|
|
1939
|
+
* (`requireJsonBody`/schema). Default `false` — the framework owns the body
|
|
1940
|
+
* (the responder reads it via `ctx.body`); native only needs the bytes when
|
|
1941
|
+
* a body stage is enabled in the route's `IngressHandlerOptions`.
|
|
1942
|
+
*/
|
|
1943
|
+
readBody?: boolean;
|
|
2079
1944
|
}
|
|
2080
1945
|
/**
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2083
|
-
*
|
|
2084
|
-
*
|
|
2085
|
-
* Returns a cleanup function that removes the signal listeners. Call it (or
|
|
2086
|
-
* `process.exit`) to tear down without hanging.
|
|
1946
|
+
* Build a responder route: native pipeline decides + rejects (terminal
|
|
1947
|
+
* response in the chosen style); on success the JS `responder` builds the 2xx.
|
|
1948
|
+
* Returns an async `RouteHandler` (`(req, srv?, params?) => Promise<Response>`).
|
|
2087
1949
|
*/
|
|
2088
|
-
export declare function
|
|
2089
|
-
/** Build a Bun.serve config from pre-baked route handlers. */
|
|
2090
|
-
export declare function createIngressServer(options: CreateIngressServerOptions): BakedServer;
|
|
1950
|
+
export declare function nativeResponderRoute(ingress: OptimizedIngressHandler, responder: NativeResponder, opts?: ResponderRouteOptions): RouteHandler;
|
|
2091
1951
|
/** A Node.js ingress server: {@link BakedServer} plus an async ready signal. */
|
|
2092
1952
|
export interface NodeIngressServer extends BakedServer {
|
|
2093
1953
|
/**
|
|
@@ -2108,6 +1968,7 @@ export interface NodeIngressServer extends BakedServer {
|
|
|
2108
1968
|
export declare function createIngressServerNode(options: CreateIngressServerOptions): NodeIngressServer;
|
|
2109
1969
|
/** Sentinel value used by Rust to mean "rate limiting disabled". */
|
|
2110
1970
|
export declare const RATE_LIMIT_U32_MAX = 4294967295;
|
|
1971
|
+
/** Runtime configuration for the pre-baked handler path (`createIngressHandler`). */
|
|
2111
1972
|
export interface BakedIngressRuntime {
|
|
2112
1973
|
/** Emit an `x-request-id` header on responses. Default: false. */
|
|
2113
1974
|
emitRequestIdHeader?: boolean;
|
|
@@ -2179,100 +2040,22 @@ export interface BakedIngressRuntime {
|
|
|
2179
2040
|
*/
|
|
2180
2041
|
export declare function createIngressHandler(options?: IngressHandlerOptions, runtime?: BakedIngressRuntime): OptimizedIngressHandler;
|
|
2181
2042
|
/**
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
* @returns A shared buffer aliased across calls — decode/copy before the next
|
|
2185
|
-
* `generateRequestId()` invocation.
|
|
2186
|
-
*/
|
|
2187
|
-
export declare function generateRequestId(): Uint8Array;
|
|
2188
|
-
/** A single precomputed header-variant template. */
|
|
2189
|
-
export interface HeaderTemplate {
|
|
2190
|
-
readonly entries: ReadonlyArray<readonly [
|
|
2191
|
-
string,
|
|
2192
|
-
string
|
|
2193
|
-
]>;
|
|
2194
|
-
readonly needsOriginEcho: boolean;
|
|
2195
|
-
readonly needsRateDynamic: boolean;
|
|
2196
|
-
readonly needsRetryAfter: boolean;
|
|
2197
|
-
readonly needsDynamicHsts: boolean;
|
|
2198
|
-
}
|
|
2199
|
-
/** Precomputed per-handler response context. */
|
|
2200
|
-
export interface ResponseBuildContext {
|
|
2201
|
-
readonly templates: HeaderTemplate[];
|
|
2202
|
-
readonly corsStatic: CorsStaticStrings | null;
|
|
2203
|
-
readonly hstsValue: string | null;
|
|
2204
|
-
}
|
|
2205
|
-
/**
|
|
2206
|
-
* Build the full response context (header templates + static CORS strings +
|
|
2207
|
-
* HSTS value) for a handler configuration.
|
|
2208
|
-
*/
|
|
2209
|
-
export declare function buildResponseContext(options: {
|
|
2210
|
-
cors?: CorsOptions;
|
|
2211
|
-
security?: SecurityHeadersOptions;
|
|
2212
|
-
https?: boolean;
|
|
2213
|
-
enableSecurityHeaders?: boolean;
|
|
2214
|
-
rateLimit?: {
|
|
2215
|
-
limit?: number;
|
|
2216
|
-
};
|
|
2217
|
-
}): ResponseBuildContext;
|
|
2218
|
-
/**
|
|
2219
|
-
* Build the response headers for a decoded result, applying the dynamic parts
|
|
2220
|
-
* (request-id, origin echo, rate-limit counters, retry-after, dynamic HSTS).
|
|
2221
|
-
*/
|
|
2222
|
-
export declare function headersForResult(ctx: ResponseBuildContext, r: {
|
|
2223
|
-
readonly headerVariant: number;
|
|
2224
|
-
readonly corsAllowed: boolean;
|
|
2225
|
-
readonly rateRemaining: number;
|
|
2226
|
-
readonly rateResetMs: number;
|
|
2227
|
-
readonly retryAfterMs: number;
|
|
2228
|
-
readonly https: boolean;
|
|
2229
|
-
}, req: Request, requestId: string): Headers;
|
|
2230
|
-
/**
|
|
2231
|
-
* Build a terminal response for a result, or `null` when the result is not
|
|
2232
|
-
* terminal (the caller should serve the body instead).
|
|
2043
|
+
* Liveness probe: `200 {"status":"ok"}` as long as the process is alive.
|
|
2044
|
+
* Never touches dependencies.
|
|
2233
2045
|
*/
|
|
2234
|
-
export declare function
|
|
2235
|
-
readonly terminal: boolean;
|
|
2236
|
-
readonly isPreflight: boolean;
|
|
2237
|
-
readonly corsAllowed: boolean;
|
|
2238
|
-
readonly errorCode: number;
|
|
2239
|
-
readonly status: number;
|
|
2240
|
-
readonly headerVariant: number;
|
|
2241
|
-
readonly https: boolean;
|
|
2242
|
-
readonly rateLimit: number;
|
|
2243
|
-
readonly rateRemaining: number;
|
|
2244
|
-
readonly rateResetMs: number;
|
|
2245
|
-
readonly retryAfterMs: number;
|
|
2246
|
-
}, req: Request, requestId: string): Response | null;
|
|
2247
|
-
/** Default maximum request body size in bytes. */
|
|
2248
|
-
export declare const DEFAULT_MAX_BODY_BYTES = 1048576;
|
|
2046
|
+
export declare function livenessHandler(): (_req: Request) => Response;
|
|
2249
2047
|
/**
|
|
2250
|
-
*
|
|
2251
|
-
*
|
|
2252
|
-
*
|
|
2048
|
+
* Readiness probe: runs the optional async `check` (dependency readiness).
|
|
2049
|
+
* Returns `200 {"status":"ready"}` when the check passes (or no check is
|
|
2050
|
+
* given), `503 {"status":"not_ready"}` when it throws or returns false.
|
|
2253
2051
|
*/
|
|
2254
|
-
export declare
|
|
2255
|
-
/** Maps HTTP methods to the native Ingress method-kind enum. */
|
|
2256
|
-
export declare const METHOD_KIND: Record<string, number>;
|
|
2257
|
-
/** Which request headers to extract into the packed input. */
|
|
2258
|
-
export interface HeaderPlan {
|
|
2259
|
-
cookie: boolean;
|
|
2260
|
-
cors: boolean;
|
|
2261
|
-
proxy: boolean;
|
|
2262
|
-
proto: boolean;
|
|
2263
|
-
}
|
|
2052
|
+
export declare function readinessHandler(check?: () => boolean | Promise<boolean>): (_req: Request) => Promise<Response>;
|
|
2264
2053
|
/**
|
|
2265
|
-
*
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
2268
|
-
* the native `Ingress.handleRequestPacked` decodes them. Responses use the
|
|
2269
|
-
* `{"error":{...}}` wire format with `x-ratelimit-*` headers.
|
|
2270
|
-
*
|
|
2271
|
-
* Prefer this when you want the lowest-overhead pipeline and will call `run()`
|
|
2272
|
-
* yourself with a **synchronous** callback. For ready-made route handlers and a
|
|
2273
|
-
* server builder, use `createIngressHandler` (path 2).
|
|
2054
|
+
* Combined health probe: liveness by default, plus an optional readiness
|
|
2055
|
+
* `check`. When a `check` is provided the response is 503 on failure
|
|
2056
|
+
* (readiness semantics), otherwise always 200.
|
|
2274
2057
|
*/
|
|
2275
|
-
export declare function
|
|
2058
|
+
export declare function healthHandler(check?: () => boolean | Promise<boolean>): ((_req: Request) => Response) | ((_req: Request) => Promise<Response>);
|
|
2276
2059
|
/** The runtime hooks + registry for ingress metrics. */
|
|
2277
2060
|
export interface IngressMetrics {
|
|
2278
2061
|
registry: MetricsRegistry;
|
|
@@ -2293,53 +2076,142 @@ export declare function createIngressMetrics(): IngressMetrics;
|
|
|
2293
2076
|
* createIngressServer({ routes: { "/metrics": { read: metricsHandler(m) } } })
|
|
2294
2077
|
*/
|
|
2295
2078
|
export declare function metricsHandler(metrics: IngressMetrics): (_req: Request) => Response;
|
|
2296
|
-
/**
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2079
|
+
/** Per-route spec for {@link createIngressRouter}. */
|
|
2080
|
+
export interface RouterRouteSpec {
|
|
2081
|
+
/**
|
|
2082
|
+
* Per-route ingress options — compiled into a DEDICATED native `IngressInner`
|
|
2083
|
+
* for this route (parseCookies, parseQuery, schema, cors, rateLimit, limits,
|
|
2084
|
+
* requireJsonBody, ...). This is what makes the pipeline prune per route.
|
|
2085
|
+
*/
|
|
2086
|
+
options?: IngressHandlerOptions;
|
|
2087
|
+
/** Wire GET + HEAD read handlers over this route's compiled handler. */
|
|
2088
|
+
read?: boolean;
|
|
2089
|
+
/** Wire POST/PUT/PATCH JSON-write handlers over this route's compiled handler. */
|
|
2090
|
+
write?: boolean;
|
|
2091
|
+
/** Wire a POST echo handler over this route's compiled handler. */
|
|
2092
|
+
echo?: boolean;
|
|
2093
|
+
/** Wire a GET read handler (cookies-style) over this route's compiled handler. */
|
|
2094
|
+
cookies?: boolean;
|
|
2095
|
+
/** Wire a DELETE read-style handler over this route's compiled handler. */
|
|
2096
|
+
delete?: boolean;
|
|
2097
|
+
/**
|
|
2098
|
+
* A JS responder route: the native pipeline decides + rejects (terminal
|
|
2099
|
+
* response in {@link terminalStyle}); on success the responder builds the
|
|
2100
|
+
* 2xx body (async OK) from a decoded snapshot. Wired for `methods`
|
|
2101
|
+
* (default `['GET']`). When set, the standard `read`/`write`/etc. flags for
|
|
2102
|
+
* this route are ignored.
|
|
2103
|
+
*/
|
|
2104
|
+
responder?: {
|
|
2105
|
+
handler: NativeResponder;
|
|
2106
|
+
/** HTTP methods to wire (default `['GET']`). */
|
|
2107
|
+
methods?: ReadonlyArray<string>;
|
|
2108
|
+
/** Read the body for native validation (default false — framework owns it). */
|
|
2109
|
+
readBody?: boolean;
|
|
2110
|
+
};
|
|
2111
|
+
/**
|
|
2112
|
+
* A LEAN native-stack responder route: the route-wire v3 per-route stack
|
|
2113
|
+
* (`createNativeRoute` over `castrum_route_*`) runs ONLY the stages in
|
|
2114
|
+
* `plan` (parseQuery/parseCookies/requireJsonBody/validateBody) in ONE
|
|
2115
|
+
* native call — no CORS/rate-limit/security/IP/metadata envelope. On a
|
|
2116
|
+
* verdict failure the route rejects (400 non-JSON / 422 schema); on success
|
|
2117
|
+
* the responder builds the 2xx from the decoded snapshot. Measured ~580ns
|
|
2118
|
+
* cheaper per request than the full-pipeline responder on a
|
|
2119
|
+
* parseQuery+parseCookies route (bench/cost/native-route-vs-router.ts).
|
|
2120
|
+
* Wired for `methods` (default `['GET']`). When set, `read`/`write`/etc.
|
|
2121
|
+
* for this route are ignored.
|
|
2122
|
+
*/
|
|
2123
|
+
native?: {
|
|
2124
|
+
/** The route-wire v3 plan (parse/validate stages + limits). */
|
|
2125
|
+
plan: NativeRoutePlan;
|
|
2126
|
+
/** The JS 2xx builder (receives the decoded query/cookies/body snapshot). */
|
|
2127
|
+
handler: NativeResponder;
|
|
2128
|
+
/** HTTP methods to wire (default `['GET']`). */
|
|
2129
|
+
methods?: ReadonlyArray<string>;
|
|
2130
|
+
/** Read the body for `requireJsonBody`/`validateBody` (default false). */
|
|
2131
|
+
readBody?: boolean;
|
|
2132
|
+
};
|
|
2133
|
+
/** Terminal envelope style for this route's responder (default: router-level
|
|
2134
|
+
* `terminalStyle` or `'castrum'`). */
|
|
2135
|
+
terminalStyle?: TerminalStyle;
|
|
2136
|
+
/**
|
|
2137
|
+
* A raw request→Response handler served directly for GET, OUTSIDE the
|
|
2138
|
+
* ingress pipeline (health/metrics probes, /metrics). When set, `read`/
|
|
2139
|
+
* `write`/etc. for this route are ignored.
|
|
2140
|
+
*/
|
|
2141
|
+
raw?: RouteHandler;
|
|
2142
|
+
/** Overrides `maxBodyBytes` for this route's write/echo handlers. */
|
|
2143
|
+
maxBodyBytes?: number;
|
|
2144
|
+
/** Overrides `bodyTimeoutMs` for this route's write/echo handlers. */
|
|
2145
|
+
bodyTimeoutMs?: number;
|
|
2146
|
+
}
|
|
2147
|
+
/** Options for {@link createIngressRouter}. */
|
|
2148
|
+
export interface CreateIngressRouterOptions {
|
|
2149
|
+
/** Route table: path → per-route spec. */
|
|
2150
|
+
routes: Record<string, RouterRouteSpec>;
|
|
2151
|
+
/** Shared runtime hooks (onRequest/onResponse/onError, security headers,
|
|
2152
|
+
* output buffer size) applied to EVERY compiled route handler. */
|
|
2153
|
+
runtime?: BakedIngressRuntime;
|
|
2154
|
+
/** Shared `getIp` resolver for route handlers. */
|
|
2155
|
+
getIp?: (req: Request, srv: unknown) => string | undefined;
|
|
2156
|
+
/** Shared `copyBody` default for route handlers. */
|
|
2157
|
+
copyBody?: boolean;
|
|
2158
|
+
/** Default terminal envelope style for responder routes (`'castrum'`). */
|
|
2159
|
+
terminalStyle?: TerminalStyle;
|
|
2160
|
+
/** Pre-warm every compiled route at construction (JIT the packed pipeline +
|
|
2161
|
+
* the FFI ingress call before the first real request). Default: false. */
|
|
2162
|
+
warmOnCreate?: boolean;
|
|
2163
|
+
}
|
|
2164
|
+
/** A compiled per-route router. */
|
|
2165
|
+
export interface IngressRouter {
|
|
2166
|
+
/** The per-route compiled handlers keyed by path (for custom route wiring). */
|
|
2167
|
+
routeHandlers: Record<string, OptimizedIngressHandler>;
|
|
2168
|
+
/** The route → method → handler map (compatible with Bun.serve `routes` and
|
|
2169
|
+
* `createIngressServerNode`). */
|
|
2170
|
+
routes: Record<string, Record<string, unknown>>;
|
|
2171
|
+
/** The path matcher (`:param`/`*` dynamic routes). */
|
|
2172
|
+
match: (pathname: string) => PathMatch | undefined;
|
|
2173
|
+
/** A fetch-style dispatcher: match path + method → the route's handler. */
|
|
2174
|
+
fetch: (req: Request, srv?: unknown) => Response | Promise<Response>;
|
|
2175
|
+
/** Pre-warm every compiled route (JIT the packed pipeline + FFI call). */
|
|
2176
|
+
prewarm: () => void;
|
|
2325
2177
|
}
|
|
2326
2178
|
/**
|
|
2327
|
-
*
|
|
2328
|
-
*
|
|
2329
|
-
*
|
|
2179
|
+
* Compile a route table into per-route native ingress instances.
|
|
2180
|
+
*
|
|
2181
|
+
* Each route with a pipeline-backed spec (`read`/`write`/`echo`/`cookies`/
|
|
2182
|
+
* `delete`) compiles its OWN `createIngressHandler(options)` — a dedicated
|
|
2183
|
+
* `IngressInner` pruned to that route's stages + a per-route header plan.
|
|
2184
|
+
* Routes that need nothing (a bare `read` with no cookies/query/cors) therefore
|
|
2185
|
+
* gather zero headers and run a near-empty native pipeline. All routes share
|
|
2186
|
+
* the same wire format and the shared process-wide rate-limiter budgets.
|
|
2187
|
+
*
|
|
2188
|
+
* @example
|
|
2189
|
+
* ```ts
|
|
2190
|
+
* const router = createIngressRouter({
|
|
2191
|
+
* warmOnCreate: true,
|
|
2192
|
+
* runtime: ingressMetrics?.runtime,
|
|
2193
|
+
* routes: {
|
|
2194
|
+
* '/health': { read: true, options: { parseCookies: false, parseQuery: false } },
|
|
2195
|
+
* '/api/users': {
|
|
2196
|
+
* read: true,
|
|
2197
|
+
* write: true,
|
|
2198
|
+
* options: { parseCookies: true, parseQuery: true, schema: USER_SCHEMA_BYTES },
|
|
2199
|
+
* },
|
|
2200
|
+
* '/metrics': { raw: metricsHandler(ingressMetrics) },
|
|
2201
|
+
* },
|
|
2202
|
+
* })
|
|
2203
|
+
* const server = Bun.serve({ routes: router.routes }) // or router.fetch
|
|
2204
|
+
* ```
|
|
2330
2205
|
*/
|
|
2331
|
-
export declare function
|
|
2332
|
-
/** Random 32-hex trace id (crypto-backed where available). */
|
|
2333
|
-
export declare function createTraceId(): string;
|
|
2334
|
-
/** Random 16-hex span id. */
|
|
2335
|
-
export declare function createSpanId(): string;
|
|
2336
|
-
/** Serialize a trace context back to a `traceparent` header value. */
|
|
2337
|
-
export declare function serializeTraceParent(ctx: TraceContext): string;
|
|
2206
|
+
export declare function createIngressRouter(options: CreateIngressRouterOptions): IngressRouter;
|
|
2338
2207
|
/**
|
|
2339
2208
|
* Synchronous convenience wrapper over `createIngressFast` (path 1).
|
|
2340
2209
|
*
|
|
2341
2210
|
* The callback runs on the fast-path result and MUST be synchronous. Use
|
|
2342
2211
|
* {@link createIngress} when you want automatic body reading + error contexts.
|
|
2212
|
+
*
|
|
2213
|
+
* @param options fast-path ingress options
|
|
2214
|
+
* @returns a sync handler (callback must be synchronous)
|
|
2343
2215
|
*/
|
|
2344
2216
|
export declare function createIngressSync(options?: IngressOptions): SyncIngressHandler;
|
|
2345
2217
|
/**
|
|
@@ -2348,6 +2220,9 @@ export declare function createIngressSync(options?: IngressOptions): SyncIngress
|
|
|
2348
2220
|
*
|
|
2349
2221
|
* Recommended default for serverless/edge-style `fetch` handlers. For maximum
|
|
2350
2222
|
* throughput use `createIngressFast` / `createIngressSync` directly.
|
|
2223
|
+
*
|
|
2224
|
+
* @param options fast-path ingress options (security applied here, not forwarded)
|
|
2225
|
+
* @returns an async handler returning a snapshot context
|
|
2351
2226
|
*/
|
|
2352
2227
|
export declare function createIngress(options?: IngressOptions): IngressHandler;
|
|
2353
2228
|
/** Readonly snapshot of the ingress decision, safe after `run()` returns. */
|
|
@@ -2373,6 +2248,7 @@ export interface PipelineContext {
|
|
|
2373
2248
|
/** Mutable per-request storage for middleware/user state. */
|
|
2374
2249
|
readonly locals: Map<string, unknown>;
|
|
2375
2250
|
}
|
|
2251
|
+
/** Result of running `preprocess`: terminal short-circuit or pass-through. */
|
|
2376
2252
|
export interface PreprocessOutcome {
|
|
2377
2253
|
/** True when the pipeline short-circuited (error/denied) — serve `response`. */
|
|
2378
2254
|
readonly terminal: boolean;
|
|
@@ -2380,6 +2256,7 @@ export interface PreprocessOutcome {
|
|
|
2380
2256
|
readonly result: PipelineResult | null;
|
|
2381
2257
|
readonly ctx: PipelineContext;
|
|
2382
2258
|
}
|
|
2259
|
+
/** Options for `createPipeline`. */
|
|
2383
2260
|
export interface CreatePipelineOptions {
|
|
2384
2261
|
/** Ingress options (same surface as `createIngressHandler`). */
|
|
2385
2262
|
options?: IngressHandlerOptions;
|
|
@@ -2400,6 +2277,7 @@ export interface CreatePipelineOptions {
|
|
|
2400
2277
|
*/
|
|
2401
2278
|
render?: (result: PipelineResult, ctx: PipelineContext) => Response | Promise<Response>;
|
|
2402
2279
|
}
|
|
2280
|
+
/** The composed ingress pipeline: preprocess / handleRequest / readBody. */
|
|
2403
2281
|
export interface IngressPipeline {
|
|
2404
2282
|
/** The underlying pre-baked handler (custom route factories / responses). */
|
|
2405
2283
|
readonly ingress: OptimizedIngressHandler;
|
|
@@ -2429,49 +2307,7 @@ export interface IngressPipeline {
|
|
|
2429
2307
|
* ```
|
|
2430
2308
|
*/
|
|
2431
2309
|
export declare function createPipeline(opts?: CreatePipelineOptions): IngressPipeline;
|
|
2432
|
-
export interface
|
|
2433
|
-
/** Subprotocols this server supports; the first client-requested match wins. */
|
|
2434
|
-
protocols?: ReadonlyArray<string>;
|
|
2435
|
-
}
|
|
2436
|
-
export interface WebSocketUpgradeResult {
|
|
2437
|
-
/** The 101 Switching Protocols response — return it from your fetch handler. */
|
|
2438
|
-
readonly response: Response;
|
|
2439
|
-
/** The client's `Sec-WebSocket-Key` (handy for connection logging). */
|
|
2440
|
-
readonly key: string;
|
|
2441
|
-
/** The negotiated subprotocol, if any. */
|
|
2442
|
-
readonly protocol: string | null;
|
|
2443
|
-
}
|
|
2444
|
-
/**
|
|
2445
|
-
* Build a WebSocket 101 upgrade handshake response for a request.
|
|
2446
|
-
*
|
|
2447
|
-
* Validates `Sec-WebSocket-Key` presence and computes the RFC 6455 accept key
|
|
2448
|
-
* via the native `rust.wsAcceptKey`, optionally negotiates a subprotocol, and
|
|
2449
|
-
* returns a ready-to-serve `Response`. Returns `null` when the request is not a
|
|
2450
|
-
* valid upgrade (missing/empty key).
|
|
2451
|
-
*
|
|
2452
|
-
* This only produces the handshake — pair it with your framework's own
|
|
2453
|
-
* WebSocket server (Bun.serve's `websocket` handler, or a WS library), which
|
|
2454
|
-
* owns the subsequent frames:
|
|
2455
|
-
*
|
|
2456
|
-
* @example
|
|
2457
|
-
* ```ts
|
|
2458
|
-
* const srv = Bun.serve({
|
|
2459
|
-
* port: 3000,
|
|
2460
|
-
* fetch(req, server) {
|
|
2461
|
-
* const up = createWebSocketUpgrade(req, { protocols: ["chat"] });
|
|
2462
|
-
* if (up && server.upgrade(req, { data: { protocol: up.protocol } })) {
|
|
2463
|
-
* return up.response;
|
|
2464
|
-
* }
|
|
2465
|
-
* return new Response("Not a websocket", { status: 400 });
|
|
2466
|
-
* },
|
|
2467
|
-
* websocket: {
|
|
2468
|
-
* message(ws, msg) { ws.send(msg); },
|
|
2469
|
-
* },
|
|
2470
|
-
* });
|
|
2471
|
-
* ```
|
|
2472
|
-
*/
|
|
2473
|
-
export declare function createWebSocketUpgrade(req: Request, opts?: WebSocketUpgradeOptions): WebSocketUpgradeResult | null;
|
|
2474
|
-
export interface SseEvent {
|
|
2310
|
+
/** One SSE event to frame via `sseResponse` (event/id/retry/data). */ export interface SseEvent {
|
|
2475
2311
|
/** Optional `event:` field. */
|
|
2476
2312
|
event?: string | null;
|
|
2477
2313
|
/** Optional `id:` field. */
|
|
@@ -2802,7 +2638,7 @@ export declare const LOADER_OPS: {
|
|
|
2802
2638
|
};
|
|
2803
2639
|
readonly hexEncode: {
|
|
2804
2640
|
readonly kind: "bytes";
|
|
2805
|
-
readonly scalar: (i: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2641
|
+
readonly scalar: (i: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2806
2642
|
readonly batch: (items: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2807
2643
|
readonly element: typeof byteElem;
|
|
2808
2644
|
};
|
|
@@ -2814,7 +2650,7 @@ export declare const LOADER_OPS: {
|
|
|
2814
2650
|
};
|
|
2815
2651
|
readonly base64Encode: {
|
|
2816
2652
|
readonly kind: "bytes";
|
|
2817
|
-
readonly scalar: (i: Uint8Array, urlSafe?: boolean, padding?: boolean) => Uint8Array<ArrayBufferLike>;
|
|
2653
|
+
readonly scalar: (i: Uint8Array, urlSafe?: boolean, padding?: boolean) => string | Uint8Array<ArrayBufferLike>;
|
|
2818
2654
|
readonly batch: (items: Uint8Array[], urlSafe?: boolean, padding?: boolean) => Uint8Array<ArrayBufferLike>[];
|
|
2819
2655
|
readonly element: typeof byteElem;
|
|
2820
2656
|
};
|
|
@@ -2826,7 +2662,7 @@ export declare const LOADER_OPS: {
|
|
|
2826
2662
|
};
|
|
2827
2663
|
readonly base64UrlEncode: {
|
|
2828
2664
|
readonly kind: "bytes";
|
|
2829
|
-
readonly scalar: (i: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2665
|
+
readonly scalar: (i: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2830
2666
|
readonly batch: (items: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2831
2667
|
readonly element: typeof byteElem;
|
|
2832
2668
|
};
|
|
@@ -2862,13 +2698,13 @@ export declare const LOADER_OPS: {
|
|
|
2862
2698
|
};
|
|
2863
2699
|
readonly etag: {
|
|
2864
2700
|
readonly kind: "bytes";
|
|
2865
|
-
readonly scalar: (i: Uint8Array, weak?: boolean | null) => Uint8Array<ArrayBufferLike>;
|
|
2701
|
+
readonly scalar: (i: Uint8Array, weak?: boolean | null) => string | Uint8Array<ArrayBufferLike>;
|
|
2866
2702
|
readonly batch: (items: Uint8Array[], weak?: boolean | null) => Uint8Array<ArrayBufferLike>[];
|
|
2867
2703
|
readonly element: typeof byteElem;
|
|
2868
2704
|
};
|
|
2869
2705
|
readonly urlEncode: {
|
|
2870
2706
|
readonly kind: "bytes";
|
|
2871
|
-
readonly scalar: (i: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2707
|
+
readonly scalar: (i: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2872
2708
|
readonly batch: (items: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2873
2709
|
readonly element: typeof byteElem;
|
|
2874
2710
|
};
|
|
@@ -2886,19 +2722,19 @@ export declare const LOADER_OPS: {
|
|
|
2886
2722
|
};
|
|
2887
2723
|
readonly wsAcceptKey: {
|
|
2888
2724
|
readonly kind: "bytes";
|
|
2889
|
-
readonly scalar: (i: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2725
|
+
readonly scalar: (i: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2890
2726
|
readonly batch: (items: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2891
2727
|
readonly element: typeof byteElem;
|
|
2892
2728
|
};
|
|
2893
2729
|
readonly mimeFromExtension: {
|
|
2894
2730
|
readonly kind: "bytes";
|
|
2895
|
-
readonly scalar: (i: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2731
|
+
readonly scalar: (i: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2896
2732
|
readonly batch: (items: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2897
2733
|
readonly element: typeof byteElem;
|
|
2898
2734
|
};
|
|
2899
2735
|
readonly signCookie: {
|
|
2900
2736
|
readonly kind: "bytes";
|
|
2901
|
-
readonly scalar: (i: Uint8Array, secret: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2737
|
+
readonly scalar: (i: Uint8Array, secret: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2902
2738
|
readonly batch: (items: Uint8Array[], secret: Uint8Array) => Uint8Array<ArrayBufferLike>[];
|
|
2903
2739
|
readonly element: typeof byteElem;
|
|
2904
2740
|
};
|
|
@@ -2916,7 +2752,7 @@ export declare const LOADER_OPS: {
|
|
|
2916
2752
|
};
|
|
2917
2753
|
readonly passwordHash: {
|
|
2918
2754
|
readonly kind: "bytes";
|
|
2919
|
-
readonly scalar: (i: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null) => Uint8Array<ArrayBufferLike>;
|
|
2755
|
+
readonly scalar: (i: Uint8Array, salt: Uint8Array, options?: PasswordHashOptions | null) => string | Uint8Array<ArrayBufferLike>;
|
|
2920
2756
|
readonly batch: (items: Uint8Array[], salt: Uint8Array, options?: PasswordHashOptions | null) => Uint8Array<ArrayBufferLike>[];
|
|
2921
2757
|
readonly element: typeof byteElem;
|
|
2922
2758
|
};
|
|
@@ -2958,7 +2794,7 @@ export declare const LOADER_OPS: {
|
|
|
2958
2794
|
readonly pairedRest: [
|
|
2959
2795
|
0
|
|
2960
2796
|
];
|
|
2961
|
-
readonly scalar: (i: Uint8Array, patch: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2797
|
+
readonly scalar: (i: Uint8Array, patch: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2962
2798
|
readonly batch: (items: Uint8Array[], patches: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2963
2799
|
readonly element: typeof byteElem;
|
|
2964
2800
|
};
|
|
@@ -2967,7 +2803,7 @@ export declare const LOADER_OPS: {
|
|
|
2967
2803
|
readonly pairedRest: [
|
|
2968
2804
|
0
|
|
2969
2805
|
];
|
|
2970
|
-
readonly scalar: (i: Uint8Array, base: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2806
|
+
readonly scalar: (i: Uint8Array, base: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2971
2807
|
readonly batch: (items: Uint8Array[], bases: Uint8Array[]) => Uint8Array<ArrayBufferLike>[];
|
|
2972
2808
|
readonly element: typeof byteElem;
|
|
2973
2809
|
};
|
|
@@ -2991,7 +2827,7 @@ export declare const LOADER_OPS: {
|
|
|
2991
2827
|
};
|
|
2992
2828
|
readonly hmacSha256: {
|
|
2993
2829
|
readonly kind: "bytes";
|
|
2994
|
-
readonly scalar: (i: Uint8Array, key: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
2830
|
+
readonly scalar: (i: Uint8Array, key: Uint8Array) => string | Uint8Array<ArrayBufferLike>;
|
|
2995
2831
|
readonly batch: (items: Uint8Array[], key: Uint8Array) => Uint8Array<ArrayBufferLike>[];
|
|
2996
2832
|
readonly element: typeof byteElem;
|
|
2997
2833
|
};
|