@bjornpagen/bumbledb 0.15.0 → 0.17.0

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 +11 -15
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +9 -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 +25 -290
  40. package/dist/native.d.ts.map +1 -1
  41. package/dist/native.js +6 -66
  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 +64 -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 +9 -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 +47 -323
  94. package/src/query/atom.ts +26 -313
  95. package/src/query/find.ts +24 -110
  96. package/src/query/lower.ts +132 -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,19 @@ 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
- */
426
260
  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
- */
261
+
431
262
  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. */
263
+
438
264
  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
- */
265
+
447
266
  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
- */
267
+
452
268
  dbGeneration(db: DbHandle): bigint
453
- /** Publishes an admitted heap instance at `path` without re-judgment. */
269
+
454
270
  dbFromInstance(path: string, instance: OwnedHandle): Promise<DbHandle>
455
271
 
456
272
  /**
@@ -461,6 +277,8 @@ interface Native {
461
277
  dbRead<R>(db: DbHandle, callback: (instance: InstanceHandle, witness: WitnessHandle) => R): R
462
278
  instanceGeneration(instance: InstanceHandle): bigint
463
279
  instanceScan(instance: InstanceHandle, relationId: number): FactValue[][]
280
+
281
+ instanceCount(instance: InstanceHandle, relationId: number): bigint
464
282
  instanceContains(instance: InstanceHandle, relationId: number, values: readonly FactValue[]): boolean
465
283
  instanceGet(
466
284
  instance: InstanceHandle,
@@ -471,92 +289,54 @@ interface Native {
471
289
  instancePrepare(instance: InstanceHandle, query: ParsedQuery): PrepareResult
472
290
  witnessClose(witness: WitnessHandle): void
473
291
 
474
- /**
475
- * Runs `callback` synchronously inside the engine write region.
476
- * Return `true` to commit, `false` to abandon. Nested writes throw.
477
- */
478
292
  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
- */
293
+
483
294
  dbWriteFrom(db: DbHandle, witness: WitnessHandle, callback: (tx: TxHandle) => boolean): NativeWriteOutcome
484
295
  /**
485
296
  * 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).
297
+ * `{ submitted, changed }` report. `cells` is ONE flat row-major array
298
+ * (length rows×arity) in sealed field order, and `rows` is the EXPLICIT
299
+ * row count the caller states — the one collection crossing
300
+ * (proposals/one-representation/20): the JS side alone knows N when the
301
+ * roster is fieldless (N nullary facts project to 0 cells, so no
302
+ * derivation can recover N), and the bridge verifies
303
+ * `cells.length === rows × arity` exactly against its resident sealed
304
+ * roster before building the engine's shape-proved collection in a
305
+ * single pass. Empty (`rows === 0n`, no cells) is lawful and still a
306
+ * mutation. Nothing is judged until commit; shape violations throw
307
+ * typed, naming relation and field.
506
308
  */
309
+ txInsert(tx: TxHandle, relationId: number, rows: bigint, cells: readonly FactValue[]): WireMutationReport
310
+
311
+ txDelete(tx: TxHandle, relationId: number, rows: bigint, cells: readonly FactValue[]): WireMutationReport
312
+
507
313
  txContains(tx: TxHandle, relationId: number, values: readonly FactValue[]): boolean
508
- /** Final-state point lookup through a key statement; `null` on a miss. */
314
+
509
315
  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
- */
316
+
514
317
  txReserve(tx: TxHandle, relationId: number, fieldId: number, count: bigint): WireFreshRange
515
318
 
516
- /**
517
- * Prepares a query (IR as data, ids only; plan pinned at prepare).
518
- * Roster errors return as data.
519
- */
520
319
  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
- */
320
+
526
321
  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. */
322
+
536
323
  preparedClose(prepared: PreparedHandle): void
537
324
 
538
325
  instanceBuilderNew(spec: SchemaSpec): BuilderHandle
326
+
539
327
  instanceBuilderLoad(
540
328
  builder: BuilderHandle,
541
329
  relationId: number,
542
- rows: readonly (readonly FactValue[])[]
543
- ): WireMutationReport
544
- instanceBuilderLoadColumns(
545
- builder: BuilderHandle,
546
- relationId: number,
547
- columns: readonly (readonly FactValue[])[]
330
+ rows: bigint,
331
+ cells: readonly FactValue[]
548
332
  ): WireMutationReport
549
333
  instanceBuilderDelete(
550
334
  builder: BuilderHandle,
551
335
  relationId: number,
552
- rows: readonly (readonly FactValue[])[]
336
+ rows: bigint,
337
+ cells: readonly FactValue[]
553
338
  ): WireMutationReport
