@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/dist/db.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * A store read is one callback: `db.read((instance, witness) => …)`. The
9
9
  * instance is invalid the moment the callback returns; the witness is a
10
10
  * cloneable token and may escape. There is no handle-shaped read and no
11
- * `using snap = db.read()`. Builder, owned instance, and witness
11
+ * `using snap = db.read`. Builder, owned instance, and witness
12
12
  * implement `Symbol.dispose`. Prepared plans are plain values whose
13
13
  * engine-side half is reclaimed by a GC finalizer — reclamation only,
14
14
  * never correctness.
@@ -38,43 +38,38 @@ import { bridged, bridgedAsync, errorFromThrow, native } from "#native.ts";
38
38
  import { lowerQuery } from "#query/lower.ts";
39
39
  import { decodeAnswers, wireParams } from "#query/run.ts";
40
40
  import { isStatement } from "#statements.ts";
41
- function isColumnBatch(value) {
42
- return !(Symbol.iterator in value);
43
- }
44
- function rowsOf(relation, facts) {
45
- const rows = [];
46
- for (const fact of facts) {
47
- rows.push(rowOf(relation.data, recordOf(fact)));
48
- }
49
- return rows;
50
- }
51
41
  /**
52
- * Lowers a column batch to per-field wire arrays in sealed field order.
53
- * Allocates one array per field — never a JS array per row.
42
+ * The flat projector: every fact's cells land in ONE row-major
43
+ * `FactValue` array (length rows×arity) — no JS array per fact exists
44
+ * anywhere between the caller's objects and the native crossing
45
+ * (proposals/one-representation/20, V1) — and the row count is counted
46
+ * while projecting (the {@link FlatCollection} law: the stated count is
47
+ * what the bridge verifies against `rows × arity`, exactly, for every
48
+ * arity). The per-cell judgment is `cellOf` — the one cell judge `rowOf`
49
+ * also speaks (closed handle→id, well-formedness, interval shape) — and
50
+ * the missing-field refusal is `rowOf`'s, byte for byte; only the output
51
+ * form differs (flat, never per-row).
54
52
  */
55
- function columnsOf(relation, batch) {
56
- const record = recordOf(batch);
57
- let count;
58
- return relation.data.fields.map(function marshalColumn(declared) {
59
- const raw = record[declared.name];
60
- if (!Array.isArray(raw)) {
61
- throw errors.new(`relation ${relation.name}: column ${declared.name} is not an array`);
62
- }
63
- if (count === undefined) {
64
- count = raw.length;
65
- }
66
- else if (raw.length !== count) {
67
- throw errors.new(`relation ${relation.name}: column ${declared.name} has length ${raw.length}, expected ${count}`);
53
+ function rowsOf(relation, facts) {
54
+ const data = relation.data;
55
+ const cells = [];
56
+ let rows = 0n;
57
+ for (const fact of facts) {
58
+ rows += 1n;
59
+ const record = recordOf(fact);
60
+ for (const declared of data.fields) {
61
+ const value = record[declared.name];
62
+ if (value === undefined) {
63
+ throw errors.new(`relation ${data.name}: fact is missing field ${declared.name}`);
64
+ }
65
+ cells.push(cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value));
68
66
  }
69
- return raw.map(function marshalCell(value) {
70
- return cellOf(`relation ${relation.name} field ${declared.name}`, declared.field, value);
71
- });
72
- });
67
+ }
68
+ return { rows, cells };
73
69
  }
74
- function mutateCollection(relation, facts, applyRows, applyColumns) {
75
- const report = isColumnBatch(facts)
76
- ? applyColumns(columnsOf(relation, facts))
77
- : applyRows(rowsOf(relation, facts));
70
+ function mutateCollection(relation, facts, apply) {
71
+ const flat = rowsOf(relation, facts);
72
+ const report = apply(flat.rows, flat.cells);
78
73
  return Object.freeze({ submitted: report.submitted, changed: report.changed });
79
74
  }
80
75
  function freshRangeOf(wire) {
@@ -111,43 +106,16 @@ function freshRangeOf(wire) {
111
106
  }
112
107
  });
113
108
  }
114
- /**
115
- * The runtime discriminant of {@link Abandon} values — a property probe is
116
- * how `write`/`writeFrom` distinguish "abort without committing" from an
117
- * ordinary callback result, never a guess about the host's own value shapes.
118
- */
119
109
  const abandonMark = Symbol("bumbledb.abandon");
