@bjornpagen/bumbledb 0.15.0 → 0.17.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 (103) hide show
  1. package/COOKBOOK.md +33 -49
  2. package/README.md +3 -3
  3. package/dist/capacity.d.ts +24 -136
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +18 -40
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/closed.d.ts +0 -156
  8. package/dist/closed.d.ts.map +1 -1
  9. package/dist/closed.js +0 -104
  10. package/dist/closed.js.map +1 -1
  11. package/dist/db.d.ts +7 -223
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +147 -396
  14. package/dist/db.js.map +1 -1
  15. package/dist/face.d.ts +0 -133
  16. package/dist/face.d.ts.map +1 -1
  17. package/dist/face.js +0 -33
  18. package/dist/face.js.map +1 -1
  19. package/dist/fields.d.ts +1 -145
  20. package/dist/fields.d.ts.map +1 -1
  21. package/dist/fields.js +2 -91
  22. package/dist/fields.js.map +1 -1
  23. package/dist/index.d.ts +12 -15
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +10 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/law.d.ts +111 -93
  28. package/dist/law.d.ts.map +1 -1
  29. package/dist/law.js +23 -27
  30. package/dist/law.js.map +1 -1
  31. package/dist/lower.d.ts +9 -35
  32. package/dist/lower.d.ts.map +1 -1
  33. package/dist/lower.js +8 -53
  34. package/dist/lower.js.map +1 -1
  35. package/dist/marshal.d.ts +0 -65
  36. package/dist/marshal.d.ts.map +1 -1
  37. package/dist/marshal.js +0 -72
  38. package/dist/marshal.js.map +1 -1
  39. package/dist/native.d.ts +31 -289
  40. package/dist/native.d.ts.map +1 -1
  41. package/dist/native.js +15 -64
  42. package/dist/native.js.map +1 -1
  43. package/dist/query/atom.d.ts +10 -276
  44. package/dist/query/atom.d.ts.map +1 -1
  45. package/dist/query/atom.js +1 -96
  46. package/dist/query/atom.js.map +1 -1
  47. package/dist/query/find.d.ts +10 -76
  48. package/dist/query/find.d.ts.map +1 -1
  49. package/dist/query/find.js +0 -30
  50. package/dist/query/find.js.map +1 -1
  51. package/dist/query/lower.d.ts +58 -146
  52. package/dist/query/lower.d.ts.map +1 -1
  53. package/dist/query/lower.js +19 -256
  54. package/dist/query/lower.js.map +1 -1
  55. package/dist/query/parse-ir.d.ts +0 -7
  56. package/dist/query/parse-ir.d.ts.map +1 -1
  57. package/dist/query/parse-ir.js +1 -13
  58. package/dist/query/parse-ir.js.map +1 -1
  59. package/dist/query/run.d.ts +0 -36
  60. package/dist/query/run.d.ts.map +1 -1
  61. package/dist/query/run.js +0 -44
  62. package/dist/query/run.js.map +1 -1
  63. package/dist/query/scope.d.ts +24 -180
  64. package/dist/query/scope.d.ts.map +1 -1
  65. package/dist/query/scope.js +2 -66
  66. package/dist/query/scope.js.map +1 -1
  67. package/dist/relation.d.ts +2 -50
  68. package/dist/relation.d.ts.map +1 -1
  69. package/dist/relation.js +2 -37
  70. package/dist/relation.js.map +1 -1
  71. package/dist/schema.d.ts +13 -63
  72. package/dist/schema.d.ts.map +1 -1
  73. package/dist/schema.js +118 -92
  74. package/dist/schema.js.map +1 -1
  75. package/dist/spec.d.ts +1 -140
  76. package/dist/spec.d.ts.map +1 -1
  77. package/dist/spec.js +1 -68
  78. package/dist/spec.js.map +1 -1
  79. package/dist/statements.d.ts +6 -137
  80. package/dist/statements.d.ts.map +1 -1
  81. package/dist/statements.js +16 -119
  82. package/dist/statements.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/capacity.ts +26 -140
  85. package/src/closed.ts +5 -206
  86. package/src/db.ts +203 -692
  87. package/src/face.ts +0 -142
  88. package/src/fields.ts +4 -172
  89. package/src/index.ts +10 -15
  90. package/src/law.ts +201 -129
  91. package/src/lower.ts +8 -53
  92. package/src/marshal.ts +1 -85
  93. package/src/native.ts +58 -321
  94. package/src/query/atom.ts +26 -313
  95. package/src/query/find.ts +24 -110
  96. package/src/query/lower.ts +126 -377
  97. package/src/query/parse-ir.ts +1 -14
  98. package/src/query/run.ts +0 -45
  99. package/src/query/scope.ts +25 -186
  100. package/src/relation.ts +2 -66
  101. package/src/schema.ts +143 -122
  102. package/src/spec.ts +1 -160
  103. package/src/statements.ts +22 -174
