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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/native/ffi/build.ts — the `build()` orchestrator for the BunFFI surface.
|
|
2
|
+
//
|
|
3
|
+
// `build(sym, useBufferLength)` sets up the shared per-bind state — the
|
|
4
|
+
// probe-gated `lenOrView`/`oneArg` argument adapters and the pooled scratch
|
|
5
|
+
// buffers — then delegates to the four domain builders (each receives the raw
|
|
6
|
+
// dlopen'd symbols + this context) and merges their method objects into the
|
|
7
|
+
// final `BunFFI`. Extracted from the former monolithic `ffi.ts` so the
|
|
8
|
+
// per-call wrapper surface is navigable by domain:
|
|
9
|
+
// - `build/codecs.ts` hashing / codec / crypto / auth / ws-key / etag
|
|
10
|
+
// - `build/compress.ts` gzip / brotli / json patch
|
|
11
|
+
// - `build/parse.ts` packed parsers / media-type / http-date / sse
|
|
12
|
+
// - `build/instances.ts` opaque-handle evals + ingress + route stack
|
|
13
|
+
|
|
14
|
+
import { buildCodecs } from './build/codecs'
|
|
15
|
+
import { buildCompress } from './build/compress'
|
|
16
|
+
import { buildInstances } from './build/instances'
|
|
17
|
+
import { buildParse } from './build/parse'
|
|
18
|
+
import type { BuildCtx } from './build/util'
|
|
19
|
+
import type { BunFFI } from './types'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Assemble the `BunFFI` surface from the raw dlopen'd symbols.
|
|
23
|
+
*
|
|
24
|
+
* @param sym The `symbols` object from `dlopen(path, {...})`.
|
|
25
|
+
* @param useBufferLength `true` when the engine's `buffer`/`buffer_length`
|
|
26
|
+
* ABI pair is live (see the probe in `ffi.ts`).
|
|
27
|
+
*/
|
|
28
|
+
export function build(
|
|
29
|
+
sym: Record<string, (...a: unknown[]) => unknown>,
|
|
30
|
+
useBufferLength: boolean,
|
|
31
|
+
): BunFFI {
|
|
32
|
+
// Single-buffer input fns: with the probe-gated `buffer/buffer_length` ABI the
|
|
33
|
+
// engine reads ptr + byteLength off the SAME TypedArray at call time (an
|
|
34
|
+
// atomic snapshot — pass the view twice, per Bun's docs); with `(ptr,len)` we
|
|
35
|
+
// pass the length explicitly. The branch is on a bind-time constant, so the
|
|
36
|
+
// JIT folds it away at the call site.
|
|
37
|
+
const oneArg = (raw: (...a: unknown[]) => number | bigint, v: Uint8Array): number | bigint =>
|
|
38
|
+
useBufferLength ? raw(v, v) : raw(v, v.length)
|
|
39
|
+
// Phase 1.1: under `buffer`/`buffer_length` the engine reads ptr + byteLength
|
|
40
|
+
// off the SAME view, so every `(ptr,len)` pair passes the view itself for the
|
|
41
|
+
// length slot (atomic snapshot, one JS arg fewer); under `(ptr,usize)` it's
|
|
42
|
+
// the explicit length. The branch is a bind-time constant → JIT folds it.
|
|
43
|
+
const lenOrView = (v: Uint8Array): Uint8Array | number => (useBufferLength ? v : v.length)
|
|
44
|
+
|
|
45
|
+
// ── Reusable scratch for the STRING-returning encode wrappers ────────
|
|
46
|
+
// Each wrapper writes its result and decodes it into an immutable JS string
|
|
47
|
+
// (or primitive) SYNCHRONOUSLY before the next call reuses the buffer — the
|
|
48
|
+
// same safety rationale as `jsonSumOut` / `generateRequestId`. Per-Worker
|
|
49
|
+
// module state (single-threaded event loop); never shared across Workers.
|
|
50
|
+
// NOT used for byte-returning ops (hexDecode/urlDecode/base64Decode/aead/
|
|
51
|
+
// wsFrame/packed parsers): their returned `Uint8Array` aliases the buffer
|
|
52
|
+
// and escapes to the caller — those stay on `*Into` (caller-owned).
|
|
53
|
+
let encodeScratch = new Uint8Array(0)
|
|
54
|
+
const scratchFor = (size: number): Uint8Array => {
|
|
55
|
+
if (encodeScratch.byteLength < size) encodeScratch = new Uint8Array(size)
|
|
56
|
+
return encodeScratch
|
|
57
|
+
}
|
|
58
|
+
// Pooled 9-byte scratch for jsonSumIds (the packed `[u8 ok][i64 sum LE]`
|
|
59
|
+
// output). The wrapper returns the BigInt VALUE (no byte aliasing escapes),
|
|
60
|
+
// so a single reused buffer + cached DataView is safe — removes the per-call
|
|
61
|
+
// Uint8Array + DataView allocs (measured ~400ns of the FFI wrapper cost).
|
|
62
|
+
const jsonSumOut = new Uint8Array(9)
|
|
63
|
+
const jsonSumView = new DataView(jsonSumOut.buffer)
|
|
64
|
+
// Fixed-size packed-verdict scratch + cached DataViews (parseHttpDate 9 B,
|
|
65
|
+
// rate-limit check 13 B) — same pooled pattern as `jsonSumView`. Kept
|
|
66
|
+
// separate from the growable `encodeScratch` so the cached DataViews are
|
|
67
|
+
// never invalidated by a buffer replacement.
|
|
68
|
+
const dateScratch = new Uint8Array(9)
|
|
69
|
+
const dateScratchView = new DataView(dateScratch.buffer)
|
|
70
|
+
const rateScratch = new Uint8Array(13)
|
|
71
|
+
const rateScratchView = new DataView(rateScratch.buffer)
|
|
72
|
+
|
|
73
|
+
const ctx: BuildCtx = {
|
|
74
|
+
useBufferLength,
|
|
75
|
+
lenOrView,
|
|
76
|
+
oneArg,
|
|
77
|
+
scratchFor,
|
|
78
|
+
jsonSumOut,
|
|
79
|
+
jsonSumView,
|
|
80
|
+
dateScratch,
|
|
81
|
+
dateScratchView,
|
|
82
|
+
rateScratch,
|
|
83
|
+
rateScratchView,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Merge the domain surfaces. Each builder's method bodies are verbatim from
|
|
87
|
+
// the original single return object; the cast asserts the merged shape.
|
|
88
|
+
return {
|
|
89
|
+
...buildCodecs(sym, ctx),
|
|
90
|
+
...buildCompress(sym, ctx),
|
|
91
|
+
...buildParse(sym, ctx),
|
|
92
|
+
...buildInstances(sym, ctx),
|
|
93
|
+
} as BunFFI
|
|
94
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/native/ffi/constants.ts — caps / sizing heuristics / self-test
|
|
2
|
+
// vectors for the bun:ffi transport (pure data, no logic).
|
|
3
|
+
//
|
|
4
|
+
// The caps mirror the Rust napi layer (decompression-bomb guard, JSON-patch
|
|
5
|
+
// output bound). The sizing heuristics are ALLOCATION caps only — `growExact`
|
|
6
|
+
// in the core covers any residual miss with one exact-size retry, so a guess
|
|
7
|
+
// that is too small costs one extra native pass, never a wrong result.
|
|
8
|
+
|
|
9
|
+
// ── Caps mirrored from the Rust napi layer ────────────────────────
|
|
10
|
+
/** rust/payload/compress.rs DEFAULT_MAX_DECOMPRESSED (64 MiB decompression-bomb
|
|
11
|
+
* guard — must stay in sync; do not raise without benchmarking). */
|
|
12
|
+
export const MAX_DECOMPRESSED = 64 * 1024 * 1024
|
|
13
|
+
/** rust/json/json_patch_ops.rs MAX_JSON_PATCH_OUTPUT (128 MiB). */
|
|
14
|
+
export const MAX_JSON_PATCH_OUTPUT = 128 * 1024 * 1024
|
|
15
|
+
|
|
16
|
+
// ── Output-buffer sizing heuristics (measured) ─────────────────────
|
|
17
|
+
// These are ALLOCATION caps only, not correctness bounds: `growExact` covers
|
|
18
|
+
// any residual miss with at most one exact-size retry (no re-run loop), so a
|
|
19
|
+
// guess that is too small costs one extra native pass, never a wrong result.
|
|
20
|
+
//
|
|
21
|
+
// Compress: a 75 KiB input compresses to <1 KiB, so a 16 KiB initial is
|
|
22
|
+
// plenty and single-pass; the 1 MiB ceiling bounds incompressible data.
|
|
23
|
+
/** gzip/brotli compress: initial output-buffer size (bytes). */
|
|
24
|
+
export const COMPRESS_INITIAL_CAP = 16 * 1024
|
|
25
|
+
/** gzip/brotli compress: ceiling that bounds incompressible input (bytes). */
|
|
26
|
+
export const COMPRESS_MAX_CAP = 1024 * 1024
|
|
27
|
+
/** gzip/brotli compress: small-input slack added above `data.length` (bytes). */
|
|
28
|
+
export const COMPRESS_HEADROOM = 64
|
|
29
|
+
// Decompress (no trailer / expensive trailer): typical JSON/text ratios.
|
|
30
|
+
/** gzip decompress: output-size guess multiplier over the input length. */
|
|
31
|
+
export const DECOMPRESS_GUESS_MULTIPLIER_GZIP = 16
|
|
32
|
+
/** brotli decompress: output-size guess multiplier over the input length. */
|
|
33
|
+
export const DECOMPRESS_GUESS_MULTIPLIER_BROTLI = 32
|
|
34
|
+
/** decompress: 4 MiB over-alloc bound used as a last-resort initial size. */
|
|
35
|
+
export const DECOMPRESS_FALLBACK_CAP = 4 * 1024 * 1024
|
|
36
|
+
/** decompress: minimum initial output size (bytes). */
|
|
37
|
+
export const DECOMPRESS_MIN_INITIAL = 1024
|
|
38
|
+
// JWT: token ≈ header(~36) + payload(≈4/3× claims) + sig(~43) + 2 dots. The
|
|
39
|
+
// old `claims.length + 128` under-sized a typical token (measured 168 B for a
|
|
40
|
+
// 30 B claim → grow-retry double-run, making ffi slower than napi). 2×+128
|
|
41
|
+
// with a 256-byte floor covers typical claims in one pass.
|
|
42
|
+
/** jwt sign: claims-length multiplier for the initial output-size guess. */
|
|
43
|
+
export const JWT_INITIAL_MULTIPLIER = 2
|
|
44
|
+
/** jwt sign: fixed byte slack added to the claims-length guess. */
|
|
45
|
+
export const JWT_INITIAL_EXTRA = 128
|
|
46
|
+
/** jwt sign: minimum initial output size (bytes). */
|
|
47
|
+
export const JWT_INITIAL_FLOOR = 256
|
|
48
|
+
|
|
49
|
+
// ── Known-good vectors for the bind-time self-test ───────────────
|
|
50
|
+
// These mirror the Rust `#[cfg(test)]` vectors in rust/ffi.rs.
|
|
51
|
+
/** Self-test vector: ASCII "hello" (hex-encodes to `68656c6c6f`). */
|
|
52
|
+
export const SELFTEST_HEX = new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]) // "hello" -> 68656c6c6f
|
|
53
|
+
/** Self-test vector: JSON `{"a":1}` (validates via the packed JSON ops). */
|
|
54
|
+
export const SELFTEST_JSON = new Uint8Array([0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x7d]) // "{\"a\":1}"
|
|
55
|
+
|
|
56
|
+
/** Empty view for `null` body slots in (ptr, len) pairs — the C side treats
|
|
57
|
+
* len 0 as "no body" regardless of the pointer value. */
|
|
58
|
+
export const EMPTY_VIEW = new Uint8Array(0)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/native/ffi/selftest.ts — bind-time self-test for the bun:ffi transport.
|
|
2
|
+
//
|
|
3
|
+
// Every bound function is checked against known-good vectors at bind time. If
|
|
4
|
+
// ANY check fails (ABI mismatch, platform quirk, a future Bun regression), the
|
|
5
|
+
// whole ffi layer is disabled and the caller falls back to the napi addon —
|
|
6
|
+
// the public API never sees a wrong result. `bun:ffi` is experimental; this is
|
|
7
|
+
// the safety net. New C-ABI symbols MUST be added to the self-test for their
|
|
8
|
+
// domain: each check lives in the same `ffi/build/` file where the method is
|
|
9
|
+
// bound (`selfTestCodecs` / `selfTestCompress` / `selfTestParse` /
|
|
10
|
+
// `selfTestInstances`), and this entry point ANDs them together so any single
|
|
11
|
+
// failure disables the whole layer.
|
|
12
|
+
|
|
13
|
+
import { selfTestCodecs } from './build/codecs'
|
|
14
|
+
import { selfTestCompress } from './build/compress'
|
|
15
|
+
import { selfTestInstances } from './build/instances'
|
|
16
|
+
import { selfTestParse } from './build/parse'
|
|
17
|
+
import type { BunFFI } from './types'
|
|
18
|
+
|
|
19
|
+
/** Verify every bound function against known-good results; false disables ffi. */
|
|
20
|
+
export function selfTest(b: BunFFI): boolean {
|
|
21
|
+
return selfTestCodecs(b) && selfTestCompress(b) && selfTestParse(b) && selfTestInstances(b)
|
|
22
|
+
}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// src/native/ffi/types.ts — bun:ffi C-ABI type surface (pure types).
|
|
2
|
+
//
|
|
3
|
+
// The bound-function contract (`BunFFI`), the transport selection
|
|
4
|
+
// (`FfiMode`), and the raw `(ptr,len)` symbol signatures (`Raw2..Raw10`)
|
|
5
|
+
// used by the transport core. No runtime code — erased at compile time.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The bound bun:ffi C-ABI surface. Each member is a dlopen'd `castrum_*`
|
|
9
|
+
* symbol wrapped so inputs are `(ptr,len)` byte slices and text outputs are
|
|
10
|
+
* returned as JS strings via JSC's native transfer (cstring clone) — zero
|
|
11
|
+
* `TextEncoder`/`TextDecoder` on the Bun path. `*Into` variants write into a
|
|
12
|
+
* caller buffer and return the byte count; variable-size ops return the exact
|
|
13
|
+
* required size on a too-small buffer so the transport grows once and retries
|
|
14
|
+
* exactly once.
|
|
15
|
+
*/
|
|
16
|
+
export interface BunFFI {
|
|
17
|
+
crc32(input: Uint8Array): number
|
|
18
|
+
fnv1a64(input: Uint8Array): bigint
|
|
19
|
+
xxh3(input: Uint8Array): bigint
|
|
20
|
+
jsonValid(input: Uint8Array): boolean
|
|
21
|
+
/** UTF-8 validity probe (native replacement for a fatal `TextDecoder`). */
|
|
22
|
+
utf8Valid(input: Uint8Array): boolean
|
|
23
|
+
/** Lowercase-hex encode → string (native transfer, zero encode). */
|
|
24
|
+
hexEncode(input: Uint8Array): string
|
|
25
|
+
/** Lowercase-hex encode into `output`; returns bytes written. */
|
|
26
|
+
hexEncodeInto(input: Uint8Array, output: Uint8Array): number
|
|
27
|
+
/** RFC 3986 percent-encode → string. */
|
|
28
|
+
urlEncode(input: Uint8Array): string
|
|
29
|
+
/** RFC 3986 percent-encode into `output`; returns bytes written. */
|
|
30
|
+
urlEncodeInto(input: Uint8Array, output: Uint8Array): number
|
|
31
|
+
|
|
32
|
+
// ── Validators (u8 → boolean) — `cstring` ARG ────────────────────
|
|
33
|
+
// The input crosses as a `cstring` ARG: the engine transcodes the JS string
|
|
34
|
+
// to a call-scoped NUL-terminated UTF-8 buffer in-engine, so the JS side does
|
|
35
|
+
// ZERO `encoder.encode` work and Rust borrows via `CStr::from_ptr`.
|
|
36
|
+
validateEmail(input: string): boolean
|
|
37
|
+
validateUuid(input: string): boolean
|
|
38
|
+
validateIpv4(input: string): boolean
|
|
39
|
+
validateIpv6(input: string): boolean
|
|
40
|
+
/** Sum of `id` fields across a JSON array → bigint (throws on non-array input). */
|
|
41
|
+
jsonSumIds(input: Uint8Array): bigint
|
|
42
|
+
|
|
43
|
+
// ── Constant-time verify (u8 → boolean) ───────────────────────────
|
|
44
|
+
hmacSha256Verify(key: Uint8Array, data: Uint8Array, signature: Uint8Array): boolean
|
|
45
|
+
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean
|
|
46
|
+
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean
|
|
47
|
+
/** `phc` is a `cstring` ARG (the engine transcodes the JS PHC string in-engine). */
|
|
48
|
+
passwordVerifyBcrypt(password: Uint8Array, phc: string): boolean
|
|
49
|
+
|
|
50
|
+
// ── Decoders (throw on malformed input — napi `Result` parity) ──
|
|
51
|
+
/** Hex-decode into a fresh buffer (size `input.length / 2`). */
|
|
52
|
+
hexDecode(input: Uint8Array): Uint8Array
|
|
53
|
+
/** Hex-decode into `output`; returns bytes written (throws if too small). */
|
|
54
|
+
hexDecodeInto(input: Uint8Array, output: Uint8Array): number
|
|
55
|
+
/** Percent-decode into a fresh buffer (size `input.length`). */
|
|
56
|
+
urlDecode(input: Uint8Array): Uint8Array
|
|
57
|
+
/** Percent-decode into `output`; returns bytes written (throws if too small). */
|
|
58
|
+
urlDecodeInto(input: Uint8Array, output: Uint8Array): number
|
|
59
|
+
/** base64-decode into a fresh buffer (size `ceil(len/4)*3`). */
|
|
60
|
+
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array
|
|
61
|
+
/** base64-decode into `output`; returns bytes written (throws if too small). */
|
|
62
|
+
base64DecodeInto(
|
|
63
|
+
input: Uint8Array,
|
|
64
|
+
output: Uint8Array,
|
|
65
|
+
urlSafe?: boolean,
|
|
66
|
+
padding?: boolean,
|
|
67
|
+
): number
|
|
68
|
+
|
|
69
|
+
// ── Fixed-size output writers ─────────────────────────────────────
|
|
70
|
+
/** RFC 6455 Sec-WebSocket-Accept → string (cstring return; `key` is a `cstring` ARG). */
|
|
71
|
+
wsAcceptKey(key: string): string
|
|
72
|
+
/** RFC 6455 Sec-WebSocket-Accept into `output`; returns bytes written. */
|
|
73
|
+
wsAcceptKeyInto(key: Uint8Array, output: Uint8Array): number
|
|
74
|
+
/** crc32 ETag → string (10 strong / 12 weak chars, cstring return). */
|
|
75
|
+
etag(data: Uint8Array, weak?: boolean): string
|
|
76
|
+
/** crc32 ETag into `output`; returns bytes written. */
|
|
77
|
+
etagInto(data: Uint8Array, output: Uint8Array, weak?: boolean): number
|
|
78
|
+
/** Evaluate a precompiled `ConditionalRequest` (opaque `inner` handle from its
|
|
79
|
+
* napi `inner_ptr()`) — true → 304. `null` header args = absent (flags encode
|
|
80
|
+
* presence so a present-but-empty header is distinct). A null (0) handle → false. */
|
|
81
|
+
conditionalIsNotModified(
|
|
82
|
+
inner: number,
|
|
83
|
+
ifNoneMatch: Uint8Array | null,
|
|
84
|
+
ifModifiedSince: Uint8Array | null,
|
|
85
|
+
): boolean
|
|
86
|
+
/** Precompiled `MediaTypeMatcher` wildcard match (opaque `inner` handle). */
|
|
87
|
+
mediaTypeMatcherMatches(inner: number, actual: Uint8Array): boolean
|
|
88
|
+
/** Precompiled `AcceptNegotiator` → best supported encoding (opaque `inner`
|
|
89
|
+
* handle); `null` = identity. */
|
|
90
|
+
acceptNegotiatorNegotiate(inner: number, header: Uint8Array): string | null
|
|
91
|
+
/** Precompiled `AcceptNegotiator` → best supported encoding with SERVER-
|
|
92
|
+
* preference tie-breaking (RFC 7231 server semantics, opaque `inner` handle);
|
|
93
|
+
* `null` = identity. */
|
|
94
|
+
acceptNegotiatorNegotiateServer(inner: number, header: Uint8Array): string | null
|
|
95
|
+
/** Precompiled `JwtSigner` → compact token from pre-serialized claim JSON
|
|
96
|
+
* (opaque `inner` handle); throws on invalid claims / null handle. */
|
|
97
|
+
jwtSignerSign(inner: number, claimsJson: Uint8Array, nowSeconds: number): Uint8Array
|
|
98
|
+
/** Precompiled `JwtSigner` → claims JSON bytes (opaque `inner` handle);
|
|
99
|
+
* `null` = invalid signature / expired / malformed. */
|
|
100
|
+
jwtSignerVerify(inner: number, token: Uint8Array, nowSeconds: number): Uint8Array | null
|
|
101
|
+
/** Ed25519 keypair generation → `{ privateKey, publicKey }` (PKCS#8 v1 DER
|
|
102
|
+
* private + SPKI DER public bytes); throws on CSPRNG failure. */
|
|
103
|
+
ed25519GenerateKeypair(): { privateKey: Uint8Array; publicKey: Uint8Array }
|
|
104
|
+
/** Ed25519 sign → 64-byte signature; throws on invalid private key. */
|
|
105
|
+
ed25519Sign(msg: Uint8Array, privateKey: Uint8Array): Uint8Array
|
|
106
|
+
/** Ed25519 verify → whether `signature` is valid for `msg` under `publicKey`. */
|
|
107
|
+
ed25519Verify(msg: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean
|
|
108
|
+
/** EdDSA (Ed25519) JWT sign → compact token; `null` = invalid claims/private
|
|
109
|
+
* key. `ttl <= 0` = no `iat`/`exp` injection. */
|
|
110
|
+
jwtEdDSASign(
|
|
111
|
+
claimsJson: Uint8Array,
|
|
112
|
+
privateKey: Uint8Array,
|
|
113
|
+
ttl: number,
|
|
114
|
+
nowSeconds: number,
|
|
115
|
+
): string | null
|
|
116
|
+
/** EdDSA (Ed25519) JWT verify → claims JSON string; `null` = invalid. */
|
|
117
|
+
jwtEdDSAVerify(
|
|
118
|
+
token: Uint8Array,
|
|
119
|
+
publicKey: Uint8Array,
|
|
120
|
+
nowSeconds: number,
|
|
121
|
+
): string | null
|
|
122
|
+
/** Compiled `TemplateRenderer` → UTF-8 bytes from a pre-serialized JSON
|
|
123
|
+
* context (opaque `inner` handle); throws on invalid context / render error. */
|
|
124
|
+
templateRender(inner: number, contextJson: Uint8Array): Uint8Array
|
|
125
|
+
/** Compiled `SchemaValidator` → whether a document is valid (opaque `inner` handle). */
|
|
126
|
+
schemaValidatorValidate(inner: number, doc: Uint8Array): boolean
|
|
127
|
+
/** `RateLimiter` string-key check → { allowed, remaining, resetMs } (opaque
|
|
128
|
+
* `inner` handle; `key` is a `cstring` ARG); throws on a null handle. */
|
|
129
|
+
rateLimiterCheck(inner: number, key: string, nowMs: number): RateLimiterVerdict
|
|
130
|
+
/** `RateLimiter` pre-hashed key check → { allowed, remaining, resetMs }. */
|
|
131
|
+
rateLimiterCheckKey(inner: number, key: number, nowMs: number): RateLimiterVerdict
|
|
132
|
+
/** `byteLen` random bytes → `byteLen * 2` hex-char string (cstring return). */
|
|
133
|
+
randomToken(byteLen: number): string
|
|
134
|
+
/** base64-encode → string. */
|
|
135
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): string
|
|
136
|
+
/** base64-encode into `output`; returns bytes written. */
|
|
137
|
+
base64EncodeInto(
|
|
138
|
+
input: Uint8Array,
|
|
139
|
+
output: Uint8Array,
|
|
140
|
+
urlSafe?: boolean,
|
|
141
|
+
padding?: boolean,
|
|
142
|
+
): number
|
|
143
|
+
/** HMAC-SHA256 → 64-char lowercase-hex string. */
|
|
144
|
+
hmacSha256(key: Uint8Array, data: Uint8Array): string
|
|
145
|
+
/** HMAC-SHA256 hex into `output`; returns bytes written (throws if too small). */
|
|
146
|
+
hmacSha256Into(key: Uint8Array, data: Uint8Array, output: Uint8Array): number
|
|
147
|
+
/** Sign cookie `value` as `value.<64-hex>` → string (cstring return). */
|
|
148
|
+
signCookie(value: Uint8Array, secret: Uint8Array): string
|
|
149
|
+
/** Sign cookie into `output`; returns bytes written (throws if too small). */
|
|
150
|
+
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number
|
|
151
|
+
/** Verify a signed cookie → value string, or `null` on bad signature. */
|
|
152
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): string | null
|
|
153
|
+
/** Verify a signed cookie into `output`; returns value length or `null` on bad signature. */
|
|
154
|
+
verifyCookieInto(signed: Uint8Array, secret: Uint8Array, output: Uint8Array): number | null
|
|
155
|
+
/** CSRF token (129 chars: 64 rnd-hex + '.' + 64 sig-hex) → string. */
|
|
156
|
+
csrfToken(secret: Uint8Array): string
|
|
157
|
+
/** CSRF token into `output`; returns bytes written (throws if too small). */
|
|
158
|
+
csrfTokenInto(secret: Uint8Array, output: Uint8Array): number
|
|
159
|
+
/** Argon2id PHC hash → string (m/t/p/out_len params). */
|
|
160
|
+
passwordHash(
|
|
161
|
+
password: Uint8Array,
|
|
162
|
+
salt: Uint8Array,
|
|
163
|
+
mCost: number,
|
|
164
|
+
tCost: number,
|
|
165
|
+
pCost: number,
|
|
166
|
+
outLen: number,
|
|
167
|
+
): string
|
|
168
|
+
/** bcrypt `$2b$` PHC → string (cost clamped 4..=31). */
|
|
169
|
+
passwordHashBcrypt(password: Uint8Array, cost: number): string
|
|
170
|
+
/** PBKDF2-HMAC-SHA256 → `dkLen` bytes (rounds, dkLen clamped). */
|
|
171
|
+
pbkdf2Sha256(password: Uint8Array, salt: Uint8Array, rounds: number, dkLen: number): Uint8Array
|
|
172
|
+
/** AEAD encrypt (0 = AES-256-GCM, 1 = ChaCha20-Poly1305) → ct+tag. */
|
|
173
|
+
aeadEncrypt(
|
|
174
|
+
key: Uint8Array,
|
|
175
|
+
nonce: Uint8Array,
|
|
176
|
+
plaintext: Uint8Array,
|
|
177
|
+
algorithm?: number,
|
|
178
|
+
): Uint8Array
|
|
179
|
+
/** AEAD encrypt into `output` (ct + 16-byte tag); returns bytes written. */
|
|
180
|
+
aeadEncryptInto(
|
|
181
|
+
key: Uint8Array,
|
|
182
|
+
nonce: Uint8Array,
|
|
183
|
+
plaintext: Uint8Array,
|
|
184
|
+
output: Uint8Array,
|
|
185
|
+
algorithm?: number,
|
|
186
|
+
): number
|
|
187
|
+
/** AEAD decrypt → plaintext, or `null` on auth failure. */
|
|
188
|
+
aeadDecrypt(
|
|
189
|
+
key: Uint8Array,
|
|
190
|
+
nonce: Uint8Array,
|
|
191
|
+
ciphertext: Uint8Array,
|
|
192
|
+
algorithm?: number,
|
|
193
|
+
): Uint8Array | null
|
|
194
|
+
|
|
195
|
+
// ── Frame / patch / compression (variable-size → grow-retry) ─────
|
|
196
|
+
/** RFC 6455 frame encode into a fresh buffer. */
|
|
197
|
+
wsFrameEncode(opcode: number, payload: Uint8Array, mask: boolean, fin: boolean): Uint8Array
|
|
198
|
+
/** RFC 6455 frame encode into `output`; returns bytes written (throws if too small). */
|
|
199
|
+
wsFrameEncodeInto(
|
|
200
|
+
opcode: number,
|
|
201
|
+
payload: Uint8Array,
|
|
202
|
+
mask: boolean,
|
|
203
|
+
fin: boolean,
|
|
204
|
+
output: Uint8Array,
|
|
205
|
+
): number
|
|
206
|
+
/** RFC 6902 JSON patch → JSON text string. */
|
|
207
|
+
jsonPatch(doc: Uint8Array, patch: Uint8Array): string
|
|
208
|
+
/** gzip-compress into a fresh buffer (level clamped 0..=9, default 6). */
|
|
209
|
+
gzipCompress(data: Uint8Array, level?: number): Uint8Array
|
|
210
|
+
/** gzip-compress into `output`; returns bytes written (throws if too small). */
|
|
211
|
+
gzipCompressInto(data: Uint8Array, output: Uint8Array, level?: number): number
|
|
212
|
+
/** gzip-decompress into a fresh buffer (capped by `maxDecompressed`). */
|
|
213
|
+
gzipDecompress(data: Uint8Array, maxDecompressed?: number): Uint8Array
|
|
214
|
+
/** gzip-decompress into `output`; returns bytes written (throws if too small). */
|
|
215
|
+
gzipDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number): number
|
|
216
|
+
/** brotli-compress into a fresh buffer (quality clamped 0..=11, default 5). */
|
|
217
|
+
brotliCompress(data: Uint8Array, quality?: number): Uint8Array
|
|
218
|
+
/** brotli-compress into `output`; returns bytes written (throws if too small). */
|
|
219
|
+
brotliCompressInto(data: Uint8Array, output: Uint8Array, quality?: number): number
|
|
220
|
+
/** brotli-decompress into a fresh buffer (capped by `maxDecompressed`). */
|
|
221
|
+
brotliDecompress(data: Uint8Array, maxDecompressed?: number): Uint8Array
|
|
222
|
+
/** brotli-decompress into `output`; returns bytes written (throws if too small). */
|
|
223
|
+
brotliDecompressInto(data: Uint8Array, output: Uint8Array, maxDecompressed?: number): number
|
|
224
|
+
|
|
225
|
+
// ── Packed parsers (into caller buffers) ─────────────────────────
|
|
226
|
+
/** HTTP request parse → packed output into `output`; returns bytes written. */
|
|
227
|
+
httpParseRequestPackedInto(input: Uint8Array, output: Uint8Array): number
|
|
228
|
+
/** Query string parse → packed output into `output`; returns bytes written. */
|
|
229
|
+
queryParsePackedInto(input: Uint8Array, output: Uint8Array): number
|
|
230
|
+
/** Cookie header parse → packed output into `output`; returns bytes written. */
|
|
231
|
+
cookieParsePackedInto(input: Uint8Array, output: Uint8Array): number
|
|
232
|
+
|
|
233
|
+
// ── Excluded-surface additions (packed / opaque-handle) ─────────
|
|
234
|
+
/** Sign JWT (HS256) from pre-serialized claim JSON (ttl<=0 → no iat/exp) → string. */
|
|
235
|
+
jwtSignBytes(claimsJson: Uint8Array, secret: Uint8Array, ttl: number, now: number): string
|
|
236
|
+
/** Sign JWT into `output`; returns bytes written (throws if too small). */
|
|
237
|
+
jwtSignBytesInto(
|
|
238
|
+
claimsJson: Uint8Array,
|
|
239
|
+
secret: Uint8Array,
|
|
240
|
+
ttl: number,
|
|
241
|
+
now: number,
|
|
242
|
+
output: Uint8Array,
|
|
243
|
+
): number
|
|
244
|
+
/** Decode a WS frame into packed `[flags][opcode][u32 len][payload]`; null on malformed. */
|
|
245
|
+
wsFrameDecodePacked(data: Uint8Array): Uint8Array | null
|
|
246
|
+
/**
|
|
247
|
+
* Decode a WS frame into `output` (sized ≥ `data.length + 6`); returns bytes
|
|
248
|
+
* written, null on malformed. Pooled sibling of `wsFrameDecodePacked`.
|
|
249
|
+
*/
|
|
250
|
+
wsFrameDecodePackedInto(data: Uint8Array, output: Uint8Array): number | null
|
|
251
|
+
/** Parse multipart/form-data into the packed parts layout. */
|
|
252
|
+
multipartParsePacked(body: Uint8Array, boundary: Uint8Array): Uint8Array
|
|
253
|
+
/** Parse multipart/form-data into `output`; returns bytes written (throws if too small). */
|
|
254
|
+
multipartParsePackedInto(body: Uint8Array, boundary: Uint8Array, output: Uint8Array): number
|
|
255
|
+
/** Parse x-www-form-urlencoded into packed pairs into `output`. */
|
|
256
|
+
formParsePackedInto(input: Uint8Array, output: Uint8Array): number
|
|
257
|
+
/**
|
|
258
|
+
* Verify an HS256 JWT → claims JSON text (cstring return; `null` on invalid
|
|
259
|
+
* signature / expired / malformed). `nowSeconds` is an i64 C arg.
|
|
260
|
+
*/
|
|
261
|
+
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds: number): string | null
|
|
262
|
+
/**
|
|
263
|
+
* Parse JSON → packed token stream bytes (structural, no re-parse). Layout:
|
|
264
|
+
* `[u32 strCount]{[u32 len][utf8]}... [u32 treeLen][tree]` — a deduplicated
|
|
265
|
+
* string table followed by a start/end-marker value tree:
|
|
266
|
+
* `0=null 1=false 2=true 3=number(f64 LE) 4=string(u32 table idx)
|
|
267
|
+
* 5=array start 6=object start 7=array end 8=object end
|
|
268
|
+
* 9=object key(u32 table idx)` (object body `6,(9,keyIdx,value)*,8`).
|
|
269
|
+
* Throws on invalid JSON / too-small output. The scalar wrapper decodes the
|
|
270
|
+
* table once and assembles the value — no second parse.
|
|
271
|
+
*/
|
|
272
|
+
jsonParsePacked(input: Uint8Array): Uint8Array
|
|
273
|
+
/**
|
|
274
|
+
* Parse a Content-Type header → packed verdict bytes:
|
|
275
|
+
* `[u32 mediaTypeLen][mediaType][u32 charsetLen (0xFFFFFFFF = none)][charset]
|
|
276
|
+
* [u32 boundaryLen][boundary][u32 paramCount]{[u32 keyLen][key][u32 valLen][val]}`.
|
|
277
|
+
* Throws on invalid media type / too-small output.
|
|
278
|
+
*/
|
|
279
|
+
parseMediaType(input: Uint8Array): Uint8Array
|
|
280
|
+
/** Parse an IMF-fixdate → unix seconds, or `null` on malformed (packed
|
|
281
|
+
* `[u8 ok][i64 secs LE]` output, decoded here). */
|
|
282
|
+
parseHttpDate(input: Uint8Array): bigint | null
|
|
283
|
+
/**
|
|
284
|
+
* Parse an Accept-Encoding header → packed verdict bytes:
|
|
285
|
+
* `[u32 count]{[u32 encLen][enc][f32 q][u32 order]}` (empty header → count 0).
|
|
286
|
+
* Throws on too-small output.
|
|
287
|
+
*/
|
|
288
|
+
parseAcceptEncoding(input: Uint8Array): Uint8Array
|
|
289
|
+
/** Percent-encode a query from packed pairs (the `packPairs` layout) → string,
|
|
290
|
+
* keys SORTED (matches napi `BTreeMap` ordering); `null` on malformed input. */
|
|
291
|
+
urlEncodeQuery(input: Uint8Array): string | null
|
|
292
|
+
/** RFC 3986 URL resolution (base + reference) → string; `null` on non-UTF-8. */
|
|
293
|
+
urlResolve(base: Uint8Array, reference: Uint8Array): string | null
|
|
294
|
+
/** Resolve a reference against a `UrlBuilder`'s PRECOMPILED base (opaque
|
|
295
|
+
* `inner` handle from its napi `inner_ptr()`) → bytes; throws on a null
|
|
296
|
+
* handle / non-UTF-8 reference / too-small output. */
|
|
297
|
+
urlBuilderResolve(inner: number, reference: Uint8Array): Uint8Array
|
|
298
|
+
/** Extension → MIME type → string (unknown → `application/octet-stream`; `ext` is a `cstring` ARG). */
|
|
299
|
+
mimeFromExtension(ext: string): string | null
|
|
300
|
+
/**
|
|
301
|
+
* 29-byte HTTP-date (`Sun, 06 Nov 1994 08:49:37 GMT`) into `output`; returns
|
|
302
|
+
* bytes written (throws on too-small / out-of-range year). FFI sibling of the
|
|
303
|
+
* napi `httpDateInto` — kills the napi crossing on the hot path.
|
|
304
|
+
*/
|
|
305
|
+
httpDateInto(secs: number, output: Uint8Array): number
|
|
306
|
+
/** 29-byte HTTP-date → string (allocating sibling of httpDateInto). */
|
|
307
|
+
httpDate(secs?: number): string
|
|
308
|
+
/**
|
|
309
|
+
* Encode one SSE event → bytes (fresh buffer). event/id null = line omitted
|
|
310
|
+
* (a present-but-empty string emits the line, matching napi `Option`).
|
|
311
|
+
* FFI sibling of the napi `sse_encode_event` — kills the napi crossing.
|
|
312
|
+
*/
|
|
313
|
+
sseEncodeEvent(
|
|
314
|
+
event: string | null,
|
|
315
|
+
data: Uint8Array,
|
|
316
|
+
id: string | null,
|
|
317
|
+
retry: number | null,
|
|
318
|
+
): Uint8Array
|
|
319
|
+
/**
|
|
320
|
+
* Encode one SSE event into `output` (sized ≥ `data.length + 64`); returns
|
|
321
|
+
* bytes written (throws if too small / invalid UTF-8). Pooled sibling of
|
|
322
|
+
* `sseEncodeEvent` — removes the per-call output-buffer alloc.
|
|
323
|
+
*/
|
|
324
|
+
sseEncodeEventInto(
|
|
325
|
+
event: string | null,
|
|
326
|
+
data: Uint8Array,
|
|
327
|
+
id: string | null,
|
|
328
|
+
retry: number | null,
|
|
329
|
+
output: Uint8Array,
|
|
330
|
+
): number
|
|
331
|
+
/** Random hex token into `output`; returns bytes written (throws if too small). */
|
|
332
|
+
randomTokenInto(byteLen: number, output: Uint8Array): number
|
|
333
|
+
/**
|
|
334
|
+
* Run the ingress pipeline on a packed frame via the opaque inner handle from
|
|
335
|
+
* `Ingress.ingressInnerPtr()` (valid only while the instance is alive — the
|
|
336
|
+
* caller must hold it). Returns bytes written; throws on error / too-small.
|
|
337
|
+
*/
|
|
338
|
+
ingressHandlePacked(
|
|
339
|
+
inner: number,
|
|
340
|
+
input: Uint8Array,
|
|
341
|
+
body: Uint8Array | null,
|
|
342
|
+
output: Uint8Array,
|
|
343
|
+
): number
|
|
344
|
+
/**
|
|
345
|
+
* Run the ingress pipeline from raw request components via the opaque inner
|
|
346
|
+
* handle (valid only while the instance is alive). `url`/`ip` are JS strings
|
|
347
|
+
* passed as `bun:ffi` `cstring` ARGs — the engine transcodes them to
|
|
348
|
+
* call-scoped NUL-terminated UTF-8 buffers in-engine, so the JS side skips
|
|
349
|
+
* the frame assembly + `Buffer.write` encode for the URL/IP. `rid`/`headers`
|
|
350
|
+
* are the packed byte slices; `body` is the request body (or null); `output`
|
|
351
|
+
* receives the packed decision. Same wire format as {@link ingressHandlePacked}.
|
|
352
|
+
* Returns bytes written; throws on error / too-small.
|
|
353
|
+
*/
|
|
354
|
+
ingressHandleComponents(
|
|
355
|
+
inner: number,
|
|
356
|
+
methodKind: number,
|
|
357
|
+
url: string,
|
|
358
|
+
ip: string,
|
|
359
|
+
rid: Uint8Array,
|
|
360
|
+
headers: Uint8Array,
|
|
361
|
+
body: Uint8Array | null,
|
|
362
|
+
output: Uint8Array,
|
|
363
|
+
): number
|
|
364
|
+
/**
|
|
365
|
+
* Write the ingress binary-layout constants (38 × u32 LE — `rust/ffi.rs`
|
|
366
|
+
* `IngressLayout`, numeric source `rust/ingress/output.rs`) into `output`;
|
|
367
|
+
* returns bytes written. Lets `src/ingress/constants.ts` read the layout via
|
|
368
|
+
* bun:ffi on Bun so importing the package does NOT dlopen the napi addon.
|
|
369
|
+
*/
|
|
370
|
+
ingressLayout(out: Uint8Array): number
|
|
371
|
+
|
|
372
|
+
// ── Per-route native stack (`castrum_route_*`) ──────────────────
|
|
373
|
+
/**
|
|
374
|
+
* Compile a route descriptor (the `@ignex/native` route-wire v3 format:
|
|
375
|
+
* magic `ROUT`, version 3, limits, stage tags, draft-07 body schema) into an
|
|
376
|
+
* opaque handle. Returns the handle (`0` = invalid descriptor / compile
|
|
377
|
+
* failure / panic — throws). The handle is owned by the caller and must be
|
|
378
|
+
* released with {@link routeDestroy}.
|
|
379
|
+
*/
|
|
380
|
+
routeCompile(descriptor: Uint8Array): number
|
|
381
|
+
/**
|
|
382
|
+
* Run a compiled route on one request frame, writing the packed verdict
|
|
383
|
+
* (`[flags u32][errorCode u32]` + optional query/cookie pair sections) into
|
|
384
|
+
* `output`. Returns bytes written (`0` = real error — throws; `> output.length`
|
|
385
|
+
* = the EXACT required size — the caller allocates once and retries, per the
|
|
386
|
+
* growExact convention).
|
|
387
|
+
*/
|
|
388
|
+
routeRun(handle: number, frame: Uint8Array, output: Uint8Array): number
|
|
389
|
+
/** Destroy a compiled route handle (frees the native `Box<NativeRoute>`). */
|
|
390
|
+
routeDestroy(handle: number): void
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* bun:ffi transport selection (from `CASTRUM_FFI_MODE`; legacy `RUST_FFI_MODE`
|
|
395
|
+
* accepted):
|
|
396
|
+
*
|
|
397
|
+
* - `auto` (default): use bun:ffi on Bun, silently fall back to napi when the
|
|
398
|
+
* bind or self-test fails (and always on Node).
|
|
399
|
+
* - `ffi`: force bun:ffi on Bun — throw a clear error if it can't bind or the
|
|
400
|
+
* self-test fails (use in benches/CI that MUST run ffi).
|
|
401
|
+
* - `napi`: never bind — every call goes through the napi addon (the fallback;
|
|
402
|
+
* useful for exercising the fallback path on Bun).
|
|
403
|
+
*/
|
|
404
|
+
export type FfiMode = 'auto' | 'ffi' | 'napi'
|
|
405
|
+
|
|
406
|
+
/** Unpacked rate-limit verdict (from the packed
|
|
407
|
+
* `[u8 allowed][u32 remaining LE][i64 reset_ms LE]` C-ABI output). */
|
|
408
|
+
export interface RateLimiterVerdict {
|
|
409
|
+
allowed: boolean
|
|
410
|
+
remaining: number
|
|
411
|
+
resetMs: number
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Raw 2-arg C-ABI symbol signature (`(ptr,len)` pairs → scalar result). */
|
|
415
|
+
export type Raw2 = (a: unknown, b: unknown) => number | bigint
|
|
416
|
+
/** Raw 3-arg C-ABI symbol signature. */
|
|
417
|
+
export type Raw3 = (a: unknown, b: unknown, c: unknown) => number | bigint
|
|
418
|
+
/** Raw 4-arg C-ABI symbol signature. */
|
|
419
|
+
export type Raw4 = (a: unknown, b: unknown, c: unknown, d: unknown) => number | bigint
|
|
420
|
+
/** Raw 5-arg C-ABI symbol signature. */
|
|
421
|
+
export type Raw5 = (a: unknown, b: unknown, c: unknown, d: unknown, e: unknown) => number | bigint
|
|
422
|
+
/** Raw 6-arg C-ABI symbol signature. */
|
|
423
|
+
export type Raw6 = (
|
|
424
|
+
a: unknown,
|
|
425
|
+
b: unknown,
|
|
426
|
+
c: unknown,
|
|
427
|
+
d: unknown,
|
|
428
|
+
e: unknown,
|
|
429
|
+
f: unknown,
|
|
430
|
+
) => number | bigint
|
|
431
|
+
/** Raw 7-arg C-ABI symbol signature. */
|
|
432
|
+
export type Raw7 = (
|
|
433
|
+
a: unknown,
|
|
434
|
+
b: unknown,
|
|
435
|
+
c: unknown,
|
|
436
|
+
d: unknown,
|
|
437
|
+
e: unknown,
|
|
438
|
+
f: unknown,
|
|
439
|
+
g: unknown,
|
|
440
|
+
) => number | bigint
|
|
441
|
+
/** Raw 8-arg C-ABI symbol signature. */
|
|
442
|
+
export type Raw8 = (
|
|
443
|
+
a: unknown,
|
|
444
|
+
b: unknown,
|
|
445
|
+
c: unknown,
|
|
446
|
+
d: unknown,
|
|
447
|
+
e: unknown,
|
|
448
|
+
f: unknown,
|
|
449
|
+
g: unknown,
|
|
450
|
+
h: unknown,
|
|
451
|
+
) => number | bigint
|
|
452
|
+
/** Raw 9-arg C-ABI symbol signature. */
|
|
453
|
+
export type Raw9 = (
|
|
454
|
+
a: unknown,
|
|
455
|
+
b: unknown,
|
|
456
|
+
c: unknown,
|
|
457
|
+
d: unknown,
|
|
458
|
+
e: unknown,
|
|
459
|
+
f: unknown,
|
|
460
|
+
g: unknown,
|
|
461
|
+
h: unknown,
|
|
462
|
+
i: unknown,
|
|
463
|
+
) => number | bigint
|
|
464
|
+
/** Raw 10-arg C-ABI symbol signature. */
|
|
465
|
+
export type Raw10 = (
|
|
466
|
+
a: unknown,
|
|
467
|
+
b: unknown,
|
|
468
|
+
c: unknown,
|
|
469
|
+
d: unknown,
|
|
470
|
+
e: unknown,
|
|
471
|
+
f: unknown,
|
|
472
|
+
g: unknown,
|
|
473
|
+
h: unknown,
|
|
474
|
+
i: unknown,
|
|
475
|
+
j: unknown,
|
|
476
|
+
) => number | bigint
|
|
477
|
+
|
|
478
|
+
/** Raw 12-arg C-ABI symbol signature (`castrum_ingress_handle_components`). */
|
|
479
|
+
export type Raw12 = (
|
|
480
|
+
a: unknown,
|
|
481
|
+
b: unknown,
|
|
482
|
+
c: unknown,
|
|
483
|
+
d: unknown,
|
|
484
|
+
e: unknown,
|
|
485
|
+
f: unknown,
|
|
486
|
+
g: unknown,
|
|
487
|
+
h: unknown,
|
|
488
|
+
i: unknown,
|
|
489
|
+
j: unknown,
|
|
490
|
+
k: unknown,
|
|
491
|
+
l: unknown,
|
|
492
|
+
) => number | bigint
|
|
493
|
+
|
|
494
|
+
/** A raw C-ABI symbol whose return type is `cstring` — the engine clones the
|
|
495
|
+
* null-terminated string at return (`null` = C NULL, i.e. a failure sentinel).
|
|
496
|
+
* Used by the fixed-size ASCII writers (ws_accept_key, etag, random_token,
|
|
497
|
+
* sign/verify_cookie, csrf_token, jwt_sign_bytes).
|
|
498
|
+
*/
|
|
499
|
+
export type RawCStr = (...a: unknown[]) => string | null
|