120
- /**
121
- * Wraps a payload in the {@link Abandon} sentinel — the one way a write
122
- * callback declines to commit: `return abandon(payload)` aborts the delta
123
- * (nothing is committed, not even an empty commit) and the write resolves
124
- * to `{ tag: "abandoned", abandoned: payload }`, from `write` and `writeFrom`
125
- * alike (R10).
126
- */
127
110
  function abandon(payload) {
128
111
  return Object.freeze({ [abandonMark]: true, payload });
129
112
  }
130
- /**
131
- * Narrows a write callback result to the abandon sentinel. The probe is
132
- * the private {@link abandonMark} symbol only {@link abandon} sets, and
133
- * `R`'s `Abandon` arm is the only way a sentinel can flow out of the
134
- * callback — so the narrowed payload type is sound by construction.
135
- */
136
113
  function isAbandon(value) {
137
114
  return typeof value === "object" && value !== null && abandonMark in value;
138
115
  }
139
- /**
140
- * The abandon outcome's trusted admission seam: the value's shape is the
141
- * checkable half (the sentinel mark only {@link abandon} mints, and the
142
- * outcome carrying that sentinel's own payload), and the sentinel's
143
- * existence IS the proof `R` carries an `Abandon` arm — so the outcome is
144
- * admitted at the conditional {@link AbandonedArm} face the type tier
145
- * cannot resolve over an open `R`.
146
- */
147
116
  function isAbandonedOutcome(outcome, sentinel) {
148
117
  return isAbandon(sentinel) && outcome.abandoned === sentinel.payload;
149
118
  }