package/src/native.ts CHANGED
@@ -2,98 +2,41 @@ import { createRequire } from "node:module"
2
2
  import * as errors from "@superbuilders/errors"
3
3
  import type { SchemaSpec, ValueSpec, ValueTypeSpec } from "#spec.ts"
4
4
 
5
- /**
6
- * The complete typed surface of the bumbledb-node napi bridge. ALL FFI
7
- * typing lives in this one file — no other module may know the `.node`
8
- * artifact exists. The bridge is a dumb bridge (PRD-04): descriptor in as
9
- * data, queries in as IR data, facts in/out as value rows, rejections out as
10
- * structured violation sets; anything smart lives in the SDK or the engine.
11
- *
12
- * Marshaling law: fact rows cross as NATURAL JS values, schema-directed by
13
- * the engine descriptor (`boolean ⇄ bool`, `bigint ⇄ u64/i64`,
14
- * `string ⇄ str`, `Uint8Array ⇄ bytes<N>`, `{ start, end }` ⇄ interval);
15
- * IR, spec, and query params cross as TAGGED plain objects mirroring the
16
- * engine's own data enums 1:1. Every u64/i64 crosses as `bigint`, never
17
- * `number`. Domain outcomes (schema errors, fingerprint mismatches,
18
- * rejections, generation moves, IR errors) are DATA; marshaling/shape
19
- * violations and use-after-close THROW.
20
- */
21
-
22
5
  /** The opaque database handle (owns the LMDB environment + exclusive lock). */
23
6
  type DbHandle = { readonly __brand: "bumbledb.db" }
24
7
 
25
- /** One live borrowed instance valid only inside a read callback. */
26
8
  type InstanceHandle = { readonly __brand: "bumbledb.instance" }
27
9
 
28
- /** One cloneable generation witness. May outlive the read that minted it. */
29
10
  type WitnessHandle = { readonly __brand: "bumbledb.witness" }
30
11
 
31
- /** One unproved heap builder. Spent by `instanceBuilderAdmit` / close. */
32
12
  type BuilderHandle = { readonly __brand: "bumbledb.builder" }
33
13
 
34
- /** One admitted heap instance. */
35
14
  type OwnedHandle = { readonly __brand: "bumbledb.owned" }
36
15
 
37
- /**
38
- * One live write transaction — the submitted delta with the engine's
39
- * final-state point-read view. Valid only inside a write callback.
40
- */
41
16
  type TxHandle = { readonly __brand: "bumbledb.tx" }
42
17
 
43
- /** One prepared query (plan pinned at prepare). */
44
18
  type PreparedHandle = { readonly __brand: "bumbledb.prepared" }
45
19
 
46
- /**
47
- * Engine mutation report as it crosses napi: both counts are engine
48
- * values, never reconstructed from JS length.
49
- */
50
20
  interface WireMutationReport {
51
21
  readonly submitted: bigint
52
22
  readonly changed: bigint
53
23
  }
54
24
 
55
- /**
56
- * Engine fresh-id range as it crosses napi. Empty cannot yield a start —
57
- * `start` is a minted id only on the nonempty arm. (C wires empty as
58
- * `BDB_FRESH_RANGE_TAG_EMPTY`. The JS wire is `{ empty: true }`, not that
59
- * C sentinel.)
60
- */
61
25
  type WireFreshRange =
62
26
  | { readonly empty: true }
63
27
  | { readonly empty: false; readonly start: bigint; readonly endExclusive: bigint }
64
28
 
65
- /** A half-open interval `[start, end)` as it crosses the boundary. */
66
29
  interface IntervalValue {
67
30
  readonly start: bigint
68
31
  readonly end: bigint
69
32
  }
70
33
 
71
- /**
72
- * One fact-row cell as a natural JS value. The expected engine type comes
73
- * from the schema descriptor (marshaling is schema-directed, never guessed):
74
- * `boolean` for bool, `bigint` for u64/i64, `string` for str, `Uint8Array`
75
- * for bytes<N> (width-checked), `{ start, end }` for intervals.
76
- */
77
34
  type FactValue = boolean | bigint | string | Uint8Array | IntervalValue
78
35
 
79
- /**
80
- * One tagged engine value — the 1:1 mirror of `bumbledb::Value` for the
81
- * positions no schema field directs (IR literals, query params).
82
- */
83
36
  type TaggedValue = ValueSpec
84
37
 
85
- /**
86
- * One positional execution argument: a tagged scalar, or a param SET
87
- * (`Term.paramSet` positions) as `{ kind: "set", values }`.
88
- */
89
38
  type QueryParam = TaggedValue | { readonly kind: "set"; readonly values: readonly TaggedValue[] }
90
39
 
91
- /**
92
- * The IR mirror (`bumbledb::ir`, 1:1): relations, fields, interiors, and
93
- * params by NUMERIC id — the SDK resolves names through the manifest and
94
- * sends ids; the bridge never sees names in queries. Q1 is a tagged sum:
95
- * CQ carries no rec; Reach carries `rec` by value.
96
- */
97
40
  type QueryIr =