554
- instanceBuilderReserve(
555
- builder: BuilderHandle,
556
- relationId: number,
557
- fieldId: number,
558
- count: bigint
559
- ): WireFreshRange
339
+ instanceBuilderReserve(builder: BuilderHandle, relationId: number, fieldId: number, count: bigint): WireFreshRange
560
340
  instanceBuilderContains(builder: BuilderHandle, relationId: number, values: readonly FactValue[]): boolean
561
341
  instanceBuilderGet(
562
342
  builder: BuilderHandle,
@@ -568,6 +348,8 @@ interface Native {
568
348
  instanceBuilderAdmit(builder: BuilderHandle): Promise<AdmitResult>
569
349
  ownedInstanceClose(instance: OwnedHandle): void
570
350
  ownedScan(instance: OwnedHandle, relationId: number): FactValue[][]
351
+
352
+ ownedCount(instance: OwnedHandle, relationId: number): bigint
571
353
  ownedContains(instance: OwnedHandle, relationId: number, values: readonly FactValue[]): boolean
572
354
  ownedGet(
573
355
  instance: OwnedHandle,
@@ -579,54 +361,17 @@ interface Native {
579
361
  ownedExecute(prepared: PreparedHandle, instance: OwnedHandle, params: readonly QueryParam[]): FactValue[][]
580
362
  }
581
363
 
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
364
  const SHIPPED_PLATFORMS = "darwin-arm64"
594
365
 
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
366
  const requireNative = createRequire(import.meta.url)
606
367
 
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 {
368
+ interface NativeBinding extends Native {
369
+ dbClose(db: DbHandle): void
370
+ }
371
+
372
+ function loadNativeBinding(platform: string, arch: string): NativeBinding {
625
373
  const platformPackage = `@bjornpagen/bumbledb-${platform}-${arch}`
626
374
 
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
375
  const present = errors.trySync(() => requireNative.resolve(`${platformPackage}/package.json`))
631
376
  if (present.error) {
632
377
  throw errors.wrap(
@@ -635,8 +380,6 @@ function loadNativeBinding(platform: string, arch: string): Native {
635
380
  )
636
381
  }
637
382
 
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
383
  const loaded = errors.trySync(() => requireNative(platformPackage))
641
384
  if (loaded.error) {
642
385
  throw errors.wrap(loaded.error, `load the ${platformPackage} native binary (package present but unloadable)`)
@@ -644,18 +387,13 @@ function loadNativeBinding(platform: string, arch: string): Native {
644
387
  return loaded.data
645
388
  }
646
389
 
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)
390
+ const binding: NativeBinding = loadNativeBinding(process.platform, process.arch)
391
+ const native: Native = binding
392
+
393
+ function dbClose(db: DbHandle): void {
394
+ binding.dbClose(db)
395
+ }
654
396
 
655
- /**
656
- * Engine throw identity: a real `Error` carrying `kind` from the
657
- * `ErrorFamily` table, or a leftover `{ kind, message }` object.
658
- */
659
397
  function isEngineThrow(value: unknown): value is { kind: ErrorFamilyKind; message: string } {
660
398
  if (typeof value !== "object" || value === null) {
661
399
  return false
@@ -676,13 +414,6 @@ function errorFromThrow(caught: unknown): Error {
676
414
  return errors.new(String(caught))
677
415
  }
678
416
 
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
417
  function bridged<T>(context: string, run: () => T): T {
687
418
  try {
688
419
  return run()
@@ -692,10 +423,6 @@ function bridged<T>(context: string, run: () => T): T {
692
423
  }
693
424
  }
694
425
 
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
426
  async function bridgedAsync<T>(context: string, run: () => Promise<T>): Promise<T> {
700
427
  try {
701
428
  return await run()
@@ -719,7 +446,6 @@ export type {
719
446
  DbHandle,
720
447
  DbOpenResult,
721
448
  ErrorFamilyKind,
722
- Explain,
723
449
  FactValue,
724
450
  FindTermIr,
725
451
  FoldOpIr,
@@ -735,7 +461,6 @@ export type {
735
461
  ManifestStatement,
736
462
  Native,
737
463
  NativeWriteOutcome,
738
- OccurrenceDrift,
739
464
  OpenKind,
740
465
  OwnedHandle,
741
466
  ParsedQuery,
@@ -746,7 +471,6 @@ export type {
746
471
  QueryParam,
747
472
  RecIr,
748
473
  RuleIr,
749
- Staleness,
750
474
  StatementKindTag,
751
475
  TaggedValue,
752
476
  TermIr,
@@ -758,4 +482,4 @@ export type {
758
482
  WitnessHandle,
759
483
  WriteTag
760
484
  }
761
- export { bridged, bridgedAsync, errorFromThrow, loadNativeBinding, native, SHIPPED_PLATFORMS }
485
+ export { bridged, bridgedAsync, dbClose, errorFromThrow, loadNativeBinding, native, SHIPPED_PLATFORMS }