@bjornpagen/bumbledb-log 0.18.0 → 0.19.1

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/src/vector.ts ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The per-braid generation map. Sum, domination, checkpoint order,
3
+ * apply's increment, and the coordinate's one binary encoding live
4
+ * here. Overflow is a refusal of `sum` and of nowhere else.
5
+ */
6
+
7
+ import { ByteReader, ByteWriter, U64_MAX } from "#bytes.ts"
8
+ import type { Braid } from "#descriptor.ts"
9
+ import { braidHex } from "#descriptor.ts"
10
+ import type { Generation } from "#keys.ts"
11
+ import { generation } from "#keys.ts"
12
+
13
+ /** One braid id and its applied count on the wire: u32le + u64le. */
14
+ const PAIR_BYTES = 12n
15
+
16
+ const Overflow = { tag: "overflow" } as const
17
+ type Overflow = typeof Overflow
18
+
19
+ type CheckpointOrder = "before" | "equal" | "after"
20
+
21
+ type VectorParse =
22
+ | { readonly tag: "ok"; readonly vector: Vector }
23
+ | { readonly tag: "truncated" }
24
+ | { readonly tag: "trailing"; readonly bytes: number }
25
+ | { readonly tag: "malformed" }
26
+ | { readonly tag: "overflow" }
27
+
28
+ function braidIdOf(id: Braid): number {
29
+ return Number.parseInt(id.slice(1), 16)
30
+ }
31
+
32
+ /** Applied counts keyed by braid. Any vector is a legal restore point. */
33
+ class Vector {
34
+ readonly #counts: Map<Braid, bigint>
35
+
36
+ constructor(entries: Iterable<readonly [Braid, bigint]> = []) {
37
+ const counts = new Map<Braid, bigint>()
38
+ for (const [braid, g] of [...entries].sort(function byBraid(left, right) {
39
+ if (left[0] < right[0]) {
40
+ return -1
41
+ }
42
+ if (left[0] > right[0]) {
43
+ return 1
44
+ }
45
+ return 0
46
+ })) {
47
+ counts.set(braid, g)
48
+ }
49
+ this.#counts = counts
50
+ }
51
+
52
+ static from(map: ReadonlyMap<Braid, bigint>): Vector {
53
+ return new Vector(map)
54
+ }
55
+
56
+ /** The wholeness arithmetic. The one overflow site. */
57
+ sum(): bigint | Overflow {
58
+ let acc = 0n
59
+ for (const g of this.#counts.values()) {
60
+ const next = acc + g
61
+ if (next > U64_MAX) {
62
+ return Overflow
63
+ }
64
+ acc = next
65
+ }
66
+ return acc
67
+ }
68
+
69
+ /** Pointwise `this[braid] >= other[braid]`; an absent braid is zero. */
70
+ dominates(other: Vector): boolean {
71
+ for (const [braid, g] of other.#counts) {
72
+ if (this.at(braid) < g) {
73
+ return false
74
+ }
75
+ }
76
+ return true
77
+ }
78
+
79
+ /** The total order the manifest CAS installs. */
80
+ order(other: Vector): CheckpointOrder {
81
+ const left = this.sum()
82
+ const right = other.sum()
83
+ const leftOverflow = typeof left !== "bigint"
84
+ const rightOverflow = typeof right !== "bigint"
85
+ if (leftOverflow && rightOverflow) {
86
+ return "equal"
87
+ }
88
+ if (leftOverflow) {
89
+ return "after"
90
+ }
91
+ if (rightOverflow) {
92
+ return "before"
93
+ }
94
+ if (left < right) {
95
+ return "before"
96
+ }
97
+ if (left > right) {
98
+ return "after"
99
+ }
100
+ return "equal"
101
+ }
102
+
103
+ /** The applied count for `braid`; absent is zero. */
104
+ at(braid: Braid): Generation {
105
+ return generation(this.#counts.get(braid) ?? 0n)
106
+ }
107
+
108
+ /** Apply's one mutation: this braid's count advances by one. */
109
+ advance(braid: Braid): Vector {
110
+ const next = new Map(this.#counts)
111
+ const current = next.get(braid) ?? 0n
112
+ next.set(braid, current === U64_MAX ? U64_MAX : current + 1n)
113
+ return new Vector(next)
114
+ }
115
+
116
+ /** `u32le` count, then `(u32le braid, u64le g)` pairs in braid order. */
117
+ encode(): Uint8Array {
118
+ const braids = [...this.#counts.keys()].sort()
119
+ const out = new ByteWriter(4 + braids.length * 12)
120
+ out.u32le(braids.length)
121
+ for (const id of braids) {
122
+ out.u32le(braidIdOf(id))
123
+ out.u64le(this.#counts.get(id) ?? 0n)
124
+ }
125
+ return out.finish()
126
+ }
127
+
128
+ /**
129
+ * Inverse of `encode`. A count the remaining bytes cannot open is
130
+ * truncated; entries must ascend; overflow is `sum`.
131
+ */
132
+ static parse(bytes: Uint8Array): VectorParse {
133
+ const short = { tag: "short" } as const
134
+ const reader = new ByteReader(bytes, {
135
+ fail(): never {
136
+ throw short
137
+ }
138
+ })
139
+ try {
140
+ const count = BigInt(reader.u32le("count"))
141
+ if (count > 0n && BigInt(reader.remaining()) / PAIR_BYTES < count) {
142
+ return { tag: "truncated" }
143
+ }
144
+ const entries: Array<readonly [Braid, bigint]> = []
145
+ let last: Braid | undefined
146
+ for (let i = 0n; i < count; i++) {
147
+ const name = braidHex(reader.u32le("braid"))
148
+ const g = reader.u64le("g")
149
+ if (last !== undefined && last >= name) {
150
+ return { tag: "malformed" }
151
+ }
152
+ entries.push([name, g])
153
+ last = name
154
+ }
155
+ if (reader.remaining() !== 0) {
156
+ return { tag: "trailing", bytes: reader.remaining() }
157
+ }
158
+ const vector = new Vector(entries)
159
+ if (typeof vector.sum() !== "bigint") {
160
+ return { tag: "overflow" }
161
+ }
162
+ return { tag: "ok", vector }
163
+ } catch (error) {
164
+ if (error === short) {
165
+ return { tag: "truncated" }
166
+ }
167
+ throw error
168
+ }
169
+ }
170
+ }
171
+
172
+ export type { CheckpointOrder, VectorParse }
173
+ export { Overflow, Vector }