@bjornpagen/bumbledb-log 0.17.0 → 0.18.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/README.md +22 -15
- package/package.json +3 -2
- package/src/braids.ts +5 -8
- package/src/bytes.ts +2 -2
- package/src/chain.ts +16 -17
- package/src/codec.ts +21 -23
- package/src/descriptor.ts +245 -276
- package/src/errors.ts +8 -6
- package/src/index.ts +10 -8
- package/src/keys.ts +69 -15
- package/src/manifest.ts +11 -7
- package/src/replica.ts +42 -44
- package/src/store-s3.ts +222 -0
- package/src/store.ts +76 -31
- package/src/value.ts +16 -16
- package/src/writer.ts +37 -34
package/src/errors.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as errors from "@superbuilders/errors"
|
|
11
|
+
import type { Braid } from "#descriptor.ts"
|
|
12
|
+
import type { Generation } from "#keys.ts"
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Typed refusal of a protocol object before any apply: batch shape,
|
|
@@ -54,7 +56,7 @@ type RefusalCause =
|
|
|
54
56
|
| { readonly kind: "UnknownOpKind"; readonly op: number; readonly opKind: number }
|
|
55
57
|
| { readonly kind: "UnknownRelation"; readonly op: number; readonly relation: number }
|
|
56
58
|
| { readonly kind: "ClosedRelation"; readonly op: number; readonly relation: number }
|
|
57
|
-
| { readonly kind: "OpRelationOutsideBraid"; readonly op: number; readonly relation: number; readonly braid:
|
|
59
|
+
| { readonly kind: "OpRelationOutsideBraid"; readonly op: number; readonly relation: number; readonly braid: Braid }
|
|
58
60
|
| { readonly kind: "TagMismatch"; readonly relation: string; readonly row: number; readonly field: string }
|
|
59
61
|
| { readonly kind: "BoolByte"; readonly relation: string; readonly row: number; readonly field: string }
|
|
60
62
|
| { readonly kind: "InvalidUtf8"; readonly relation: string; readonly row: number; readonly field: string }
|
|
@@ -66,14 +68,14 @@ type RefusalCause =
|
|
|
66
68
|
| { readonly kind: "CheckpointShape" }
|
|
67
69
|
| { readonly kind: "CheckpointBraids"; readonly carried: readonly string[]; readonly derived: readonly string[] }
|
|
68
70
|
| { readonly kind: "CheckpointDigest"; readonly expected: string; readonly computed: string }
|
|
69
|
-
| { readonly kind: "NoOpSlot"; readonly braid:
|
|
71
|
+
| { readonly kind: "NoOpSlot"; readonly braid: Braid; readonly slot: Generation; readonly writer: bigint }
|
|
70
72
|
|
|
71
73
|
type ChainCause = "prev" | "slot" | "timestamp"
|
|
72
74
|
|
|
73
75
|
interface ChainMismatchData {
|
|
74
76
|
readonly cause: ChainCause
|
|
75
|
-
readonly braid:
|
|
76
|
-
readonly slot:
|
|
77
|
+
readonly braid: Braid
|
|
78
|
+
readonly slot: Generation
|
|
77
79
|
readonly writer: bigint
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -83,10 +85,10 @@ type ContentionCause =
|
|
|
83
85
|
readonly statement: string
|
|
84
86
|
readonly determinants: ReadonlyArray<Readonly<Record<string, unknown>>>
|
|
85
87
|
}
|
|
86
|
-
| { readonly kind: "slot-race"; readonly tip:
|
|
88
|
+
| { readonly kind: "slot-race"; readonly tip: Generation }
|
|
87
89
|
|
|
88
90
|
interface ContentionData {
|
|
89
|
-
readonly braid:
|
|
91
|
+
readonly braid: Braid
|
|
90
92
|
readonly cause: ContentionCause
|
|
91
93
|
}
|
|
92
94
|
|
package/src/index.ts
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
* bumbledb, a thin peer of the engine SDK. Public surface: the pure
|
|
4
4
|
* protocol pair (`encodeBatch`/`decodeBatch`, `braidsOf`) mirrored
|
|
5
5
|
* byte-exactly against the Rust driver, the five-verb object store
|
|
6
|
-
* with its tier-1 `fsStore`, and `openReplica`/`openWriter`/
|
|
6
|
+
* with its tier-1 `fsStore`, in-process `memStore`, and the `s3Store` AWS S3 client, and `openReplica`/`openWriter`/
|
|
7
7
|
* `openTenants` composed from the engine SDK's own verbs — the replica
|
|
8
8
|
* hands out the SDK's `Db`, and no engine surface is duplicated.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export type { Braid } from "#braids.ts"
|
|
12
|
-
export { braidsOf, serialAtStatementsOf } from "#braids.ts"
|
|
13
|
-
export type { BatchHeader,
|
|
12
|
+
export { braid, braidsOf, serialAtStatementsOf } from "#braids.ts"
|
|
13
|
+
export type { BatchHeader, ChainEntry, DecodedBatch, Op } from "#codec.ts"
|
|
14
14
|
export { decodeBatch, encodeBatch, verifyChain } from "#codec.ts"
|
|
15
|
-
export type {
|
|
15
|
+
export type { Descriptor, SerialStatement, Theory } from "#descriptor.ts"
|
|
16
16
|
export { descriptorOf } from "#descriptor.ts"
|
|
17
17
|
export type { ChainCause, ChainMismatchData, ContentionCause, ContentionData, RefusalCause } from "#errors.ts"
|
|
18
18
|
export {
|
|
@@ -27,12 +27,14 @@ export {
|
|
|
27
27
|
ErrStore,
|
|
28
28
|
refusalOf
|
|
29
29
|
} from "#errors.ts"
|
|
30
|
+
export type { Generation, StoreKey } from "#keys.ts"
|
|
31
|
+
export { generation, storeKey } from "#keys.ts"
|
|
30
32
|
export type { OpenReplicaOptions, Replica } from "#replica.ts"
|
|
31
33
|
export { openReplica } from "#replica.ts"
|
|
32
|
-
export type { Create, Fetched, ObjectStore, Poll, Swap } from "#store.ts"
|
|
33
|
-
export { fsStore } from "#store.ts"
|
|
34
|
+
export type { Create, Etag, Fetched, ObjectStore, Poll, S3Config, S3Credentials, Swap } from "#store.ts"
|
|
35
|
+
export { etag, fsStore, memStore, s3Store } from "#store.ts"
|
|
34
36
|
export type { OpenTenantsOptions, Tenants } from "#tenants.ts"
|
|
35
37
|
export { openTenants } from "#tenants.ts"
|
|
36
|
-
export type {
|
|
37
|
-
export type { BraidOutcome, Commit, CommitSplit, Durability,
|
|
38
|
+
export type { Interval, Value } from "#value.ts"
|
|
39
|
+
export type { Batch, BraidOutcome, Commit, CommitSplit, Durability, Writer } from "#writer.ts"
|
|
38
40
|
export { openWriter } from "#writer.ts"
|
package/src/keys.ts
CHANGED
|
@@ -1,34 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The key layout
|
|
3
|
-
*
|
|
2
|
+
* The key layout: generation numbers zero-padded lowercase hex, 16
|
|
3
|
+
* chars; a prefix is a store; a tenant is a prefix under `t/`. A
|
|
4
|
+
* StoreKey is parsed here, once — the verbs take the proof and never
|
|
5
|
+
* re-check. An empty prefix joins as the rest alone, so a leading
|
|
6
|
+
* slash is unrepresentable.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
import * as errors from "@superbuilders/errors"
|
|
10
|
+
import { U64_MAX } from "#bytes.ts"
|
|
11
|
+
import type { Braid } from "#descriptor.ts"
|
|
12
|
+
|
|
13
|
+
/** Suffix of the per-key pid-lockfile. A segment wearing it cannot be a key. */
|
|
14
|
+
const LOCK_SUFFIX = ".lock"
|
|
15
|
+
|
|
16
|
+
declare const storeKeyBrand: unique symbol
|
|
17
|
+
type StoreKey = string & { readonly [storeKeyBrand]: typeof storeKeyBrand }
|
|
18
|
+
|
|
19
|
+
declare const generationBrand: unique symbol
|
|
20
|
+
type Generation = bigint & { readonly [generationBrand]: typeof generationBrand }
|
|
21
|
+
|
|
22
|
+
function storeKey(raw: string): StoreKey {
|
|
23
|
+
if (raw.length === 0 || raw.startsWith("/") || raw.endsWith("/")) {
|
|
24
|
+
throw errors.new(`store key is not a slash path: ${raw}`)
|
|
25
|
+
}
|
|
26
|
+
for (const segment of raw.split("/")) {
|
|
27
|
+
if (segment.length === 0 || segment === "." || segment === "..") {
|
|
28
|
+
throw errors.new(`store key segment is illegal: ${raw}`)
|
|
29
|
+
}
|
|
30
|
+
if (segment.endsWith(LOCK_SUFFIX)) {
|
|
31
|
+
throw errors.new(`store key collides with the lockfile suffix: ${raw}`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return raw as StoreKey
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function generation(raw: bigint): Generation {
|
|
38
|
+
if (raw < 0n || raw > U64_MAX) {
|
|
39
|
+
throw errors.new(`generation is a u64: ${raw}`)
|
|
40
|
+
}
|
|
41
|
+
return raw as Generation
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hex16(g: Generation): string {
|
|
7
45
|
return g.toString(16).padStart(16, "0")
|
|
8
46
|
}
|
|
9
47
|
|
|
10
|
-
function
|
|
11
|
-
return `${prefix}
|
|
48
|
+
function assemble(prefix: string, rest: string): StoreKey {
|
|
49
|
+
return storeKey(prefix.length === 0 ? rest : `${prefix}/${rest}`)
|
|
12
50
|
}
|
|
13
51
|
|
|
14
|
-
function
|
|
15
|
-
return
|
|
52
|
+
function manifestKey(prefix: string): StoreKey {
|
|
53
|
+
return assemble(prefix, "manifest.json")
|
|
16
54
|
}
|
|
17
55
|
|
|
18
|
-
function
|
|
19
|
-
return
|
|
56
|
+
function logKey(prefix: string, braid: Braid, g: Generation): StoreKey {
|
|
57
|
+
return assemble(prefix, `log/${braid}/${hex16(g)}`)
|
|
20
58
|
}
|
|
21
59
|
|
|
22
|
-
function
|
|
23
|
-
return
|
|
60
|
+
function checkpointMdbKey(prefix: string, digest: string): StoreKey {
|
|
61
|
+
return assemble(prefix, `ckpt/${digest}.mdb`)
|
|
24
62
|
}
|
|
25
63
|
|
|
26
|
-
function
|
|
27
|
-
return
|
|
64
|
+
function checkpointJsonKey(prefix: string, digest: string): StoreKey {
|
|
65
|
+
return assemble(prefix, `ckpt/${digest}.json`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function idsKey(prefix: string, relation: number, field: number): StoreKey {
|
|
69
|
+
return assemble(prefix, `ids/${relation.toString(16).padStart(8, "0")}/${field.toString(16).padStart(4, "0")}`)
|
|
28
70
|
}
|
|
29
71
|
|
|
30
72
|
function tenantPrefix(root: string, tenant: string): string {
|
|
31
|
-
return
|
|
73
|
+
return assemble(root, `t/${tenant}`)
|
|
32
74
|
}
|
|
33
75
|
|
|
34
|
-
export {
|
|
76
|
+
export type { Generation, StoreKey }
|
|
77
|
+
export {
|
|
78
|
+
checkpointJsonKey,
|
|
79
|
+
checkpointMdbKey,
|
|
80
|
+
generation,
|
|
81
|
+
hex16,
|
|
82
|
+
idsKey,
|
|
83
|
+
LOCK_SUFFIX,
|
|
84
|
+
logKey,
|
|
85
|
+
manifestKey,
|
|
86
|
+
storeKey,
|
|
87
|
+
tenantPrefix
|
|
88
|
+
}
|
package/src/manifest.ts
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import * as errors from "@superbuilders/errors"
|
|
10
10
|
import { utf8Encoder, utf8StrictDecoder } from "#bytes.ts"
|
|
11
|
+
import type { Braid } from "#descriptor.ts"
|
|
12
|
+
import { braid } from "#descriptor.ts"
|
|
11
13
|
import { refuse } from "#errors.ts"
|
|
14
|
+
import type { Generation } from "#keys.ts"
|
|
15
|
+
import { generation } from "#keys.ts"
|
|
12
16
|
|
|
13
17
|
interface Manifest {
|
|
14
18
|
readonly fingerprint: string
|
|
@@ -59,13 +63,13 @@ function parseManifest(bytes: Uint8Array): Manifest {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
interface CheckpointHead {
|
|
62
|
-
readonly g:
|
|
66
|
+
readonly g: Generation
|
|
63
67
|
readonly hash: string
|
|
64
68
|
readonly ts: bigint
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
interface CheckpointFacts {
|
|
68
|
-
readonly braids: ReadonlyMap<
|
|
72
|
+
readonly braids: ReadonlyMap<Braid, CheckpointHead>
|
|
69
73
|
readonly catalog: string
|
|
70
74
|
readonly writer: bigint
|
|
71
75
|
readonly prev: string | null
|
|
@@ -83,19 +87,19 @@ function parseCheckpoint(bytes: Uint8Array): CheckpointFacts {
|
|
|
83
87
|
if (typeof rawBraids !== "object" || rawBraids === null) {
|
|
84
88
|
refuse({ kind: "CheckpointShape" }, "checkpoint json carries no braids map")
|
|
85
89
|
}
|
|
86
|
-
const braids = new Map<
|
|
87
|
-
for (const [
|
|
90
|
+
const braids = new Map<Braid, CheckpointHead>()
|
|
91
|
+
for (const [name, head] of Object.entries(rawBraids as Record<string, unknown>)) {
|
|
88
92
|
if (typeof head !== "object" || head === null) {
|
|
89
|
-
refuse({ kind: "CheckpointShape" }, `checkpoint braid ${
|
|
93
|
+
refuse({ kind: "CheckpointShape" }, `checkpoint braid ${name} head is not an object`)
|
|
90
94
|
}
|
|
91
95
|
const headRecord = head as Record<string, unknown>
|
|
92
96
|
const g = headRecord.g
|
|
93
97
|
const hash = headRecord.hash
|
|
94
98
|
const ts = headRecord.ts
|
|
95
99
|
if (typeof g !== "number" || typeof ts !== "number" || typeof hash !== "string" || !HEX64.test(hash)) {
|
|
96
|
-
refuse({ kind: "CheckpointShape" }, `checkpoint braid ${
|
|
100
|
+
refuse({ kind: "CheckpointShape" }, `checkpoint braid ${name} head is malformed`)
|
|
97
101
|
}
|
|
98
|
-
braids.set(braid, { g: BigInt(g), hash, ts: BigInt(ts) })
|
|
102
|
+
braids.set(braid(name), { g: generation(BigInt(g)), hash, ts: BigInt(ts) })
|
|
99
103
|
}
|
|
100
104
|
const catalog = record.catalog
|
|
101
105
|
const writer = record.writer
|
package/src/replica.ts
CHANGED
|
@@ -14,18 +14,19 @@ import type { Db, Fact, MemberRelation, Schema, SchemaRelations, WriteOutcome }
|
|
|
14
14
|
import { internalBlake3, Db as SdkDb } from "@bjornpagen/bumbledb"
|
|
15
15
|
import * as errors from "@superbuilders/errors"
|
|
16
16
|
import { bytesEqual, toHex } from "#bytes.ts"
|
|
17
|
-
import type { ChainEntry,
|
|
17
|
+
import type { ChainEntry, Pending } from "#chain.ts"
|
|
18
18
|
import { readSidecar, writeSidecar } from "#chain.ts"
|
|
19
|
-
import type {
|
|
19
|
+
import type { Op } from "#codec.ts"
|
|
20
20
|
import { decodeBatch, encodeBatch, verifyChain } from "#codec.ts"
|
|
21
|
-
import type {
|
|
21
|
+
import type { Braid, Descriptor, RelationInfo } from "#descriptor.ts"
|
|
22
22
|
import { descriptorOf } from "#descriptor.ts"
|
|
23
23
|
import { ErrGapDetected, ErrReplayDiverged, refuse } from "#errors.ts"
|
|
24
|
-
import {
|
|
24
|
+
import type { Generation } from "#keys.ts"
|
|
25
|
+
import { checkpointJsonKey, checkpointMdbKey, generation, logKey, manifestKey } from "#keys.ts"
|
|
25
26
|
import type { CheckpointFacts } from "#manifest.ts"
|
|
26
27
|
import { parseCheckpoint, parseManifest, renderManifest } from "#manifest.ts"
|
|
27
|
-
import type { ObjectStore } from "#store.ts"
|
|
28
|
-
import type {
|
|
28
|
+
import type { Etag, ObjectStore } from "#store.ts"
|
|
29
|
+
import type { Value } from "#value.ts"
|
|
29
30
|
|
|
30
31
|
const ZERO_HASH = "0".repeat(64)
|
|
31
32
|
|
|
@@ -45,9 +46,9 @@ interface OpenReplicaOptions<Rels extends SchemaRelations> {
|
|
|
45
46
|
|
|
46
47
|
interface Replica<Rels extends SchemaRelations> extends AsyncDisposable {
|
|
47
48
|
readonly db: Db<Rels>
|
|
48
|
-
readonly vector: ReadonlyMap<
|
|
49
|
-
refresh(braid?:
|
|
50
|
-
waitFor(vector: ReadonlyMap<
|
|
49
|
+
readonly vector: ReadonlyMap<Braid, Generation>
|
|
50
|
+
refresh(braid?: Braid): Promise<ReadonlyMap<Braid, Generation>>
|
|
51
|
+
waitFor(vector: ReadonlyMap<Braid, Generation>): Promise<void>
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
interface Core<Rels extends SchemaRelations> {
|
|
@@ -55,13 +56,13 @@ interface Core<Rels extends SchemaRelations> {
|
|
|
55
56
|
readonly prefix: string
|
|
56
57
|
readonly dir: string
|
|
57
58
|
readonly theory: Schema<Rels>
|
|
58
|
-
readonly descriptor:
|
|
59
|
+
readonly descriptor: Descriptor
|
|
59
60
|
db: Db<Rels>
|
|
60
|
-
chain: Map<
|
|
61
|
-
pending:
|
|
62
|
-
pendingOps: readonly
|
|
61
|
+
chain: Map<Braid, ChainEntry>
|
|
62
|
+
pending: Pending | null
|
|
63
|
+
pendingOps: readonly Op[] | null
|
|
63
64
|
pendingApplied: boolean
|
|
64
|
-
manifestEtag:
|
|
65
|
+
manifestEtag: Etag | null
|
|
65
66
|
checkpoint: CheckpointFacts | null
|
|
66
67
|
checkpointDigest: string | null
|
|
67
68
|
passes: number
|
|
@@ -117,15 +118,15 @@ function storePath<Rels extends SchemaRelations>(core: Core<Rels>): string {
|
|
|
117
118
|
return path.join(core.dir, core.storeName)
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
function zeroChain(descriptor:
|
|
121
|
-
const chain = new Map<
|
|
122
|
-
for (const
|
|
123
|
-
chain.set(
|
|
121
|
+
function zeroChain(descriptor: Descriptor): Map<Braid, ChainEntry> {
|
|
122
|
+
const chain = new Map<Braid, ChainEntry>()
|
|
123
|
+
for (const id of descriptor.braidMembers.keys()) {
|
|
124
|
+
chain.set(id, { g: generation(0n), prev: ZERO_HASH, ts: 0n })
|
|
124
125
|
}
|
|
125
126
|
return chain
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function chainEntry<Rels extends SchemaRelations>(core: Core<Rels>, braid:
|
|
129
|
+
function chainEntry<Rels extends SchemaRelations>(core: Core<Rels>, braid: Braid): ChainEntry {
|
|
129
130
|
const entry = core.chain.get(braid)
|
|
130
131
|
if (entry === undefined) {
|
|
131
132
|
throw errors.new(`braid ${braid} is not derived from this theory`)
|
|
@@ -166,7 +167,7 @@ async function clearPending<Rels extends SchemaRelations>(core: Core<Rels>): Pro
|
|
|
166
167
|
function factOf<Rels extends SchemaRelations>(
|
|
167
168
|
core: Core<Rels>,
|
|
168
169
|
relation: RelationInfo,
|
|
169
|
-
row: readonly
|
|
170
|
+
row: readonly Value[]
|
|
170
171
|
): Record<string, unknown> {
|
|
171
172
|
const fact: Record<string, unknown> = {}
|
|
172
173
|
relation.fields.forEach(function liftCell(field, ordinal) {
|
|
@@ -191,7 +192,7 @@ function factOf<Rels extends SchemaRelations>(
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
/** One `db.write` applying ops in listed order, rows in listed order. */
|
|
194
|
-
function applyOps<Rels extends SchemaRelations>(core: Core<Rels>, ops: readonly
|
|
195
|
+
function applyOps<Rels extends SchemaRelations>(core: Core<Rels>, ops: readonly Op[]): WriteOutcome<Rels, number> {
|
|
195
196
|
return core.db.write(function applyBatch(tx) {
|
|
196
197
|
for (const op of ops) {
|
|
197
198
|
const info = core.descriptor.relationByName.get(op.relation)
|
|
@@ -225,8 +226,8 @@ type SlotApply = { readonly tag: "applied"; readonly generation: bigint } | { re
|
|
|
225
226
|
*/
|
|
226
227
|
async function applySlot<Rels extends SchemaRelations>(
|
|
227
228
|
core: Core<Rels>,
|
|
228
|
-
braid:
|
|
229
|
-
slot:
|
|
229
|
+
braid: Braid,
|
|
230
|
+
slot: Generation,
|
|
230
231
|
bytes: Uint8Array,
|
|
231
232
|
phase: ApplyPhase
|
|
232
233
|
): Promise<SlotApply> {
|
|
@@ -256,7 +257,7 @@ async function applySlot<Rels extends SchemaRelations>(
|
|
|
256
257
|
* The gc floor rule (10): below the current checkpoint's vector a 404 is
|
|
257
258
|
* a collected hole, at or above it the honest tip.
|
|
258
259
|
*/
|
|
259
|
-
function holeAt<Rels extends SchemaRelations>(core: Core<Rels>, braid:
|
|
260
|
+
function holeAt<Rels extends SchemaRelations>(core: Core<Rels>, braid: Braid, next: Generation): boolean {
|
|
260
261
|
if (core.checkpoint === null) {
|
|
261
262
|
return false
|
|
262
263
|
}
|
|
@@ -269,11 +270,11 @@ function holeAt<Rels extends SchemaRelations>(core: Core<Rels>, braid: string, n
|
|
|
269
270
|
|
|
270
271
|
async function catchUpBraid<Rels extends SchemaRelations>(
|
|
271
272
|
core: Core<Rels>,
|
|
272
|
-
braid:
|
|
273
|
+
braid: Braid,
|
|
273
274
|
phase: ApplyPhase
|
|
274
275
|
): Promise<"tip" | "discard"> {
|
|
275
276
|
for (;;) {
|
|
276
|
-
const next = chainEntry(core, braid).g + 1n
|
|
277
|
+
const next = generation(chainEntry(core, braid).g + 1n)
|
|
277
278
|
const fetched = await core.store.get(logKey(core.prefix, braid, next))
|
|
278
279
|
if (fetched === null) {
|
|
279
280
|
if (holeAt(core, braid, next)) {
|
|
@@ -322,7 +323,7 @@ function wholenessHolds<Rels extends SchemaRelations>(core: Core<Rels>): boolean
|
|
|
322
323
|
async function adoptManifest<Rels extends SchemaRelations>(
|
|
323
324
|
core: Core<Rels>,
|
|
324
325
|
bytes: Uint8Array,
|
|
325
|
-
etag:
|
|
326
|
+
etag: Etag
|
|
326
327
|
): Promise<void> {
|
|
327
328
|
const manifest = parseManifest(bytes)
|
|
328
329
|
if (manifest.fingerprint !== core.descriptor.fingerprint) {
|
|
@@ -502,7 +503,7 @@ async function resolvePendingAtOpen<Rels extends SchemaRelations>(core: Core<Rel
|
|
|
502
503
|
return
|
|
503
504
|
}
|
|
504
505
|
const decoded = errors.trySync(function decodePending() {
|
|
505
|
-
return decodeBatch(core.descriptor, (core.pending as
|
|
506
|
+
return decodeBatch(core.descriptor, (core.pending as Pending).bytes)
|
|
506
507
|
})
|
|
507
508
|
if (decoded.error) {
|
|
508
509
|
await clearPending(core)
|
|
@@ -529,10 +530,7 @@ async function resolvePendingAtOpen<Rels extends SchemaRelations>(core: Core<Rel
|
|
|
529
530
|
* absorbs it (L10's idempotent replay), and everything else takes the
|
|
530
531
|
* one loss path's re-judgment at the tip.
|
|
531
532
|
*/
|
|
532
|
-
async function resolveColdPending<Rels extends SchemaRelations>(
|
|
533
|
-
core: Core<Rels>,
|
|
534
|
-
pending: PendingBatch
|
|
535
|
-
): Promise<void> {
|
|
533
|
+
async function resolveColdPending<Rels extends SchemaRelations>(core: Core<Rels>, pending: Pending): Promise<void> {
|
|
536
534
|
const decoded = errors.trySync(function decodePending() {
|
|
537
535
|
return decodeBatch(core.descriptor, pending.bytes)
|
|
538
536
|
})
|
|
@@ -583,7 +581,7 @@ async function openCore<Rels extends SchemaRelations>(options: OpenReplicaOption
|
|
|
583
581
|
const existing = await newestStoreDir(core.dir)
|
|
584
582
|
const sidecar = await readSidecar(sidecarPath(core))
|
|
585
583
|
let opened = false
|
|
586
|
-
let coldPending:
|
|
584
|
+
let coldPending: Pending | null = null
|
|
587
585
|
if (existing !== null) {
|
|
588
586
|
core.storeName = existing
|
|
589
587
|
const openedDb = await errors.try(SdkDb.open(storePath(core), options.theory))
|
|
@@ -592,7 +590,7 @@ async function openCore<Rels extends SchemaRelations>(options: OpenReplicaOption
|
|
|
592
590
|
core.chain = new Map(sidecar.chain)
|
|
593
591
|
for (const braid of descriptor.braidMembers.keys()) {
|
|
594
592
|
if (!core.chain.has(braid)) {
|
|
595
|
-
core.chain.set(braid, { g: 0n, prev: ZERO_HASH, ts: 0n })
|
|
593
|
+
core.chain.set(braid, { g: generation(0n), prev: ZERO_HASH, ts: 0n })
|
|
596
594
|
}
|
|
597
595
|
}
|
|
598
596
|
core.pending = sidecar.pending
|
|
@@ -645,7 +643,7 @@ async function repairDiscard<Rels extends SchemaRelations>(core: Core<Rels>): Pr
|
|
|
645
643
|
*/
|
|
646
644
|
async function readdressPending<Rels extends SchemaRelations>(
|
|
647
645
|
core: Core<Rels>,
|
|
648
|
-
ops: readonly
|
|
646
|
+
ops: readonly Op[],
|
|
649
647
|
writerId: bigint
|
|
650
648
|
): Promise<void> {
|
|
651
649
|
const first = ops[0]
|
|
@@ -666,14 +664,14 @@ async function readdressPending<Rels extends SchemaRelations>(
|
|
|
666
664
|
{
|
|
667
665
|
fingerprint: core.descriptor.fingerprint,
|
|
668
666
|
braid,
|
|
669
|
-
braidGen: entry.g + 1n,
|
|
667
|
+
braidGen: generation(entry.g + 1n),
|
|
670
668
|
prev: entry.prev,
|
|
671
669
|
writer: writerId,
|
|
672
670
|
timestamp
|
|
673
671
|
},
|
|
674
672
|
ops
|
|
675
673
|
)
|
|
676
|
-
core.pending = { braid, gen: entry.g + 1n, bytes }
|
|
674
|
+
core.pending = { braid, gen: generation(entry.g + 1n), bytes }
|
|
677
675
|
core.pendingOps = ops
|
|
678
676
|
core.pendingApplied = true
|
|
679
677
|
await persistSidecar(core)
|
|
@@ -683,15 +681,15 @@ function maxBigint(a: bigint, b: bigint): bigint {
|
|
|
683
681
|
return a > b ? a : b
|
|
684
682
|
}
|
|
685
683
|
|
|
686
|
-
function vectorOf<Rels extends SchemaRelations>(core: Core<Rels>): ReadonlyMap<
|
|
687
|
-
const vector = new Map<
|
|
688
|
-
for (const [
|
|
689
|
-
vector.set(
|
|
684
|
+
function vectorOf<Rels extends SchemaRelations>(core: Core<Rels>): ReadonlyMap<Braid, Generation> {
|
|
685
|
+
const vector = new Map<Braid, Generation>()
|
|
686
|
+
for (const [id, entry] of core.chain) {
|
|
687
|
+
vector.set(id, entry.g)
|
|
690
688
|
}
|
|
691
689
|
return vector
|
|
692
690
|
}
|
|
693
691
|
|
|
694
|
-
function dominates(have: ReadonlyMap<
|
|
692
|
+
function dominates(have: ReadonlyMap<Braid, Generation>, want: ReadonlyMap<Braid, Generation>): boolean {
|
|
695
693
|
for (const [braid, generation] of want) {
|
|
696
694
|
if ((have.get(braid) ?? -1n) < generation) {
|
|
697
695
|
return false
|
|
@@ -700,7 +698,7 @@ function dominates(have: ReadonlyMap<string, bigint>, want: ReadonlyMap<string,
|
|
|
700
698
|
return true
|
|
701
699
|
}
|
|
702
700
|
|
|
703
|
-
async function refreshPass<Rels extends SchemaRelations>(core: Core<Rels>, braid?:
|
|
701
|
+
async function refreshPass<Rels extends SchemaRelations>(core: Core<Rels>, braid?: Braid): Promise<void> {
|
|
704
702
|
core.passes += 1
|
|
705
703
|
if (core.passes % HEARTBEAT_PASSES === 0) {
|
|
706
704
|
await refreshManifest(core, false)
|
|
@@ -729,7 +727,7 @@ async function openReplica<Rels extends SchemaRelations>(options: OpenReplicaOpt
|
|
|
729
727
|
get vector() {
|
|
730
728
|
return vectorOf(core)
|
|
731
729
|
},
|
|
732
|
-
async refresh(braid?:
|
|
730
|
+
async refresh(braid?: Braid) {
|
|
733
731
|
return withGate(core, async function refreshBody() {
|
|
734
732
|
if (core.closed) {
|
|
735
733
|
throw errors.new("replica is disposed")
|