castrum 0.9.7 → 0.9.9
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/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/castrum.darwin-arm64.node +0 -0
- package/castrum.darwin-x64.node +0 -0
- package/castrum.linux-arm64-gnu.node +0 -0
- package/castrum.linux-arm64-musl.node +0 -0
- package/castrum.linux-x64-gnu.node +0 -0
- package/castrum.linux-x64-musl.node +0 -0
- package/castrum.linux-x64-v3-gnu.node +0 -0
- package/castrum.win32-x64-msvc.node +0 -0
- package/dist/index.d.ts +664 -8
- package/dist/index.js +2014 -1327
- package/docs/BENCHMARKS.md +13 -0
- package/docs/CASE_STUDY.md +1 -1
- package/docs/ENVIRONMENT.md +8 -1
- package/docs/FFI_BUN_GUIDE.md +1 -1
- package/docs/INGRESS.md +4 -4
- package/docs/REPO_MAP.md +7 -5
- package/docs/RND-CONCURRENCY.md +20 -4
- package/docs/superpowers/plans/2026-09-18-castrum-hardening-cleanup.md +617 -0
- package/docs/superpowers/plans/2026-09-18-elysia-learnings-adoption.md +634 -0
- package/index.ts +6 -0
- package/package.json +4 -2
- package/rust/crypto/aead.rs +44 -0
- package/rust/crypto/argon2.rs +32 -2
- package/rust/crypto/jwt/tests.rs +19 -0
- package/rust/crypto/jwt/token.rs +43 -28
- package/rust/crypto/session.rs +6 -0
- package/rust/ffi/crypto.rs +63 -32
- package/rust/ffi/hashing.rs +48 -33
- package/rust/ffi/http.rs +62 -36
- package/rust/ffi/metrics.rs +6 -2
- package/rust/ffi/payload.rs +47 -27
- package/rust/ffi/task.rs +55 -35
- package/rust/ffi/tests.rs +24 -0
- package/rust/ffi/util.rs +40 -0
- package/rust/http/headers.rs +13 -0
- package/rust/http/multipart.rs +139 -0
- package/rust/ingress/api.rs +21 -5
- package/rust/ingress/mod.rs +7 -0
- package/rust/ingress/native_route.rs +25 -5
- package/rust/ingress/schema_cache.rs +266 -0
- package/rust/task/completion.rs +7 -0
- package/rust/task/mod.rs +2 -1
- package/rust/task/runtime.rs +55 -7
- package/rust/task/tests.rs +128 -1
- package/src/ingress/abort.ts +48 -0
- package/src/ingress/body.ts +104 -8
- package/src/ingress/decode/baked-result.ts +13 -5
- package/src/ingress/decode/fast-result.ts +1 -1
- package/src/ingress/decode/result-base.ts +16 -2
- package/src/ingress/handlers.ts +104 -31
- package/src/ingress/headers/memoized-headers.ts +40 -0
- package/src/ingress/health.ts +22 -0
- package/src/ingress/index.ts +3 -1
- package/src/ingress/native-route.ts +25 -6
- package/src/ingress/options.ts +26 -2
- package/src/ingress/packing/gather-raw-headers.ts +24 -8
- package/src/ingress/response/baked-response.ts +118 -28
- package/src/ingress/router.ts +50 -3
- package/src/ingress/routes/common.ts +35 -11
- package/src/ingress/routes/echo.ts +60 -7
- package/src/ingress/routes/json-write.ts +32 -8
- package/src/ingress/routes/native.ts +24 -5
- package/src/ingress/routes/options.ts +6 -2
- package/src/ingress/routes/responder.ts +29 -9
- package/src/ingress/routes/safe-record.ts +57 -0
- package/src/ingress/server-error.ts +111 -0
- package/src/ingress/server-node.ts +135 -37
- package/src/ingress/server.ts +143 -36
- package/src/ingress/shared.ts +1 -1
- package/src/ingress/types.ts +15 -0
- package/src/integration/pipeline.ts +6 -0
- package/src/loader/ops.ts +1 -1
- package/src/native/ffi/build/codecs-selftest.ts +305 -0
- package/src/native/ffi/build/codecs.ts +0 -296
- package/src/native/ffi/build/instances-selftest.ts +264 -0
- package/src/native/ffi/build/instances.ts +10 -259
- package/src/native/ffi/build/parse-selftest.ts +238 -0
- package/src/native/ffi/build/parse.ts +11 -234
- package/src/native/ffi/build/task.ts +2 -1
- package/src/native/ffi/build.ts +5 -1
- package/src/native/ffi/selftest.ts +3 -3
- package/src/native/ffi/symbols.ts +460 -0
- package/src/native/ffi/types.ts +6 -1
- package/src/native/ffi.ts +2 -434
- package/src/native/ingress-binding.ts +39 -0
- package/src/native/types.ts +6 -0
- package/src/runtime/codec.ts +24 -0
- package/src/runtime/detect.ts +14 -0
- package/src/runtime/types.ts +11 -0
- package/src/rust-ffi/batch/build.ts +18 -8
- package/src/rust-ffi/batch/types.ts +15 -0
- package/src/rust-ffi/context.ts +46 -3
- package/src/rust-ffi/options.ts +19 -0
- package/src/rust-ffi/scalar/crypto.ts +8 -0
- package/src/rust-ffi/scalar/factories.ts +53 -20
- package/src/rust-ffi/scalar/http.ts +60 -10
- package/src/rust-ffi/scalar/interface.ts +18 -1
- package/src/rust-ffi/scalar/payload.ts +27 -1
- package/src/rust-ffi/text.ts +5 -4
- package/src/shared/buffer-pool.ts +54 -9
- package/src/shared/codec.ts +26 -0
- package/src/shared/memory.ts +68 -0
- package/src/shared/metrics.ts +86 -3
- package/src/shared/packed/parsers.ts +64 -7
- package/src/shared/runtime.ts +7 -0
- package/src/task/runtime.ts +81 -37
package/dist/index.d.ts
CHANGED
|
@@ -116,7 +116,11 @@ export interface NativeRoute {
|
|
|
116
116
|
runFrame(frame: Uint8Array): RouteWireResult;
|
|
117
117
|
/** Convenience: pack a `(query, cookie, body)` frame then {@link runFrame}. */
|
|
118
118
|
run(query: string, cookie: string, body: Uint8Array | null): RouteWireResult;
|
|
119
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Free the native handle. Idempotent: a second call is a no-op. After it,
|
|
121
|
+
* {@link runFrame}/{@link run} throw rather than pass a freed handle into the
|
|
122
|
+
* native stack.
|
|
123
|
+
*/
|
|
120
124
|
destroy(): void;
|
|
121
125
|
}
|
|
122
126
|
/**
|
|
@@ -182,6 +186,13 @@ declare abstract class IngressResultBase {
|
|
|
182
186
|
/** Request body bytes (may be an empty view). */
|
|
183
187
|
body: Uint8Array;
|
|
184
188
|
protected _buf: Uint8Array;
|
|
189
|
+
/**
|
|
190
|
+
* Valid byte length of `_buf` (the native `written` count). `_buf` may be the
|
|
191
|
+
* WHOLE pooled output buffer (`outputBufferSize` bytes), so bounds checks must
|
|
192
|
+
* use this, not `_buf.byteLength` — that lets `run()` skip a per-request
|
|
193
|
+
* `buffer.subarray(0, written)` view.
|
|
194
|
+
*/
|
|
195
|
+
protected _len: number;
|
|
185
196
|
protected _bodyJsonStart: number;
|
|
186
197
|
protected _bodyJsonLen: number;
|
|
187
198
|
private _cookiesBuf;
|
|
@@ -206,7 +217,7 @@ declare abstract class IngressResultBase {
|
|
|
206
217
|
* baked path passes the already-clamped `layout.safeBodyJsonLen`. Sections
|
|
207
218
|
* are stored as slices and decoded lazily by `cookiesJson()`/`queryJson()`.
|
|
208
219
|
*/
|
|
209
|
-
protected setSections(buf: Uint8Array, layout: PackedSectionLayout, bodyJsonLen: number): void;
|
|
220
|
+
protected setSections(buf: Uint8Array, layout: PackedSectionLayout, bodyJsonLen: number, len?: number): void;
|
|
210
221
|
/** Decoded cookies-JSON section, or `"{}"` when absent. */
|
|
211
222
|
cookiesJson(): string;
|
|
212
223
|
/** Decoded query-JSON section, or `"{}"` when absent. */
|
|
@@ -216,7 +227,7 @@ declare abstract class IngressResultBase {
|
|
|
216
227
|
}
|
|
217
228
|
/** Zero-alloc, reusable result decoder for the pre-baked handler path. */
|
|
218
229
|
export declare class BakedIngressResult extends IngressResultBase {
|
|
219
|
-
refresh(buf: Uint8Array, body: Uint8Array, view: DataView): void;
|
|
230
|
+
refresh(buf: Uint8Array, body: Uint8Array, view: DataView, len?: number): void;
|
|
220
231
|
invalidate(): void;
|
|
221
232
|
setInternalError(): void;
|
|
222
233
|
bodyJson(copy: boolean): Uint8Array;
|
|
@@ -427,6 +438,14 @@ export interface OptimizedIngressHandler {
|
|
|
427
438
|
string,
|
|
428
439
|
string
|
|
429
440
|
][];
|
|
441
|
+
/**
|
|
442
|
+
* Success-path headers as a ready `Headers` instance: the memoized base
|
|
443
|
+
* (template + reflected origin) plus per-request rate/request-id extras,
|
|
444
|
+
* without rebuilding `Headers` from an array on every response. Optional so
|
|
445
|
+
* hand-rolled mocks need not provide it; callers fall back to
|
|
446
|
+
* `memoizedHeaders(responseHeaders(...))`.
|
|
447
|
+
*/
|
|
448
|
+
successHeaders?(variant: number, requestIdHeader: string | null, origin: string | null, rateRemaining?: number, rateResetSecs?: number): Headers;
|
|
430
449
|
terminalHeaders(variant: number, ctx: BakedContext, result: BakedIngressResult | null): [
|
|
431
450
|
string,
|
|
432
451
|
string
|
|
@@ -534,8 +553,45 @@ export interface PathMatch {
|
|
|
534
553
|
* adapter (Bun's native router handles matching; the ingress pipeline does
|
|
535
554
|
* not echo params in its response). */
|
|
536
555
|
type RouteHandler = (req: Request, srv?: unknown, params?: Record<string, string>) => Response | Promise<Response>;
|
|
556
|
+
/**
|
|
557
|
+
* A prebuilt constant response for static route promotion.
|
|
558
|
+
*
|
|
559
|
+
* Either a bare `Response` or a factory that builds a FRESH `Response` per
|
|
560
|
+
* request. Behavior differs by runtime:
|
|
561
|
+
*
|
|
562
|
+
* - **Bun**: a bare `Response` is snapshotted into `Bun.serve`'s native route
|
|
563
|
+
* table at bind time (verified on Bun 1.4.2 — the body is NOT consumed by the
|
|
564
|
+
* first request) and served on every request without entering JS.
|
|
565
|
+
* - **Node** (`createIngressServerNode`): the adapter reads the bare `Response`
|
|
566
|
+
* body ONCE and materializes a fresh `Response` per request (a web body is
|
|
567
|
+
* one-shot).
|
|
568
|
+
*
|
|
569
|
+
* A factory is invoked by the runtime on every request, so it MUST return a new
|
|
570
|
+
* `Response` each call. Returning the SAME already-sent instance fails on the
|
|
571
|
+
* second request with `ERR_BODY_ALREADY_USED` (Bun) / a consumed stream
|
|
572
|
+
* (Node) — use a factory only when the response genuinely varies per request.
|
|
573
|
+
*/
|
|
574
|
+
export type StaticRoute = Response | (() => Response);
|
|
537
575
|
/** Route spec for the Bun.serve builder. */
|
|
538
576
|
export interface BakedRoute {
|
|
577
|
+
/**
|
|
578
|
+
* A prebuilt constant response served for GET, bypassing the ingress
|
|
579
|
+
* pipeline entirely. Takes precedence over `read`/`cookies` for GET (the
|
|
580
|
+
* pipeline handler is never wired); other methods (`write`/`echo`/`delete`)
|
|
581
|
+
* are unaffected. See {@link StaticRoute} for the one-shot-body contract.
|
|
582
|
+
*
|
|
583
|
+
* HEAD is intentionally NOT wired for `static` (a bare `Response` carries a
|
|
584
|
+
* body; static responses are probes/constants served over GET). This matches
|
|
585
|
+
* the raw-`read` probe behavior — but note that a route which previously
|
|
586
|
+
* served HEAD via a native `read` now 405s (Node) on HEAD once `static` is
|
|
587
|
+
* set. When `static` is combined with `write`/`echo`/`delete`, those dynamic
|
|
588
|
+
* methods remain wired.
|
|
589
|
+
*
|
|
590
|
+
* Runtime note: `createIngressRouter` treats `static` differently — there,
|
|
591
|
+
* like `raw`, it OWNS THE WHOLE SPEC (the router has no compiled ingress for
|
|
592
|
+
* the other methods; see `RouterRouteSpec.static` in src/ingress/router.ts).
|
|
593
|
+
*/
|
|
594
|
+
static?: StaticRoute;
|
|
539
595
|
/**
|
|
540
596
|
* Wires GET + HEAD read handlers. Accepts an optimized ingress handler OR a
|
|
541
597
|
* raw `RouteHandler` function (health/metrics probes are raw — see
|
|
@@ -612,14 +668,20 @@ export interface CreateIngressServerOptions {
|
|
|
612
668
|
routes: Record<string, BakedRoute>;
|
|
613
669
|
fallback?: OptimizedIngressHandler;
|
|
614
670
|
/**
|
|
615
|
-
* Invoked when an unhandled error escapes a request handler (
|
|
616
|
-
*
|
|
617
|
-
*
|
|
671
|
+
* Invoked when an unhandled error escapes a request handler (raw `read`
|
|
672
|
+
* functions, `responder`/`native` responders, or the Bun/Node adapter
|
|
673
|
+
* catch-all). The error is masked to a generic 500 on the wire. Never
|
|
674
|
+
* throws — hook failures are swallowed.
|
|
618
675
|
*/
|
|
619
676
|
onError?: (info: {
|
|
620
677
|
error: Error;
|
|
621
678
|
request?: Request;
|
|
622
679
|
}) => void;
|
|
680
|
+
/**
|
|
681
|
+
* Optional structured-log sink for escaped handler errors (one JSON line per
|
|
682
|
+
* masked 500, message only). Never throws.
|
|
683
|
+
*/
|
|
684
|
+
logger?: (line: string) => void;
|
|
623
685
|
/** Node adapter only: ms to receive the complete request (headers + body).
|
|
624
686
|
* Guards slowloris/trickling requests. Default: 30_000. */
|
|
625
687
|
requestTimeoutMs?: number;
|
|
@@ -949,6 +1011,453 @@ export interface Argon2HasherInstance {
|
|
|
949
1011
|
hash(password: Uint8Array, salt: Uint8Array): Uint8Array;
|
|
950
1012
|
verify(password: Uint8Array, phc: Uint8Array): boolean;
|
|
951
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* The bound bun:ffi C-ABI surface. Each member is a dlopen'd `castrum_*`
|
|
1016
|
+
* symbol wrapped so inputs are `(ptr,len)` byte slices and text outputs are
|
|
1017
|
+
* returned as JS strings via JSC's native transfer (cstring clone) — zero
|
|
1018
|
+
* `TextEncoder`/`TextDecoder` on the Bun path. `*Into` variants write into a
|
|
1019
|
+
* caller buffer and return the byte count; variable-size ops return the exact
|
|
1020
|
+
* required size on a too-small buffer so the transport grows once and retries
|
|
1021
|
+
* exactly once.
|
|
1022
|
+
*/
|
|
1023
|
+
export interface BunFFI {
|
|
1024
|
+
crc32(input: Uint8Array): number;
|
|
1025
|
+
fnv1a64(input: Uint8Array): bigint;
|
|
1026
|
+
xxh3(input: Uint8Array): bigint;
|
|
1027
|
+
jsonValid(input: Uint8Array): boolean;
|
|
1028
|
+
/** UTF-8 validity probe (native replacement for a fatal `TextDecoder`). */
|
|
1029
|
+
utf8Valid(input: Uint8Array): boolean;
|
|
1030
|
+
/** Lowercase-hex encode → string (native transfer, zero encode). */
|
|
1031
|
+
hexEncode(input: Uint8Array): string;
|
|
1032
|
+
/** Lowercase-hex encode into `output`; returns bytes written. */
|
|
1033
|
+
hexEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1034
|
+
/** RFC 3986 percent-encode → string. */
|
|
1035
|
+
urlEncode(input: Uint8Array): string;
|
|
1036
|
+
/** RFC 3986 percent-encode into `output`; returns bytes written. */
|
|
1037
|
+
urlEncodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1038
|
+
validateEmail(input: string): boolean;
|
|
1039
|
+
validateUuid(input: string): boolean;
|
|
1040
|
+
validateIpv4(input: string): boolean;
|
|
1041
|
+
validateIpv6(input: string): boolean;
|
|
1042
|
+
/** Byte-input sibling of {@link validateEmail} (`(ptr,len)` — no decode). */
|
|
1043
|
+
validateEmailBytes(input: Uint8Array): boolean;
|
|
1044
|
+
/** Byte-input sibling of {@link validateUuid} (`(ptr,len)` — no decode). */
|
|
1045
|
+
validateUuidBytes(input: Uint8Array): boolean;
|
|
1046
|
+
/** Byte-input sibling of {@link validateIpv4} (`(ptr,len)` — no decode). */
|
|
1047
|
+
validateIpv4Bytes(input: Uint8Array): boolean;
|
|
1048
|
+
/** Byte-input sibling of {@link validateIpv6} (`(ptr,len)` — no decode). */
|
|
1049
|
+
validateIpv6Bytes(input: Uint8Array): boolean;
|
|
1050
|
+
/** Sum of `id` fields across a JSON array → bigint (throws on non-array input). */
|
|
1051
|
+
jsonSumIds(input: Uint8Array): bigint;
|
|
1052
|
+
hmacSha256Verify(key: Uint8Array, data: Uint8Array, signature: Uint8Array): boolean;
|
|
1053
|
+
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean;
|
|
1054
|
+
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean;
|
|
1055
|
+
/** `phc` is a `cstring` ARG (the engine transcodes the JS PHC string in-engine). */
|
|
1056
|
+
passwordVerifyBcrypt(password: Uint8Array, phc: string): boolean;
|
|
1057
|
+
/** Hex-decode into a fresh buffer (size `input.length / 2`). */
|
|
1058
|
+
hexDecode(input: Uint8Array): Uint8Array;
|
|
1059
|
+
/** Hex-decode into `output`; returns bytes written (throws if too small). */
|
|
1060
|
+
hexDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1061
|
+
/** Percent-decode into a fresh buffer (size `input.length`). */
|
|
1062
|
+
urlDecode(input: Uint8Array): Uint8Array;
|
|
1063
|
+
/** Percent-decode into `output`; returns bytes written (throws if too small). */
|
|
1064
|
+
urlDecodeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1065
|
+
/** base64-decode into a fresh buffer (size `ceil(len/4)*3`). */
|
|
1066
|
+
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array;
|
|
1067
|
+
/** base64-decode into `output`; returns bytes written (throws if too small). */
|
|
1068
|
+
base64DecodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
1069
|
+
/** RFC 6455 Sec-WebSocket-Accept → string (cstring return; `key` is a `cstring` ARG). */
|
|
1070
|
+
wsAcceptKey(key: string): string;
|
|
1071
|
+
/** RFC 6455 Sec-WebSocket-Accept into `output`; returns bytes written. */
|
|
1072
|
+
wsAcceptKeyInto(key: Uint8Array, output: Uint8Array): number;
|
|
1073
|
+
/** crc32 ETag → string (10 strong / 12 weak chars, cstring return). */
|
|
1074
|
+
etag(data: Uint8Array, weak?: boolean): string;
|
|
1075
|
+
/** crc32 ETag into `output`; returns bytes written. */
|
|
1076
|
+
etagInto(data: Uint8Array, output: Uint8Array, weak?: boolean): number;
|
|
1077
|
+
/** Evaluate a precompiled `ConditionalRequest` (opaque `inner` handle from its
|
|
1078
|
+
* napi `inner_ptr()`) — true → 304. `null` header args = absent (flags encode
|
|
1079
|
+
* presence so a present-but-empty header is distinct). A null (0) handle → false. */
|
|
1080
|
+
conditionalIsNotModified(inner: number, ifNoneMatch: Uint8Array | null, ifModifiedSince: Uint8Array | null): boolean;
|
|
1081
|
+
/** Precompiled `MediaTypeMatcher` wildcard match (opaque `inner` handle). */
|
|
1082
|
+
mediaTypeMatcherMatches(inner: number, actual: Uint8Array): boolean;
|
|
1083
|
+
/** Precompiled `AcceptNegotiator` → best supported encoding (opaque `inner`
|
|
1084
|
+
* handle); `null` = identity. */
|
|
1085
|
+
acceptNegotiatorNegotiate(inner: number, header: Uint8Array): string | null;
|
|
1086
|
+
/** Precompiled `AcceptNegotiator` → best supported encoding with SERVER-
|
|
1087
|
+
* preference tie-breaking (RFC 7231 server semantics, opaque `inner` handle);
|
|
1088
|
+
* `null` = identity. */
|
|
1089
|
+
acceptNegotiatorNegotiateServer(inner: number, header: Uint8Array): string | null;
|
|
1090
|
+
/** Precompiled `JwtSigner` → compact token from pre-serialized claim JSON
|
|
1091
|
+
* (opaque `inner` handle); throws on invalid claims / null handle. */
|
|
1092
|
+
jwtSignerSign(inner: number, claimsJson: Uint8Array, nowSeconds: number): Uint8Array;
|
|
1093
|
+
/** Precompiled `JwtSigner` → claims JSON bytes (opaque `inner` handle);
|
|
1094
|
+
* `null` = invalid signature / expired / malformed. */
|
|
1095
|
+
jwtSignerVerify(inner: number, token: Uint8Array, nowSeconds: number): Uint8Array | null;
|
|
1096
|
+
/** Ed25519 keypair generation → `{ privateKey, publicKey }` (PKCS#8 v1 DER
|
|
1097
|
+
* private + SPKI DER public bytes); throws on CSPRNG failure. */
|
|
1098
|
+
ed25519GenerateKeypair(): {
|
|
1099
|
+
privateKey: Uint8Array;
|
|
1100
|
+
publicKey: Uint8Array;
|
|
1101
|
+
};
|
|
1102
|
+
/** Ed25519 sign → 64-byte signature; throws on invalid private key. */
|
|
1103
|
+
ed25519Sign(msg: Uint8Array, privateKey: Uint8Array): Uint8Array;
|
|
1104
|
+
/** Ed25519 verify → whether `signature` is valid for `msg` under `publicKey`. */
|
|
1105
|
+
ed25519Verify(msg: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean;
|
|
1106
|
+
/** EdDSA (Ed25519) JWT sign → compact token; `null` = invalid claims/private
|
|
1107
|
+
* key. `ttl <= 0` = no `iat`/`exp` injection. */
|
|
1108
|
+
jwtEdDSASign(claimsJson: Uint8Array, privateKey: Uint8Array, ttl: number, nowSeconds: number): string | null;
|
|
1109
|
+
/** EdDSA (Ed25519) JWT verify → claims JSON string; `null` = invalid. */
|
|
1110
|
+
jwtEdDSAVerify(token: Uint8Array, publicKey: Uint8Array, nowSeconds: number): string | null;
|
|
1111
|
+
/** Compiled `TemplateRenderer` → UTF-8 bytes from a pre-serialized JSON
|
|
1112
|
+
* context (opaque `inner` handle); throws on invalid context / render error. */
|
|
1113
|
+
templateRender(inner: number, contextJson: Uint8Array): Uint8Array;
|
|
1114
|
+
/** Compiled `SchemaValidator` → whether a document is valid (opaque `inner` handle). */
|
|
1115
|
+
schemaValidatorValidate(inner: number, doc: Uint8Array): boolean;
|
|
1116
|
+
/** `RateLimiter` string-key check → { allowed, remaining, resetMs } (opaque
|
|
1117
|
+
* `inner` handle; `key` is a `cstring` ARG); throws on a null handle. */
|
|
1118
|
+
rateLimiterCheck(inner: number, key: string, nowMs: number): RateLimiterVerdict;
|
|
1119
|
+
/** `RateLimiter` pre-hashed key check → { allowed, remaining, resetMs }. */
|
|
1120
|
+
rateLimiterCheckKey(inner: number, key: number, nowMs: number): RateLimiterVerdict;
|
|
1121
|
+
/** `byteLen` random bytes → `byteLen * 2` hex-char string (cstring return). */
|
|
1122
|
+
randomToken(byteLen: number): string;
|
|
1123
|
+
/** base64-encode → string. */
|
|
1124
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): string;
|
|
1125
|
+
/** base64-encode into `output`; returns bytes written. */
|
|
1126
|
+
base64EncodeInto(input: Uint8Array, output: Uint8Array, urlSafe?: boolean, padding?: boolean): number;
|
|
1127
|
+
/** HMAC-SHA256 → 64-char lowercase-hex string. */
|
|
1128
|
+
hmacSha256(key: Uint8Array, data: Uint8Array): string;
|
|
1129
|
+
/** HMAC-SHA256 hex into `output`; returns bytes written (throws if too small). */
|
|
1130
|
+
hmacSha256Into(key: Uint8Array, data: Uint8Array, output: Uint8Array): number;
|
|
1131
|
+
/** Sign cookie `value` as `value.<64-hex>` → string (cstring return). */
|
|
1132
|
+
signCookie(value: Uint8Array, secret: Uint8Array): string;
|
|
1133
|
+
/** Sign cookie into `output`; returns bytes written (throws if too small). */
|
|
1134
|
+
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number;
|
|
1135
|
+
/** Verify a signed cookie → value string, or `null` on bad signature. */
|
|
1136
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): string | null;
|
|
1137
|
+
/** Verify a signed cookie into `output`; returns value length or `null` on bad signature. */
|
|
1138
|
+
verifyCookieInto(signed: Uint8Array, secret: Uint8Array, output: Uint8Array): number | null;
|
|
1139
|
+
/** CSRF token (129 chars: 64 rnd-hex + '.' + 64 sig-hex) → string. */
|
|
1140
|
+
csrfToken(secret: Uint8Array): string;
|
|
1141
|
+
/** CSRF token into `output`; returns bytes written (throws if too small). */
|
|
1142
|
+
csrfTokenInto(secret: Uint8Array, output: Uint8Array): number;
|
|
1143
|
+
/** Argon2id PHC hash → string (m/t/p/out_len params). */
|
|
1144
|
+
passwordHash(password: Uint8Array, salt: Uint8Array, mCost: number, tCost: number, pCost: number, outLen: number): string;
|
|
1145
|
+
/** bcrypt `$2b$` PHC → string (cost clamped 4..=31). */
|
|
1146
|
+
passwordHashBcrypt(password: Uint8Array, cost: number): string;
|
|
1147
|
+
/** PBKDF2-HMAC-SHA256 → `dkLen` bytes (rounds, dkLen clamped). */
|
|
1148
|
+
pbkdf2Sha256(password: Uint8Array, salt: Uint8Array, rounds: number, dkLen: number): Uint8Array;
|
|
1149
|
+
/** AEAD encrypt (0 = AES-256-GCM, 1 = ChaCha20-Poly1305) → ct+tag. */
|
|
1150
|
+
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, algorithm?: number): Uint8Array;
|
|
1151
|
+
/** AEAD encrypt into `output` (ct + 16-byte tag); returns bytes written. */
|
|
1152
|
+
aeadEncryptInto(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, output: Uint8Array, algorithm?: number): number;
|
|
1153
|
+
/** AEAD decrypt → plaintext, or `null` on auth failure. */
|
|
1154
|
+
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, algorithm?: number): Uint8Array | null;
|
|
1155
|
+
/** RFC 6455 frame encode into a fresh buffer. */
|
|
1156
|
+
wsFrameEncode(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean): Uint8Array;
|
|
1157
|
+
/** RFC 6455 frame encode into `output`; returns bytes written (throws if too small). */
|
|
1158
|
+
wsFrameEncodeInto(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean, output: Uint8Array): number;
|
|
1159
|
+
/** RFC 6902 JSON patch → JSON text string. */
|
|
1160
|
+
jsonPatch(doc: Uint8Array, patch: Uint8Array): string;
|
|
1161
|
+
/** gzip-compress into a fresh buffer (level clamped 0..=9, default 6). */
|
|
1162
|
+
gzipCompress(data: Uint8Array, level?: number): Uint8Array;
|
|
1163
|
+
/** gzip-compress into `output`; returns bytes written (throws if too small). */
|
|
1164
|
+
gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number): number;
|
|
1165
|
+
/** gzip-decompress into a fresh buffer (capped by `maxDecompressed`). */
|
|
1166
|
+
gzipDecompress(data: Uint8Array, maxDecompressed?: number): Uint8Array;
|
|
1167
|
+
/** gzip-decompress into `output`; returns bytes written (throws if too small). */
|
|
1168
|
+
gzipDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number): number;
|
|
1169
|
+
/**
|
|
1170
|
+
* gzip ISIZE trailer probe → the uncompressed size for a single-member gzip
|
|
1171
|
+
* stream, or `0` when unknown (invalid stream / multi-member). Used to
|
|
1172
|
+
* PRE-SIZE a destination buffer for the zero-copy task path.
|
|
1173
|
+
*/
|
|
1174
|
+
gzipIsize(data: Uint8Array): number;
|
|
1175
|
+
/** brotli-compress into a fresh buffer (quality clamped 0..=11, default 5). */
|
|
1176
|
+
brotliCompress(data: Uint8Array, quality?: number): Uint8Array;
|
|
1177
|
+
/** brotli-compress into `output`; returns bytes written (throws if too small). */
|
|
1178
|
+
brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number): number;
|
|
1179
|
+
/** brotli-decompress into a fresh buffer (capped by `maxDecompressed`). */
|
|
1180
|
+
brotliDecompress(data: Uint8Array, maxDecompressed?: number): Uint8Array;
|
|
1181
|
+
/** brotli-decompress into `output`; returns bytes written (throws if too small). */
|
|
1182
|
+
brotliDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number): number;
|
|
1183
|
+
/** HTTP request parse → packed output into `output`; returns bytes written. */
|
|
1184
|
+
httpParseRequestPackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1185
|
+
/** Query string parse → packed output into `output`; returns bytes written. */
|
|
1186
|
+
queryParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1187
|
+
/** Cookie header parse → packed output into `output`; returns bytes written. */
|
|
1188
|
+
cookieParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1189
|
+
/** Sign JWT (HS256) from pre-serialized claim JSON (ttl<=0 → no iat/exp) → string. */
|
|
1190
|
+
jwtSignBytes(claimsJson: Uint8Array, secret: Uint8Array, ttl: number, now: number): string;
|
|
1191
|
+
/** Sign JWT into `output`; returns bytes written (throws if too small). */
|
|
1192
|
+
jwtSignBytesInto(claimsJson: Uint8Array, secret: Uint8Array, ttl: number, now: number, output: Uint8Array): number;
|
|
1193
|
+
/** Decode a WS frame into packed `[flags][opcode][u32 len][payload]`; null on malformed. */
|
|
1194
|
+
wsFrameDecodePacked(data: Uint8Array): Uint8Array | null;
|
|
1195
|
+
/**
|
|
1196
|
+
* Decode a WS frame into `output` (sized ≥ `data.length + 6`); returns bytes
|
|
1197
|
+
* written, null on malformed. Pooled sibling of `wsFrameDecodePacked`.
|
|
1198
|
+
*/
|
|
1199
|
+
wsFrameDecodePackedInto(data: Uint8Array, output: Uint8Array): number | null;
|
|
1200
|
+
/** Parse multipart/form-data into the packed parts layout. */
|
|
1201
|
+
multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array;
|
|
1202
|
+
/** Parse multipart/form-data into `output`; returns bytes written (throws if too small). */
|
|
1203
|
+
multipartParsePackedInto(body: Uint8Array, boundary: Uint8Array, output: Uint8Array): number;
|
|
1204
|
+
/** Parse x-www-form-urlencoded into packed pairs into `output`. */
|
|
1205
|
+
formParsePackedInto(input: Uint8Array, output: Uint8Array): number;
|
|
1206
|
+
/**
|
|
1207
|
+
* Verify an HS256 JWT → claims JSON text (cstring return; `null` on invalid
|
|
1208
|
+
* signature / expired / malformed). `nowSeconds` is an i64 C arg.
|
|
1209
|
+
*/
|
|
1210
|
+
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds: number): string | null;
|
|
1211
|
+
/**
|
|
1212
|
+
* Parse JSON → packed token stream bytes (structural, no re-parse). Layout:
|
|
1213
|
+
* `[u32 strCount]{[u32 len][utf8]}... [u32 treeLen][tree]` — a deduplicated
|
|
1214
|
+
* string table followed by a start/end-marker value tree:
|
|
1215
|
+
* `0=null 1=false 2=true 3=number(f64 LE) 4=string(u32 table idx)
|
|
1216
|
+
* 5=array start 6=object start 7=array end 8=object end
|
|
1217
|
+
* 9=object key(u32 table idx)` (object body `6,(9,keyIdx,value)*,8`).
|
|
1218
|
+
* Throws on invalid JSON / too-small output. The scalar wrapper decodes the
|
|
1219
|
+
* table once and assembles the value — no second parse.
|
|
1220
|
+
*/
|
|
1221
|
+
jsonParsePacked(input: Uint8Array): Uint8Array;
|
|
1222
|
+
/**
|
|
1223
|
+
* Parse a Content-Type header → packed verdict bytes:
|
|
1224
|
+
* `[u32 mediaTypeLen][mediaType][u32 charsetLen (0xFFFFFFFF = none)][charset]
|
|
1225
|
+
* [u32 boundaryLen][boundary][u32 paramCount]{[u32 keyLen][key][u32 valLen][val]}`.
|
|
1226
|
+
* Throws on invalid media type / too-small output.
|
|
1227
|
+
*/
|
|
1228
|
+
parseMediaType(input: Uint8Array): Uint8Array;
|
|
1229
|
+
/** Parse an IMF-fixdate → unix seconds, or `null` on malformed (packed
|
|
1230
|
+
* `[u8 ok][i64 secs LE]` output, decoded here). */
|
|
1231
|
+
parseHttpDate(input: Uint8Array): bigint | null;
|
|
1232
|
+
/**
|
|
1233
|
+
* Parse an Accept-Encoding header → packed verdict bytes:
|
|
1234
|
+
* `[u32 count]{[u32 encLen][enc][f32 q][u32 order]}` (empty header → count 0).
|
|
1235
|
+
* Throws on too-small output.
|
|
1236
|
+
*/
|
|
1237
|
+
parseAcceptEncoding(input: Uint8Array): Uint8Array;
|
|
1238
|
+
/** Percent-encode a query from packed pairs (the `packPairs` layout) → string,
|
|
1239
|
+
* keys SORTED (matches napi `BTreeMap` ordering); `null` on malformed input. */
|
|
1240
|
+
urlEncodeQuery(input: Uint8Array): string | null;
|
|
1241
|
+
/** RFC 3986 URL resolution (base + reference) → string; `null` on non-UTF-8. */
|
|
1242
|
+
urlResolve(base: Uint8Array, reference: Uint8Array): string | null;
|
|
1243
|
+
/** Resolve a reference against a `UrlBuilder`'s PRECOMPILED base (opaque
|
|
1244
|
+
* `inner` handle from its napi `inner_ptr()`) → bytes; throws on a null
|
|
1245
|
+
* handle / non-UTF-8 reference / too-small output. */
|
|
1246
|
+
urlBuilderResolve(inner: number, reference: Uint8Array): Uint8Array;
|
|
1247
|
+
/** Extension → MIME type → string (unknown → `application/octet-stream`; `ext` is a `cstring` ARG). */
|
|
1248
|
+
mimeFromExtension(ext: string): string | null;
|
|
1249
|
+
/**
|
|
1250
|
+
* 29-byte HTTP-date (`Sun, 06 Nov 1994 08:49:37 GMT`) into `output`; returns
|
|
1251
|
+
* bytes written (throws on too-small / out-of-range year). FFI sibling of the
|
|
1252
|
+
* napi `httpDateInto` — kills the napi crossing on the hot path.
|
|
1253
|
+
*/
|
|
1254
|
+
httpDateInto(secs: number, output: Uint8Array): number;
|
|
1255
|
+
/** 29-byte HTTP-date → string (allocating sibling of httpDateInto). */
|
|
1256
|
+
httpDate(secs?: number): string;
|
|
1257
|
+
/**
|
|
1258
|
+
* Encode one SSE event → bytes (fresh buffer). event/id null = line omitted
|
|
1259
|
+
* (a present-but-empty string emits the line, matching napi `Option`).
|
|
1260
|
+
* NOTE: event/id stay `(ptr,len)` byte args — a cstring-ARG conversion was
|
|
1261
|
+
* attempted and reverted (2026-08-23); see docs/FFI_BUN_GUIDE.md §14 before
|
|
1262
|
+
* touching this signature.
|
|
1263
|
+
* FFI sibling of the napi `sse_encode_event`.
|
|
1264
|
+
*/
|
|
1265
|
+
sseEncodeEvent(event: string | null, data: Uint8Array, id: string | null, retry: number | null): Uint8Array;
|
|
1266
|
+
/**
|
|
1267
|
+
* Encode one SSE event into `output` (sized ≥ `data.length + 64`); returns
|
|
1268
|
+
* bytes written (throws if too small / invalid UTF-8). Pooled sibling of
|
|
1269
|
+
* `sseEncodeEvent` — removes the per-call output-buffer alloc.
|
|
1270
|
+
*/
|
|
1271
|
+
sseEncodeEventInto(event: string | null, data: Uint8Array, id: string | null, retry: number | null, output: Uint8Array): number;
|
|
1272
|
+
/** Random hex token into `output`; returns bytes written (throws if too small). */
|
|
1273
|
+
randomTokenInto(byteLen: number, output: Uint8Array): number;
|
|
1274
|
+
/**
|
|
1275
|
+
* Run the ingress pipeline on a packed frame via the opaque inner handle from
|
|
1276
|
+
* `Ingress.ingressInnerPtr()` (valid only while the instance is alive — the
|
|
1277
|
+
* caller must hold it). Returns bytes written; throws on error / too-small.
|
|
1278
|
+
*/
|
|
1279
|
+
ingressHandlePacked(inner: number, input: Uint8Array, body: Uint8Array | null, output: Uint8Array): number;
|
|
1280
|
+
/**
|
|
1281
|
+
* Run the ingress pipeline from raw request components via the opaque inner
|
|
1282
|
+
* handle (valid only while the instance is alive). `url`/`ip` are JS strings
|
|
1283
|
+
* passed as `bun:ffi` `cstring` ARGs — the engine transcodes them to
|
|
1284
|
+
* call-scoped NUL-terminated UTF-8 buffers in-engine, so the JS side skips
|
|
1285
|
+
* the frame assembly + `Buffer.write` encode for the URL/IP. `rid`/`headers`
|
|
1286
|
+
* are the packed byte slices; `body` is the request body (or null); `output`
|
|
1287
|
+
* receives the packed decision. Same wire format as {@link ingressHandlePacked}.
|
|
1288
|
+
* Returns bytes written; throws on error / too-small.
|
|
1289
|
+
*/
|
|
1290
|
+
ingressHandleComponents(inner: number, methodKind: number, url: string, ip: string, rid: Uint8Array, headers: Uint8Array, body: Uint8Array | null, output: Uint8Array): number;
|
|
1291
|
+
/**
|
|
1292
|
+
* Write the ingress binary-layout constants (38 × u32 LE — `rust/ffi.rs`
|
|
1293
|
+
* `IngressLayout`, numeric source `rust/ingress/output.rs`) into `output`;
|
|
1294
|
+
* returns bytes written. Lets `src/ingress/constants.ts` read the layout via
|
|
1295
|
+
* bun:ffi on Bun so importing the package does NOT dlopen the napi addon.
|
|
1296
|
+
*/
|
|
1297
|
+
ingressLayout(out: Uint8Array): number;
|
|
1298
|
+
/**
|
|
1299
|
+
* Compile a route descriptor (the `@ignex/native` route-wire v3 format:
|
|
1300
|
+
* magic `ROUT`, version 3, limits, stage tags, draft-07 body schema) into an
|
|
1301
|
+
* opaque handle. Returns the handle (`0` = invalid descriptor / compile
|
|
1302
|
+
* failure / panic — throws). The handle is owned by the caller and must be
|
|
1303
|
+
* released with {@link routeDestroy}.
|
|
1304
|
+
*/
|
|
1305
|
+
routeCompile(descriptor: Uint8Array): number;
|
|
1306
|
+
/**
|
|
1307
|
+
* Run a compiled route on one request frame, writing the packed verdict
|
|
1308
|
+
* (`[flags u32][errorCode u32]` + optional query/cookie pair sections) into
|
|
1309
|
+
* `output`. Returns bytes written (`0` = real error — throws; `> output.length`
|
|
1310
|
+
* = the EXACT required size — the caller allocates once and retries, per the
|
|
1311
|
+
* growExact convention).
|
|
1312
|
+
*/
|
|
1313
|
+
routeRun(handle: number, frame: Uint8Array, output: Uint8Array): number;
|
|
1314
|
+
/** Destroy a compiled route handle (frees the native `Box<NativeRoute>`). */
|
|
1315
|
+
routeDestroy(handle: number): void;
|
|
1316
|
+
/** Create an empty metrics registry → a caller-owned opaque handle. */
|
|
1317
|
+
metricsCreate(): number;
|
|
1318
|
+
/**
|
|
1319
|
+
* Declare a counter family → its `u32` series id (`0xFFFFFFFF` = error).
|
|
1320
|
+
* `name` and `labelKeys` are `cstring` ARGs; label keys are
|
|
1321
|
+
* `\u001f`-separated (empty string = no labels).
|
|
1322
|
+
*/
|
|
1323
|
+
metricsCounter(handle: number, name: string, labelKeys: string): number;
|
|
1324
|
+
/** Declare a gauge family → its series id. Same ABI as {@link metricsCounter}. */
|
|
1325
|
+
metricsGauge(handle: number, name: string, labelKeys: string): number;
|
|
1326
|
+
/**
|
|
1327
|
+
* Declare a histogram family → its series id. `bucketsCsv` is a
|
|
1328
|
+
* comma-separated finite positive list (empty string = default buckets).
|
|
1329
|
+
*/
|
|
1330
|
+
metricsHistogram(handle: number, name: string, labelKeys: string, bucketsCsv: string): number;
|
|
1331
|
+
/**
|
|
1332
|
+
* Counter/gauge `+= amount`, or histogram observe `amount`. `values` is the
|
|
1333
|
+
* packed `\u001f`-separated label values. Returns true on success.
|
|
1334
|
+
*/
|
|
1335
|
+
metricsRecord(handle: number, series: number, values: Uint8Array, amount: number): boolean;
|
|
1336
|
+
/** Gauge assignment (`= value`). Returns true on success. */
|
|
1337
|
+
metricsGaugeSet(handle: number, series: number, values: Uint8Array, value: number): boolean;
|
|
1338
|
+
/**
|
|
1339
|
+
* Zero-encode sibling of {@link metricsRecord}: `values` is the JOINED
|
|
1340
|
+
* `\u001f`-separated JS string — the engine transcodes it in-engine.
|
|
1341
|
+
*/
|
|
1342
|
+
metricsRecordStr(handle: number, series: number, values: string, amount: number): boolean;
|
|
1343
|
+
/** Zero-encode sibling of {@link metricsGaugeSet} (joined string values). */
|
|
1344
|
+
metricsGaugeSetStr(handle: number, series: number, values: string, value: number): boolean;
|
|
1345
|
+
/**
|
|
1346
|
+
* Render the Prometheus text format into `output`; returns bytes written
|
|
1347
|
+
* (`0` = null handle / error; `> output.length` = exact required size).
|
|
1348
|
+
*/
|
|
1349
|
+
metricsRender(handle: number, output: Uint8Array): number;
|
|
1350
|
+
/**
|
|
1351
|
+
* Dump the packed v1 series snapshot into `output`; returns bytes written
|
|
1352
|
+
* (`0` = null handle / error; `> output.length` = exact required size).
|
|
1353
|
+
*/
|
|
1354
|
+
metricsSnapshot(handle: number, output: Uint8Array): number;
|
|
1355
|
+
/** Destroy a registry handle (frees the native `Box<MetricsRegistry>`). */
|
|
1356
|
+
metricsDestroy(handle: number): void;
|
|
1357
|
+
/**
|
|
1358
|
+
* Record a BATCH of events in one crossing. Packed layout:
|
|
1359
|
+
* `[u32 n]{[u32 series][u32 valsLen][vals][f64 amount]}`; returns true when
|
|
1360
|
+
* all entries applied.
|
|
1361
|
+
*/
|
|
1362
|
+
metricsRecordBatch(handle: number, packed: Uint8Array): boolean;
|
|
1363
|
+
/**
|
|
1364
|
+
* Fused wire-level validation: RAW query string → JSON → draft-07 validate
|
|
1365
|
+
* against the compiled schema (`inner` from `SchemaValidator.innerPtr()`).
|
|
1366
|
+
*/
|
|
1367
|
+
queryValidate(inner: number, qs: string): boolean;
|
|
1368
|
+
/** Fused cookie-header variant of {@link queryValidate}. */
|
|
1369
|
+
cookieValidate(inner: number, header: string): boolean;
|
|
1370
|
+
/**
|
|
1371
|
+
* Seal a session envelope natively: builds
|
|
1372
|
+
* `{"id":"…","data":<dataJson>,"exp":exp}` and HMAC-signs it →
|
|
1373
|
+
* `payload.<hex>` token string (`null` = failure).
|
|
1374
|
+
*/
|
|
1375
|
+
sessionSeal(id: string, dataJson: string, expSecs: number, secret: string): string | null;
|
|
1376
|
+
/**
|
|
1377
|
+
* Open a sealed session token: verify + extract in one crossing into
|
|
1378
|
+
* `output` as `[u8 ok][i64 exp][u32 idLen][id][u32 dataLen][dataJson]`.
|
|
1379
|
+
* Returns bytes written (`0` = bad signature / error; `> output.length` =
|
|
1380
|
+
* exact required size).
|
|
1381
|
+
*/
|
|
1382
|
+
sessionOpen(token: string, secret: string, output: Uint8Array): number;
|
|
1383
|
+
/**
|
|
1384
|
+
* Byte-arg `{@link sessionSeal}`: same envelope + wire format, inputs as
|
|
1385
|
+
* bytes. Preferred for user-derived `id`/`dataJson`, because a `cstring` ARG
|
|
1386
|
+
* is engine-transcoded (a per-arg cost) and SILENTLY TRUNCATES at an
|
|
1387
|
+
* embedded `U+0000` before Rust sees it. `null` = failure.
|
|
1388
|
+
*/
|
|
1389
|
+
sessionSealBytes(id: Uint8Array, dataJson: Uint8Array, expSecs: number, secret: Uint8Array): string | null;
|
|
1390
|
+
/**
|
|
1391
|
+
* Byte-arg `{@link sessionOpen}`: same output layout and needed-size
|
|
1392
|
+
* convention, `token`/`secret` as bytes (exact length, no transcode).
|
|
1393
|
+
*/
|
|
1394
|
+
sessionOpenBytes(token: Uint8Array, secret: Uint8Array, output: Uint8Array): number;
|
|
1395
|
+
/**
|
|
1396
|
+
* Validate NEWLINE-separated lines as fixed-width hex into `output`
|
|
1397
|
+
* (one verdict byte per line); returns bytes written (`0` = bad width /
|
|
1398
|
+
* error; `> output.length` = exact required size).
|
|
1399
|
+
*/
|
|
1400
|
+
hexValidateBatchInto(input: Uint8Array, width: number, output: Uint8Array): number;
|
|
1401
|
+
/**
|
|
1402
|
+
* String-input sibling of {@link hexValidateBatchInto}: the NEWLINE-
|
|
1403
|
+
* separated ids cross as a `cstring` ARG (engine-transcoded — no encode).
|
|
1404
|
+
*/
|
|
1405
|
+
hexValidateBatchStr(ids: string, width: number, output: Uint8Array): number;
|
|
1406
|
+
/**
|
|
1407
|
+
* Escape JS-RegExp metacharacters into `output`; returns bytes written
|
|
1408
|
+
* (`0` = error; `> output.length` = exact required size).
|
|
1409
|
+
*/
|
|
1410
|
+
regexEscapeInto(input: Uint8Array, output: Uint8Array): number;
|
|
1411
|
+
/**
|
|
1412
|
+
* Zero-copy text sibling of {@link regexEscapeInto}: cstring ARG in,
|
|
1413
|
+
* cstring return out (engine-cloned — zero JS decode). `null` = failure.
|
|
1414
|
+
*/
|
|
1415
|
+
regexEscapeStr(input: string): string | null;
|
|
1416
|
+
/** Start the shared native task pool; 0 = started, 1 = already running. */
|
|
1417
|
+
taskInit(threads: number): number;
|
|
1418
|
+
/**
|
|
1419
|
+
* Submit a task op with packed `args` under `taskId`. Returns `1` accepted,
|
|
1420
|
+
* `2` overloaded (bounded queue full), `0` invalid args.
|
|
1421
|
+
*/
|
|
1422
|
+
taskSubmit(op: number, args: Uint8Array, taskId: number): number;
|
|
1423
|
+
/**
|
|
1424
|
+
* Zero-copy submit: the op writes its result DIRECTLY into `output` on the
|
|
1425
|
+
* pool thread. `output` must be a stable, caller-owned buffer that stays
|
|
1426
|
+
* alive (and untouched) until the completion is drained — the runtime holds
|
|
1427
|
+
* the reference. The completion body is the 8-byte LE written length.
|
|
1428
|
+
* Returns `1` accepted, `2` overloaded, `0` invalid args.
|
|
1429
|
+
*/
|
|
1430
|
+
taskSubmitOut(op: number, args: Uint8Array, taskId: number, output: Uint8Array): number;
|
|
1431
|
+
/**
|
|
1432
|
+
* Zero-copy INPUT: only `hdr` is copied; `data` is read in place by the pool
|
|
1433
|
+
* thread, so the caller must keep it alive (and untouched) until completion.
|
|
1434
|
+
* Returns `1` accepted, `2` overloaded, `0` invalid args.
|
|
1435
|
+
*/
|
|
1436
|
+
taskSubmitSlice(op: number, hdr: Uint8Array, data: Uint8Array, taskId: number): number;
|
|
1437
|
+
/**
|
|
1438
|
+
* Drain finished tasks into `output` (packed `[u32 count][entry…]`).
|
|
1439
|
+
* Returns bytes written, `0` when nothing is pending, or the EXACT required
|
|
1440
|
+
* size when `output` is too small (nothing is consumed in that case).
|
|
1441
|
+
*/
|
|
1442
|
+
taskDrain(output: Uint8Array): number;
|
|
1443
|
+
/** Finished-but-undrained completion count. */
|
|
1444
|
+
taskPending(): number;
|
|
1445
|
+
/** Mark a task cancelled; 1 = newly marked, 0 = unknown/already finished. */
|
|
1446
|
+
taskCancel(taskId: number): number;
|
|
1447
|
+
/** Register the thread-safe doorbell trampoline address (`0` disables it). */
|
|
1448
|
+
taskSetDoorbell(ptr: number): number;
|
|
1449
|
+
/** Stop the task pool and drop pending completions. */
|
|
1450
|
+
taskShutdown(): number;
|
|
1451
|
+
/** Configured task-pool worker count (0 before the pool starts). */
|
|
1452
|
+
taskThreads(): number;
|
|
1453
|
+
}
|
|
1454
|
+
/** Unpacked rate-limit verdict (from the packed
|
|
1455
|
+
* `[u8 allowed][u32 remaining LE][i64 reset_ms LE]` C-ABI output). */
|
|
1456
|
+
export interface RateLimiterVerdict {
|
|
1457
|
+
allowed: boolean;
|
|
1458
|
+
remaining: number;
|
|
1459
|
+
resetMs: number;
|
|
1460
|
+
}
|
|
952
1461
|
/**
|
|
953
1462
|
* Create the runtime-appropriate ingress server: `Bun.serve` on Bun, the
|
|
954
1463
|
* `node:http` adapter on Node. Selected ONCE at module load — the branch
|
|
@@ -987,6 +1496,41 @@ export declare function createTraceId(): string;
|
|
|
987
1496
|
export declare function createSpanId(): string;
|
|
988
1497
|
/** Serialize a trace context back to a `traceparent` header value. */
|
|
989
1498
|
export declare function serializeTraceParent(ctx: TraceContext): string;
|
|
1499
|
+
/**
|
|
1500
|
+
* The `err.code` value a body read carries when it is rejected because the
|
|
1501
|
+
* client disconnected (`Request.signal` aborted).
|
|
1502
|
+
*/
|
|
1503
|
+
export declare const ABORT_CODE = "REQUEST_ABORTED";
|
|
1504
|
+
/**
|
|
1505
|
+
* Whether a thrown value is the cancellation error produced when `req.signal`
|
|
1506
|
+
* aborts during a body read (see `readBodyWithLimit` in `./body`).
|
|
1507
|
+
*
|
|
1508
|
+
* @param err - The caught value.
|
|
1509
|
+
* @returns `true` when `err.code === 'REQUEST_ABORTED'`.
|
|
1510
|
+
* @example
|
|
1511
|
+
* ```ts
|
|
1512
|
+
* try {
|
|
1513
|
+
* await readBodyWithLimit(req, 1 << 20, true)
|
|
1514
|
+
* } catch (err) {
|
|
1515
|
+
* if (isAbortError(err)) return abortResponse()
|
|
1516
|
+
* throw err
|
|
1517
|
+
* }
|
|
1518
|
+
* ```
|
|
1519
|
+
*/
|
|
1520
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
1521
|
+
/**
|
|
1522
|
+
* Build the cancelled-client response: status **499** ("Client Closed
|
|
1523
|
+
* Request", a de-facto nginx status) with no body. This is the only new
|
|
1524
|
+
* outcome added by cancellation — it never replaces an existing success or
|
|
1525
|
+
* error status.
|
|
1526
|
+
*
|
|
1527
|
+
* @returns A bodyless `Response` with status 499.
|
|
1528
|
+
* @example
|
|
1529
|
+
* ```ts
|
|
1530
|
+
* if (req.signal?.aborted) return abortResponse()
|
|
1531
|
+
* ```
|
|
1532
|
+
*/
|
|
1533
|
+
export declare function abortResponse(): Response;
|
|
990
1534
|
/** Offset of the verdict byte (0 = accepted, nonzero = terminal error). */
|
|
991
1535
|
export declare const OUT_VERDICT: number;
|
|
992
1536
|
/** Offset of the error-code byte (see ERR_CODE_* below). */
|
|
@@ -1227,7 +1771,7 @@ export declare function readHandler(ingress: OptimizedIngressHandler, opts?: Bak
|
|
|
1227
1771
|
export declare const deleteHandler: typeof readHandler;
|
|
1228
1772
|
/**
|
|
1229
1773
|
* Pre-baked echo handler: streams the request body back with the client's
|
|
1230
|
-
* Content-Type, bounded by `maxBodyBytes`.
|
|
1774
|
+
* Content-Type (coerced to a non-active type), bounded by `maxBodyBytes`.
|
|
1231
1775
|
*/
|
|
1232
1776
|
export declare function echoHandler(ingress: OptimizedIngressHandler, opts?: BakedHandlerOptions): (req: Request, srv?: unknown) => Promise<Response>;
|
|
1233
1777
|
/**
|
|
@@ -1368,6 +1912,20 @@ export declare function createIngressHandler(options?: IngressHandlerOptions, ru
|
|
|
1368
1912
|
* Never touches dependencies.
|
|
1369
1913
|
*/
|
|
1370
1914
|
export declare function livenessHandler(): (_req: Request) => Response;
|
|
1915
|
+
/**
|
|
1916
|
+
* Liveness probe as a PREBUILT `Response`, for static route promotion.
|
|
1917
|
+
*
|
|
1918
|
+
* Wire it as `{ static: staticLiveness() }` so `Bun.serve` snapshots the
|
|
1919
|
+
* constant body into its native route table and serves `/livez` without
|
|
1920
|
+
* entering the JavaScript ingress pipeline at all (see `BakedRoute.static` in
|
|
1921
|
+
* src/ingress/server.ts). On the node:http adapter the bare `Response` is
|
|
1922
|
+
* re-materialized per request from cached body bytes, so the one-shot body is
|
|
1923
|
+
* never replayed. Unlike {@link readinessHandler}, it never runs a dependency
|
|
1924
|
+
* check.
|
|
1925
|
+
*
|
|
1926
|
+
* @returns A fresh 200 `{"status":"ok"}` `Response`.
|
|
1927
|
+
*/
|
|
1928
|
+
export declare function staticLiveness(): Response;
|
|
1371
1929
|
/**
|
|
1372
1930
|
* Readiness probe: runs the optional async `check` (dependency readiness).
|
|
1373
1931
|
* Returns `200 {"status":"ready"}` when the check passes (or no check is
|
|
@@ -1494,6 +2052,20 @@ export interface RouterRouteSpec {
|
|
|
1494
2052
|
/** Terminal envelope style for this route's responder (default: router-level
|
|
1495
2053
|
* `terminalStyle` or `'castrum'`). */
|
|
1496
2054
|
terminalStyle?: TerminalStyle;
|
|
2055
|
+
/**
|
|
2056
|
+
* A prebuilt constant response served directly for GET, OUTSIDE the ingress
|
|
2057
|
+
* pipeline (static route promotion). Like `raw`, it OWNS THE WHOLE SPEC: the
|
|
2058
|
+
* router compiles no `IngressInner` for a `static` route, so `read`/`write`/
|
|
2059
|
+
* `echo`/etc. on the same spec are ignored.
|
|
2060
|
+
*
|
|
2061
|
+
* This differs from `BakedRoute.static` in src/ingress/server.ts, where
|
|
2062
|
+
* `static` only takes GET precedence and leaves `write`/`echo`/`delete`
|
|
2063
|
+
* wired — the router cannot mirror that because a `static`-only spec has no
|
|
2064
|
+
* compiled ingress to hand those methods. Do not unify the two semantics.
|
|
2065
|
+
*
|
|
2066
|
+
* See `BakedRoute.static` for the one-shot-body contract.
|
|
2067
|
+
*/
|
|
2068
|
+
static?: StaticRoute;
|
|
1497
2069
|
/**
|
|
1498
2070
|
* A raw request→Response handler served directly for GET, OUTSIDE the
|
|
1499
2071
|
* ingress pipeline (health/metrics probes, /metrics). When set, `read`/
|
|
@@ -1586,6 +2158,23 @@ export declare function createIngressSync(options?: IngressOptions): SyncIngress
|
|
|
1586
2158
|
* @returns an async handler returning a snapshot context
|
|
1587
2159
|
*/
|
|
1588
2160
|
export declare function createIngress(options?: IngressOptions): IngressHandler;
|
|
2161
|
+
/**
|
|
2162
|
+
* Synchronous ingress writers backed by castrum's process-lifetime FFI binding.
|
|
2163
|
+
* Handles MUST originate from addonPath and remain owned/alive while in use.
|
|
2164
|
+
* Writers throw on native failure; a return larger than output.byteLength is
|
|
2165
|
+
* the exact required capacity, not a completed write. No buffer is retained.
|
|
2166
|
+
*/
|
|
2167
|
+
export type IngressBinding = Pick<BunFFI, "ingressHandleComponents" | "ingressHandlePacked" | "ingressLayout"> & {
|
|
2168
|
+
readonly addonPath: string;
|
|
2169
|
+
};
|
|
2170
|
+
/**
|
|
2171
|
+
* Share castrum's self-tested ingress writers without a second dlopen/map.
|
|
2172
|
+
* @returns The binding and actual binary path, or null when FFI is unavailable.
|
|
2173
|
+
* @example
|
|
2174
|
+
* const binding = getIngressBinding()
|
|
2175
|
+
* // Verify binding.addonPath matches the addon that owns your ingress handle.
|
|
2176
|
+
*/
|
|
2177
|
+
export declare function getIngressBinding(): IngressBinding | null;
|
|
1589
2178
|
/** Parse a query string (`a=1&b=2`) into an object via the native parser. */
|
|
1590
2179
|
export declare function parseQueryString(query: string): Record<string, string | string[]>;
|
|
1591
2180
|
/** Parse a cookie header (`a=1; b=2`) into an object via the native parser. */
|
|
@@ -2513,8 +3102,23 @@ export interface RustBatch {
|
|
|
2513
3102
|
wsAcceptKey(items: Uint8Array[]): Uint8Array[];
|
|
2514
3103
|
base64UrlEncode(items: Uint8Array[]): Uint8Array[];
|
|
2515
3104
|
base64UrlDecode(items: Uint8Array[]): Uint8Array[];
|
|
3105
|
+
/**
|
|
3106
|
+
* Parallel password hashing. WARNING: the SAME `salt` is applied to every
|
|
3107
|
+
* item. Reusing one salt across real user passwords enables a single
|
|
3108
|
+
* precomputation to attack them all — generate a unique salt per password in
|
|
3109
|
+
* production (this batch form is for precomputed/benchmark workloads).
|
|
3110
|
+
*/
|
|
2516
3111
|
passwordHash(passwords: Uint8Array[], salt: Uint8Array, options?: PasswordHashOptions | null): Uint8Array[];
|
|
3112
|
+
/**
|
|
3113
|
+
* Parallel AEAD encrypt. `nonce` MUST be exactly 12 bytes and MUST be fresh
|
|
3114
|
+
* for every batch call with the same key: a unique per-item nonce is derived
|
|
3115
|
+
* by XOR-ing the item index into the low 8 bytes, so reusing a base nonce
|
|
3116
|
+
* across calls reuses keystream/tag material for colliding indices
|
|
3117
|
+
* (catastrophic for AES-GCM / ChaCha20-Poly1305). A non-12-byte nonce is
|
|
3118
|
+
* rejected with an error.
|
|
3119
|
+
*/
|
|
2517
3120
|
aeadEncrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
3121
|
+
/** Parallel AEAD decrypt. Pass the SAME base nonce and item order as the encrypt call. */
|
|
2518
3122
|
aeadDecrypt(key: Uint8Array, nonce: Uint8Array, items: Uint8Array[], algorithm?: string | null): Uint8Array[];
|
|
2519
3123
|
gzipCompress(items: Uint8Array[], level?: number | null): Uint8Array[];
|
|
2520
3124
|
gzipDecompress(items: Uint8Array[], maxDecompressed?: number | null): Uint8Array[];
|
|
@@ -2781,7 +3385,10 @@ export interface RustScalar {
|
|
|
2781
3385
|
createMediaTypeMatcher(expected: Uint8Array): MediaTypeMatcherInstance;
|
|
2782
3386
|
/**
|
|
2783
3387
|
* Sharded fixed-window per-key rate limiter. `maxEntries` clamps internally
|
|
2784
|
-
* (default 1,048,576)
|
|
3388
|
+
* (default 1,048,576) and has an effective floor of 16,384
|
|
3389
|
+
* (`max_per_shard = max(maxEntries / 256, 64)` × 256 shards) — values below
|
|
3390
|
+
* that are silently raised, so don't rely on it to bound a tiny key space.
|
|
3391
|
+
* Each instance owns an independent budget.
|
|
2785
3392
|
* @remarks Scalar per-check cost: ~3-14x SLOWER than the JS Map baseline —
|
|
2786
3393
|
* prefer the ingress pipeline (one FFI for all stages) for native rate
|
|
2787
3394
|
* limiting in a request path. [measured 2026-08]
|
|
@@ -2796,6 +3403,20 @@ export interface RustScalar {
|
|
|
2796
3403
|
createMetricsRegistry(): MetricsRegistryInstance;
|
|
2797
3404
|
initThreadPool(rayonThreads?: number): void;
|
|
2798
3405
|
rayonNumThreads(): number;
|
|
3406
|
+
/**
|
|
3407
|
+
* Drop the process-wide compiled request-body schema cache. Identical schema
|
|
3408
|
+
* bytes are compiled once and shared across `Ingress` / `NativeRoute`
|
|
3409
|
+
* instances; this releases those compiled validators (already-constructed
|
|
3410
|
+
* instances keep their own shared reference and are unaffected). Idempotent,
|
|
3411
|
+
* and a safe no-op when the native addon is absent or predates the symbol
|
|
3412
|
+
* (the public `flushMemory()` relies on that).
|
|
3413
|
+
*
|
|
3414
|
+
* Maintenance-only escape hatch — NOT a benchmarked op, so it is intentionally
|
|
3415
|
+
* exempt from `PROVEN_SELECTION` / `selection.json` (there is no native-vs-JS
|
|
3416
|
+
* winner to classify). `test/unit/contract/proven.test.ts` only requires
|
|
3417
|
+
* registry coverage for selection.json ops + Bun built-ins.
|
|
3418
|
+
*/
|
|
3419
|
+
clearSchemaCache(): void;
|
|
2799
3420
|
jwtSign(claims: Record<string, unknown>, secret: Uint8Array, ttlSeconds?: number | null, nowSeconds?: number): Uint8Array;
|
|
2800
3421
|
/**
|
|
2801
3422
|
* Sign a JWT from pre-serialized claim JSON bytes — skips the napi
|
|
@@ -3198,6 +3819,41 @@ export declare const encoder: {
|
|
|
3198
3819
|
export declare const decoder: {
|
|
3199
3820
|
decode(bytes: Uint8Array | string): string;
|
|
3200
3821
|
};
|
|
3822
|
+
/** Options for {@link flushMemory}. */
|
|
3823
|
+
export interface FlushMemoryOptions {
|
|
3824
|
+
/**
|
|
3825
|
+
* When true (the default), also request a full garbage collection from the
|
|
3826
|
+
* runtime: `Bun.gc(true)` on Bun, `globalThis.gc?.()` on Node when the
|
|
3827
|
+
* process runs with `--expose-gc`. Set false to flush caches only.
|
|
3828
|
+
*/
|
|
3829
|
+
gc?: boolean;
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Flush process-level caches and (by default) request a full GC. Intended for
|
|
3833
|
+
* explicit memory-pressure handling — e.g. after a burst or before a snapshot.
|
|
3834
|
+
*
|
|
3835
|
+
* Clears:
|
|
3836
|
+
* - the global loader LRU cache (`loader.clear()`),
|
|
3837
|
+
* - the process-wide MIME caches (`clearMimeCaches()`),
|
|
3838
|
+
* - the native compiled-schema cache (`rust.clearSchemaCache()`).
|
|
3839
|
+
*
|
|
3840
|
+
* The metrics registry is intentionally left untouched.
|
|
3841
|
+
*
|
|
3842
|
+
* Idempotent, and safe to call before the native addon has ever been loaded
|
|
3843
|
+
* (every cache starts empty, the loader cache is a plain Map, and the native
|
|
3844
|
+
* schema-cache clear is a defensive no-op when the addon/symbol is absent).
|
|
3845
|
+
*
|
|
3846
|
+
* @param options - `gc: false` skips the runtime GC request. Defaults to `{}`
|
|
3847
|
+
* (GC requested).
|
|
3848
|
+
* @example
|
|
3849
|
+
* ```ts
|
|
3850
|
+
* import { flushMemory } from 'castrum'
|
|
3851
|
+
*
|
|
3852
|
+
* flushMemory() // drop caches + request GC
|
|
3853
|
+
* flushMemory({ gc: false }) // drop caches only
|
|
3854
|
+
* ```
|
|
3855
|
+
*/
|
|
3856
|
+
export declare function flushMemory(options?: FlushMemoryOptions): void;
|
|
3201
3857
|
/**
|
|
3202
3858
|
* Generate a UUIDv7 (time-ordered) string, preferring Bun's native built-in.
|
|
3203
3859
|
* Under Node this falls back to `crypto.randomUUID` (v4).
|