@bjornpagen/bumbledb 0.3.0 → 0.5.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 +246 -92
- package/README.md +31 -16
- package/dist/closed.d.ts +80 -75
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +102 -127
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +37 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +95 -55
- package/dist/db.js.map +1 -1
- package/dist/exhume.d.ts.map +1 -1
- package/dist/exhume.js +1 -14
- package/dist/exhume.js.map +1 -1
- package/dist/face.d.ts +40 -40
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +9 -17
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +46 -15
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +58 -29
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +2 -1
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +15 -14
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +1 -7
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +33 -6
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +75 -26
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +21 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +20 -3
- package/dist/native.js.map +1 -1
- package/dist/order.d.ts +36 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +135 -0
- package/dist/order.js.map +1 -0
- package/dist/query/atom.d.ts +76 -28
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +12 -16
- package/dist/query/atom.js.map +1 -1
- package/dist/query/lower.d.ts +5 -8
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +308 -72
- package/dist/query/lower.js.map +1 -1
- package/dist/query/predicate.d.ts.map +1 -1
- package/dist/query/predicate.js +34 -2
- package/dist/query/predicate.js.map +1 -1
- package/dist/query/run.d.ts +15 -5
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +27 -8
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +37 -29
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +19 -47
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +17 -29
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +41 -38
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -31
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +3 -2
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +13 -4
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +75 -8
- package/dist/statements.js.map +1 -1
- package/package.json +2 -5
- package/src/closed.ts +144 -206
- package/src/db.ts +143 -68
- package/src/exhume.ts +1 -15
- package/src/face.ts +38 -48
- package/src/fields.ts +103 -49
- package/src/index.ts +11 -10
- package/src/law.ts +15 -14
- package/src/lower.ts +2 -9
- package/src/marshal.ts +82 -31
- package/src/native.ts +22 -4
- package/src/order.ts +156 -0
- package/src/query/atom.ts +70 -35
- package/src/query/lower.ts +354 -82
- package/src/query/predicate.ts +39 -4
- package/src/query/run.ts +27 -9
- package/src/query/scope.ts +52 -70
- package/src/relation.ts +54 -68
- package/src/schema.ts +7 -33
- package/src/spec.ts +3 -2
- package/src/statements.ts +82 -8
package/src/db.ts
CHANGED
|
@@ -32,10 +32,22 @@
|
|
|
32
32
|
|
|
33
33
|
import * as path from "node:path"
|
|
34
34
|
import * as errors from "@superbuilders/errors"
|
|
35
|
+
import { isClosedMember, sealedFieldsOf } from "#closed.ts"
|
|
35
36
|
import type { Exhumed } from "#exhume.ts"
|
|
36
37
|
import { exhumeStore } from "#exhume.ts"
|
|
38
|
+
import { rosterOf } from "#fields.ts"
|
|
37
39
|
import { lower } from "#lower.ts"
|
|
38
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
factOf,
|
|
42
|
+
handleOf,
|
|
43
|
+
isFreshField,
|
|
44
|
+
isMintedFresh,
|
|
45
|
+
type KeyFact,
|
|
46
|
+
keyRowOf,
|
|
47
|
+
type Minted,
|
|
48
|
+
recordOf,
|
|
49
|
+
rowOf
|
|
50
|
+
} from "#marshal.ts"
|
|
39
51
|
|
|
40
52
|
import type {
|
|
41
53
|
DbHandle,
|
|
@@ -49,7 +61,7 @@ import type {
|
|
|
49
61
|
Violation as WireViolation,
|
|
50
62
|
ViolationFact as WireViolationFact
|
|
51
63
|
} from "#native.ts"
|
|
52
|
-
import { native } from "#native.ts"
|
|
64
|
+
import { bridged, native } from "#native.ts"
|
|
53
65
|
import type { SelectColumn } from "#query/atom.ts"
|
|
54
66
|
import type { Query } from "#query/lower.ts"
|
|
55
67
|
import { lowerQuery } from "#query/lower.ts"
|
|
@@ -79,7 +91,10 @@ type DeclaredKeyFact<R extends AnyRelation, Projection extends readonly string[]
|
|
|
79
91
|
/**
|
|
80
92
|
* One offending fact of a violation: the cited relation's name (a member
|
|
81
93
|
* of the schema's record) and the fact decoded to a named natural-value
|
|
82
|
-
* object — partial exactly as the engine cites it.
|
|
94
|
+
* object — partial exactly as the engine cites it. Closed-referencing
|
|
95
|
+
* cells arrive as handle NAMES (the marshal bijection's read half), so the
|
|
96
|
+
* record and the violation's `canonical` string — which the engine already
|
|
97
|
+
* renders with handle names — agree on the one spelling.
|
|
83
98
|
*/
|
|
84
99
|
interface OffendingFact<Rels extends SchemaRelations> {
|
|
85
100
|
readonly relation: keyof Rels & string
|
|
@@ -335,11 +350,16 @@ interface Db<Rels extends SchemaRelations> {
|
|
|
335
350
|
* The ONE witnessed-write form: snapshot → `fn` (premise reads via
|
|
336
351
|
* `snap`, delta via `tx`) → witnessed commit, which lands only if no
|
|
337
352
|
* state-changing commit intervened since the snapshot. On a moved
|
|
338
|
-
* generation the WHOLE `fn` reruns on a fresh snapshot
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
353
|
+
* generation the WHOLE `fn` reruns on a fresh snapshot: this process
|
|
354
|
+
* holds the store's only write handle, so every generation move is
|
|
355
|
+
* self-inflicted by the host's own interleaved writes, and each rerun
|
|
356
|
+
* witnesses a strictly newer generation — the benign race converges.
|
|
357
|
+
* The loop's honesty bound is {@link WITNESSED_ATTEMPT_CAP}: a callback
|
|
358
|
+
* that moves the generation on EVERY attempt (a plain `db.write` before
|
|
359
|
+
* its first tx verb) would spin forever, so past the cap the typed
|
|
360
|
+
* {@link ErrWitnessedLivelock} is thrown instead of a silent loop (the
|
|
361
|
+
* engine's ruling: the error, never a loop — retry is host policy, and
|
|
362
|
+
* the cap is that policy's own diagnostic). `fn` may decline to commit
|
|
343
363
|
* by returning {@link abandon}`(payload)` — the outcome is then
|
|
344
364
|
* `{ ok: false, abandoned: payload }` and NO commit (not even an empty
|
|
345
365
|
* one) is issued.
|
|
@@ -355,19 +375,6 @@ interface Db<Rels extends SchemaRelations> {
|
|
|
355
375
|
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
356
376
|
}
|
|
357
377
|
|
|
358
|
-
/**
|
|
359
|
-
* The bridge guard: runs one native call and wraps anything it throws —
|
|
360
|
-
* marshal-shape refusals and handle-lifecycle refusals cross as genuine
|
|
361
|
-
* typed failures, never bare foreign errors.
|
|
362
|
-
*/
|
|
363
|
-
function bridged<T>(context: string, run: () => T): T {
|
|
364
|
-
const result = errors.trySync(run)
|
|
365
|
-
if (result.error) {
|
|
366
|
-
throw errors.wrap(result.error, context)
|
|
367
|
-
}
|
|
368
|
-
return result.data
|
|
369
|
-
}
|
|
370
|
-
|
|
371
378
|
/** One relation's runtime tables: engine id, the identical schema member, field ids, primary key. */
|
|
372
379
|
interface RelationEntry {
|
|
373
380
|
readonly id: number
|
|
@@ -430,7 +437,7 @@ function materializedEntries(theory: AnySchema): StatementEntry[] {
|
|
|
430
437
|
function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
431
438
|
const entries: StatementEntry[] = []
|
|
432
439
|
for (const member of Object.values(theory.relations)) {
|
|
433
|
-
if (
|
|
440
|
+
if (isClosedMember(member)) {
|
|
434
441
|
continue
|
|
435
442
|
}
|
|
436
443
|
for (const declared of member.data.fields) {
|
|
@@ -444,7 +451,7 @@ function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
|
444
451
|
}
|
|
445
452
|
}
|
|
446
453
|
for (const member of Object.values(theory.relations)) {
|
|
447
|
-
if (
|
|
454
|
+
if (isClosedMember(member)) {
|
|
448
455
|
entries.push({
|
|
449
456
|
kind: "functionality",
|
|
450
457
|
statement: undefined,
|
|
@@ -514,6 +521,33 @@ function isStatementValue<R extends AnyRelation, P extends readonly string[]>(
|
|
|
514
521
|
return typeof data === "object" && data !== null && "kind" in data
|
|
515
522
|
}
|
|
516
523
|
|
|
524
|
+
/**
|
|
525
|
+
* THE one selector dispatch of the `get` overload pair (primary-key vs
|
|
526
|
+
* key-statement, `docs/architecture/70-api.md` § the freeze): judges the
|
|
527
|
+
* middle argument once and hands the narrowed pieces to the chosen
|
|
528
|
+
* continuation. `Db.get` and the read scope's `get` both dispatch through
|
|
529
|
+
* here, so the two mismatch refusals speak with one voice and the symmetry
|
|
530
|
+
* rule (`db.get(...) === db.read(snap => snap.get(...))`) holds by
|
|
531
|
+
* construction.
|
|
532
|
+
*/
|
|
533
|
+
function selectKeyRead<R extends AnyRelation, P extends readonly string[], T>(
|
|
534
|
+
keyOrStatement: KeyFact<R> | KeyStatement<R, P>,
|
|
535
|
+
declaredKey: DeclaredKeyFact<R, P> | undefined,
|
|
536
|
+
byStatement: (statement: KeyStatement<R, P>, key: DeclaredKeyFact<R, P>) => T,
|
|
537
|
+
byPrimary: (key: KeyFact<R>) => T
|
|
538
|
+
): T {
|
|
539
|
+
if (declaredKey !== undefined) {
|
|
540
|
+
if (!isStatementValue(keyOrStatement)) {
|
|
541
|
+
throw errors.new("keyed get takes a key() statement value as its second argument")
|
|
542
|
+
}
|
|
543
|
+
return byStatement(keyOrStatement, declaredKey)
|
|
544
|
+
}
|
|
545
|
+
if (isStatementValue(keyOrStatement)) {
|
|
546
|
+
throw errors.new("keyed get with a statement selector also takes the key object — get(relation, keyStatement, key)")
|
|
547
|
+
}
|
|
548
|
+
return byPrimary(keyOrStatement)
|
|
549
|
+
}
|
|
550
|
+
|
|
517
551
|
/** Maps a slot's reversal flag to the violation's `orientation` payload. */
|
|
518
552
|
function orientationOf(reversed: boolean | undefined): "written" | "mirrored" | undefined {
|
|
519
553
|
if (reversed === undefined) {
|
|
@@ -638,6 +672,32 @@ const planReclaimer = new FinalizationRegistry<PreparedHandle>(function reclaimP
|
|
|
638
672
|
*/
|
|
639
673
|
const generationMovedSignal = errors.new("bumbledb witnessed generation moved")
|
|
640
674
|
|
|
675
|
+
/**
|
|
676
|
+
* The witnessed loop's attempt cap — a generous power of two. Benign
|
|
677
|
+
* self-inflicted contention (the host's own commits landing between an
|
|
678
|
+
* attempt's snapshot and its witnessed begin) converges in a handful of
|
|
679
|
+
* retries because each rerun reads a FRESHER snapshot; a workload that moves
|
|
680
|
+
* the generation on EVERY one of this many consecutive attempts is not
|
|
681
|
+
* converging and never will (see {@link ErrWitnessedLivelock}).
|
|
682
|
+
*/
|
|
683
|
+
const WITNESSED_ATTEMPT_CAP = 64
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* The typed livelock refusal `writeWitnessed` throws past
|
|
687
|
+
* {@link WITNESSED_ATTEMPT_CAP} attempts: every attempt found the generation
|
|
688
|
+
* moved, which is only sustainable when the callback ITSELF (even
|
|
689
|
+
* indirectly) issues an interleaved plain `db.write` before its first tx
|
|
690
|
+
* verb on every attempt — each rerun then re-moves the generation it is
|
|
691
|
+
* about to witness, forever. That is host-policy pathology, not engine
|
|
692
|
+
* judgment (the engine ships the error, never a loop), so it THROWS rather
|
|
693
|
+
* than returning a result arm. Match with `errors.is`; the remedy is to
|
|
694
|
+
* move the interleaved write out of the callback (or make it first-attempt
|
|
695
|
+
* only — the delta belongs on `tx`, premise reads on `snap`).
|
|
696
|
+
*/
|
|
697
|
+
const ErrWitnessedLivelock = errors.new(
|
|
698
|
+
"bumbledb writeWitnessed livelock: the generation moved on every attempt — the callback itself commits an interleaved write each try, so no snapshot can ever stay current"
|
|
699
|
+
)
|
|
700
|
+
|
|
641
701
|
/**
|
|
642
702
|
* Fills one insert's omitted fresh cells through the engine's
|
|
643
703
|
* alloc-then-insert dyn lane (there is no insert-with-omitted-fields wire
|
|
@@ -696,7 +756,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
696
756
|
if (entry === undefined || entry.member !== relation) {
|
|
697
757
|
throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`)
|
|
698
758
|
}
|
|
699
|
-
if (
|
|
759
|
+
if (isClosedMember(relation)) {
|
|
700
760
|
throw errors.new(
|
|
701
761
|
`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`
|
|
702
762
|
)
|
|
@@ -705,12 +765,21 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
705
765
|
}
|
|
706
766
|
|
|
707
767
|
function offendingFactOf(fact: WireViolationFact): OffendingFact<Rels> {
|
|
708
|
-
|
|
768
|
+
const entry = tables.relations.get(fact.relation)
|
|
769
|
+
if (entry === undefined || !isMemberName(fact.relation)) {
|
|
709
770
|
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
|
|
710
771
|
}
|
|
772
|
+
const declared = sealedFieldsOf(entry.member)
|
|
711
773
|
const decoded: Record<string, FactValue> = {}
|
|
712
774
|
for (const cell of fact.fields) {
|
|
713
|
-
|
|
775
|
+
const cited = declared.find(function byName(candidate) {
|
|
776
|
+
return candidate.name === cell.name
|
|
777
|
+
})
|
|
778
|
+
const roster = rosterOf(cited?.field)
|
|
779
|
+
decoded[cell.name] =
|
|
780
|
+
roster !== undefined
|
|
781
|
+
? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
|
|
782
|
+
: cell.value
|
|
714
783
|
}
|
|
715
784
|
return Object.freeze({ relation: fact.relation, fact: Object.freeze(decoded) })
|
|
716
785
|
}
|
|
@@ -791,25 +860,22 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
791
860
|
): Fact<R> | undefined {
|
|
792
861
|
assertLive()
|
|
793
862
|
const entry = resolveOrdinary(relation)
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
863
|
+
return selectKeyRead(
|
|
864
|
+
keyOrStatement,
|
|
865
|
+
declaredKey,
|
|
866
|
+
function byStatement(statement, key) {
|
|
867
|
+
return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key))
|
|
868
|
+
},
|
|
869
|
+
function byPrimary(key) {
|
|
870
|
+
const primaryKey = entry.primaryKey
|
|
871
|
+
if (primaryKey === undefined) {
|
|
872
|
+
throw errors.new(
|
|
873
|
+
`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`
|
|
874
|
+
)
|
|
875
|
+
}
|
|
876
|
+
return readThroughKey(relation, entry, primaryKey, recordOf(key))
|
|
797
877
|
}
|
|
798
|
-
|
|
799
|
-
return readThroughKey(relation, entry, selected, recordOf(declaredKey))
|
|
800
|
-
}
|
|
801
|
-
if (isStatementValue(keyOrStatement)) {
|
|
802
|
-
throw errors.new(
|
|
803
|
-
"keyed get with a statement selector also takes the key object — get(relation, keyStatement, key)"
|
|
804
|
-
)
|
|
805
|
-
}
|
|
806
|
-
const primaryKey = entry.primaryKey
|
|
807
|
-
if (primaryKey === undefined) {
|
|
808
|
-
throw errors.new(
|
|
809
|
-
`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`
|
|
810
|
-
)
|
|
811
|
-
}
|
|
812
|
-
return readThroughKey(relation, entry, primaryKey, recordOf(keyOrStatement))
|
|
878
|
+
)
|
|
813
879
|
}
|
|
814
880
|
return { contains, get }
|
|
815
881
|
}
|
|
@@ -966,18 +1032,16 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
966
1032
|
declaredKey?: DeclaredKeyFact<R, P>
|
|
967
1033
|
): Fact<R> | undefined {
|
|
968
1034
|
return read(function getInScope(snap) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1035
|
+
return selectKeyRead(
|
|
1036
|
+
keyOrStatement,
|
|
1037
|
+
declaredKey,
|
|
1038
|
+
function byStatement(statement, key) {
|
|
1039
|
+
return snap.get(relation, statement, key)
|
|
1040
|
+
},
|
|
1041
|
+
function byPrimary(key) {
|
|
1042
|
+
return snap.get(relation, key)
|
|
972
1043
|
}
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
if (isStatementValue(keyOrStatement)) {
|
|
976
|
-
throw errors.new(
|
|
977
|
-
"keyed get with a statement selector also takes the key object — get(relation, keyStatement, key)"
|
|
978
|
-
)
|
|
979
|
-
}
|
|
980
|
-
return snap.get(relation, keyOrStatement)
|
|
1044
|
+
)
|
|
981
1045
|
})
|
|
982
1046
|
}
|
|
983
1047
|
|
|
@@ -1261,13 +1325,32 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1261
1325
|
return commitWitnessed(state, txHandle)
|
|
1262
1326
|
}
|
|
1263
1327
|
|
|
1328
|
+
/**
|
|
1329
|
+
* The witnessed retry loop. What it retries: the benign race — the
|
|
1330
|
+
* host's OWN interleaved commit landing between an attempt's snapshot
|
|
1331
|
+
* and its witnessed begin (every writer shares this handle, so a move
|
|
1332
|
+
* is always self-inflicted) — by rerunning the WHOLE callback on a
|
|
1333
|
+
* fresh snapshot, which converges because each rerun witnesses a
|
|
1334
|
+
* strictly newer generation. What it refuses: the pathology where the
|
|
1335
|
+
* callback itself (even indirectly) issues a plain `db.write` before
|
|
1336
|
+
* its first tx verb, moving the generation on EVERY attempt — an
|
|
1337
|
+
* unbounded loop would spin forever with no diagnostic, so past
|
|
1338
|
+
* {@link WITNESSED_ATTEMPT_CAP} attempts the loop throws the typed
|
|
1339
|
+
* {@link ErrWitnessedLivelock} instead (the engine's ruling: the
|
|
1340
|
+
* error, never a loop — retry is host policy, and this is the host
|
|
1341
|
+
* policy's own honesty bound).
|
|
1342
|
+
*/
|
|
1264
1343
|
function writeWitnessed<R>(fn: (snap: ReadScope<Rels>, tx: Tx<Rels>) => R): WitnessedWriteResult<Rels, R> {
|
|
1265
|
-
for (
|
|
1344
|
+
for (let attempts = 0; attempts < WITNESSED_ATTEMPT_CAP; attempts += 1) {
|
|
1266
1345
|
const attempt = witnessedAttempt(fn)
|
|
1267
1346
|
if (attempt !== undefined) {
|
|
1268
1347
|
return attempt
|
|
1269
1348
|
}
|
|
1270
1349
|
}
|
|
1350
|
+
throw errors.wrap(
|
|
1351
|
+
ErrWitnessedLivelock,
|
|
1352
|
+
`writeWitnessed livelock: the generation moved on all ${WITNESSED_ATTEMPT_CAP} attempts against schema ${theory.name}`
|
|
1353
|
+
)
|
|
1271
1354
|
}
|
|
1272
1355
|
|
|
1273
1356
|
/**
|
|
@@ -1289,17 +1372,9 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1289
1372
|
if (member === undefined) {
|
|
1290
1373
|
throw errors.new(`bumbledb manifest drift: schema ${theory.name} lost relation ${name}`)
|
|
1291
1374
|
}
|
|
1292
|
-
const sealed =
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
"id",
|
|
1296
|
-
...member.data.columns.map(function columnName(column) {
|
|
1297
|
-
return column.name
|
|
1298
|
-
})
|
|
1299
|
-
]
|
|
1300
|
-
: member.data.fields.map(function fieldName(declared) {
|
|
1301
|
-
return declared.name
|
|
1302
|
-
})
|
|
1375
|
+
const sealed = sealedFieldsOf(member).map(function fieldName(declared) {
|
|
1376
|
+
return declared.name
|
|
1377
|
+
})
|
|
1303
1378
|
sealed.forEach(function verifyField(fieldName, fieldOrdinal) {
|
|
1304
1379
|
if (entry.fieldIds.get(fieldName) !== fieldOrdinal) {
|
|
1305
1380
|
throw errors.new(
|
|
@@ -1544,4 +1619,4 @@ export type {
|
|
|
1544
1619
|
WitnessedWriteResult,
|
|
1545
1620
|
WriteResult
|
|
1546
1621
|
}
|
|
1547
|
-
export { abandon, Db, ErrNewtypeMismatch }
|
|
1622
|
+
export { abandon, Db, ErrNewtypeMismatch, ErrWitnessedLivelock, WITNESSED_ATTEMPT_CAP }
|
package/src/exhume.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import * as errors from "@superbuilders/errors"
|
|
28
28
|
import type { FactValue, Manifest } from "#native.ts"
|
|
29
|
-
import { native } from "#native.ts"
|
|
29
|
+
import { bridged, native } from "#native.ts"
|
|
30
30
|
import type { ValueTypeSpec } from "#spec.ts"
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -132,20 +132,6 @@ interface Exhumed {
|
|
|
132
132
|
scan(relation: string): readonly ExhumedFact[]
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/**
|
|
136
|
-
* The bridge guard (db.ts's twin over this module's calls): runs one
|
|
137
|
-
* native call and wraps anything it throws, so marshal-shape and
|
|
138
|
-
* handle-lifecycle refusals cross as genuine typed failures, never bare
|
|
139
|
-
* foreign errors.
|
|
140
|
-
*/
|
|
141
|
-
function bridged<T>(context: string, run: () => T): T {
|
|
142
|
-
const result = errors.trySync(run)
|
|
143
|
-
if (result.error) {
|
|
144
|
-
throw errors.wrap(result.error, context)
|
|
145
|
-
}
|
|
146
|
-
return result.data
|
|
147
|
-
}
|
|
148
|
-
|
|
149
135
|
/**
|
|
150
136
|
* Shapes the bridge's manifest rendering of the stored descriptor into the
|
|
151
137
|
* SDK's {@link ExhumedDescriptor}: same relations in the same engine-id
|
package/src/face.ts
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
* positional: tuple order is preserved in the type, and the statement
|
|
12
12
|
* constructors pair the two sides' tuples by arity ({@link SameArity}) AND
|
|
13
13
|
* by structural shape ({@link SameShapes}) — every projected field's
|
|
14
|
-
* kind/width/element
|
|
15
|
-
* kernel: descriptors are pure structure
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* kind/width/element/roster quadruple is read off the schema type (the
|
|
15
|
+
* minimal kernel: descriptors are pure structure, and a closed reference's
|
|
16
|
+
* roster IS part of that structure) and compared positionwise. There is no
|
|
17
|
+
* domain to compare at construction — domains are LAW-BORN: the statements
|
|
18
|
+
* themselves define the equivalence classes, and `schema()` is where they
|
|
19
|
+
* aggregate and get judged (the one-generator-per-class wall).
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
import * as errors from "@superbuilders/errors"
|
|
@@ -54,27 +55,6 @@ function faceParts(source: FaceSource): {
|
|
|
54
55
|
return { owner: source, selection: emptySelection }
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
/**
|
|
58
|
-
* A disjunctive literal set for a selection binding — `field == {A, B}`.
|
|
59
|
-
* The signature of {@link oneOf} demands two leading literals, so the
|
|
60
|
-
* one-element set (banned: it is the bare literal) and the empty set
|
|
61
|
-
* (banned: it selects nothing) are unwritable.
|
|
62
|
-
*/
|
|
63
|
-
interface OneOf<V> {
|
|
64
|
-
readonly literals: readonly [V, V, ...V[]]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Constructs a literal set (read disjunctively) for a `where()` binding.
|
|
69
|
-
* Two leading arguments by signature: the degenerate sets have no spelling
|
|
70
|
-
* (the canonical-utterance law, `docs/architecture/70-api.md`).
|
|
71
|
-
*/
|
|
72
|
-
function oneOf<V>(first: V, second: V, ...rest: V[]): OneOf<V> {
|
|
73
|
-
const literals: readonly [V, V, ...V[]] = [first, second, ...rest]
|
|
74
|
-
Object.freeze(literals)
|
|
75
|
-
return Object.freeze({ literals })
|
|
76
|
-
}
|
|
77
|
-
|
|
78
58
|
/** The relation a face projects from — ordinary or closed. */
|
|
79
59
|
type FaceOwner = AnyRelation | AnyClosed
|
|
80
60
|
|
|
@@ -121,30 +101,38 @@ type FaceSource = AnyRelation | AnyClosed | AnySelected | AnySelectedClosed
|
|
|
121
101
|
* names address the sealed shape).
|
|
122
102
|
*/
|
|
123
103
|
type FaceFields<S extends FaceSource> = S extends AnySelected
|
|
124
|
-
? keyof S["relation"]
|
|
104
|
+
? keyof RelationFields<S["relation"]> & string
|
|
125
105
|
: S extends AnySelectedClosed
|
|
126
106
|
? "id" | (keyof S["relation"]["columns"] & string)
|
|
127
107
|
: S extends AnyRelation
|
|
128
|
-
? keyof S
|
|
108
|
+
? keyof RelationFields<S> & string
|
|
129
109
|
: S extends { readonly axioms: Readonly<Record<string, infer Row>> }
|
|
130
110
|
? "id" | (keyof Row & string)
|
|
131
111
|
: never
|
|
132
112
|
|
|
133
113
|
/**
|
|
134
|
-
* One descriptor's structural comparand: the kind/width/element
|
|
135
|
-
* exactly the structure the minimal kernel carries
|
|
136
|
-
* engine's Q1 law pairs positions
|
|
137
|
-
* bound (bytes<16> vs bytes<32>
|
|
138
|
-
*
|
|
139
|
-
* domain and not a width, so
|
|
140
|
-
* (
|
|
141
|
-
*
|
|
142
|
-
* u64-vs-i64 interval
|
|
114
|
+
* One descriptor's structural comparand: the kind/width/element/roster
|
|
115
|
+
* quadruple — exactly the structure the minimal kernel carries. The first
|
|
116
|
+
* three slots compare exactly as the engine's Q1 law pairs positions
|
|
117
|
+
* (`schema/validate.rs`): a `bytes` width is bound (bytes<16> vs bytes<32>
|
|
118
|
+
* mismatch), while an INTERVAL width is FREE — the pointwise judgments
|
|
119
|
+
* quantify over points, which carry an element domain and not a width, so
|
|
120
|
+
* `interval(u64)` pairs with `interval(u64, 1n)` (recipe 9's extent/slot
|
|
121
|
+
* mirrors, recipe 29's mixed-width zones) and the width slot reads
|
|
122
|
+
* `undefined` for every interval. Elements stay bound: u64-vs-i64 interval
|
|
123
|
+
* pairs still mismatch. The ROSTER slot is SDK-only structure (the engine's
|
|
124
|
+
* wire carries plain u64s): a closed reference contributes its handle
|
|
125
|
+
* union, every other kind `undefined`, so a plain u64 face cannot pair with
|
|
126
|
+
* a closed `[id]` face — the vocabulary's own descriptor (`Kind.id`) is the
|
|
127
|
+
* ONE spelling of a closed reference at this surface, and a bare column
|
|
128
|
+
* cannot alias a vocabulary through a declared law. The runtime twin is the
|
|
129
|
+
* statement constructors' roster-identity walk (`statements.ts`).
|
|
143
130
|
*/
|
|
144
131
|
type ShapeOf<F extends AnyField> = readonly [
|
|
145
132
|
F["kind"],
|
|
146
133
|
F extends { readonly element: unknown } ? undefined : F extends { readonly width: infer W } ? W : undefined,
|
|
147
|
-
F extends { readonly element: infer E } ? E : undefined
|
|
134
|
+
F extends { readonly element: infer E } ? E : undefined,
|
|
135
|
+
F extends { readonly closed: { readonly handles: readonly (infer H extends string)[] } } ? H : undefined
|
|
148
136
|
]
|
|
149
137
|
|
|
150
138
|
/** One field's structural shape within a declared field block (`undefined` when the name is foreign). */
|
|
@@ -216,10 +204,11 @@ type SameArity<A extends AnyFace, B extends AnyFace> =
|
|
|
216
204
|
* bijection, or window project structurally incompatible fields at any
|
|
217
205
|
* position, this type is intersected into the second face's parameter and
|
|
218
206
|
* names both shape tuples — a u64 face against a str face, a bytes width
|
|
219
|
-
* mismatch,
|
|
207
|
+
* mismatch, an interval element mismatch, or a bare column against a
|
|
208
|
+
* closed reference (the roster slot) is a COMPILE error.
|
|
220
209
|
*/
|
|
221
210
|
interface FaceShapeMismatch<Left, Right> {
|
|
222
|
-
readonly "face shape mismatch — positionwise kind, width, and
|
|
211
|
+
readonly "face shape mismatch — positionwise kind, width, element, and closed roster must be equal on both sides": readonly [
|
|
223
212
|
Left,
|
|
224
213
|
Right
|
|
225
214
|
]
|
|
@@ -229,11 +218,12 @@ interface FaceShapeMismatch<Left, Right> {
|
|
|
229
218
|
* Resolves to `unknown` (a no-op intersection) when the two faces project
|
|
230
219
|
* positionwise-equal structural shapes, and to {@link FaceShapeMismatch}
|
|
231
220
|
* otherwise. Equality is mutual tuple assignability over the
|
|
232
|
-
* kind/width/element
|
|
233
|
-
* deliberately: there is no domain to compare
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
221
|
+
* kind/width/element/roster quadruples. This is the whole
|
|
222
|
+
* construction-time wall — deliberately: there is no domain to compare
|
|
223
|
+
* here (the roster is descriptor STRUCTURE, not a domain). The domain wall
|
|
224
|
+
* lives where domains are BORN: `schema()` computes every field's class
|
|
225
|
+
* from the statement list and holds the one-generator-per-class law, and
|
|
226
|
+
* query joins compare class names off the schema type.
|
|
237
227
|
*/
|
|
238
228
|
type SameShapes<A extends AnyFace, B extends AnyFace> =
|
|
239
229
|
FaceShapes<A> extends FaceShapes<B>
|
|
@@ -273,7 +263,8 @@ function on<S extends FaceSource>(source: S, fields: string | readonly string[])
|
|
|
273
263
|
}
|
|
274
264
|
|
|
275
265
|
/**
|
|
276
|
-
* The trusted seam of the face mint (the
|
|
266
|
+
* The trusted admission seam of the face mint (the pattern's home is
|
|
267
|
+
* `isTypedScope` in query/lower.ts): the
|
|
277
268
|
* checkable facts — the value carries exactly the source and projection it
|
|
278
269
|
* was built from, and `data.owner` is exactly the owner {@link faceParts}
|
|
279
270
|
* resolves for that source — are verified before the wide construction is
|
|
@@ -323,9 +314,8 @@ export type {
|
|
|
323
314
|
FaceShapeMismatch,
|
|
324
315
|
FaceShapes,
|
|
325
316
|
FaceSource,
|
|
326
|
-
OneOf,
|
|
327
317
|
OwnerOf,
|
|
328
318
|
SameArity,
|
|
329
319
|
SameShapes
|
|
330
320
|
}
|
|
331
|
-
export { on,
|
|
321
|
+
export { on, renderFace }
|