castrum 0.9.0 → 0.9.1
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 +1374 -1772
- package/dist/index.js +5570 -4169
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +106 -14
- package/docs/BENCHMARKS.md +83 -47
- package/docs/CASE_STUDY.md +240 -0
- package/docs/ENVIRONMENT.md +8 -6
- package/docs/FFI_BUN_GUIDE.md +327 -0
- package/docs/GETTING_STARTED.md +15 -32
- package/docs/INGRESS-ROUTER.md +81 -0
- package/docs/INGRESS.md +5 -4
- package/docs/NATIVE-ROUTE.md +53 -0
- package/docs/REPO_MAP.md +46 -26
- package/docs/adr/0003-bun-builtins-delegation.md +6 -5
- package/docs/bun-builtins-decision-matrix.md +25 -18
- package/index.ts +9 -16
- package/package.json +36 -24
- package/src/ingress/body.ts +167 -27
- package/src/ingress/handlers.ts +91 -15
- package/src/ingress/health.ts +1 -0
- package/src/ingress/index.ts +39 -27
- package/src/ingress/packing/gather-raw-headers.ts +85 -21
- package/src/ingress/packing/header-packing.ts +5 -5
- package/src/ingress/packing/input-packer.ts +17 -3
- package/src/ingress/packing/select-headers.ts +12 -6
- package/src/ingress/router.ts +237 -0
- package/src/ingress/routes/common.ts +42 -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 +7 -31
- 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 +6 -6
- package/src/ingress/server.ts +45 -13
- package/src/ingress/types.ts +35 -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/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +729 -0
- package/src/native/ffi/types.ts +474 -0
- package/src/native/ffi.ts +1063 -829
- package/src/native/loader.ts +52 -6
- package/src/native/types/instances.ts +274 -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 +2 -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 +253 -27
- package/src/rust-ffi/scalar/hashing.ts +43 -41
- package/src/rust-ffi/scalar/http.ts +208 -77
- package/src/rust-ffi/scalar/index.ts +3 -3
- package/src/rust-ffi/scalar/interface.ts +88 -213
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +28 -12
- package/src/rust-ffi/scalar/payload.ts +159 -42
- package/src/rust-ffi/text.ts +31 -5
- package/src/selection.ts +27 -31
- package/src/shared/adaptive.ts +1 -2
- package/src/shared/buffer-pool.ts +72 -17
- package/src/shared/bytes.ts +75 -6
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +4 -4
- package/src/shared/log.ts +1 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/{packed.ts → packed/wire.ts} +31 -39
- package/src/shared/proven.ts +189 -496
- package/src/shared/runtime.ts +20 -25
- package/src/shared/uuid.ts +12 -28
- package/src/data/json-rows.ts +0 -33
- package/src/rust-ffi/batch.ts +0 -431
- package/src/shared/bench-classify.ts +0 -209
package/src/loader/index.ts
CHANGED
|
@@ -18,247 +18,58 @@
|
|
|
18
18
|
// - caching → bounded LRU ("Hot Function Cache"), default key =
|
|
19
19
|
// fnv1a64(input) (skipped for sustained-unique workloads),
|
|
20
20
|
// opt-in per call via `{ key, cache }`
|
|
21
|
+
//
|
|
22
|
+
// Structure: this file is the DISPATCH CORE + factory. The pure pieces live
|
|
23
|
+
// beside it: `types.ts` (public type surface), `state.ts` (internal dispatch
|
|
24
|
+
// state), `ops.ts` (op registry), `cost.ts` (adaptive cost model), `batch.ts`
|
|
25
|
+
// (LRU cache + tick coalescer). Public types are re-exported below.
|
|
21
26
|
|
|
27
|
+
import { rust } from '../rust-ffi'
|
|
28
|
+
import { encoder, viewForArrayBuffer } from '../shared/bytes'
|
|
29
|
+
import type { SchemaValidator } from '../shared/packed'
|
|
30
|
+
import {
|
|
31
|
+
createLruCache,
|
|
32
|
+
createTickCoalescer,
|
|
33
|
+
type LoadRequest,
|
|
34
|
+
} from './batch'
|
|
35
|
+
import { createCostModel, nowNs } from './cost'
|
|
22
36
|
import {
|
|
23
|
-
LOADER_OPS,
|
|
24
37
|
fnvBatchKeysInto,
|
|
25
|
-
|
|
26
|
-
type LoaderOpArgs,
|
|
38
|
+
LOADER_OPS,
|
|
27
39
|
type LoaderOpName,
|
|
28
40
|
type LoaderOpSpec,
|
|
29
41
|
type LoaderScalar,
|
|
30
|
-
type LoaderBulk,
|
|
31
42
|
} from './ops'
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
import type { LoaderState, OpDispatchCtx, OpFnEntry } from './state'
|
|
44
|
+
import { KEY_SEP } from './state'
|
|
45
|
+
import type {
|
|
46
|
+
LoadableName,
|
|
47
|
+
Loader,
|
|
48
|
+
LoaderOpFn,
|
|
49
|
+
LoaderOpStats,
|
|
50
|
+
LoaderOptions,
|
|
51
|
+
LoaderSchemaFn,
|
|
52
|
+
LoaderStats,
|
|
53
|
+
LoadOptions,
|
|
54
|
+
} from './types'
|
|
43
55
|
|
|
44
56
|
// Re-export the op registry + types for introspection and authoring.
|
|
45
57
|
export {
|
|
46
|
-
|
|
58
|
+
type BulkResult,
|
|
47
59
|
LOADER_OP_NAMES,
|
|
48
|
-
|
|
49
|
-
type LoaderOpArgs,
|
|
50
|
-
type LoaderBulkArgs,
|
|
60
|
+
LOADER_OPS,
|
|
51
61
|
type LoaderBulk,
|
|
52
|
-
type
|
|
62
|
+
type LoaderBulkArgs,
|
|
63
|
+
type LoaderOpArgs,
|
|
64
|
+
type LoaderOpName,
|
|
53
65
|
type LoaderResultKind,
|
|
66
|
+
type LoaderScalar,
|
|
54
67
|
type ScalarResult,
|
|
55
|
-
type BulkResult,
|
|
56
68
|
} from './ops'
|
|
57
69
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export interface LoaderOptions {
|
|
62
|
-
/** Let each op adapt `batchMin` from measured dispatch cost. Default true. */
|
|
63
|
-
adaptive?: boolean
|
|
64
|
-
/** Initial batch threshold in [2, 8]. Default 2 (batch wins for n >= 2). */
|
|
65
|
-
batchMin?: number
|
|
66
|
-
/** Max cached keys (bounded LRU). 0 disables caching. Default 256. */
|
|
67
|
-
maxCacheKeys?: number
|
|
68
|
-
/** Default `cache` behavior for `load()`. Default true. */
|
|
69
|
-
caching?: boolean
|
|
70
|
-
/** Sample timing every N dispatches (hot-path cost control). Default 32. */
|
|
71
|
-
sampleEvery?: number
|
|
72
|
-
/**
|
|
73
|
-
* `load()` dispatch strategy. `"auto"` (default) switches each op between
|
|
74
|
-
* `"single"` (low load → direct scalar call, no coalescer) and
|
|
75
|
-
* `"coalesce"` (rising load → one packed batch call) from observed load.
|
|
76
|
-
*/
|
|
77
|
-
loadStrategy?: LoadStrategy
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Options for `load()` / `opFn.load()`. */
|
|
81
|
-
export interface LoadOptions {
|
|
82
|
-
/**
|
|
83
|
-
* Explicit cache key. When omitted (and caching is on) the default key is
|
|
84
|
-
* `fnv1a64(input)` — which costs one extra native call per load.
|
|
85
|
-
*/
|
|
86
|
-
key?: string
|
|
87
|
-
/** Override the loader-wide caching toggle for this call. Default true. */
|
|
88
|
-
cache?: boolean
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Per-op counters exposed on `opFn.stats`. */
|
|
92
|
-
export interface LoaderOpStats {
|
|
93
|
-
scalarCalls: number
|
|
94
|
-
batchCalls: number
|
|
95
|
-
itemsDispatched: number
|
|
96
|
-
cachedHits: number
|
|
97
|
-
/** Current learned batch threshold for this op. */
|
|
98
|
-
batchMin: number
|
|
99
|
-
/** Current `load()` strategy: "single" (low load) or "coalesce" (bulk). */
|
|
100
|
-
mode: 'single' | 'coalesce'
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Aggregate loader statistics. */
|
|
104
|
-
export interface LoaderStats {
|
|
105
|
-
scalarCalls: number
|
|
106
|
-
batchCalls: number
|
|
107
|
-
itemsDispatched: number
|
|
108
|
-
flushes: number
|
|
109
|
-
cachedHits: number
|
|
110
|
-
cacheSize: number
|
|
111
|
-
cacheEvictions: number
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** A specialized, pre-bound op function returned by `loader(op)`. */
|
|
115
|
-
export interface LoaderOpFn<K extends LoaderOpName> {
|
|
116
|
-
/** Single item → scalar result. */
|
|
117
|
-
(input: Uint8Array, ...rest: LoaderOpArgs[K]): LoaderScalar<K>
|
|
118
|
-
/** Bulk items → ONE packed batch call; same shape as `rust.batch.<op>`. */
|
|
119
|
-
(inputs: Uint8Array[], ...rest: LoaderBulkArgs<K>): LoaderBulk<K>
|
|
120
|
-
readonly name: K
|
|
121
|
-
readonly stats: LoaderOpStats
|
|
122
|
-
clear(): void
|
|
123
|
-
/** Sync cache peek (default key `fnv1a64(input)` unless `key` given). */
|
|
124
|
-
cache(input: Uint8Array, key?: string): LoaderScalar<K> | undefined
|
|
125
|
-
/**
|
|
126
|
-
* Coalesced, cache-aware load. Unavailable (`undefined`) on ops that require
|
|
127
|
-
* extra arguments (e.g. `hmacSha256`, `signCookie`) — use `run` for those.
|
|
128
|
-
*/
|
|
129
|
-
load: K extends LoadableName ? LoaderLoadFn<K> : undefined
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
type LoaderLoadFn<K extends LoaderOpName> = (
|
|
133
|
-
input: Uint8Array,
|
|
134
|
-
opts?: LoadOptions,
|
|
135
|
-
) => Promise<LoaderScalar<K>>
|
|
136
|
-
|
|
137
|
-
/** Ops whose extra args are all optional/empty → they support `load()`. */
|
|
138
|
-
type LoadableName = {
|
|
139
|
-
[K in LoaderOpName]: LoaderOpArgs[K] extends
|
|
140
|
-
| []
|
|
141
|
-
| [unknown?]
|
|
142
|
-
| [unknown?, unknown?]
|
|
143
|
-
| [unknown?, unknown?, unknown?]
|
|
144
|
-
? K
|
|
145
|
-
: never
|
|
146
|
-
}[LoaderOpName]
|
|
147
|
-
|
|
148
|
-
/** A validator-bound helper returned by `loader.schema(validator)`. */
|
|
149
|
-
export interface LoaderSchemaFn {
|
|
150
|
-
/** Single JSON document → valid? */
|
|
151
|
-
(input: Uint8Array): boolean
|
|
152
|
-
/** Bulk JSON documents → bitset (1 per doc). */
|
|
153
|
-
(inputs: Uint8Array[]): Uint8Array
|
|
154
|
-
/** Count how many bulk documents are valid. */
|
|
155
|
-
count(inputs: Uint8Array[]): number
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** The global callable loader. */
|
|
159
|
-
export interface Loader {
|
|
160
|
-
/** Higher-order function: `loader(op)` → specialized hot op function. */
|
|
161
|
-
<K extends LoaderOpName>(op: K): LoaderOpFn<K>
|
|
162
|
-
/** Single-item dispatch. */
|
|
163
|
-
run<K extends LoaderOpName>(op: K, input: Uint8Array, ...rest: LoaderOpArgs[K]): LoaderScalar<K>
|
|
164
|
-
/** Bulk dispatch (one packed batch call, or adaptive scalar loop). */
|
|
165
|
-
run<K extends LoaderOpName>(
|
|
166
|
-
op: K,
|
|
167
|
-
inputs: Uint8Array[],
|
|
168
|
-
...rest: LoaderBulkArgs<K>
|
|
169
|
-
): LoaderBulk<K>
|
|
170
|
-
/** Coalesced, cache-aware load (ops with required extra args throw). */
|
|
171
|
-
load<K extends LoadableName>(
|
|
172
|
-
op: K,
|
|
173
|
-
input: Uint8Array,
|
|
174
|
-
opts?: LoadOptions,
|
|
175
|
-
): Promise<LoaderScalar<K>>
|
|
176
|
-
/** Sync cache peek. */
|
|
177
|
-
cache<K extends LoaderOpName>(op: K, input: Uint8Array, key?: string): LoaderScalar<K> | undefined
|
|
178
|
-
/**
|
|
179
|
-
* Bind a `SchemaValidator` for repeated single/bulk JSON-schema validation
|
|
180
|
-
* plus a whole-batch `count`. The returned callable uses the loader's normal
|
|
181
|
-
* dispatch machinery (cost model + counters).
|
|
182
|
-
*/
|
|
183
|
-
schema(validator: SchemaValidator): LoaderSchemaFn
|
|
184
|
-
/** Clear the shared cache (pending coalesced loads still flush). */
|
|
185
|
-
clear(): void
|
|
186
|
-
/** Update options at runtime. */
|
|
187
|
-
configure(options: LoaderOptions): void
|
|
188
|
-
readonly stats: LoaderStats
|
|
189
|
-
readonly opNames: LoaderOpName[]
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ── Internal state ──────────────────────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
interface OpCounters {
|
|
195
|
-
scalarCalls: number
|
|
196
|
-
batchCalls: number
|
|
197
|
-
itemsDispatched: number
|
|
198
|
-
cachedHits: number
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Load-dispatch strategy selector for `load()`. */
|
|
202
|
-
type LoadStrategy = 'auto' | 'single' | 'coalesce'
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Pre-bound per-op dispatch context — avoids Map lookups on the hot path.
|
|
206
|
-
* Holds the load-aware `load()` strategy plus its cheap integer signals so
|
|
207
|
-
* the single-vs-coalesce decision costs no Map lookup or allocation.
|
|
208
|
-
*/
|
|
209
|
-
interface OpDispatchCtx {
|
|
210
|
-
cost: LoaderCostModel
|
|
211
|
-
counters: OpCounters
|
|
212
|
-
sampleEvery: number
|
|
213
|
-
/** Per-op sample tick counter (advances on every dispatch). */
|
|
214
|
-
counter: number
|
|
215
|
-
/** Current `load()` strategy for this op. */
|
|
216
|
-
mode: 'single' | 'coalesce'
|
|
217
|
-
/** Consecutive single-load flushes observed (coalesce mode only). */
|
|
218
|
-
singleStreak: number
|
|
219
|
-
/** A single-mode load is waiting on its microtask to dispatch. */
|
|
220
|
-
pendingSingle: boolean
|
|
221
|
-
pendingInput: Uint8Array | null
|
|
222
|
-
pendingResolve: ((value: unknown) => void) | null
|
|
223
|
-
pendingReject: ((error: unknown) => void) | null
|
|
224
|
-
pendingKey: string | undefined
|
|
225
|
-
/** Default-key attempts (adaptive key computation). */
|
|
226
|
-
keyAttempts: number
|
|
227
|
-
/** Default-key cache hits observed. */
|
|
228
|
-
keyHits: number
|
|
229
|
-
/** Periodic re-probe counter for skipped default keys. */
|
|
230
|
-
keyProbeCounter: number
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** A memoized op fn plus a hook to rebind its dispatch context on configure. */
|
|
234
|
-
interface OpFnEntry {
|
|
235
|
-
op: LoaderOpName
|
|
236
|
-
fn: LoaderOpFn<LoaderOpName>
|
|
237
|
-
/** Rebind to a fresh context after `configure()` so dispatch + stats stay current. */
|
|
238
|
-
rebind(ctx: OpDispatchCtx): void
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
interface LoaderState {
|
|
242
|
-
options: Required<
|
|
243
|
-
Pick<
|
|
244
|
-
LoaderOptions,
|
|
245
|
-
'adaptive' | 'batchMin' | 'maxCacheKeys' | 'caching' | 'sampleEvery' | 'loadStrategy'
|
|
246
|
-
>
|
|
247
|
-
>
|
|
248
|
-
ctxs: Map<LoaderOpName, OpDispatchCtx>
|
|
249
|
-
cache: LoaderCache
|
|
250
|
-
coalescer: TickCoalescer
|
|
251
|
-
opFns: Map<LoaderOpName, OpFnEntry>
|
|
252
|
-
flushes: number
|
|
253
|
-
/** Reusable packed-input scratch for the zero-alloc fast flush (grows). */
|
|
254
|
-
packScratch: Uint8Array
|
|
255
|
-
/** Reusable packed-output buffer for the zero-alloc fast flush (grows). */
|
|
256
|
-
outScratch: Uint8Array
|
|
257
|
-
/** Reusable packed-key output buffer for the batched default-key flush. */
|
|
258
|
-
keyScratch: Uint8Array
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const KEY_SEP = '\u0000'
|
|
70
|
+
// Re-export the public type surface so `import type { LoaderOptions } from
|
|
71
|
+
// '../loader'` keeps working (existing call sites).
|
|
72
|
+
export * from './types'
|
|
262
73
|
|
|
263
74
|
// ── Load-aware `load()` strategy + adaptive-key tuning ──
|
|
264
75
|
/** Consecutive single-load flushes before an op drops the coalescer. */
|
|
@@ -268,7 +79,6 @@ const KEY_WARMUP = 8
|
|
|
268
79
|
/** After warm-up with zero hits, re-probe the default key every N loads. */
|
|
269
80
|
const KEY_PROBE_EVERY = 64
|
|
270
81
|
|
|
271
|
-
/** Get (or lazily create) the dispatch context for an op. */
|
|
272
82
|
function ctxFor(state: LoaderState, op: LoaderOpName): OpDispatchCtx {
|
|
273
83
|
let ctx = state.ctxs.get(op)
|
|
274
84
|
if (!ctx) {
|
|
@@ -299,6 +109,11 @@ function ctxFor(state: LoaderState, op: LoaderOpName): OpDispatchCtx {
|
|
|
299
109
|
return ctx
|
|
300
110
|
}
|
|
301
111
|
|
|
112
|
+
/** Normalize a loader scalar result to bytes (the string-returning rust.* ops
|
|
113
|
+
* return strings on the Bun path — the loader's BYTES contract stays bytes). */
|
|
114
|
+
const asBytes = (v: unknown): Uint8Array =>
|
|
115
|
+
typeof v === 'string' ? encoder.encode(v) : (v as Uint8Array)
|
|
116
|
+
|
|
302
117
|
/** Build a bulk-shaped result by looping scalar calls (the adaptive fallback). */
|
|
303
118
|
function scalarLoopBulk(spec: LoaderOpSpec, items: Uint8Array[], rest: unknown[]): unknown {
|
|
304
119
|
const n = items.length
|
|
@@ -345,7 +160,7 @@ function scalarLoopBulk(spec: LoaderOpSpec, items: Uint8Array[], rest: unknown[]
|
|
|
345
160
|
const out = new Array<Uint8Array>(n)
|
|
346
161
|
let i = 0
|
|
347
162
|
for (const item of items) {
|
|
348
|
-
out[i] = spec.scalar(item, ...itemRest(i))
|
|
163
|
+
out[i] = asBytes(spec.scalar(item, ...itemRest(i)))
|
|
349
164
|
i++
|
|
350
165
|
}
|
|
351
166
|
return out
|
|
@@ -364,7 +179,7 @@ function dispatchSingle(
|
|
|
364
179
|
c.scalarCalls++
|
|
365
180
|
c.itemsDispatched++
|
|
366
181
|
const t = ++ctx.counter % ctx.sampleEvery === 0 ? nowNs() : 0
|
|
367
|
-
const out = rest.length === 0 ? spec.scalar(input) : spec.scalar(input, ...rest)
|
|
182
|
+
const out = asBytes(rest.length === 0 ? spec.scalar(input) : spec.scalar(input, ...rest))
|
|
368
183
|
if (t !== 0) ctx.cost.recordScalar(1, nowNs() - t)
|
|
369
184
|
return out
|
|
370
185
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/loader/state.ts — internal loader dispatch state (pure types).
|
|
2
|
+
//
|
|
3
|
+
// The per-op dispatch context, memoized op-fn entry, and the aggregate
|
|
4
|
+
// `LoaderState` shared by the dispatch core (index.ts). `KEY_SEP` is the
|
|
5
|
+
// cache-key separator for `op + '\0' + key`.
|
|
6
|
+
|
|
7
|
+
import type { LoaderCache, TickCoalescer } from './batch'
|
|
8
|
+
import type { LoaderCostModel } from './cost'
|
|
9
|
+
import type { LoaderOpName } from './ops'
|
|
10
|
+
import type { LoaderOpFn, LoaderOptions } from './types'
|
|
11
|
+
|
|
12
|
+
// ── Internal state ──────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/** Per-op dispatch counters: scalar calls, batch calls, items dispatched, and
|
|
15
|
+
* cache hits. Advanced on every dispatch and exposed via `loader.stats()`. */
|
|
16
|
+
export interface OpCounters {
|
|
17
|
+
scalarCalls: number
|
|
18
|
+
batchCalls: number
|
|
19
|
+
itemsDispatched: number
|
|
20
|
+
cachedHits: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pre-bound per-op dispatch context — avoids Map lookups on the hot path.
|
|
25
|
+
* Holds the load-aware `load()` strategy plus its cheap integer signals so
|
|
26
|
+
* the single-vs-coalesce decision costs no Map lookup or allocation.
|
|
27
|
+
*/
|
|
28
|
+
export interface OpDispatchCtx {
|
|
29
|
+
cost: LoaderCostModel
|
|
30
|
+
counters: OpCounters
|
|
31
|
+
sampleEvery: number
|
|
32
|
+
/** Per-op sample tick counter (advances on every dispatch). */
|
|
33
|
+
counter: number
|
|
34
|
+
/** Current `load()` strategy for this op. */
|
|
35
|
+
mode: 'single' | 'coalesce'
|
|
36
|
+
/** Consecutive single-load flushes observed (coalesce mode only). */
|
|
37
|
+
singleStreak: number
|
|
38
|
+
/** A single-mode load is waiting on its microtask to dispatch. */
|
|
39
|
+
pendingSingle: boolean
|
|
40
|
+
pendingInput: Uint8Array | null
|
|
41
|
+
pendingResolve: ((value: unknown) => void) | null
|
|
42
|
+
pendingReject: ((error: unknown) => void) | null
|
|
43
|
+
pendingKey: string | undefined
|
|
44
|
+
/** Default-key attempts (adaptive key computation). */
|
|
45
|
+
keyAttempts: number
|
|
46
|
+
/** Default-key cache hits observed. */
|
|
47
|
+
keyHits: number
|
|
48
|
+
/** Periodic re-probe counter for skipped default keys. */
|
|
49
|
+
keyProbeCounter: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A memoized op fn plus a hook to rebind its dispatch context on configure. */
|
|
53
|
+
export interface OpFnEntry {
|
|
54
|
+
op: LoaderOpName
|
|
55
|
+
fn: LoaderOpFn<LoaderOpName>
|
|
56
|
+
/** Rebind to a fresh context after `configure()` so dispatch + stats stay current. */
|
|
57
|
+
rebind(ctx: OpDispatchCtx): void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Aggregate loader state shared by the dispatch core (index.ts): the resolved
|
|
61
|
+
* options, per-op dispatch contexts, the LRU cache + tick coalescer, memoized
|
|
62
|
+
* op fns, and the reusable zero-alloc pack/out/key scratch buffers. */
|
|
63
|
+
export interface LoaderState {
|
|
64
|
+
options: Required<
|
|
65
|
+
Pick<
|
|
66
|
+
LoaderOptions,
|
|
67
|
+
'adaptive' | 'batchMin' | 'maxCacheKeys' | 'caching' | 'sampleEvery' | 'loadStrategy'
|
|
68
|
+
>
|
|
69
|
+
>
|
|
70
|
+
ctxs: Map<LoaderOpName, OpDispatchCtx>
|
|
71
|
+
cache: LoaderCache
|
|
72
|
+
coalescer: TickCoalescer
|
|
73
|
+
opFns: Map<LoaderOpName, OpFnEntry>
|
|
74
|
+
flushes: number
|
|
75
|
+
/** Reusable packed-input scratch for the zero-alloc fast flush (grows). */
|
|
76
|
+
packScratch: Uint8Array
|
|
77
|
+
/** Reusable packed-output buffer for the zero-alloc fast flush (grows). */
|
|
78
|
+
outScratch: Uint8Array
|
|
79
|
+
/** Reusable packed-key output buffer for the batched default-key flush. */
|
|
80
|
+
keyScratch: Uint8Array
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** NUL separator used to join composite cache keys (never appears in keys). */
|
|
84
|
+
export const KEY_SEP = '\u0000'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// src/loader/types.ts — public loader type surface (pure types).
|
|
2
|
+
//
|
|
3
|
+
// The callable loader contract (`Loader`), its option/stats shapes, the
|
|
4
|
+
// specialized op-function type (`LoaderOpFn`), and the `load()` helpers.
|
|
5
|
+
// No runtime code — erased at compile time.
|
|
6
|
+
|
|
7
|
+
import type { SchemaValidator } from '../shared/packed'
|
|
8
|
+
import type { LoaderBulk, LoaderBulkArgs, LoaderOpArgs, LoaderOpName, LoaderScalar } from './ops'
|
|
9
|
+
|
|
10
|
+
// ── Public types ────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/** Options for `createLoader()` / `loader.configure()`. */
|
|
13
|
+
export interface LoaderOptions {
|
|
14
|
+
/** Let each op adapt `batchMin` from measured dispatch cost. Default true. */
|
|
15
|
+
adaptive?: boolean
|
|
16
|
+
/** Initial batch threshold in [2, 8]. Default 2 (batch wins for n >= 2). */
|
|
17
|
+
batchMin?: number
|
|
18
|
+
/** Max cached keys (bounded LRU). 0 disables caching. Default 256. */
|
|
19
|
+
maxCacheKeys?: number
|
|
20
|
+
/** Default `cache` behavior for `load()`. Default true. */
|
|
21
|
+
caching?: boolean
|
|
22
|
+
/** Sample timing every N dispatches (hot-path cost control). Default 32. */
|
|
23
|
+
sampleEvery?: number
|
|
24
|
+
/**
|
|
25
|
+
* `load()` dispatch strategy. `"auto"` (default) switches each op between
|
|
26
|
+
* `"single"` (low load → direct scalar call, no coalescer) and
|
|
27
|
+
* `"coalesce"` (rising load → one packed batch call) from observed load.
|
|
28
|
+
*/
|
|
29
|
+
loadStrategy?: LoadStrategy
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Options for `load()` / `opFn.load()`. */
|
|
33
|
+
export interface LoadOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Explicit cache key. When omitted (and caching is on) the default key is
|
|
36
|
+
* `fnv1a64(input)` — which costs one extra native call per load.
|
|
37
|
+
*/
|
|
38
|
+
key?: string
|
|
39
|
+
/** Override the loader-wide caching toggle for this call. Default true. */
|
|
40
|
+
cache?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Per-op counters exposed on `opFn.stats`. */
|
|
44
|
+
export interface LoaderOpStats {
|
|
45
|
+
scalarCalls: number
|
|
46
|
+
batchCalls: number
|
|
47
|
+
itemsDispatched: number
|
|
48
|
+
cachedHits: number
|
|
49
|
+
/** Current learned batch threshold for this op. */
|
|
50
|
+
batchMin: number
|
|
51
|
+
/** Current `load()` strategy: "single" (low load) or "coalesce" (bulk). */
|
|
52
|
+
mode: 'single' | 'coalesce'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Aggregate loader statistics. */
|
|
56
|
+
export interface LoaderStats {
|
|
57
|
+
scalarCalls: number
|
|
58
|
+
batchCalls: number
|
|
59
|
+
itemsDispatched: number
|
|
60
|
+
flushes: number
|
|
61
|
+
cachedHits: number
|
|
62
|
+
cacheSize: number
|
|
63
|
+
cacheEvictions: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A specialized, pre-bound op function returned by `loader(op)`. */
|
|
67
|
+
export interface LoaderOpFn<K extends LoaderOpName> {
|
|
68
|
+
/** Single item → scalar result. */
|
|
69
|
+
(input: Uint8Array, ...rest: LoaderOpArgs[K]): LoaderScalar<K>
|
|
70
|
+
/** Bulk items → ONE packed batch call; same shape as `rust.batch.<op>`. */
|
|
71
|
+
(inputs: Uint8Array[], ...rest: LoaderBulkArgs<K>): LoaderBulk<K>
|
|
72
|
+
readonly name: K
|
|
73
|
+
readonly stats: LoaderOpStats
|
|
74
|
+
clear(): void
|
|
75
|
+
/** Sync cache peek (default key `fnv1a64(input)` unless `key` given). */
|
|
76
|
+
cache(input: Uint8Array, key?: string): LoaderScalar<K> | undefined
|
|
77
|
+
/**
|
|
78
|
+
* Coalesced, cache-aware load. Unavailable (`undefined`) on ops that require
|
|
79
|
+
* extra arguments (e.g. `hmacSha256`, `signCookie`) — use `run` for those.
|
|
80
|
+
*/
|
|
81
|
+
load: K extends LoadableName ? LoaderLoadFn<K> : undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type LoaderLoadFn<K extends LoaderOpName> = (
|
|
85
|
+
input: Uint8Array,
|
|
86
|
+
opts?: LoadOptions,
|
|
87
|
+
) => Promise<LoaderScalar<K>>
|
|
88
|
+
|
|
89
|
+
/** Ops whose extra args are all optional/empty → they support `load()`. */
|
|
90
|
+
export type LoadableName = {
|
|
91
|
+
[K in LoaderOpName]: LoaderOpArgs[K] extends
|
|
92
|
+
| []
|
|
93
|
+
| [unknown?]
|
|
94
|
+
| [unknown?, unknown?]
|
|
95
|
+
| [unknown?, unknown?, unknown?]
|
|
96
|
+
? K
|
|
97
|
+
: never
|
|
98
|
+
}[LoaderOpName]
|
|
99
|
+
|
|
100
|
+
/** A validator-bound helper returned by `loader.schema(validator)`. */
|
|
101
|
+
export interface LoaderSchemaFn {
|
|
102
|
+
/** Single JSON document → valid? */
|
|
103
|
+
(input: Uint8Array): boolean
|
|
104
|
+
/** Bulk JSON documents → bitset (1 per doc). */
|
|
105
|
+
(inputs: Uint8Array[]): Uint8Array
|
|
106
|
+
/** Count how many bulk documents are valid. */
|
|
107
|
+
count(inputs: Uint8Array[]): number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The global callable loader. */
|
|
111
|
+
export interface Loader {
|
|
112
|
+
/** Higher-order function: `loader(op)` → specialized hot op function. */
|
|
113
|
+
<K extends LoaderOpName>(op: K): LoaderOpFn<K>
|
|
114
|
+
/** Single-item dispatch. */
|
|
115
|
+
run<K extends LoaderOpName>(op: K, input: Uint8Array, ...rest: LoaderOpArgs[K]): LoaderScalar<K>
|
|
116
|
+
/** Bulk dispatch (one packed batch call, or adaptive scalar loop). */
|
|
117
|
+
run<K extends LoaderOpName>(
|
|
118
|
+
op: K,
|
|
119
|
+
inputs: Uint8Array[],
|
|
120
|
+
...rest: LoaderBulkArgs<K>
|
|
121
|
+
): LoaderBulk<K>
|
|
122
|
+
/** Coalesced, cache-aware load (ops with required extra args throw). */
|
|
123
|
+
load<K extends LoadableName>(
|
|
124
|
+
op: K,
|
|
125
|
+
input: Uint8Array,
|
|
126
|
+
opts?: LoadOptions,
|
|
127
|
+
): Promise<LoaderScalar<K>>
|
|
128
|
+
/** Sync cache peek. */
|
|
129
|
+
cache<K extends LoaderOpName>(op: K, input: Uint8Array, key?: string): LoaderScalar<K> | undefined
|
|
130
|
+
/**
|
|
131
|
+
* Bind a `SchemaValidator` for repeated single/bulk JSON-schema validation
|
|
132
|
+
* plus a whole-batch `count`. The returned callable uses the loader's normal
|
|
133
|
+
* dispatch machinery (cost model + counters).
|
|
134
|
+
*/
|
|
135
|
+
schema(validator: SchemaValidator): LoaderSchemaFn
|
|
136
|
+
/** Clear the shared cache (pending coalesced loads still flush). */
|
|
137
|
+
clear(): void
|
|
138
|
+
/** Update options at runtime. */
|
|
139
|
+
configure(options: LoaderOptions): void
|
|
140
|
+
readonly stats: LoaderStats
|
|
141
|
+
readonly opNames: LoaderOpName[]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Load-dispatch strategy selector for `load()`. */
|
|
145
|
+
export type LoadStrategy = 'auto' | 'single' | 'coalesce'
|
|
@@ -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)
|