@bjornpagen/bumbledb 0.12.2 → 0.15.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.
- package/COOKBOOK.md +35 -21
- package/README.md +82 -55
- package/dist/db.d.ts +173 -130
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +782 -371
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +7 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -9
- package/dist/index.js.map +1 -1
- package/dist/marshal.d.ts +5 -27
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +4 -21
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +157 -168
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +46 -8
- package/dist/native.js.map +1 -1
- package/dist/query/find.d.ts +14 -9
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +2 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +5 -4
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +11 -9
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/relation.d.ts +4 -25
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +3 -4
- package/dist/relation.js.map +1 -1
- package/package.json +3 -3
- package/src/db.ts +1151 -500
- package/src/index.ts +28 -24
- package/src/marshal.ts +5 -35
- package/src/native.ts +248 -175
- package/src/query/find.ts +19 -14
- package/src/query/lower.ts +7 -6
- package/src/query/parse-ir.ts +11 -9
- package/src/relation.ts +3 -28
- package/dist/exhume.d.ts +0 -143
- package/dist/exhume.d.ts.map +0 -1
- package/dist/exhume.js +0 -166
- package/dist/exhume.js.map +0 -1
- package/src/exhume.ts +0 -267
package/dist/db.js
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
* `Db` — the living half of the SDK (PRD-07): open/create a store from a
|
|
3
3
|
* `Schema`, write typed facts through delta transactions with race-free
|
|
4
4
|
* final-state point reads, receive rejections as typed violation VALUES
|
|
5
|
-
* keyed to statements, and read through
|
|
6
|
-
* the schema's relations record.
|
|
5
|
+
* keyed to statements, and read through a synchronous instance callback —
|
|
6
|
+
* all typed by the schema's relations record.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* at scope exit. Prepared plans are plain values whose engine-side half
|
|
16
|
-
* is reclaimed by a GC finalizer — reclamation only, never correctness.
|
|
8
|
+
* A store read is one callback: `db.read((instance, witness) => …)`. The
|
|
9
|
+
* instance is invalid the moment the callback returns; the witness is a
|
|
10
|
+
* cloneable token and may escape. There is no handle-shaped read and no
|
|
11
|
+
* `using snap = db.read()`. Builder, owned instance, and witness
|
|
12
|
+
* implement `Symbol.dispose`. Prepared plans are plain values whose
|
|
13
|
+
* engine-side half is reclaimed by a GC finalizer — reclamation only,
|
|
14
|
+
* never correctness.
|
|
17
15
|
*
|
|
18
16
|
* PROCESS MODEL: one process, one exclusive-lock handle per store. The
|
|
19
17
|
* `Db` value owns the LMDB environment's exclusive lock until process
|
|
@@ -24,22 +22,95 @@
|
|
|
24
22
|
* policy.
|
|
25
23
|
*
|
|
26
24
|
* REJECTION IS DATA: a rejected commit is a domain outcome (it becomes the
|
|
27
|
-
* LLM repair prompt downstream), returned as a {@link
|
|
28
|
-
* {@link Violation} values.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* LLM repair prompt downstream), returned as a {@link WriteOutcome}
|
|
26
|
+
* carrying {@link Violation} values. A moved generation on
|
|
27
|
+
* {@link Db.writeFrom} is the `{ tag: "moved" }` arm, not an exception.
|
|
28
|
+
* Genuine failures — I/O, used-after-scope, spent handle, marshal shape —
|
|
29
|
+
* throw `@superbuilders/errors` wrapped errors instead.
|
|
31
30
|
*/
|
|
32
31
|
import * as path from "node:path";
|
|
33
32
|
import * as errors from "@superbuilders/errors";
|
|
34
33
|
import { isClosedMember, sealedFieldsOf } from "#closed.ts";
|
|
35
|
-
import { exhumeStore } from "#exhume.ts";
|
|
36
34
|
import { rosterOf } from "#fields.ts";
|
|
37
35
|
import { lower } from "#lower.ts";
|
|
38
|
-
import { factOf, handleOf, isFreshField,
|
|
39
|
-
import { bridged, native } from "#native.ts";
|
|
36
|
+
import { cellOf, factOf, handleOf, isFreshField, keyRowOf, recordOf, rowOf } from "#marshal.ts";
|
|
37
|
+
import { bridged, bridgedAsync, errorFromThrow, native } from "#native.ts";
|
|
40
38
|
import { lowerQuery } from "#query/lower.ts";
|
|
41
39
|
import { decodeAnswers, wireParams } from "#query/run.ts";
|
|
42
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
|
+
/**
|
|
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.
|
|
54
|
+
*/
|
|
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}`);
|
|
68
|
+
}
|
|
69
|
+
return raw.map(function marshalCell(value) {
|
|
70
|
+
return cellOf(`relation ${relation.name} field ${declared.name}`, declared.field, value);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function mutateCollection(relation, facts, applyRows, applyColumns) {
|
|
75
|
+
const report = isColumnBatch(facts)
|
|
76
|
+
? applyColumns(columnsOf(relation, facts))
|
|
77
|
+
: applyRows(rowsOf(relation, facts));
|
|
78
|
+
return Object.freeze({ submitted: report.submitted, changed: report.changed });
|
|
79
|
+
}
|
|
80
|
+
function freshRangeOf(wire) {
|
|
81
|
+
if (wire.empty) {
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
empty: true,
|
|
84
|
+
count: 0n,
|
|
85
|
+
at(_index) {
|
|
86
|
+
return undefined;
|
|
87
|
+
},
|
|
88
|
+
*[Symbol.iterator]() { }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const start = wire.start;
|
|
92
|
+
const endExclusive = wire.endExclusive;
|
|
93
|
+
const count = endExclusive - start;
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
empty: false,
|
|
96
|
+
start,
|
|
97
|
+
endExclusive,
|
|
98
|
+
get count() {
|
|
99
|
+
return count;
|
|
100
|
+
},
|
|
101
|
+
at(index) {
|
|
102
|
+
if (index < 0n || index >= count) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return start + index;
|
|
106
|
+
},
|
|
107
|
+
*[Symbol.iterator]() {
|
|
108
|
+
for (let id = start; id < endExclusive; id++) {
|
|
109
|
+
yield id;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
43
114
|
/**
|
|
44
115
|
* The runtime discriminant of {@link Abandon} values — a property probe is
|
|
45
116
|
* how `write`/`writeFrom` distinguish "abort without committing" from an
|
|
@@ -50,7 +121,7 @@ const abandonMark = Symbol("bumbledb.abandon");
|
|
|
50
121
|
* Wraps a payload in the {@link Abandon} sentinel — the one way a write
|
|
51
122
|
* callback declines to commit: `return abandon(payload)` aborts the delta
|
|
52
123
|
* (nothing is committed, not even an empty commit) and the write resolves
|
|
53
|
-
* to `{
|
|
124
|
+
* to `{ tag: "abandoned", abandoned: payload }`, from `write` and `writeFrom`
|
|
54
125
|
* alike (R10).
|
|
55
126
|
*/
|
|
56
127
|
function abandon(payload) {
|
|
@@ -78,12 +149,13 @@ function isAbandonedOutcome(outcome, sentinel) {
|
|
|
78
149
|
}
|
|
79
150
|
/** Builds the abandoned write outcome from the callback's own sentinel (the R10 arm's one mint). */
|
|
80
151
|
function abandonedOutcome(sentinel) {
|
|
81
|
-
const outcome = Object.freeze({
|
|
152
|
+
const outcome = Object.freeze({ tag: "abandoned", abandoned: sentinel.payload });
|
|
82
153
|
if (!isAbandonedOutcome(outcome, sentinel)) {
|
|
83
154
|
throw errors.new("bumbledb abandon outcome construction incomplete");
|
|
84
155
|
}
|
|
85
156
|
return outcome;
|
|
86
157
|
}
|
|
158
|
+
const witnessTypes = Symbol("bumbledb.witness.types");
|
|
87
159
|
/**
|
|
88
160
|
* The module-private inference slot of {@link Prepared}: an optional symbol
|
|
89
161
|
* property (never set at runtime) that keeps the prepared value's `Row` and
|
|
@@ -278,8 +350,38 @@ function tablesOf(theory, manifest) {
|
|
|
278
350
|
});
|
|
279
351
|
return Object.freeze({ relations, statements: Object.freeze(entries) });
|
|
280
352
|
}
|
|
281
|
-
|
|
282
|
-
const
|
|
353
|
+
function tablesFromTheory(theory) {
|
|
354
|
+
const entries = materializedEntries(theory);
|
|
355
|
+
const relations = new Map();
|
|
356
|
+
Object.keys(theory.relations).forEach(function byOrdinal(name, ordinal) {
|
|
357
|
+
const member = theory.relations[name];
|
|
358
|
+
if (member === undefined) {
|
|
359
|
+
throw errors.new(`bumbledb theory has no relation ${name}`);
|
|
360
|
+
}
|
|
361
|
+
const fieldIds = new Map();
|
|
362
|
+
sealedFieldsOf(member).forEach(function byField(declared, fieldOrdinal) {
|
|
363
|
+
fieldIds.set(declared.name, fieldOrdinal);
|
|
364
|
+
});
|
|
365
|
+
let primaryKey;
|
|
366
|
+
entries.forEach(function firstOwnedKey(entry, index) {
|
|
367
|
+
if (primaryKey === undefined && entry.kind === "functionality" && entry.owner === name) {
|
|
368
|
+
primaryKey = Object.freeze({ statementId: index, projection: entry.projection });
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
relations.set(name, Object.freeze({ id: ordinal, member, fieldIds, primaryKey }));
|
|
372
|
+
});
|
|
373
|
+
return Object.freeze({ relations, statements: Object.freeze(entries) });
|
|
374
|
+
}
|
|
375
|
+
const instanceStates = new WeakMap();
|
|
376
|
+
const witnessStates = new WeakMap();
|
|
377
|
+
const witnessReclaimer = new FinalizationRegistry(function reclaimWitness(handle) {
|
|
378
|
+
const closed = errors.trySync(function closeWitness() {
|
|
379
|
+
native.witnessClose(handle);
|
|
380
|
+
});
|
|
381
|
+
if (closed.error) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
283
385
|
/** The private engine halves of this module's prepared values. */
|
|
284
386
|
const preparedPlans = new WeakMap();
|
|
285
387
|
/**
|
|
@@ -296,41 +398,207 @@ const planReclaimer = new FinalizationRegistry(function reclaimPlan(handle) {
|
|
|
296
398
|
return;
|
|
297
399
|
}
|
|
298
400
|
});
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
* insert's return. Mutates `values` in place with the minted cells.
|
|
310
|
-
*/
|
|
311
|
-
function mintFreshCells(txHandle, entry, relation, values) {
|
|
312
|
-
const fresh = {};
|
|
313
|
-
for (const declared of relation.data.fields) {
|
|
314
|
-
if (!isFreshField(declared.field)) {
|
|
315
|
-
continue;
|
|
401
|
+
const ErrAsyncCallback = errors.new("bumbledb asyncCallback: a read or write callback returned a thenable — the callback is synchronous");
|
|
402
|
+
const ErrSpentHandle = errors.new("bumbledb spentHandle: a consumed builder, instance, or witness was used");
|
|
403
|
+
const ErrUseAfterScope = errors.new("bumbledb useAfterScope: a stashed read instance or write transaction was used after its callback returned");
|
|
404
|
+
const ErrForeignPrepared = errors.new("bumbledb foreignPrepared: a prepared query met a foreign instance");
|
|
405
|
+
const ErrForeignWitness = errors.new("bumbledb foreignWitness: a witness met a foreign store");
|
|
406
|
+
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}`);
|
|
316
411
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
function planOf(prepared) {
|
|
434
|
+
const plan = preparedPlans.get(prepared);
|
|
435
|
+
if (plan === undefined) {
|
|
436
|
+
throw errors.wrap(ErrForeignPrepared, "bumbledb execute target is not a prepared value of this SDK");
|
|
437
|
+
}
|
|
438
|
+
if (plan.owner !== owner) {
|
|
439
|
+
throw errors.wrap(ErrForeignPrepared, `bumbledb prepared value was prepared by a different store than this one (schema ${theory.name})`);
|
|
440
|
+
}
|
|
441
|
+
return plan;
|
|
442
|
+
}
|
|
443
|
+
function contains(relation, fact) {
|
|
444
|
+
assertLive();
|
|
445
|
+
const entry = resolveOrdinary(relation);
|
|
446
|
+
return bridged("bumbledb instance contains", function readContains() {
|
|
447
|
+
return ops.contains(entry.id, rowOf(relation.data, recordOf(fact)));
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
function get(relation, keyOrStatement, declaredKey) {
|
|
451
|
+
assertLive();
|
|
452
|
+
const entry = resolveOrdinary(relation);
|
|
453
|
+
return selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
|
|
454
|
+
const selected = declaredKeyOf(relation, statement);
|
|
455
|
+
const row = bridged("bumbledb instance get", function readGet() {
|
|
456
|
+
return ops.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, recordOf(key)));
|
|
457
|
+
});
|
|
458
|
+
return row === null ? undefined : factOf(relation, row);
|
|
459
|
+
}, function byPrimary(key) {
|
|
460
|
+
const primaryKey = entry.primaryKey;
|
|
461
|
+
if (primaryKey === undefined) {
|
|
462
|
+
throw errors.new(`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`);
|
|
322
463
|
}
|
|
323
|
-
|
|
324
|
-
return
|
|
464
|
+
const row = bridged("bumbledb instance get", function readGet() {
|
|
465
|
+
return ops.get(entry.id, primaryKey.statementId, keyRowOf(relation.data, primaryKey.projection, recordOf(key)));
|
|
325
466
|
});
|
|
326
|
-
|
|
467
|
+
return row === null ? undefined : factOf(relation, row);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function scan(relation) {
|
|
471
|
+
assertLive();
|
|
472
|
+
const entry = resolveOrdinary(relation);
|
|
473
|
+
const rows = bridged("bumbledb instance scan", function readScan() {
|
|
474
|
+
return ops.scan(entry.id);
|
|
475
|
+
});
|
|
476
|
+
return rows.map(function decodeRow(row) {
|
|
477
|
+
return factOf(relation, row);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function execute(prepared, params) {
|
|
481
|
+
assertLive();
|
|
482
|
+
const plan = planOf(prepared);
|
|
483
|
+
const wire = wireParams(plan.params, recordOf(params));
|
|
484
|
+
const rows = bridged("execute bumbledb prepared query", function callExecute() {
|
|
485
|
+
return ops.execute(plan.handle, wire);
|
|
486
|
+
});
|
|
487
|
+
return decodeAnswers(plan.finds, rows);
|
|
488
|
+
}
|
|
489
|
+
function prepare(q) {
|
|
490
|
+
assertLive();
|
|
491
|
+
if (q.schema !== theory) {
|
|
492
|
+
throw errors.new(`query was built against schema ${q.schema.name}, not the identical schema value this store opened with — schema identity is the membership rule`);
|
|
327
493
|
}
|
|
328
|
-
|
|
329
|
-
|
|
494
|
+
const queryIr = lowerQuery(q);
|
|
495
|
+
const outcome = bridged("prepare bumbledb query", function callPrepare() {
|
|
496
|
+
return ops.prepare(queryIr);
|
|
497
|
+
});
|
|
498
|
+
if (!outcome.ok) {
|
|
499
|
+
throwPrepareRefusal(outcome.message);
|
|
330
500
|
}
|
|
331
|
-
|
|
501
|
+
const prepared = Object.freeze({});
|
|
502
|
+
preparedPlans.set(prepared, Object.freeze({
|
|
503
|
+
handle: outcome.prepared,
|
|
504
|
+
owner,
|
|
505
|
+
params: q.data.params,
|
|
506
|
+
finds: q.data.finds
|
|
507
|
+
}));
|
|
508
|
+
planReclaimer.register(prepared, outcome.prepared);
|
|
509
|
+
return prepared;
|
|
332
510
|
}
|
|
333
|
-
return
|
|
511
|
+
return { scan, get, contains, execute, prepare };
|
|
512
|
+
}
|
|
513
|
+
function ordinaryEntry(tables, theory, relation) {
|
|
514
|
+
const entry = tables.relations.get(relation.name);
|
|
515
|
+
if (entry === undefined || entry.member !== relation) {
|
|
516
|
+
throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`);
|
|
517
|
+
}
|
|
518
|
+
if (isClosedMember(relation)) {
|
|
519
|
+
throw errors.new(`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`);
|
|
520
|
+
}
|
|
521
|
+
return entry;
|
|
522
|
+
}
|
|
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 });
|
|
539
|
+
}
|
|
540
|
+
function contains(relation, fact) {
|
|
541
|
+
assertLive();
|
|
542
|
+
const entry = ordinaryEntry(tables, theory, relation);
|
|
543
|
+
return reads.contains(entry.id, rowOf(relation.data, recordOf(fact)));
|
|
544
|
+
}
|
|
545
|
+
function readThroughKey(relation, entry, selected, key) {
|
|
546
|
+
const row = reads.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, key));
|
|
547
|
+
if (row === null) {
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
return factOf(relation, row);
|
|
551
|
+
}
|
|
552
|
+
function get(relation, keyOrStatement, declaredKey) {
|
|
553
|
+
assertLive();
|
|
554
|
+
const entry = ordinaryEntry(tables, theory, relation);
|
|
555
|
+
return selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
|
|
556
|
+
return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key));
|
|
557
|
+
}, function byPrimary(key) {
|
|
558
|
+
const primaryKey = entry.primaryKey;
|
|
559
|
+
if (primaryKey === undefined) {
|
|
560
|
+
throw errors.new(`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`);
|
|
561
|
+
}
|
|
562
|
+
return readThroughKey(relation, entry, primaryKey, recordOf(key));
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return { contains, get };
|
|
566
|
+
}
|
|
567
|
+
function createReadInstance(nativeHandle, theory, tables, owner) {
|
|
568
|
+
const state = { handle: nativeHandle, live: true, owner };
|
|
569
|
+
function assertLive() {
|
|
570
|
+
if (!state.live) {
|
|
571
|
+
throw errors.wrap(ErrUseAfterScope, "bumbledb read instance is invalidated — its owning callback already returned");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const methods = catalogMethods(theory, tables, owner, assertLive, {
|
|
575
|
+
scan(relationId) {
|
|
576
|
+
return native.instanceScan(state.handle, relationId);
|
|
577
|
+
},
|
|
578
|
+
contains(relationId, values) {
|
|
579
|
+
return native.instanceContains(state.handle, relationId, values);
|
|
580
|
+
},
|
|
581
|
+
get(relationId, statementId, keyValues) {
|
|
582
|
+
return native.instanceGet(state.handle, relationId, statementId, keyValues);
|
|
583
|
+
},
|
|
584
|
+
prepare(query) {
|
|
585
|
+
return native.instancePrepare(state.handle, query);
|
|
586
|
+
},
|
|
587
|
+
execute(prepared, params) {
|
|
588
|
+
return native.preparedExecute(prepared, state.handle, params);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
const instance = Object.freeze({
|
|
592
|
+
get generation() {
|
|
593
|
+
assertLive();
|
|
594
|
+
return bridged("bumbledb instance generation", function readGeneration() {
|
|
595
|
+
return native.instanceGeneration(state.handle);
|
|
596
|
+
});
|
|
597
|
+
},
|
|
598
|
+
...methods
|
|
599
|
+
});
|
|
600
|
+
instanceStates.set(instance, state);
|
|
601
|
+
return instance;
|
|
334
602
|
}
|
|
335
603
|
/**
|
|
336
604
|
* Constructs one open `Db` over an already-admitted handle: builds the
|
|
@@ -472,172 +740,88 @@ function openDb(handle, theory, manifest) {
|
|
|
472
740
|
}
|
|
473
741
|
return { contains, get };
|
|
474
742
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
throw errors.new(`bumbledb prepared value was prepared by a different store than this one (schema ${theory.name})`);
|
|
486
|
-
}
|
|
487
|
-
return plan;
|
|
743
|
+
function pinPrepared(preparedHandle, q) {
|
|
744
|
+
const prepared = Object.freeze({});
|
|
745
|
+
preparedPlans.set(prepared, Object.freeze({
|
|
746
|
+
handle: preparedHandle,
|
|
747
|
+
owner,
|
|
748
|
+
params: q.data.params,
|
|
749
|
+
finds: q.data.finds
|
|
750
|
+
}));
|
|
751
|
+
planReclaimer.register(prepared, preparedHandle);
|
|
752
|
+
return prepared;
|
|
488
753
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
throw errors.new("bumbledb read scope is invalidated — its owning read callback already returned");
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const reads = pointReadsOf(assertLive, {
|
|
503
|
-
contains(relationId, row) {
|
|
504
|
-
return bridged("bumbledb snapshot contains", function readContains() {
|
|
505
|
-
return native.snapshotContains(state.handle, relationId, row);
|
|
506
|
-
});
|
|
507
|
-
},
|
|
508
|
-
get(relationId, statementId, key) {
|
|
509
|
-
return bridged("bumbledb snapshot get", function readGet() {
|
|
510
|
-
return native.snapshotGet(state.handle, relationId, statementId, key);
|
|
754
|
+
function makeWitness(nativeHandle) {
|
|
755
|
+
const state = { handle: nativeHandle, spent: false, owner };
|
|
756
|
+
const witness = Object.freeze({
|
|
757
|
+
[Symbol.dispose]() {
|
|
758
|
+
if (state.spent) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
state.spent = true;
|
|
762
|
+
bridged("close bumbledb witness", function closeWitness() {
|
|
763
|
+
native.witnessClose(nativeHandle);
|
|
511
764
|
});
|
|
512
765
|
}
|
|
513
766
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const rows = bridged("bumbledb snapshot scan", function readScan() {
|
|
518
|
-
return native.snapshotScan(state.handle, entry.id);
|
|
519
|
-
});
|
|
520
|
-
return rows.map(function decodeRow(row) {
|
|
521
|
-
return factOf(relation, row);
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
function execute(prepared, params) {
|
|
525
|
-
assertLive();
|
|
526
|
-
const plan = planOf(prepared);
|
|
527
|
-
const wire = wireParams(plan.params, recordOf(params));
|
|
528
|
-
const rows = bridged("execute bumbledb prepared query", function callExecute() {
|
|
529
|
-
return native.preparedExecute(plan.handle, state.handle, wire);
|
|
530
|
-
});
|
|
531
|
-
return decodeAnswers(plan.finds, rows);
|
|
532
|
-
}
|
|
533
|
-
/** The R12 teardown: invalidate, then close — idempotent through the state's close latch. */
|
|
534
|
-
function dispose() {
|
|
535
|
-
state.live = false;
|
|
536
|
-
closeScopeState(state);
|
|
537
|
-
}
|
|
538
|
-
const scope = Object.freeze({
|
|
539
|
-
generation: state.generation,
|
|
540
|
-
scan,
|
|
541
|
-
get: reads.get,
|
|
542
|
-
contains: reads.contains,
|
|
543
|
-
execute,
|
|
544
|
-
[Symbol.dispose]: dispose
|
|
545
|
-
});
|
|
546
|
-
scopeStates.set(scope, state);
|
|
547
|
-
return scope;
|
|
767
|
+
witnessStates.set(witness, state);
|
|
768
|
+
witnessReclaimer.register(witness, nativeHandle);
|
|
769
|
+
return witness;
|
|
548
770
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
* snapshot open/close is counted so a write-begin failure can report how
|
|
552
|
-
* many read handles were live at the fault — a leaked scope is invisible
|
|
553
|
-
* until the exact moment it matters, so the failure carries the census.
|
|
554
|
-
*/
|
|
555
|
-
let liveSnapshots = 0;
|
|
556
|
-
/**
|
|
557
|
-
* Opens one snapshot and its scope state (live until the owner flips
|
|
558
|
-
* it). The witnessed generation rides the snapshot open itself — one
|
|
559
|
-
* crossing carries both (finding 016), so the fault-pairing close
|
|
560
|
-
* branch a second `dbGeneration` call needed is structurally gone.
|
|
561
|
-
*/
|
|
562
|
-
function openScopeState() {
|
|
563
|
-
const opened = bridged("open bumbledb snapshot", function openSnapshot() {
|
|
564
|
-
return native.dbSnapshot(handle);
|
|
565
|
-
});
|
|
566
|
-
liveSnapshots += 1;
|
|
567
|
-
return { handle: opened.snapshot, generation: opened.generation, live: true, closed: false, owner };
|
|
771
|
+
function makeInstance(nativeHandle) {
|
|
772
|
+
return createReadInstance(nativeHandle, theory, tables, owner);
|
|
568
773
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const scope = makeScope(state);
|
|
589
|
-
if (fn === undefined) {
|
|
590
|
-
/**
|
|
591
|
-
* The `using` acquisition (R12): the caller owns the lifetime —
|
|
592
|
-
* `using snap = db.read()` — and the scope's `Symbol.dispose`
|
|
593
|
-
* is the deterministic release, scope-shaped in the language's
|
|
594
|
-
* own syntax.
|
|
595
|
-
*/
|
|
596
|
-
return scope;
|
|
597
|
-
}
|
|
598
|
-
const result = errors.trySync(function runRead() {
|
|
599
|
-
return fn(scope);
|
|
600
|
-
});
|
|
601
|
-
state.live = false;
|
|
602
|
-
closeScopeState(state);
|
|
603
|
-
if (result.error) {
|
|
604
|
-
throw errors.wrap(result.error, "bumbledb read");
|
|
605
|
-
}
|
|
606
|
-
return result.data;
|
|
774
|
+
function read(body) {
|
|
775
|
+
let captured;
|
|
776
|
+
const result = bridged("bumbledb read", function runRead() {
|
|
777
|
+
return native.dbRead(handle, function onRead(nativeInstance, nativeWitness) {
|
|
778
|
+
const instance = makeInstance(nativeInstance);
|
|
779
|
+
const witness = makeWitness(nativeWitness);
|
|
780
|
+
const value = body(instance, witness);
|
|
781
|
+
const state = instanceStates.get(instance);
|
|
782
|
+
if (state !== undefined) {
|
|
783
|
+
state.live = false;
|
|
784
|
+
}
|
|
785
|
+
if (isThenable(value)) {
|
|
786
|
+
throw errors.wrap(ErrAsyncCallback, "bumbledb read callback returned a thenable");
|
|
787
|
+
}
|
|
788
|
+
captured = value;
|
|
789
|
+
return value;
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
return (captured ?? result);
|
|
607
793
|
}
|
|
608
794
|
function scan(relation) {
|
|
609
|
-
return read(function scanInScope(
|
|
610
|
-
return
|
|
795
|
+
return read(function scanInScope(instance) {
|
|
796
|
+
return instance.scan(relation);
|
|
611
797
|
});
|
|
612
798
|
}
|
|
613
799
|
function get(relation, keyOrStatement, declaredKey) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
800
|
+
let found;
|
|
801
|
+
read(function getInScope(instance) {
|
|
802
|
+
found = selectKeyRead(keyOrStatement, declaredKey, function byStatement(statement, key) {
|
|
803
|
+
return instance.get(relation, statement, key);
|
|
617
804
|
}, function byPrimary(key) {
|
|
618
|
-
return
|
|
805
|
+
return instance.get(relation, key);
|
|
619
806
|
});
|
|
620
807
|
});
|
|
808
|
+
return found;
|
|
621
809
|
}
|
|
622
810
|
function contains(relation, fact) {
|
|
623
|
-
return read(function containsInScope(
|
|
624
|
-
return
|
|
811
|
+
return read(function containsInScope(instance) {
|
|
812
|
+
return instance.contains(relation, fact);
|
|
625
813
|
});
|
|
626
814
|
}
|
|
627
815
|
function execute(prepared, params) {
|
|
628
|
-
return read(function executeInScope(
|
|
629
|
-
return
|
|
816
|
+
return read(function executeInScope(instance) {
|
|
817
|
+
return instance.execute(prepared, params);
|
|
630
818
|
});
|
|
631
819
|
}
|
|
632
|
-
/**
|
|
633
|
-
* Builds one {@link Tx} over a transaction-handle thunk: `write` and
|
|
634
|
-
* `writeFrom` pass an already-begun handle.
|
|
635
|
-
*/
|
|
636
820
|
function makeTx(resolveTx) {
|
|
637
821
|
const txState = { spent: false };
|
|
638
822
|
function assertLive() {
|
|
639
823
|
if (txState.spent) {
|
|
640
|
-
throw errors.
|
|
824
|
+
throw errors.wrap(ErrUseAfterScope, "bumbledb write transaction is spent");
|
|
641
825
|
}
|
|
642
826
|
}
|
|
643
827
|
const reads = pointReadsOf(assertLive, {
|
|
@@ -654,35 +838,52 @@ function openDb(handle, theory, manifest) {
|
|
|
654
838
|
});
|
|
655
839
|
}
|
|
656
840
|
});
|
|
657
|
-
function insert(relation,
|
|
841
|
+
function insert(relation, facts) {
|
|
658
842
|
assertLive();
|
|
659
843
|
const entry = resolveOrdinary(relation);
|
|
660
844
|
const txHandle = resolveTx();
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return
|
|
845
|
+
return mutateCollection(relation, facts, function applyRows(rows) {
|
|
846
|
+
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);
|
|
852
|
+
});
|
|
667
853
|
});
|
|
668
|
-
const inserted = Object.freeze({ changed, ...fresh });
|
|
669
|
-
if (!isInserted(relation, inserted)) {
|
|
670
|
-
throw errors.new(`relation ${relation.name}: insert return record is incomplete`);
|
|
671
|
-
}
|
|
672
|
-
return inserted;
|
|
673
854
|
}
|
|
674
|
-
function remove(relation,
|
|
855
|
+
function remove(relation, facts) {
|
|
675
856
|
assertLive();
|
|
676
857
|
const entry = resolveOrdinary(relation);
|
|
677
858
|
const txHandle = resolveTx();
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
859
|
+
const report = bridged("bumbledb tx delete", function record() {
|
|
860
|
+
return native.txDelete(txHandle, entry.id, rowsOf(relation, facts));
|
|
861
|
+
});
|
|
862
|
+
return Object.freeze({ submitted: report.submitted, changed: report.changed });
|
|
863
|
+
}
|
|
864
|
+
function reserve(relation, field, count) {
|
|
865
|
+
assertLive();
|
|
866
|
+
const entry = resolveOrdinary(relation);
|
|
867
|
+
const declared = relation.data.fields.find(function byName(candidate) {
|
|
868
|
+
return candidate.name === field;
|
|
869
|
+
});
|
|
870
|
+
if (declared === undefined || !isFreshField(declared.field)) {
|
|
871
|
+
throw errors.new(`relation ${relation.name}: field ${field} is not a fresh cell`);
|
|
872
|
+
}
|
|
873
|
+
const fieldId = entry.fieldIds.get(field);
|
|
874
|
+
if (fieldId === undefined) {
|
|
875
|
+
throw errors.new(`bumbledb manifest drift: relation ${relation.name} has no field id for ${field}`);
|
|
876
|
+
}
|
|
877
|
+
const txHandle = resolveTx();
|
|
878
|
+
const range = bridged("bumbledb tx reserve", function mint() {
|
|
879
|
+
return native.txReserve(txHandle, entry.id, fieldId, count);
|
|
681
880
|
});
|
|
881
|
+
return freshRangeOf(range);
|
|
682
882
|
}
|
|
683
883
|
const tx = Object.freeze({
|
|
684
884
|
insert,
|
|
685
885
|
delete: remove,
|
|
886
|
+
reserve,
|
|
686
887
|
contains: reads.contains,
|
|
687
888
|
get: reads.get
|
|
688
889
|
});
|
|
@@ -691,104 +892,80 @@ function openDb(handle, theory, manifest) {
|
|
|
691
892
|
}
|
|
692
893
|
return { tx, spend };
|
|
693
894
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
return
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
});
|
|
701
|
-
made.spend();
|
|
702
|
-
if (built.error) {
|
|
703
|
-
bridged("abort bumbledb write transaction", function abort() {
|
|
704
|
-
native.txAbort(txHandle);
|
|
705
|
-
});
|
|
706
|
-
throw errors.wrap(built.error, "build write delta");
|
|
707
|
-
}
|
|
708
|
-
if (isThenable(built.data)) {
|
|
709
|
-
/**
|
|
710
|
-
* An `async` callback TYPECHECKS (Promise<void> is assignable where
|
|
711
|
-
* a `void` return is expected) but its body runs after the tx is
|
|
712
|
-
* spent: committing here would be a silent EMPTY commit reported
|
|
713
|
-
* ok while the callback's real inserts throw "spent" as unhandled
|
|
714
|
-
* rejections. Refused typed instead — abort, nothing committed
|
|
715
|
-
* (the same one-writer law as the thrown-callback path).
|
|
716
|
-
*/
|
|
717
|
-
bridged("abort bumbledb write transaction", function abort() {
|
|
718
|
-
native.txAbort(txHandle);
|
|
719
|
-
});
|
|
720
|
-
throw errors.new("bumbledb write callback returned a thenable — the delta build is synchronous; an async callback is refused, nothing was committed");
|
|
721
|
-
}
|
|
722
|
-
if (isAbandon(built.data)) {
|
|
723
|
-
/**
|
|
724
|
-
* The caller's explicit decline to commit (R10): the sentinel's
|
|
725
|
-
* contract is unconditional — roll back, nothing committed, not
|
|
726
|
-
* even an empty commit; commit is unreachable for a sentinel
|
|
727
|
-
* result.
|
|
728
|
-
*/
|
|
729
|
-
bridged("abort bumbledb write transaction", function abort() {
|
|
730
|
-
native.txAbort(txHandle);
|
|
895
|
+
function mapNativeWrite(nativeOutcome, built) {
|
|
896
|
+
if (nativeOutcome.tag === "moved") {
|
|
897
|
+
return Object.freeze({
|
|
898
|
+
tag: "moved",
|
|
899
|
+
witnessed: nativeOutcome.witnessed,
|
|
900
|
+
current: nativeOutcome.current
|
|
731
901
|
});
|
|
732
|
-
return abandonedOutcome(built.data);
|
|
733
902
|
}
|
|
734
|
-
|
|
735
|
-
return
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
});
|
|
739
|
-
if (committed.error) {
|
|
740
|
-
/**
|
|
741
|
-
* A THROWN commit (engine I/O failure, bridge fault) must never
|
|
742
|
-
* leave the write transaction live: LMDB holds one writer per
|
|
743
|
-
* environment, and a leaked handle turns every later begin into
|
|
744
|
-
* EINVAL for the process's lifetime. The abort is best-effort —
|
|
745
|
-
* the native side may already have consumed the handle.
|
|
746
|
-
*/
|
|
747
|
-
const aborted = errors.trySync(function abortAfterFailedCommit() {
|
|
748
|
-
native.txAbort(txHandle);
|
|
903
|
+
if (nativeOutcome.tag === "rejected") {
|
|
904
|
+
return Object.freeze({
|
|
905
|
+
tag: "rejected",
|
|
906
|
+
violations: Object.freeze(nativeOutcome.violations.map(violationOf))
|
|
749
907
|
});
|
|
750
|
-
if (aborted.error) {
|
|
751
|
-
}
|
|
752
|
-
throw errors.wrap(committed.error, "commit bumbledb write transaction");
|
|
753
908
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
909
|
+
if (nativeOutcome.tag === "abandoned") {
|
|
910
|
+
if (built === undefined || !isAbandon(built)) {
|
|
911
|
+
throw errors.new("bumbledb write abandoned without an abandon sentinel");
|
|
912
|
+
}
|
|
913
|
+
return abandonedOutcome(built);
|
|
757
914
|
}
|
|
758
915
|
return Object.freeze({
|
|
759
|
-
|
|
760
|
-
|
|
916
|
+
tag: "accepted",
|
|
917
|
+
value: Object.freeze({
|
|
918
|
+
value: built,
|
|
919
|
+
generation: nativeOutcome.generation
|
|
920
|
+
})
|
|
761
921
|
});
|
|
762
922
|
}
|
|
763
|
-
function
|
|
764
|
-
|
|
765
|
-
|
|
923
|
+
function runWrite(invoke, fn) {
|
|
924
|
+
let built;
|
|
925
|
+
const nativeOutcome = bridged("bumbledb write", function callWrite() {
|
|
926
|
+
return invoke(function onWrite(txHandle) {
|
|
927
|
+
const made = makeTx(function resolveTx() {
|
|
928
|
+
return txHandle;
|
|
929
|
+
});
|
|
930
|
+
const result = errors.trySync(function buildDelta() {
|
|
931
|
+
return fn(made.tx);
|
|
932
|
+
});
|
|
933
|
+
made.spend();
|
|
934
|
+
if (result.error) {
|
|
935
|
+
throw errors.wrap(result.error, "build write delta");
|
|
936
|
+
}
|
|
937
|
+
if (isThenable(result.data)) {
|
|
938
|
+
throw errors.wrap(ErrAsyncCallback, "bumbledb write callback returned a thenable");
|
|
939
|
+
}
|
|
940
|
+
built = result.data;
|
|
941
|
+
return !isAbandon(result.data);
|
|
942
|
+
});
|
|
766
943
|
});
|
|
767
|
-
return
|
|
944
|
+
return mapNativeWrite(nativeOutcome, built);
|
|
768
945
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const snapState = scopeStates.get(snap);
|
|
776
|
-
if (snapState === undefined) {
|
|
777
|
-
throw errors.new("bumbledb writeFrom witness is not a read scope of this SDK");
|
|
946
|
+
function write(fn) {
|
|
947
|
+
const outcome = runWrite(function invoke(callback) {
|
|
948
|
+
return native.dbWrite(handle, callback);
|
|
949
|
+
}, fn);
|
|
950
|
+
if (outcome.tag === "moved") {
|
|
951
|
+
throw errors.new("bumbledb write reported moved — unconditional writes cannot move");
|
|
778
952
|
}
|
|
779
|
-
|
|
780
|
-
|
|
953
|
+
return outcome;
|
|
954
|
+
}
|
|
955
|
+
function writeFrom(witness, fn) {
|
|
956
|
+
const state = witnessStates.get(witness);
|
|
957
|
+
if (state === undefined) {
|
|
958
|
+
throw errors.wrap(ErrForeignWitness, "bumbledb writeFrom witness is not a witness of this SDK");
|
|
781
959
|
}
|
|
782
|
-
if (
|
|
783
|
-
throw errors.
|
|
960
|
+
if (state.owner !== owner) {
|
|
961
|
+
throw errors.wrap(ErrForeignWitness, `bumbledb writeFrom witness belongs to a different store (schema ${theory.name})`);
|
|
784
962
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
});
|
|
788
|
-
if (!witnessed.ok) {
|
|
789
|
-
throw errors.wrap(ErrGenerationMoved, `writeFrom: generation moved (witnessed ${witnessed.witnessed} current ${witnessed.current}) against schema ${theory.name}`);
|
|
963
|
+
if (state.spent) {
|
|
964
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb writeFrom witness has been disposed");
|
|
790
965
|
}
|
|
791
|
-
return
|
|
966
|
+
return runWrite(function invoke(callback) {
|
|
967
|
+
return native.dbWriteFrom(handle, state.handle, callback);
|
|
968
|
+
}, fn);
|
|
792
969
|
}
|
|
793
970
|
function prepare(q) {
|
|
794
971
|
if (q.schema !== theory) {
|
|
@@ -799,18 +976,9 @@ function openDb(handle, theory, manifest) {
|
|
|
799
976
|
return native.dbPrepare(handle, queryIr);
|
|
800
977
|
});
|
|
801
978
|
if (!outcome.ok) {
|
|
802
|
-
|
|
979
|
+
throwPrepareRefusal(outcome.message);
|
|
803
980
|
}
|
|
804
|
-
|
|
805
|
-
const prepared = Object.freeze({});
|
|
806
|
-
preparedPlans.set(prepared, Object.freeze({
|
|
807
|
-
handle: preparedHandle,
|
|
808
|
-
owner,
|
|
809
|
-
params: q.data.params,
|
|
810
|
-
finds: q.data.finds
|
|
811
|
-
}));
|
|
812
|
-
planReclaimer.register(prepared, preparedHandle);
|
|
813
|
-
return prepared;
|
|
981
|
+
return pinPrepared(outcome.prepared, q);
|
|
814
982
|
}
|
|
815
983
|
return Object.freeze({
|
|
816
984
|
schema: theory,
|
|
@@ -835,97 +1003,340 @@ function openDb(handle, theory, manifest) {
|
|
|
835
1003
|
* what the types claim.
|
|
836
1004
|
*/
|
|
837
1005
|
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");
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
* record and stay legal.
|
|
846
|
-
*/
|
|
847
|
-
function refuseShadowedChanged(theory) {
|
|
848
|
-
for (const [name, member] of Object.entries(theory.relations)) {
|
|
849
|
-
for (const declared of sealedFieldsOf(member)) {
|
|
850
|
-
if (declared.name === "changed" && isFreshField(declared.field)) {
|
|
851
|
-
throw errors.new(`relation ${name}: a fresh field named "changed" would shadow tx.insert's changed-state report in its { changed, ...fresh } return (R11) — rename the fresh field; a supplied field named "changed" stays legal (only fresh cells ride the return)`);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
1006
|
+
const ErrSchemaError = errors.new("bumbledb schemaError: the declaration failed validation");
|
|
1007
|
+
const ErrFingerprintMismatch = errors.new("bumbledb fingerprintMismatch: the store's schema does not match this theory");
|
|
1008
|
+
const ErrIrError = errors.new("bumbledb irError: the query failed validation");
|
|
1009
|
+
function throwOpenRefusal(verb, canonical, kind, message) {
|
|
1010
|
+
const detail = `${verb} ${canonical}: ${message}`;
|
|
1011
|
+
if (kind === "newtypeMismatch") {
|
|
1012
|
+
throw errors.wrap(ErrNewtypeMismatch, detail);
|
|
854
1013
|
}
|
|
1014
|
+
if (kind === "schemaError") {
|
|
1015
|
+
throw errors.wrap(ErrSchemaError, detail);
|
|
1016
|
+
}
|
|
1017
|
+
throw errors.wrap(ErrFingerprintMismatch, detail);
|
|
855
1018
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
*/
|
|
867
|
-
function admit(verb, storePath, theory) {
|
|
868
|
-
refuseShadowedChanged(theory);
|
|
1019
|
+
function throwPrepareRefusal(message) {
|
|
1020
|
+
throw errors.wrap(ErrIrError, `prepare: ${message}`);
|
|
1021
|
+
}
|
|
1022
|
+
function openFromHandle(dbHandle, theory) {
|
|
1023
|
+
const manifest = bridged("fetch bumbledb manifest", function fetchManifest() {
|
|
1024
|
+
return native.dbManifest(dbHandle);
|
|
1025
|
+
});
|
|
1026
|
+
return openDb(dbHandle, theory, manifest);
|
|
1027
|
+
}
|
|
1028
|
+
async function createStore(storePath, theory) {
|
|
869
1029
|
const canonical = path.resolve(storePath);
|
|
870
1030
|
const spec = lower(theory);
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
1031
|
+
const created = await bridgedAsync(`create bumbledb store at ${canonical}`, function callBridge() {
|
|
1032
|
+
return native.dbCreate(canonical, spec);
|
|
1033
|
+
});
|
|
1034
|
+
if (created.tag === "schemaError" || created.tag === "newtypeMismatch") {
|
|
1035
|
+
throwOpenRefusal("create", canonical, created.tag, created.message);
|
|
1036
|
+
}
|
|
1037
|
+
if (created.tag === "rejected") {
|
|
1038
|
+
return Object.freeze({
|
|
1039
|
+
tag: "rejected",
|
|
1040
|
+
violations: Object.freeze(created.violations.map(function mapWire(wire) {
|
|
1041
|
+
return mapViolationWithoutStore(theory, wire);
|
|
1042
|
+
}))
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
return Object.freeze({ tag: "accepted", value: openFromHandle(created.db, theory) });
|
|
1046
|
+
}
|
|
1047
|
+
function mapViolationWithoutStore(theory, wire) {
|
|
1048
|
+
const entries = materializedEntries(theory);
|
|
1049
|
+
const entry = entries[wire.statementId];
|
|
1050
|
+
if (entry === undefined) {
|
|
1051
|
+
throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`);
|
|
1052
|
+
}
|
|
1053
|
+
function offending(fact) {
|
|
1054
|
+
const member = theory.relations[fact.relation];
|
|
1055
|
+
if (member === undefined || !(fact.relation in theory.relations)) {
|
|
1056
|
+
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`);
|
|
874
1057
|
}
|
|
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
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
async function openStore(storePath, theory) {
|
|
1114
|
+
const canonical = path.resolve(storePath);
|
|
1115
|
+
const spec = lower(theory);
|
|
1116
|
+
const opened = await bridgedAsync(`open bumbledb store at ${canonical}`, function callBridge() {
|
|
875
1117
|
return native.dbOpen(canonical, spec);
|
|
876
1118
|
});
|
|
877
1119
|
if (!opened.ok) {
|
|
878
|
-
|
|
879
|
-
|
|
1120
|
+
throwOpenRefusal("open", canonical, opened.kind, opened.message);
|
|
1121
|
+
}
|
|
1122
|
+
return openFromHandle(opened.db, theory);
|
|
1123
|
+
}
|
|
1124
|
+
const ownedRecords = new WeakMap();
|
|
1125
|
+
const builderRecords = new WeakMap();
|
|
1126
|
+
const ownedReclaimer = new FinalizationRegistry(function reclaimOwned(handle) {
|
|
1127
|
+
const closed = errors.trySync(function closeOwned() {
|
|
1128
|
+
native.ownedInstanceClose(handle);
|
|
1129
|
+
});
|
|
1130
|
+
if (closed.error) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
const builderReclaimer = new FinalizationRegistry(function reclaimBuilder(handle) {
|
|
1135
|
+
const closed = errors.trySync(function closeBuilder() {
|
|
1136
|
+
native.instanceBuilderClose(handle);
|
|
1137
|
+
});
|
|
1138
|
+
if (closed.error) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
function wrapOwned(nativeHandle, theory) {
|
|
1143
|
+
const owner = Object.freeze({});
|
|
1144
|
+
const rec = { handle: nativeHandle, theory, spent: false, owner };
|
|
1145
|
+
const tables = tablesFromTheory(theory);
|
|
1146
|
+
function assertLive() {
|
|
1147
|
+
if (rec.spent) {
|
|
1148
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb owned instance has been disposed");
|
|
880
1149
|
}
|
|
881
|
-
throw errors.new(`bumbledb ${opened.kind} (${verb} ${canonical}): ${opened.message}`);
|
|
882
1150
|
}
|
|
883
|
-
const
|
|
884
|
-
|
|
1151
|
+
const methods = catalogMethods(theory, tables, owner, assertLive, {
|
|
1152
|
+
scan(relationId) {
|
|
1153
|
+
return native.ownedScan(nativeHandle, relationId);
|
|
1154
|
+
},
|
|
1155
|
+
contains(relationId, values) {
|
|
1156
|
+
return native.ownedContains(nativeHandle, relationId, values);
|
|
1157
|
+
},
|
|
1158
|
+
get(relationId, statementId, keyValues) {
|
|
1159
|
+
return native.ownedGet(nativeHandle, relationId, statementId, keyValues);
|
|
1160
|
+
},
|
|
1161
|
+
prepare(query) {
|
|
1162
|
+
return native.ownedPrepare(nativeHandle, query);
|
|
1163
|
+
},
|
|
1164
|
+
execute(prepared, params) {
|
|
1165
|
+
return native.ownedExecute(prepared, nativeHandle, params);
|
|
1166
|
+
}
|
|
885
1167
|
});
|
|
886
|
-
|
|
1168
|
+
const instance = Object.freeze({
|
|
1169
|
+
...methods,
|
|
1170
|
+
[Symbol.dispose]() {
|
|
1171
|
+
if (rec.spent) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
native.ownedInstanceClose(nativeHandle);
|
|
1176
|
+
}
|
|
1177
|
+
catch (caught) {
|
|
1178
|
+
const error = errorFromThrow(caught);
|
|
1179
|
+
if (/leased for publish/.test(error.message)) {
|
|
1180
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb owned instance is leased for publish");
|
|
1181
|
+
}
|
|
1182
|
+
throw errors.wrap(error, "close bumbledb owned instance");
|
|
1183
|
+
}
|
|
1184
|
+
rec.spent = true;
|
|
1185
|
+
ownedReclaimer.unregister(instance);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
ownedRecords.set(instance, rec);
|
|
1189
|
+
ownedReclaimer.register(instance, nativeHandle, instance);
|
|
1190
|
+
return instance;
|
|
1191
|
+
}
|
|
1192
|
+
function wrapBuilder(nativeHandle, theory) {
|
|
1193
|
+
const rec = { handle: nativeHandle, theory, spent: false };
|
|
1194
|
+
const tables = tablesFromTheory(theory);
|
|
1195
|
+
function assertLive() {
|
|
1196
|
+
if (rec.spent) {
|
|
1197
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb instance builder has been spent");
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const overlay = overlayMethods(theory, tables, assertLive, {
|
|
1201
|
+
contains(relationId, row) {
|
|
1202
|
+
return bridged("bumbledb builder contains", function readContains() {
|
|
1203
|
+
return native.instanceBuilderContains(nativeHandle, relationId, row);
|
|
1204
|
+
});
|
|
1205
|
+
},
|
|
1206
|
+
get(relationId, statementId, key) {
|
|
1207
|
+
return bridged("bumbledb builder get", function readGet() {
|
|
1208
|
+
return native.instanceBuilderGet(nativeHandle, relationId, statementId, key);
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
const builder = Object.freeze({
|
|
1213
|
+
load(relation, facts) {
|
|
1214
|
+
assertLive();
|
|
1215
|
+
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);
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
},
|
|
1226
|
+
delete(relation, facts) {
|
|
1227
|
+
assertLive();
|
|
1228
|
+
const entry = ordinaryEntry(tables, theory, relation);
|
|
1229
|
+
const report = bridged("bumbledb builder delete", function remove() {
|
|
1230
|
+
return native.instanceBuilderDelete(nativeHandle, entry.id, rowsOf(relation, facts));
|
|
1231
|
+
});
|
|
1232
|
+
return Object.freeze({ submitted: report.submitted, changed: report.changed });
|
|
1233
|
+
},
|
|
1234
|
+
reserve(relation, field, count) {
|
|
1235
|
+
assertLive();
|
|
1236
|
+
const entry = ordinaryEntry(tables, theory, relation);
|
|
1237
|
+
const declared = relation.data.fields.find(function byName(candidate) {
|
|
1238
|
+
return candidate.name === field;
|
|
1239
|
+
});
|
|
1240
|
+
if (declared === undefined || !isFreshField(declared.field)) {
|
|
1241
|
+
throw errors.new(`relation ${relation.name}: field ${field} is not a fresh cell`);
|
|
1242
|
+
}
|
|
1243
|
+
const fieldId = entry.fieldIds.get(field);
|
|
1244
|
+
if (fieldId === undefined) {
|
|
1245
|
+
throw errors.new(`bumbledb manifest drift: relation ${relation.name} has no field id for ${field}`);
|
|
1246
|
+
}
|
|
1247
|
+
const range = bridged("bumbledb builder reserve", function mint() {
|
|
1248
|
+
return native.instanceBuilderReserve(nativeHandle, entry.id, fieldId, count);
|
|
1249
|
+
});
|
|
1250
|
+
return freshRangeOf(range);
|
|
1251
|
+
},
|
|
1252
|
+
contains: overlay.contains,
|
|
1253
|
+
get: overlay.get,
|
|
1254
|
+
async admit() {
|
|
1255
|
+
if (rec.spent) {
|
|
1256
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb instance builder has been spent");
|
|
1257
|
+
}
|
|
1258
|
+
rec.spent = true;
|
|
1259
|
+
builderReclaimer.unregister(builder);
|
|
1260
|
+
let outcome;
|
|
1261
|
+
try {
|
|
1262
|
+
outcome = await native.instanceBuilderAdmit(nativeHandle);
|
|
1263
|
+
}
|
|
1264
|
+
catch (caught) {
|
|
1265
|
+
throw errors.wrap(errorFromThrow(caught), "admit bumbledb instance");
|
|
1266
|
+
}
|
|
1267
|
+
if (outcome.tag === "rejected") {
|
|
1268
|
+
return Object.freeze({
|
|
1269
|
+
tag: "rejected",
|
|
1270
|
+
violations: Object.freeze(outcome.violations.map(function mapWire(wire) {
|
|
1271
|
+
return mapViolationWithoutStore(theory, wire);
|
|
1272
|
+
}))
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
return Object.freeze({
|
|
1276
|
+
tag: "accepted",
|
|
1277
|
+
value: wrapOwned(outcome.value, theory)
|
|
1278
|
+
});
|
|
1279
|
+
},
|
|
1280
|
+
[Symbol.dispose]() {
|
|
1281
|
+
if (rec.spent) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
rec.spent = true;
|
|
1285
|
+
builderReclaimer.unregister(builder);
|
|
1286
|
+
bridged("close bumbledb instance builder", function closeBuilder() {
|
|
1287
|
+
native.instanceBuilderClose(nativeHandle);
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
builderRecords.set(builder, rec);
|
|
1292
|
+
builderReclaimer.register(builder, nativeHandle, builder);
|
|
1293
|
+
return builder;
|
|
887
1294
|
}
|
|
1295
|
+
const InstanceBuilder = Object.freeze({
|
|
1296
|
+
create(theory) {
|
|
1297
|
+
const spec = lower(theory);
|
|
1298
|
+
const handle = bridged("create bumbledb instance builder", function make() {
|
|
1299
|
+
return native.instanceBuilderNew(spec);
|
|
1300
|
+
});
|
|
1301
|
+
return wrapBuilder(handle, theory);
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
888
1304
|
/**
|
|
889
1305
|
* The store lifecycle — `Db.create(path, schema)` / `Db.open(path, schema)`.
|
|
890
1306
|
* Create refuses an already-initialized directory; open verifies format
|
|
891
|
-
* version
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
1307
|
+
* version and the schema fingerprint. A second live handle on the same
|
|
1308
|
+
* path is the engine's `EnvironmentLocked`. There is no close anywhere:
|
|
1309
|
+
* the process owns the environment until GC/exit (durability is the
|
|
1310
|
+
* engine's per-commit fsync). Resume = reopen in a fresh process, or
|
|
1311
|
+
* hold the `Db` this process opened.
|
|
896
1312
|
*/
|
|
897
1313
|
const Db = Object.freeze({
|
|
898
1314
|
/** Creates a fresh durable store at `path` from the schema. */
|
|
899
|
-
async create(
|
|
900
|
-
return
|
|
1315
|
+
async create(storePath, theory) {
|
|
1316
|
+
return createStore(storePath, theory);
|
|
901
1317
|
},
|
|
902
1318
|
/**
|
|
903
1319
|
* Opens an existing durable store at `path` with the same theory.
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
* 50-storage.md § the `_meta` block), so a legacy store becomes
|
|
907
|
-
* exhumable after one ordinary open — adoption is automatic, never a
|
|
908
|
-
* separate verb. A second open of a still-live path is
|
|
909
|
-
* `EnvironmentLocked`.
|
|
1320
|
+
* Format 8 open never back-fills a descriptor. A second open of a
|
|
1321
|
+
* still-live path is `EnvironmentLocked`.
|
|
910
1322
|
*/
|
|
911
|
-
async open(
|
|
912
|
-
return
|
|
1323
|
+
async open(storePath, theory) {
|
|
1324
|
+
return openStore(storePath, theory);
|
|
913
1325
|
},
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
return exhumeStore(path.resolve(storePath));
|
|
1326
|
+
async fromInstance(storePath, instance) {
|
|
1327
|
+
const rec = ownedRecords.get(instance);
|
|
1328
|
+
if (rec === undefined) {
|
|
1329
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb fromInstance target is not an owned instance of this SDK");
|
|
1330
|
+
}
|
|
1331
|
+
if (rec.spent) {
|
|
1332
|
+
throw errors.wrap(ErrSpentHandle, "bumbledb fromInstance target has been disposed");
|
|
1333
|
+
}
|
|
1334
|
+
const canonical = path.resolve(storePath);
|
|
1335
|
+
const dbHandle = await bridgedAsync(`publish bumbledb instance at ${canonical}`, function publish() {
|
|
1336
|
+
return native.dbFromInstance(canonical, rec.handle);
|
|
1337
|
+
});
|
|
1338
|
+
return openFromHandle(dbHandle, rec.theory);
|
|
928
1339
|
}
|
|
929
1340
|
});
|
|
930
|
-
export { abandon, Db,
|
|
1341
|
+
export { abandon, Db, ErrAsyncCallback, ErrForeignPrepared, ErrForeignWitness, ErrFingerprintMismatch, ErrIrError, ErrNewtypeMismatch, ErrSchemaError, ErrSpentHandle, ErrUseAfterScope, InstanceBuilder };
|
|
931
1342
|
//# sourceMappingURL=db.js.map
|