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.
Files changed (102) hide show
  1. package/README.md +153 -630
  2. package/castrum.linux-x64-gnu.node +0 -0
  3. package/dist/index.d.ts +1374 -1772
  4. package/dist/index.js +5570 -4169
  5. package/docs/API.md +337 -0
  6. package/docs/ARCHITECTURE.md +106 -14
  7. package/docs/BENCHMARKS.md +83 -47
  8. package/docs/CASE_STUDY.md +240 -0
  9. package/docs/ENVIRONMENT.md +8 -6
  10. package/docs/FFI_BUN_GUIDE.md +327 -0
  11. package/docs/GETTING_STARTED.md +15 -32
  12. package/docs/INGRESS-ROUTER.md +81 -0
  13. package/docs/INGRESS.md +5 -4
  14. package/docs/NATIVE-ROUTE.md +53 -0
  15. package/docs/REPO_MAP.md +46 -26
  16. package/docs/adr/0003-bun-builtins-delegation.md +6 -5
  17. package/docs/bun-builtins-decision-matrix.md +25 -18
  18. package/index.ts +9 -16
  19. package/package.json +36 -24
  20. package/src/ingress/body.ts +167 -27
  21. package/src/ingress/handlers.ts +91 -15
  22. package/src/ingress/health.ts +1 -0
  23. package/src/ingress/index.ts +39 -27
  24. package/src/ingress/packing/gather-raw-headers.ts +85 -21
  25. package/src/ingress/packing/header-packing.ts +5 -5
  26. package/src/ingress/packing/input-packer.ts +17 -3
  27. package/src/ingress/packing/select-headers.ts +12 -6
  28. package/src/ingress/router.ts +237 -0
  29. package/src/ingress/routes/common.ts +42 -1
  30. package/src/ingress/routes/fallback.ts +2 -2
  31. package/src/ingress/routes/head.ts +3 -13
  32. package/src/ingress/routes/index.ts +5 -4
  33. package/src/ingress/routes/json-write.ts +7 -31
  34. package/src/ingress/routes/options.ts +2 -12
  35. package/src/ingress/routes/read.ts +3 -13
  36. package/src/ingress/routes/responder.ts +159 -0
  37. package/src/ingress/server-node.ts +6 -6
  38. package/src/ingress/server.ts +45 -13
  39. package/src/ingress/types.ts +35 -1
  40. package/src/integration/pipeline.ts +3 -0
  41. package/src/integration/streaming.ts +2 -3
  42. package/src/integration/websocket.ts +4 -3
  43. package/src/loader/batch.ts +4 -0
  44. package/src/loader/cost.ts +1 -0
  45. package/src/loader/index.ts +43 -228
  46. package/src/loader/state.ts +84 -0
  47. package/src/loader/types.ts +145 -0
  48. package/src/native/ffi/constants.ts +58 -0
  49. package/src/native/ffi/selftest.ts +729 -0
  50. package/src/native/ffi/types.ts +474 -0
  51. package/src/native/ffi.ts +1063 -829
  52. package/src/native/loader.ts +52 -6
  53. package/src/native/types/instances.ts +274 -0
  54. package/src/native/types.ts +41 -238
  55. package/src/runtime/builtins.ts +131 -0
  56. package/src/runtime/codec.ts +147 -0
  57. package/src/runtime/detect.ts +36 -0
  58. package/src/runtime/index.ts +28 -0
  59. package/src/runtime/native.ts +35 -0
  60. package/src/runtime/server.ts +31 -0
  61. package/src/runtime/transport.ts +64 -0
  62. package/src/runtime/types.ts +173 -0
  63. package/src/runtime/uuid.ts +45 -0
  64. package/src/runtime/websocket.ts +30 -0
  65. package/src/rust-ffi/addon.ts +1 -1
  66. package/src/rust-ffi/batch/build.ts +155 -0
  67. package/src/rust-ffi/batch/index.ts +8 -0
  68. package/src/rust-ffi/batch/types.ts +92 -0
  69. package/src/rust-ffi/client.ts +5 -6
  70. package/src/rust-ffi/context.ts +39 -10
  71. package/src/rust-ffi/index.ts +28 -17
  72. package/src/rust-ffi/into.ts +21 -0
  73. package/src/rust-ffi/options.ts +1 -1
  74. package/src/rust-ffi/packed.ts +2 -2
  75. package/src/rust-ffi/proven.ts +16 -30
  76. package/src/rust-ffi/scalar/crypto.ts +188 -107
  77. package/src/rust-ffi/scalar/factories.ts +253 -27
  78. package/src/rust-ffi/scalar/hashing.ts +43 -41
  79. package/src/rust-ffi/scalar/http.ts +208 -77
  80. package/src/rust-ffi/scalar/index.ts +3 -3
  81. package/src/rust-ffi/scalar/interface.ts +88 -213
  82. package/src/rust-ffi/scalar/json-packed.ts +175 -0
  83. package/src/rust-ffi/scalar/json.ts +28 -12
  84. package/src/rust-ffi/scalar/payload.ts +159 -42
  85. package/src/rust-ffi/text.ts +31 -5
  86. package/src/selection.ts +27 -31
  87. package/src/shared/adaptive.ts +1 -2
  88. package/src/shared/buffer-pool.ts +72 -17
  89. package/src/shared/bytes.ts +75 -6
  90. package/src/shared/codec.ts +78 -0
  91. package/src/shared/env.ts +4 -4
  92. package/src/shared/log.ts +1 -0
  93. package/src/shared/packed/index.ts +10 -0
  94. package/src/shared/packed/parsers.ts +31 -0
  95. package/src/shared/packed/schema.ts +23 -0
  96. package/src/shared/{packed.ts → packed/wire.ts} +31 -39
  97. package/src/shared/proven.ts +189 -496
  98. package/src/shared/runtime.ts +20 -25
  99. package/src/shared/uuid.ts +12 -28
  100. package/src/data/json-rows.ts +0 -33
  101. package/src/rust-ffi/batch.ts +0 -431
  102. package/src/shared/bench-classify.ts +0 -209
@@ -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
- type LoaderBulkArgs,
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 { createCostModel, nowNs, type LoaderCostModel } from './cost'
33
- import {
34
- createLruCache,
35
- createTickCoalescer,
36
- type LoaderCache,
37
- type LoadRequest,
38
- type TickCoalescer,
39
- } from './batch'
40
- import { rust } from '../rust-ffi'
41
- import { viewForArrayBuffer } from '../shared/bytes'
42
- import type { SchemaValidator } from '../shared/packed'
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
- LOADER_OPS,
58
+ type BulkResult,
47
59
  LOADER_OP_NAMES,
48
- type LoaderOpName,
49
- type LoaderOpArgs,
50
- type LoaderBulkArgs,
60
+ LOADER_OPS,
51
61
  type LoaderBulk,
52
- type LoaderScalar,
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
- // ── Public types ────────────────────────────────────────────────────────────
59
-
60
- /** Options for `createLoader()` / `loader.configure()`. */
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)) as Uint8Array
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)