98
41
  | {
99
42
  readonly kind: "cq"
@@ -109,26 +52,21 @@ type QueryIr =
109
52
  readonly rules: readonly RuleIr[]
110
53
  }
111
54
 
112
- /** One named interior: the head shape its rules align against, and the rules. */
113
55
  interface InteriorIr {
114
56
  readonly head: readonly HeadTermIr[]
115
57
  readonly rules: readonly RuleIr[]
116
58
  }
117
59
 
118
- /** The linear rec on a Reach query: shared head, base arms, rec arms. */
119
60
  interface RecIr {
120
61
  readonly head: readonly HeadTermIr[]
121
62
  readonly base: readonly RuleIr[]
122
63
  readonly rec: readonly RuleIr[]
123
64
  }
124
65
 
125
- /** One head position: a plain variable slot or an aggregate-op kind. */
126
66
  type HeadTermIr = { readonly kind: "var" } | { readonly kind: "aggregate"; readonly op: HeadOpIr }
127
67
 
128
- /** The var-free aggregate-op kind at a head position. */
129
68
  type HeadOpIr = "sum" | "min" | "max" | "count" | "pack"
130
69
 
131
- /** One rule: conjunctive body, anti-join atoms, condition trees. */
132
70
  interface RuleIr {
133
71
  readonly finds: readonly FindTermIr[]
134
72
  readonly atoms: readonly AtomIr[]
@@ -136,7 +74,6 @@ interface RuleIr {
136
74
  readonly conditions: readonly ConditionTreeIr[]
137
75
  }
138
76
 
139
- /** One find term (mirrors `ir::FindTerm`). Count is nullary; pack and folds carry `over`. */
140
77
  type FoldOpIr = { readonly kind: "sum" } | { readonly kind: "min" } | { readonly kind: "max" }
141
78
 
142
79
  type FindTermIr =
@@ -144,16 +81,11 @@ type FindTermIr =
144
81
  | { readonly kind: "count" }
145
82
  | { readonly kind: "aggregate"; readonly op: FoldOpIr; readonly over: number }
146
83
  | { readonly kind: "pack"; readonly over: number }
147
- | { readonly kind: "measure"; readonly var: number }
148
- | { readonly kind: "aggregateMeasure"; readonly op: FoldOpIr; readonly over: number }
149
84
 
150
- /** Host brand: only {@link parseQueryIr} and `lowerQuery` inhabit this. Phantom — not a runtime key. */
151
85
  declare const parsedQueryBrand: unique symbol
152
86
 
153
- /** A `QueryIr` that passed the host shape parse (rec/main nonempty, aggregate finds split). */
154
87
  type ParsedQuery = QueryIr & { readonly [parsedQueryBrand]: true }
155
88
 
156
- /** One aggregate operator (mirrors `ir::AggOp`; Arg ops carry their key). */
157
89
  type AggOpIr =
158
90
  | { readonly kind: "sum" }
159
91
  | { readonly kind: "min" }
@@ -161,29 +93,21 @@ type AggOpIr =
161
93
  | { readonly kind: "count" }
162
94
  | { readonly kind: "pack" }
163
95
 
164
- /** Where an atom draws its facts: a stored relation or a derived table. */
165
96
  type AtomSourceIr =
166
97
  | { readonly kind: "edb"; readonly relation: number }
167
98
  | { readonly kind: "interior"; readonly interior: number }
168
99
 
169
- /**
170
- * One atom: named-field bindings as `[fieldId, term]` pairs; absence of a
171
- * field is the wildcard.
172
- */
173
100
  interface AtomIr {
174
101
  readonly source: AtomSourceIr
175
102
  readonly bindings: ReadonlyArray<readonly [number, TermIr]>
176
103
  }
177
104
 
178
- /** One term of an atom binding or comparison (mirrors `ir::Term`). */
179
105
  type TermIr =
180
106
  | { readonly kind: "var"; readonly var: number }
181
107
  | { readonly kind: "param"; readonly param: number }
182
108
  | { readonly kind: "paramSet"; readonly param: number }
183
109
  | { readonly kind: "literal"; readonly value: TaggedValue }
184
- | { readonly kind: "measure"; readonly var: number }
185
110
 
186
- /** One comparison operator (mirrors `ir::CmpOp`). */
187
111
  type CmpOpIr =
188
112
  | { readonly kind: "eq" }
189
113
  | { readonly kind: "ne" }
@@ -194,46 +118,31 @@ type CmpOpIr =
194
118
  | { readonly kind: "allen"; readonly mask: number }
195
119
  | { readonly kind: "pointIn" }
196
120
 
197
- /** One comparison condition. */
198
121
  interface ComparisonIr {
199
122
  readonly op: CmpOpIr
200
123
  readonly lhs: TermIr
201
124
  readonly rhs: TermIr
202
125
  }
203
126
 
204
- /**
205
- * The input condition grammar: any boolean combination of comparisons
206
- * (validation distributes to DNF engine-side).
207
- */
208
127
  type ConditionTreeIr =
209
128
  | { readonly kind: "leaf"; readonly cmp: ComparisonIr }
210
129
  | { readonly kind: "and"; readonly children: readonly ConditionTreeIr[] }
211
130
  | { readonly kind: "or"; readonly children: readonly ConditionTreeIr[] }
212
131
 
213
- /** A statement's form tag. */
214
132
  type StatementKindTag = "functionality" | "containment" | "capacity"
215
133
 
216
- /** One field's name, dense id, and structural type. */
217
134
  interface ManifestField {
218
135
  readonly name: string
219
136
  readonly id: number
220
137
  readonly valueType: ValueTypeSpec
221
138
  }
222
139
 
223
- /**
224
- * One closed-relation ground axiom as manifest data: handle →
225
- * declaration-order id → (column, value) pairs.
226
- */
227
140
  interface ManifestRow {
228
141
  readonly handle: string
229
142
  readonly id: bigint
230
143
  readonly values: ReadonlyArray<{ readonly name: string; readonly value: FactValue }>
231
144
  }
232
145
 
233
- /**
234
- * One relation's names and ids; a closed relation's sealed field list opens
235
- * with the synthetic (`id`, u64) handle field and carries its extension.
236
- */
237
146
  interface ManifestRelation {
238
147
  readonly name: string
239
148
  readonly id: number
@@ -241,37 +150,22 @@ interface ManifestRelation {
241
150
  readonly extension?: readonly ManifestRow[]
242
151
  }
243
152
 
244
- /** One statement's identity, form tag, and canonical spelling. */
245
153
  interface ManifestStatement {
246
154
  readonly id: number
247
155
  readonly kind: StatementKindTag
248
156
  readonly spelling: string
249
157
  }
250
158
 
251
- /**
252
- * The theory's manifest: every name → id pairing as plain data (PRD-02's
253
- * tables, one JS object) — called once per open by the SDK.
254
- */
255
159
  interface Manifest {
256
160
  readonly relations: readonly ManifestRelation[]
257
161
  readonly statements: readonly ManifestStatement[]
258
162
  }
259
163
 
260
- /** One offending fact of a violation, decoded to named natural values. */
261
164
  interface ViolationFact {
262
165
  readonly relation: string
263
166
  readonly fields: ReadonlyArray<{ readonly name: string; readonly value: FactValue }>
264
167
  }
265
168
 
266
- /**
267
- * One violated statement of a rejected commit, rendered to plain data: the
268
- * statement id (materialized order), form tag, CANONICAL spelling (the
269
- * engine's one renderer — a bijection on legal statements, paste-back-able),
270
- * the form's direction/measure payloads, and the decoded offending facts.
271
- * `measure` is the capacity form's witnessed group total — the engine
272
- * accumulates in u128 and the value crosses WHOLE as bigint (C3:
273
- * truncation is unrepresentable).
274
- */
275
169
  type Violation =
276
170
  | {
277
171
  readonly statementId: number
@@ -294,11 +188,6 @@ type Violation =
294
188
  readonly facts: readonly ViolationFact[]
295
189
  }
296
190
 
297
- /**
298
- * `dbCreate`'s domain outcome. Admission is `accepted` / `rejected`.
299
- * Declaration-boundary refusals ride as their own tags (not theory
300
- * admission) and the SDK throws them.
301
- */
302
191
  type CreateResult =
303
192
  | { readonly tag: "accepted"; readonly db: DbHandle }
304
193
  | { readonly tag: "rejected"; readonly violations: readonly Violation[] }
@@ -320,45 +209,20 @@ type DbOpenResult =
320
209
  readonly message: string
321
210
  }
322
211
 
323
- /**
324
- * `dbWrite` / `dbWriteFrom` native outcome. The SDK attaches the callback
325
- * return onto the accepted arm. Moved is data, never an error kind.
326
- */
327
212
  type NativeWriteOutcome =
328
213
  | { readonly tag: "accepted"; readonly generation: bigint }
329
214
  | { readonly tag: "rejected"; readonly violations: readonly Violation[] }
330
215
  | { readonly tag: "abandoned" }
331
216
  | { readonly tag: "moved"; readonly witnessed: bigint; readonly current: bigint }
332
217
 
333
- /**
334
- * Builder `admit` native outcome.
335
- */
336
218
  type AdmitResult =
337
219
  | { readonly tag: "accepted"; readonly value: OwnedHandle }
338
220
  | { readonly tag: "rejected"; readonly violations: readonly Violation[] }
339
221
 
340
- /** `dbPrepare`/`instancePrepare`'s domain outcome (IR roster errors are data). */
341
222
  type PrepareResult =
342
223
  | { readonly ok: true; readonly prepared: PreparedHandle }
343
224
  | { readonly ok: false; readonly kind: "irError"; readonly message: string }
344
225
 
345
- /** One occurrence's plan drift (pinned vs live row counts). */
346
- interface OccurrenceDrift {
347
- readonly relation: number
348
- readonly pinned: bigint
349
- readonly live: bigint
350
- readonly ratio: number
351
- }
352
-
353
- /**
354
- * The pull-based plan-drift report: engine-policy-free — no threshold
355
- * exists engine-side; the host owns reprepare policy.
356
- */
357
- interface Staleness {
358
- readonly perOccurrence: readonly OccurrenceDrift[]
359
- readonly maxRatio: number
360
- }
361
-
362
226
  type ErrorFamilyKind =
363
227
  | "formatMismatch"
364
228
  | "schemaMismatch"
@@ -379,7 +243,6 @@ type ErrorFamilyKind =
379
243
  | "foreignPrepared"
380
244
  | "foreignWitness"
381
245
  | "param"
382
- | "measureOfRay"
383
246
  | "capacityRayMeasure"
384
247
  | "derivedBudgetExceeded"
385
248
  | "overflow"
@@ -391,66 +254,21 @@ type WriteTag = "accepted" | "rejected" | "abandoned" | "moved"
391
254
  type OpenKind = "schemaError" | "newtypeMismatch" | "fingerprintMismatch"
392
255
  type PrepareKind = "irError"
393
256
 
394
- /**
395
- * The plan-as-data report (ruled 2026-07-23, R13): the engine's
396
- * `ExecutionStats` rendered to plain objects — camelCase keys, u64
397
- * counters as `bigint`. A diagnostic surface, EXPLICITLY UNFROZEN: the
398
- * shape follows the plan representation wherever it goes and no
399
- * compatibility claim attaches, so this typing names the stable spine
400
- * (version, emits, the plan sections) and leaves each section's leaves
401
- * open for the host to introspect.
402
- */
403
- interface Explain {
404
- readonly introspectionVersion: number
405
- readonly emits: bigint
406
- readonly disjointRules?: Readonly<Record<string, unknown>>
407
- readonly subsumed: ReadonlyArray<Readonly<Record<string, unknown>>>
408
- readonly dead: ReadonlyArray<Readonly<Record<string, unknown>>>
409
- readonly rules: ReadonlyArray<Readonly<Record<string, unknown>>>
410
- readonly interiors: ReadonlyArray<Readonly<Record<string, unknown>>>
411
- readonly reach?: Readonly<Record<string, unknown>>
412
- }
413
-
414
257
  interface Native {
415
- /**
416
- * Proof-of-life export (PRD-03): a non-empty string naming the bridge
417
- * crate version and the engine's storage format version — evidence the
418
- * cargo path dependency compiled, linked, and loaded through Node-API.
419
- */
420
258
  engineVersion(): string
421
259
 
422
- /**
423
- * Creates a fresh durable store at `path`. Refuses an already-initialized
424
- * directory (throws); schema failures return as data.
425
- */
260
+ blake3Hash(data: Uint8Array): Uint8Array
261
+
426
262
  dbCreate(path: string, spec: SchemaSpec): Promise<CreateResult>
427
- /**
428
- * Opens an existing durable store, verifying format version and
429
- * schema fingerprint (`fingerprintMismatch` as data).
430
- */
263
+
431
264
  dbOpen(path: string, spec: SchemaSpec): Promise<DbOpenResult>
432
- /**
433
- * Closes the handle. Dependent handles each hold the engine alive; the
434
- * environment (and its exclusive lock) releases when the last closes.
435
- */
436
- dbClose(db: DbHandle): void
437
- /** The PRD-02 manifest — every name → id table, one plain object. */
265
+
438
266
  dbManifest(db: DbHandle): Manifest
439
- /**
440
- * The open store's schema fingerprint, 64 lowercase hex chars — the
441
- * cross-host identity readback (`dbCreate` stored this exact value,
442
- * `dbOpen` verified it). The engine computes; the bridge hex-encodes.
443
- * Test-facing (the cross-host fingerprint lock); the SDK surface stays
444
- * bijective with the Rust surface, which exposes no fingerprint
445
- * accessor on `Db` — so no `Db` method wraps this.
446
- */
267
+
447
268
  dbFingerprint(db: DbHandle): string
448
- /**
449
- * The current committed generation — diagnostics only. The write-side
450
- * witness is always a {@link WitnessHandle}, never this integer.
451
- */
269
+
452
270
  dbGeneration(db: DbHandle): bigint
453
- /** Publishes an admitted heap instance at `path` without re-judgment. */
271
+
454
272
  dbFromInstance(path: string, instance: OwnedHandle): Promise<DbHandle>
455
273
 
456
274
  /**
@@ -461,6 +279,8 @@ interface Native {
461
279
  dbRead<R>(db: DbHandle, callback: (instance: InstanceHandle, witness: WitnessHandle) => R): R
462
280
  instanceGeneration(instance: InstanceHandle): bigint
463
281
  instanceScan(instance: InstanceHandle, relationId: number): FactValue[][]
282
+
283
+ instanceCount(instance: InstanceHandle, relationId: number): bigint
464
284
  instanceContains(instance: InstanceHandle, relationId: number, values: readonly FactValue[]): boolean
465
285
  instanceGet(
466
286
  instance: InstanceHandle,
@@ -471,92 +291,53 @@ interface Native {
471
291
  instancePrepare(instance: InstanceHandle, query: ParsedQuery): PrepareResult
472
292
  witnessClose(witness: WitnessHandle): void
473
293
 
474
- /**
475
- * Runs `callback` synchronously inside the engine write region.
476
- * Return `true` to commit, `false` to abandon. Nested writes throw.
477
- */
478
294
  dbWrite(db: DbHandle, callback: (tx: TxHandle) => boolean): NativeWriteOutcome
479
- /**
480
- * Witnessed write: `moved` is data when the store advanced since the
481
- * witness was minted. The callback does not run on that arm.
482
- */
295
+
483
296
  dbWriteFrom(db: DbHandle, witness: WitnessHandle, callback: (tx: TxHandle) => boolean): NativeWriteOutcome
484
297
  /**
485
298
  * Records a collection of inserts into the delta; returns the engine
486
- * `{ submitted, changed }` report. `rows` is an array of value-arrays
487
- * in sealed field order. Empty is lawful and still a mutation (poison
488
- * is observed). Nothing is judged until commit; shape violations throw typed.
489
- */
490
- txInsert(tx: TxHandle, relationId: number, rows: readonly (readonly FactValue[])[]): WireMutationReport
491
- /**
492
- * Records a collection of inserts from per-column arrays in sealed
493
- * field order — the column transport, same parse-all-first batch as
494
- * {@link Native.txInsert}.
495
- */
496
- txInsertColumns(
497
- tx: TxHandle,
498
- relationId: number,
499
- columns: readonly (readonly FactValue[])[]
500
- ): WireMutationReport
501
- /** Records a collection of deletes; returns the engine `{ submitted, changed }` report. */
502
- txDelete(tx: TxHandle, relationId: number, rows: readonly (readonly FactValue[])[]): WireMutationReport
503
- /**
504
- * Final-state membership (base + pending delta — the exact view the
505
- * commit judgment judges; check-then-act is race-free by construction).
299
+ * `{ submitted, changed }` report. `cells` is ONE flat row-major array
300
+ * (length rows×arity) in sealed field order, and `rows` is the EXPLICIT
301
+ * row count the caller states — the one collection crossing: the JS side
302
+ * alone knows N when the roster is fieldless (N nullary facts project to 0 cells, so no
303
+ * derivation can recover N), and the bridge verifies
304
+ * `cells.length === rows × arity` exactly against its resident sealed
305
+ * roster before building the engine's shape-proved collection in a
306
+ * single pass. Empty (`rows === 0n`, no cells) is lawful and still a
307
+ * mutation. Nothing is judged until commit; shape violations throw
308
+ * typed, naming relation and field.
506
309
  */
310
+ txInsert(tx: TxHandle, relationId: number, rows: bigint, cells: readonly FactValue[]): WireMutationReport
311
+
312
+ txDelete(tx: TxHandle, relationId: number, rows: bigint, cells: readonly FactValue[]): WireMutationReport
313
+
507
314
  txContains(tx: TxHandle, relationId: number, values: readonly FactValue[]): boolean
508
- /** Final-state point lookup through a key statement; `null` on a miss. */
315
+
509
316
  txGet(tx: TxHandle, relationId: number, keyStatementId: number, keyValues: readonly FactValue[]): FactValue[] | null
510
- /**
511
- * Mints `count` consecutive fresh values for `(relationId, fieldId)`.
512
- * `count === 0n` is empty and does not yield a start.
513
- */
317
+
514
318
  txReserve(tx: TxHandle, relationId: number, fieldId: number, count: bigint): WireFreshRange
515
319
 
516
- /**
517
- * Prepares a query (IR as data, ids only; plan pinned at prepare).
518
- * Roster errors return as data.
519
- */
520
320
  dbPrepare(db: DbHandle, query: ParsedQuery): PrepareResult
521
- /**
522
- * Executes against a live instance with positional params. One-copy owned
523
- * rows out, column order = the query's head order; answers are a set
524
- * — the host sorts.
525
- */
321
+
526
322
  preparedExecute(prepared: PreparedHandle, instance: InstanceHandle, params: readonly QueryParam[]): FactValue[][]
527
- /**
528
- * Plan introspection as data (ruled 2026-07-23, R13): runs the prepared
529
- * query against a store read with counting instrumentation and returns
530
- * the structured stats. Store-read only.
531
- */
532
- preparedExplain(prepared: PreparedHandle, instance: InstanceHandle, params: readonly QueryParam[]): Explain
533
- /** The pull-based plan-drift signal against a store read. */
534
- preparedStaleness(prepared: PreparedHandle, instance: InstanceHandle): Staleness
535
- /** Releases the prepared query. */
323
+
536
324
  preparedClose(prepared: PreparedHandle): void
537
325
 
538
326
  instanceBuilderNew(spec: SchemaSpec): BuilderHandle
327
+
539
328
  instanceBuilderLoad(
540
329
  builder: BuilderHandle,
541
330
  relationId: number,
542
- rows: readonly (readonly FactValue[])[]
543
- ): WireMutationReport
544
- instanceBuilderLoadColumns(
545
- builder: BuilderHandle,
546
- relationId: number,
547
- columns: readonly (readonly FactValue[])[]
331
+ rows: bigint,
332
+ cells: readonly FactValue[]
548
333
  ): WireMutationReport
549
334
  instanceBuilderDelete(
550
335
  builder: BuilderHandle,
551
336
  relationId: number,
552
- rows: readonly (readonly FactValue[])[]
337
+ rows: bigint,
338
+ cells: readonly FactValue[]
553
339
  ): WireMutationReport
554
- instanceBuilderReserve(
555
- builder: BuilderHandle,
556
- relationId: number,
557
- fieldId: number,
558
- count: bigint
559
- ): WireFreshRange
340
+ instanceBuilderReserve(builder: BuilderHandle, relationId: number, fieldId: number, count: bigint): WireFreshRange
560
341
  instanceBuilderContains(builder: BuilderHandle, relationId: number, values: readonly FactValue[]): boolean
561
342
  instanceBuilderGet(
562
343
  builder: BuilderHandle,
@@ -568,6 +349,8 @@ interface Native {
568
349
  instanceBuilderAdmit(builder: BuilderHandle): Promise<AdmitResult>
569
350
  ownedInstanceClose(instance: OwnedHandle): void
570
351
  ownedScan(instance: OwnedHandle, relationId: number): FactValue[][]
352
+
353
+ ownedCount(instance: OwnedHandle, relationId: number): bigint
571
354
  ownedContains(instance: OwnedHandle, relationId: number, values: readonly FactValue[]): boolean
572
355
  ownedGet(
573
356
  instance: OwnedHandle,
@@ -579,54 +362,17 @@ interface Native {
579
362
  ownedExecute(prepared: PreparedHandle, instance: OwnedHandle, params: readonly QueryParam[]): FactValue[][]
580
363
  }
581
364
 
582
- /**
583
- * The sole platform this release ships (PRD-03 ruling 1: prebuilt-only,
584
- * darwin-arm64). The per-platform-package structure below makes adding
585
- * `darwin-x64`/`linux-*`/`win32-*` pure addition — one more `os`/`cpu`-gated
586
- * package plus a CI matrix — never a redesign. This constant names the
587
- * shipped set for the unsupported-platform message; the build's
588
- * `PUBLISH_PLATFORM` (`scripts/platform.ts` — src cannot import scripts,
589
- * the packaging boundary) and the `ts/.gitignore` carve-out spell the same
590
- * target, and the single-source pin in `test/build-platform.test.ts` holds
591
- * all three in lockstep.
592
- */
593
365
  const SHIPPED_PLATFORMS = "darwin-arm64"
594
366
 
595
- /**
596
- * CommonJS require anchored to this module, the only mechanism ESM has for
597
- * loading a Node-API addon without an experimental flag (static `import` of
598
- * `.node` files still sits behind `--experimental-addon-modules` on Node 24).
599
- * It resolves the per-platform binary package by name (see
600
- * {@link loadNativeBinding}); the addon never crosses as a relative path.
601
- * createRequire is the only unflagged Node-API addon loader in ESM, and this
602
- * file is the package's single sanctioned FFI boundary (the arch-split
603
- * packaging ruling).
604
- */
605
367
  const requireNative = createRequire(import.meta.url)
606
368
 
607
- /**
608
- * Resolves and loads the native bridge from its per-platform binary package
609
- * (`@bjornpagen/bumbledb-<platform>-<arch>`) — the Biome/esbuild/napi-rs
610
- * pattern. npm/pnpm install ONLY the `optionalDependency` whose `os`/`cpu`
611
- * match the host, so a matching host resolves the addon and every other host
612
- * resolves nothing. The two failure modes are distinct and both typed:
613
- *
614
- * - the platform package is ABSENT (the expected state on any
615
- * non-darwin-arm64 host, and on a foreign `platform`/`arch` passed under
616
- * test) — an actionable unsupported-platform error naming the running
617
- * `platform-arch` and the shipped set;
618
- * - the platform package is PRESENT but its `bumbledb.node` will not load
619
- * (a genuine ABI/corruption fault) — the wrapped loader error.
620
- *
621
- * Parameterized on `platform`/`arch` so the resolution law is exercised for
622
- * foreign hosts as a unit, without spawning a foreign process.
623
- */
624
- function loadNativeBinding(platform: string, arch: string): Native {
369
+ interface NativeBinding extends Native {
370
+ dbClose(db: DbHandle): void
371
+ }
372
+
373
+ function loadNativeBinding(platform: string, arch: string): NativeBinding {
625
374
  const platformPackage = `@bjornpagen/bumbledb-${platform}-${arch}`
626
375
 
627
- // Presence probe: the platform package's OWN manifest resolves iff the
628
- // matching optional dependency was installed. Its absence is the
629
- // expected, benign "unsupported platform" — never a corruption signal.
630
376
  const present = errors.trySync(() => requireNative.resolve(`${platformPackage}/package.json`))
631
377
  if (present.error) {
632
378
  throw errors.wrap(
@@ -635,8 +381,6 @@ function loadNativeBinding(platform: string, arch: string): Native {
635
381
  )
636
382
  }
637
383
 
638
- // The package is present; load its addon (its `main` is `bumbledb.node`).
639
- // A failure HERE is corruption or an ABI mismatch, not an absent platform.
640
384
  const loaded = errors.trySync(() => requireNative(platformPackage))
641
385
  if (loaded.error) {
642
386
  throw errors.wrap(loaded.error, `load the ${platformPackage} native binary (package present but unloadable)`)
@@ -644,18 +388,25 @@ function loadNativeBinding(platform: string, arch: string): Native {
644
388
  return loaded.data
645
389
  }
646
390
 
647
- /**
648
- * The loaded bumbledb-node bridge for the running host. Import this object
649
- * for every native call; the resolve-and-load happens once at module
650
- * initialization and an absent or unloadable artifact fails fast here rather
651
- * than at first use.
652
- */
653
- const native: Native = loadNativeBinding(process.platform, process.arch)
391
+ const binding: NativeBinding = loadNativeBinding(process.platform, process.arch)
392
+ const native: Native = binding
393
+
394
+ function dbClose(db: DbHandle): void {
395
+ binding.dbClose(db)
396
+ }
654
397
 
655
398
  /**
656
- * Engine throw identity: a real `Error` carrying `kind` from the
657
- * `ErrorFamily` table, or a leftover `{ kind, message }` object.
399
+ * @internal blake3 of the given bytes via the resident native binding —
400
+ * the engine's own hash, lent to the replication driver
401
+ * (`@bjornpagen/bumbledb-log`). Not SDK API; the export is deliberately
402
+ * undocumented in the package surface.
658
403
  */
404
+ function internalBlake3(data: Uint8Array): Uint8Array {
405
+ return bridged("bumbledb blake3", function hashBytes() {
406
+ return binding.blake3Hash(data)
407
+ })
408
+ }
409
+
659
410
  function isEngineThrow(value: unknown): value is { kind: ErrorFamilyKind; message: string } {
660
411
  if (typeof value !== "object" || value === null) {
661
412
  return false
@@ -676,13 +427,6 @@ function errorFromThrow(caught: unknown): Error {
676
427
  return errors.new(String(caught))
677
428
  }
678
429
 
679
- /**
680
- * The bridge guard — THE one wrapper every native call crosses (db.ts
681
- * imports it): runs one native call and wraps anything it
682
- * throws, so marshal-shape refusals and handle-lifecycle refusals cross as
683
- * genuine typed failures, never bare foreign errors. Engine throws keep
684
- * their forced kind.
685
- */
686
430
  function bridged<T>(context: string, run: () => T): T {
687
431
  try {
688
432
  return run()
@@ -692,10 +436,6 @@ function bridged<T>(context: string, run: () => T): T {
692
436
  }
693
437
  }
694
438
 
695
- /**
696
- * The async twin of {@link bridged}: every control-plane native is an
697
- * `AsyncTask` Promise, and this is the one wrapper those awaits cross.
698
- */
699
439
  async function bridgedAsync<T>(context: string, run: () => Promise<T>): Promise<T> {
700
440
  try {
701
441
  return await run()
@@ -719,7 +459,6 @@ export type {
719
459
  DbHandle,
720
460
  DbOpenResult,
721
461
  ErrorFamilyKind,
722
- Explain,
723
462
  FactValue,
724
463
  FindTermIr,
725
464
  FoldOpIr,
@@ -735,7 +474,6 @@ export type {
735
474
  ManifestStatement,
736
475
  Native,
737
476
  NativeWriteOutcome,
738
- OccurrenceDrift,
739
477
  OpenKind,
740
478
  OwnedHandle,
741
479
  ParsedQuery,
@@ -746,7 +484,6 @@ export type {
746
484
  QueryParam,
747
485
  RecIr,
748
486
  RuleIr,
749
- Staleness,
750
487
  StatementKindTag,
751
488
  TaggedValue,
752
489
  TermIr,
@@ -758,4 +495,4 @@ export type {
758
495
  WitnessHandle,
759
496
  WriteTag
760
497
  }
761
- export { bridged, bridgedAsync, errorFromThrow, loadNativeBinding, native, SHIPPED_PLATFORMS }
498
+ export { bridged, bridgedAsync, dbClose, errorFromThrow, internalBlake3, loadNativeBinding, native, SHIPPED_PLATFORMS }