150
- /** Builds the abandoned write outcome from the callback's own sentinel (the R10 arm's one mint). */
151
119
  function abandonedOutcome(sentinel) {
152
120
  const outcome = Object.freeze({ tag: "abandoned", abandoned: sentinel.payload });
153
121
  if (!isAbandonedOutcome(outcome, sentinel)) {
@@ -156,26 +124,63 @@ function abandonedOutcome(sentinel) {
156
124
  return outcome;
157
125
  }
158
126
  const witnessTypes = Symbol("bumbledb.witness.types");
159
- /**
160
- * The module-private inference slot of {@link Prepared}: an optional symbol
161
- * property (never set at runtime) that keeps the prepared value's `Row` and
162
- * `Params` type arguments load-bearing, so `execute` infers the typed rows
163
- * and the typed params object from the value alone — the query module's
164
- * `inferred` pattern, local to this module. A type-level carrier only:
165
- * values stay bare, nothing is asserted.
166
- */
167
127
  const preparedTypes = Symbol("bumbledb.prepared.types");
168
- /**
169
- * Mirrors the engine's materialized statement order
170
- * (`SchemaDescriptor::materialized_statements`, pinned by the fingerprint):
171
- * one auto-key per fresh field (relation declaration order, then field
172
- * order), one closed auto-key per closed relation (declaration order),
173
- * then the declared statements in declaration order — a `mirrors`
174
- * statement occupying TWO adjacent slots (the engine lowers `==` to two
175
- * containments, `source <= target` first), both owned by the one SDK
176
- * value. This positional match is how statement ids resolve back to SDK
177
- * statement values without the engine ever learning a wire format.
178
- */
128
+ function decodeOffendingFact(member, relation, fact) {
129
+ const declared = sealedFieldsOf(member);
130
+ const decoded = {};
131
+ for (const cell of fact.fields) {
132
+ const cited = declared.find(function byName(candidate) {
133
+ return candidate.name === cell.name;
134
+ });
135
+ const roster = rosterOf(cited?.field);
136
+ decoded[cell.name] =
137
+ roster !== undefined
138
+ ? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
139
+ : cell.value;
140
+ }
141
+ return Object.freeze({ relation, fact: Object.freeze(decoded) });
142
+ }
143
+ function violationFromEntry(entry, wire, facts) {
144
+ const canonical = wire.canonical;
145
+ if (entry.kind === "functionality") {
146
+ if (!("statement" in entry)) {
147
+ return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts });
148
+ }
149
+ return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts });
150
+ }
151
+ if (entry.kind === "capacity") {
152
+ if (wire.kind !== "capacity") {
153
+ throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`);
154
+ }
155
+ return Object.freeze({
156
+ kind: "capacity",
157
+ statement: entry.statement,
158
+ canonical,
159
+ measure: wire.measure,
160
+ facts
161
+ });
162
+ }
163
+ if (wire.kind !== "containment") {
164
+ throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`);
165
+ }
166
+ if (entry.kind === "mirrors") {
167
+ return Object.freeze({
168
+ kind: "containment",
169
+ statement: entry.statement,
170
+ canonical,
171
+ direction: wire.direction,
172
+ orientation: entry.orientation,
173
+ facts
174
+ });
175
+ }
176
+ return Object.freeze({
177
+ kind: "containment",
178
+ statement: entry.statement,
179
+ canonical,
180
+ direction: wire.direction,
181
+ facts
182
+ });
183
+ }
179
184
  function materializedEntries(theory) {
180
185
  const entries = impliedKeyEntries(theory);
181
186
  for (const statement of theory.statements) {
@@ -183,13 +188,6 @@ function materializedEntries(theory) {
183
188
  }
184
189
  return entries;
185
190
  }
186
- /**
187
- * The engine-materialized implied keys, in the engine's pinned order: one
188
- * auto-key per fresh field (relation declaration order, then field order),
189
- * then one closed auto-key `R(id) -> R` per closed relation (declaration
190
- * order). These slots carry no SDK statement value — the engine owns them
191
- * (`schema()` rejects an explicit duplicate).
192
- */
193
191
  function impliedKeyEntries(theory) {
194
192
  const entries = [];
195
193
  for (const member of Object.values(theory.relations)) {
@@ -217,12 +215,6 @@ function impliedKeyEntries(theory) {
217
215
  }
218
216
  return entries;
219
217
  }
220
- /**
221
- * One declared statement's materialized slots: a key or capacity statement
222
- * occupies one, a `mirrors` occupies two adjacent slots (the engine lowers
223
- * `==` to two containments, `source <= target` first), both owned by the
224
- * one SDK value.
225
- */
226
218
  function declaredEntries(statement) {
227
219
  const data = statement.data;
228
220
  switch (data.kind) {
@@ -259,26 +251,9 @@ function declaredEntries(statement) {
259
251
  function isThenable(value) {
260
252
  return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
261
253
  }
262
- /**
263
- * Narrows a keyed-get middle argument to a statement value (vs a key
264
- * object) through the statement module's admission brand — a
265
- * REPRESENTATION, never a shape probe: fact cell shapes are structurally
266
- * OPEN (an interval value carrying an excess `kind` property is a legal
267
- * cell), so no property probe could ever be sound here, but no host-built
268
- * key object can spell the module-private brand symbol.
269
- */
270
254
  function isStatementValue(value) {
271
255
  return isStatement(value);
272
256
  }
273
- /**
274
- * THE one selector dispatch of the `get` overload pair (primary-key vs
275
- * key-statement, `docs/architecture/70-api.md` § the freeze): judges the
276
- * middle argument once and hands the narrowed pieces to the chosen
277
- * continuation. `Db.get` and the read scope's `get` both dispatch through
278
- * here, so the two mismatch refusals speak with one voice and the symmetry
279
- * rule (`db.get(...) === db.read(snap => snap.get(...))`) holds by
280
- * construction.
281
- */
282
257
  function selectKeyRead(keyOrStatement, declaredKey, byStatement, byPrimary) {
283
258
  if (declaredKey !== undefined) {
284
259
  if (!isStatementValue(keyOrStatement)) {
@@ -291,16 +266,6 @@ function selectKeyRead(keyOrStatement, declaredKey, byStatement, byPrimary) {
291
266
  }
292
267
  return byPrimary(keyOrStatement);
293
268
  }
294
- /**
295
- * Builds the id-resolution tables from the manifest, verifying the SDK's
296
- * positional mirror against the engine's reported order — any drift
297
- * (count, kind, id, or membership) is a construction-time failure, never a
298
- * silent misattribution of a violation to the wrong statement value. The
299
- * declaration-ordinal law the query lowering leans on is verified in the
300
- * same walks: relation ids and sealed field ids both equal declaration
301
- * order, so a constructed `Tables` IS the proof and `prepare` inherits it
302
- * structurally — never a silently misaddressed query.
303
- */
304
269
  function tablesOf(theory, manifest) {
305
270
  const entries = materializedEntries(theory);
306
271
  if (entries.length !== manifest.statements.length) {
@@ -382,14 +347,7 @@ const witnessReclaimer = new FinalizationRegistry(function reclaimWitness(handle
382
347
  return;
383
348
  }
384
349
  });
385
- /** The private engine halves of this module's prepared values. */
386
350
  const preparedPlans = new WeakMap();
387
- /**
388
- * Reclaims the engine-side plan of a garbage-collected {@link Prepared}
389
- * value. RECLAMATION ONLY, never correctness: a plan the collector never
390
- * visits is idle engine memory until process exit, and a failure to close
391
- * is swallowed (there is no one left to care — the owning value is gone).
392
- */
393
351
  const planReclaimer = new FinalizationRegistry(function reclaimPlan(handle) {
394
352
  const closed = errors.trySync(function closePlan() {
395
353
  native.preparedClose(handle);
@@ -404,32 +362,6 @@ const ErrUseAfterScope = errors.new("bumbledb useAfterScope: a stashed read inst
404
362
  const ErrForeignPrepared = errors.new("bumbledb foreignPrepared: a prepared query met a foreign instance");
405
363
  const ErrForeignWitness = errors.new("bumbledb foreignWitness: a witness met a foreign store");
406
364
  function catalogMethods(theory, tables, owner, assertLive, ops) {
407
- function resolveOrdinary(relation) {
408
- const entry = tables.relations.get(relation.name);
409
- if (entry === undefined || entry.member !== relation) {
410
- throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`);
411
- }
412
- if (isClosedMember(relation)) {
413
- throw errors.new(`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`);
414
- }
415
- return entry;
416
- }
417
- function declaredKeyOf(relation, statement) {
418
- const statementId = tables.statements.findIndex(function byIdentity(candidate) {
419
- return "statement" in candidate && candidate.statement === statement;
420
- });
421
- const entry = tables.statements[statementId];
422
- if (entry === undefined) {
423
- throw errors.new(`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`);
424
- }
425
- if (entry.kind !== "functionality") {
426
- throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing");
427
- }
428
- if (entry.owner !== relation.name) {
429
- throw errors.new(`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`);
430
- }
431
- return Object.freeze({ statementId, projection: entry.projection });
432
- }
433
365
  function planOf(prepared) {
434
366
  const plan = preparedPlans.get(prepared);
435
367
  if (plan === undefined) {
@@ -442,16 +374,16 @@ function catalogMethods(theory, tables, owner, assertLive, ops) {
442
374
  }
443
375
  function contains(relation, fact) {
444
376
  assertLive();
445
- const entry = resolveOrdinary(relation);
377
+ const entry = ordinaryEntry(tables, theory, relation);
446
378
  return bridged("bumbledb instance contains", function readContains() {
447
379
  return ops.contains(entry.id, rowOf(relation.data, recordOf(fact)));
448
380
  });
449
381
  }
450
382
  function get(relation, keyOrStatement, declaredKey) {
451
383
  assertLive();
452
- const entry = resolveOrdinary(relation);
384
+ const entry = ordinaryEntry(tables, theory, relation);
453
385
  return selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
454
- const selected = declaredKeyOf(relation, statement);
386
+ const selected = declaredKeyOf(tables, theory, relation, statement);
455
387
  const row = bridged("bumbledb instance get", function readGet() {
456
388
  return ops.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, recordOf(key)));
457
389
  });
@@ -469,7 +401,7 @@ function catalogMethods(theory, tables, owner, assertLive, ops) {
469
401
  }
470
402
  function scan(relation) {
471
403
  assertLive();
472
- const entry = resolveOrdinary(relation);
404
+ const entry = ordinaryEntry(tables, theory, relation);
473
405
  const rows = bridged("bumbledb instance scan", function readScan() {
474
406
  return ops.scan(entry.id);
475
407
  });
@@ -477,6 +409,13 @@ function catalogMethods(theory, tables, owner, assertLive, ops) {
477
409
  return factOf(relation, row);
478
410
  });
479
411
  }
412
+ function count(relation) {
413
+ assertLive();
414
+ const entry = ordinaryEntry(tables, theory, relation);
415
+ return bridged("bumbledb instance count", function readCount() {
416
+ return ops.count(entry.id);
417
+ });
418
+ }
480
419
  function execute(prepared, params) {
481
420
  assertLive();
482
421
  const plan = planOf(prepared);
@@ -508,7 +447,7 @@ function catalogMethods(theory, tables, owner, assertLive, ops) {
508
447
  planReclaimer.register(prepared, outcome.prepared);
509
448
  return prepared;
510
449
  }
511
- return { scan, get, contains, execute, prepare };
450
+ return { scan, count, get, contains, execute, prepare };
512
451
  }
513
452
  function ordinaryEntry(tables, theory, relation) {
514
453
  const entry = tables.relations.get(relation.name);
@@ -520,23 +459,23 @@ function ordinaryEntry(tables, theory, relation) {
520
459
  }
521
460
  return entry;
522
461
  }
523
- function overlayMethods(theory, tables, assertLive, reads) {
524
- function declaredKeyOf(relation, statement) {
525
- const statementId = tables.statements.findIndex(function byIdentity(candidate) {
526
- return "statement" in candidate && candidate.statement === statement;
527
- });
528
- const entry = tables.statements[statementId];
529
- if (entry === undefined) {
530
- throw errors.new(`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`);
531
- }
532
- if (entry.kind !== "functionality") {
533
- throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing");
534
- }
535
- if (entry.owner !== relation.name) {
536
- throw errors.new(`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`);
537
- }
538
- return Object.freeze({ statementId, projection: entry.projection });
462
+ function declaredKeyOf(tables, theory, relation, statement) {
463
+ const statementId = tables.statements.findIndex(function byIdentity(candidate) {
464
+ return "statement" in candidate && candidate.statement === statement;
465
+ });
466
+ const entry = tables.statements[statementId];
467
+ if (entry === undefined) {
468
+ throw errors.new(`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`);
539
469
  }
470
+ if (entry.kind !== "functionality") {
471
+ throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing");
472
+ }
473
+ if (entry.owner !== relation.name) {
474
+ throw errors.new(`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`);
475
+ }
476
+ return Object.freeze({ statementId, projection: entry.projection });
477
+ }
478
+ function overlayMethods(theory, tables, assertLive, reads) {
540
479
  function contains(relation, fact) {
541
480
  assertLive();
542
481
  const entry = ordinaryEntry(tables, theory, relation);
@@ -553,7 +492,7 @@ function overlayMethods(theory, tables, assertLive, reads) {
553
492
  assertLive();
554
493
  const entry = ordinaryEntry(tables, theory, relation);
555
494
  return selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
556
- return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key));
495
+ return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key));
557
496
  }, function byPrimary(key) {
558
497
  const primaryKey = entry.primaryKey;
559
498
  if (primaryKey === undefined) {
@@ -575,6 +514,9 @@ function createReadInstance(nativeHandle, theory, tables, owner) {
575
514
  scan(relationId) {
576
515
  return native.instanceScan(state.handle, relationId);
577
516
  },
517
+ count(relationId) {
518
+ return native.instanceCount(state.handle, relationId);
519
+ },
578
520
  contains(relationId, values) {
579
521
  return native.instanceContains(state.handle, relationId, values);
580
522
  },
@@ -600,12 +542,6 @@ function createReadInstance(nativeHandle, theory, tables, owner) {
600
542
  instanceStates.set(instance, state);
601
543
  return instance;
602
544
  }
603
- /**
604
- * Constructs one open `Db` over an already-admitted handle: builds the
605
- * id-resolution tables once and closes over them — the `Db` owns handle
606
- * and tables and nothing else. Handle lifetime is the process's: the store
607
- * cache holds the environment handle until the exit hook closes it.
608
- */
609
545
  function openDb(handle, theory, manifest) {
610
546
  const tables = tablesOf(theory, manifest);
611
547
  /** This store's identity token: read scopes and prepared values carry it, so cross-store use is a typed refusal. */
@@ -613,111 +549,26 @@ function openDb(handle, theory, manifest) {
613
549
  function isMemberName(name) {
614
550
  return tables.relations.has(name);
615
551
  }
616
- function resolveOrdinary(relation) {
617
- const entry = tables.relations.get(relation.name);
618
- if (entry === undefined || entry.member !== relation) {
619
- throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`);
620
- }
621
- if (isClosedMember(relation)) {
622
- throw errors.new(`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`);
623
- }
624
- return entry;
625
- }
626
- function offendingFactOf(fact) {
627
- const entry = tables.relations.get(fact.relation);
628
- if (entry === undefined || !isMemberName(fact.relation)) {
629
- throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`);
630
- }
631
- const declared = sealedFieldsOf(entry.member);
632
- const decoded = {};
633
- for (const cell of fact.fields) {
634
- const cited = declared.find(function byName(candidate) {
635
- return candidate.name === cell.name;
636
- });
637
- const roster = rosterOf(cited?.field);
638
- decoded[cell.name] =
639
- roster !== undefined
640
- ? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
641
- : cell.value;
642
- }
643
- return Object.freeze({ relation: fact.relation, fact: Object.freeze(decoded) });
644
- }
645
552
  function violationOf(wire) {
646
553
  const entry = tables.statements[wire.statementId];
647
554
  if (entry === undefined) {
648
555
  throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`);
649
556
  }
650
- const facts = Object.freeze(wire.facts.map(offendingFactOf));
651
- const canonical = wire.canonical;
652
- if (entry.kind === "functionality") {
653
- if (!("statement" in entry)) {
654
- return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts });
655
- }
656
- return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts });
657
- }
658
- if (entry.kind === "capacity") {
659
- if (wire.kind !== "capacity") {
660
- throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`);
557
+ const facts = Object.freeze(wire.facts.map(function offending(fact) {
558
+ const rel = tables.relations.get(fact.relation);
559
+ if (rel === undefined || !isMemberName(fact.relation)) {
560
+ throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`);
661
561
  }
662
- return Object.freeze({
663
- kind: "capacity",
664
- statement: entry.statement,
665
- canonical,
666
- measure: wire.measure,
667
- facts
668
- });
669
- }
670
- if (wire.kind !== "containment") {
671
- throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`);
672
- }
673
- if (entry.kind === "mirrors") {
674
- return Object.freeze({
675
- kind: "containment",
676
- statement: entry.statement,
677
- canonical,
678
- direction: wire.direction,
679
- orientation: entry.orientation,
680
- facts
681
- });
682
- }
683
- return Object.freeze({
684
- kind: "containment",
685
- statement: entry.statement,
686
- canonical,
687
- direction: wire.direction,
688
- facts
689
- });
690
- }
691
- /**
692
- * Resolves a key-statement-selected read: the statement must be the
693
- * IDENTICAL `key()` value this schema declared (identity is the
694
- * membership rule) and must key `relation` — its materialized statement
695
- * id comes from the positional mirror, so the engine point-reads through
696
- * exactly the declared projection.
697
- */
698
- function declaredKeyOf(relation, statement) {
699
- const statementId = tables.statements.findIndex(function byIdentity(candidate) {
700
- return "statement" in candidate && candidate.statement === statement;
701
- });
702
- const entry = tables.statements[statementId];
703
- if (entry === undefined) {
704
- throw errors.new(`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`);
705
- }
706
- if (entry.kind !== "functionality") {
707
- throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing");
708
- }
709
- if (entry.owner !== relation.name) {
710
- throw errors.new(`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`);
711
- }
712
- return Object.freeze({ statementId, projection: entry.projection });
562
+ return decodeOffendingFact(rel.member, fact.relation, fact);
563
+ }));
564
+ return violationFromEntry(entry, wire, facts);
713
565
  }
714
566
  function pointReadsOf(assertLive, reads) {
715
567
  function contains(relation, fact) {
716
568
  assertLive();
717
- const entry = resolveOrdinary(relation);
569
+ const entry = ordinaryEntry(tables, theory, relation);
718
570
  return reads.contains(entry.id, rowOf(relation.data, recordOf(fact)));
719
571
  }
720
- /** One keyed point read through an already-resolved key, decoded to a fact (`undefined` on a miss). */
721
572
  function readThroughKey(relation, entry, selected, key) {
722
573
  const row = reads.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, key));
723
574
  if (row === null) {
@@ -727,9 +578,9 @@ function openDb(handle, theory, manifest) {
727
578
  }
728
579
  function get(relation, keyOrStatement, declaredKey) {
729
580
  assertLive();
730
- const entry = resolveOrdinary(relation);
581
+ const entry = ordinaryEntry(tables, theory, relation);
731
582
  return selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
732
- return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key));
583
+ return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key));
733
584
  }, function byPrimary(key) {
734
585
  const primaryKey = entry.primaryKey;
735
586
  if (primaryKey === undefined) {
@@ -791,32 +642,6 @@ function openDb(handle, theory, manifest) {
791
642
  });
792
643
  return (captured ?? result);
793
644
  }
794
- function scan(relation) {
795
- return read(function scanInScope(instance) {
796
- return instance.scan(relation);
797
- });
798
- }
799
- function get(relation, keyOrStatement, declaredKey) {
800
- let found;
801
- read(function getInScope(instance) {
802
- found = selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
803
- return instance.get(relation, statement, key);
804
- }, function byPrimary(key) {
805
- return instance.get(relation, key);
806
- });
807
- });
808
- return found;
809
- }
810
- function contains(relation, fact) {
811
- return read(function containsInScope(instance) {
812
- return instance.contains(relation, fact);
813
- });
814
- }
815
- function execute(prepared, params) {
816
- return read(function executeInScope(instance) {
817
- return instance.execute(prepared, params);
818
- });
819
- }
820
645
  function makeTx(resolveTx) {
821
646
  const txState = { spent: false };
822
647
  function assertLive() {
@@ -840,30 +665,27 @@ function openDb(handle, theory, manifest) {
840
665
  });
841
666
  function insert(relation, facts) {
842
667
  assertLive();
843
- const entry = resolveOrdinary(relation);
668
+ const entry = ordinaryEntry(tables, theory, relation);
844
669
  const txHandle = resolveTx();
845
- return mutateCollection(relation, facts, function applyRows(rows) {
670
+ return mutateCollection(relation, facts, function applyCells(rows, cells) {
846
671
  return bridged("bumbledb tx insert", function record() {
847
- return native.txInsert(txHandle, entry.id, rows);
848
- });
849
- }, function applyColumns(columns) {
850
- return bridged("bumbledb tx insert", function recordColumns() {
851
- return native.txInsertColumns(txHandle, entry.id, columns);
672
+ return native.txInsert(txHandle, entry.id, rows, cells);
852
673
  });
853
674
  });
854
675
  }
855
676
  function remove(relation, facts) {
856
677
  assertLive();
857
- const entry = resolveOrdinary(relation);
678
+ const entry = ordinaryEntry(tables, theory, relation);
858
679
  const txHandle = resolveTx();
680
+ const flat = rowsOf(relation, facts);
859
681
  const report = bridged("bumbledb tx delete", function record() {
860
- return native.txDelete(txHandle, entry.id, rowsOf(relation, facts));
682
+ return native.txDelete(txHandle, entry.id, flat.rows, flat.cells);
861
683
  });
862
684
  return Object.freeze({ submitted: report.submitted, changed: report.changed });
863
685
  }
864
686
  function reserve(relation, field, count) {
865
687
  assertLive();
866
- const entry = resolveOrdinary(relation);
688
+ const entry = ordinaryEntry(tables, theory, relation);
867
689
  const declared = relation.data.fields.find(function byName(candidate) {
868
690
  return candidate.name === field;
869
691
  });
@@ -983,25 +805,11 @@ function openDb(handle, theory, manifest) {
983
805
  return Object.freeze({
984
806
  schema: theory,
985
807
  read,
986
- scan,
987
- get,
988
- contains,
989
- execute,
990
808
  write,
991
809
  writeFrom,
992
810
  prepare
993
811
  });
994
812
  }
995
- /**
996
- * The engine twin of the schema-level class wall, as a matchable value
997
- * (`errors.is`): the shared lowering rejected a spec whose statement pairs
998
- * faces with disagreeing newtype labels — the faces of a dependency agree
999
- * on their newtype, or neither carries one. UNREACHABLE through the typed
1000
- * builder (the SDK computes every label from the laws, so its lowered
1001
- * specs cohere by construction); a raw spec handed to the bridge is the
1002
- * one road here, and the runtime referee that proves the engine judges
1003
- * what the types claim.
1004
- */
1005
813
  const ErrNewtypeMismatch = errors.new("bumbledb newtypeMismatch: a statement pairs faces whose newtypes disagree — the faces of a dependency agree on their newtype, or neither carries one");
1006
814
  const ErrSchemaError = errors.new("bumbledb schemaError: the declaration failed validation");
1007
815
  const ErrFingerprintMismatch = errors.new("bumbledb fingerprintMismatch: the store's schema does not match this theory");
@@ -1050,65 +858,14 @@ function mapViolationWithoutStore(theory, wire) {
1050
858
  if (entry === undefined) {
1051
859
  throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`);
1052
860
  }
1053
- function offending(fact) {
861
+ const facts = Object.freeze(wire.facts.map(function offending(fact) {
1054
862
  const member = theory.relations[fact.relation];
1055
863
  if (member === undefined || !(fact.relation in theory.relations)) {
1056
864
  throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`);
1057
865
  }
1058
- const declared = sealedFieldsOf(member);
1059
- const decoded = {};
1060
- for (const cell of fact.fields) {
1061
- const cited = declared.find(function byName(candidate) {
1062
- return candidate.name === cell.name;
1063
- });
1064
- const roster = rosterOf(cited?.field);
1065
- decoded[cell.name] =
1066
- roster !== undefined
1067
- ? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
1068
- : cell.value;
1069
- }
1070
- return Object.freeze({ relation: fact.relation, fact: Object.freeze(decoded) });
1071
- }
1072
- const facts = Object.freeze(wire.facts.map(offending));
1073
- const canonical = wire.canonical;
1074
- if (entry.kind === "functionality") {
1075
- if (!("statement" in entry)) {
1076
- return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts });
1077
- }
1078
- return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts });
1079
- }
1080
- if (entry.kind === "capacity") {
1081
- if (wire.kind !== "capacity") {
1082
- throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`);
1083
- }
1084
- return Object.freeze({
1085
- kind: "capacity",
1086
- statement: entry.statement,
1087
- canonical,
1088
- measure: wire.measure,
1089
- facts
1090
- });
1091
- }
1092
- if (wire.kind !== "containment") {
1093
- throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`);
1094
- }
1095
- if (entry.kind === "mirrors") {
1096
- return Object.freeze({
1097
- kind: "containment",
1098
- statement: entry.statement,
1099
- canonical,
1100
- direction: wire.direction,
1101
- orientation: entry.orientation,
1102
- facts
1103
- });
1104
- }
1105
- return Object.freeze({
1106
- kind: "containment",
1107
- statement: entry.statement,
1108
- canonical,
1109
- direction: wire.direction,
1110
- facts
1111
- });
866
+ return decodeOffendingFact(member, fact.relation, fact);
867
+ }));
868
+ return violationFromEntry(entry, wire, facts);
1112
869
  }
1113
870
  async function openStore(storePath, theory) {
1114
871
  const canonical = path.resolve(storePath);
@@ -1152,6 +909,9 @@ function wrapOwned(nativeHandle, theory) {
1152
909
  scan(relationId) {
1153
910
  return native.ownedScan(nativeHandle, relationId);
1154
911
  },
912
+ count(relationId) {
913
+ return native.ownedCount(nativeHandle, relationId);
914
+ },
1155
915
  contains(relationId, values) {
1156
916
  return native.ownedContains(nativeHandle, relationId, values);
1157
917
  },
@@ -1213,21 +973,18 @@ function wrapBuilder(nativeHandle, theory) {
1213
973
  load(relation, facts) {
1214
974
  assertLive();
1215
975
  const entry = ordinaryEntry(tables, theory, relation);
1216
- return mutateCollection(relation, facts, function applyRows(rows) {
1217
- return bridged("bumbledb builder load", function loadRows() {
1218
- return native.instanceBuilderLoad(nativeHandle, entry.id, rows);
1219
- });
1220
- }, function applyColumns(columns) {
1221
- return bridged("bumbledb builder load", function loadColumns() {
1222
- return native.instanceBuilderLoadColumns(nativeHandle, entry.id, columns);
976
+ return mutateCollection(relation, facts, function applyCells(rows, cells) {
977
+ return bridged("bumbledb builder load", function loadCells() {
978
+ return native.instanceBuilderLoad(nativeHandle, entry.id, rows, cells);
1223
979
  });
1224
980
  });
1225
981
  },
1226
982
  delete(relation, facts) {
1227
983
  assertLive();
1228
984
  const entry = ordinaryEntry(tables, theory, relation);
985
+ const flat = rowsOf(relation, facts);
1229
986
  const report = bridged("bumbledb builder delete", function remove() {
1230
- return native.instanceBuilderDelete(nativeHandle, entry.id, rowsOf(relation, facts));
987
+ return native.instanceBuilderDelete(nativeHandle, entry.id, flat.rows, flat.cells);
1231
988
  });
1232
989
  return Object.freeze({ submitted: report.submitted, changed: report.changed });
1233
990
  },
@@ -1311,15 +1068,9 @@ const InstanceBuilder = Object.freeze({
1311
1068
  * hold the `Db` this process opened.
1312
1069
  */
1313
1070
  const Db = Object.freeze({
1314
- /** Creates a fresh durable store at `path` from the schema. */
1315
1071
  async create(storePath, theory) {
1316
1072
  return createStore(storePath, theory);
1317
1073
  },
1318
- /**
1319
- * Opens an existing durable store at `path` with the same theory.
1320
- * Format 8 open never back-fills a descriptor. A second open of a
1321
- * still-live path is `EnvironmentLocked`.
1322
- */
1323
1074
  async open(storePath, theory) {
1324
1075
  return openStore(storePath, theory);
1325
1076
  },
@@ -1338,5 +1089,5 @@ const Db = Object.freeze({
1338
1089
  return openFromHandle(dbHandle, rec.theory);
1339
1090
  }
1340
1091
  });
1341
- export { abandon, Db, ErrAsyncCallback, ErrForeignPrepared, ErrForeignWitness, ErrFingerprintMismatch, ErrIrError, ErrNewtypeMismatch, ErrSchemaError, ErrSpentHandle, ErrUseAfterScope, InstanceBuilder };
1092
+ export { abandon, Db, ErrAsyncCallback, ErrFingerprintMismatch, ErrForeignPrepared, ErrForeignWitness, ErrIrError, ErrNewtypeMismatch, ErrSchemaError, ErrSpentHandle, ErrUseAfterScope, InstanceBuilder };
1342
1093
  //# sourceMappingURL=db.js.map