autobee 0.0.0 → 1.0.2

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/lib/topo.js ADDED
@@ -0,0 +1,271 @@
1
+ const b4a = require('b4a')
2
+ const encoding = require('./encoding.js')
3
+ const { createNode } = require('./writers.js')
4
+ const asserts = require('./asserts.js')
5
+
6
+ const EMPTY_HEAD = { length: 0, key: null }
7
+
8
+ exports.replay = replay
9
+ exports.sort = sort
10
+
11
+ exports.isLinking = isLinking
12
+ exports.isLinkingAll = isLinkingAll
13
+
14
+ class Clock {
15
+ constructor() {
16
+ this.map = new Map()
17
+ }
18
+
19
+ add(link) {
20
+ const hex = b4a.toString(link.key, 'hex')
21
+ const p = this.map.get(hex)
22
+ if (p < link.length) this.map.set(hex, link.length)
23
+ }
24
+
25
+ has(link) {
26
+ const hex = b4a.toString(link.key, 'hex')
27
+ const p = this.map.get(hex)
28
+ return p >= link.length
29
+ }
30
+ }
31
+
32
+ async function replay(auto) {
33
+ const ch = auto.system.bee.createChangesStream()
34
+ const replay = []
35
+
36
+ for await (const data of ch) {
37
+ const { ref, info } = getOplog(data)
38
+ replay.push(ref)
39
+ }
40
+
41
+ const inflated = await inflate(auto, replay.reverse())
42
+ return inflated.flatMap((b) => b.batch)
43
+ }
44
+
45
+ async function sort(auto, batch) {
46
+ const node = batch[0]
47
+ const ch = auto.system.bee.createChangesStream()
48
+ const reorder = []
49
+
50
+ for await (const data of ch) {
51
+ const { ref, info } = getOplog(data)
52
+ const link = {
53
+ key: ref.oplog.key,
54
+ length: ref.oplog.length,
55
+ weight: ref.oplog.weight,
56
+ timestamp: info.timestamp
57
+ }
58
+
59
+ if (!isOrderedBefore(node, link)) break
60
+ reorder.push(ref)
61
+ }
62
+
63
+ const inflated = await inflate(auto, reorder.reverse())
64
+
65
+ const { undo, shared } = addSorted(inflated, batch)
66
+
67
+ const tip = inflated.slice(shared).map((b) => b.batch)
68
+
69
+ return {
70
+ undo,
71
+ view: null,
72
+ tip
73
+ }
74
+ }
75
+
76
+ function isOrderedBefore(node, oplog) {
77
+ const { version, weight, timestamp } = oplog
78
+ if (version === 0) return false
79
+ if (node.weight < weight) return false
80
+ if (isLinking(node, oplog)) return false
81
+ return node.timestamp <= timestamp
82
+ }
83
+
84
+ function addSorted(list, batch) {
85
+ const node = batch[0]
86
+ const clock = new Clock()
87
+ for (const link of node.links) {
88
+ clock.add(link)
89
+ }
90
+
91
+ const tip = []
92
+ const linked = []
93
+
94
+ let undo = null
95
+ let shared = list.length
96
+
97
+ while (list.length) {
98
+ // reached a stable sorting point
99
+ if (cmp(node, list[list.length - 1].batch[0]) > 0) {
100
+ break
101
+ }
102
+
103
+ const b = list.pop()
104
+ const target = b.batch[0]
105
+
106
+ if (list.length < shared) {
107
+ undo = b.undo
108
+ shared = list.length
109
+ }
110
+
111
+ // move past this node
112
+ if (!clock.has(target)) {
113
+ tip.push(b)
114
+ continue
115
+ }
116
+
117
+ // remove node
118
+ linked.push(b)
119
+
120
+ // update the clock to catch linked nodes
121
+ for (const link of target.links) {
122
+ clock.add(link)
123
+ }
124
+ }
125
+
126
+ while (linked.length) addSorted(list, linked.pop())
127
+ list.push({ undo: null, batch })
128
+ while (tip.length) list.push(tip.pop())
129
+
130
+ return { undo, shared }
131
+ }
132
+
133
+ function cmp(a, b) {
134
+ const w = a.weight - b.weight
135
+ if (w) return w > 0 ? -1 : 1
136
+
137
+ const t = a.timestamp - b.timestamp
138
+ if (t) return t < 0 ? -1 : 1
139
+
140
+ const c = b4a.compare(a.key, b.key)
141
+ if (c) return c
142
+
143
+ return a.length < b.length ? -1 : 1
144
+ }
145
+
146
+ async function inflate(auto, reorder) {
147
+ const promises = []
148
+ const sessions = new Map()
149
+
150
+ for (const ref of reorder) {
151
+ const id = b4a.toString(ref.oplog.key, 'hex')
152
+
153
+ const local = auto.writers.localWriter
154
+ if (local && local.id === id) {
155
+ promises.push(getLocalBatch(ref.undo, local, ref.oplog.length, ref.oplog.weight))
156
+ continue
157
+ }
158
+
159
+ let core = sessions.get(id)
160
+ if (!core) {
161
+ core = auto.openCore(ref.oplog.key)
162
+ sessions.set(id, core)
163
+ }
164
+
165
+ promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, ref.oplog.weight))
166
+ }
167
+
168
+ try {
169
+ return await Promise.all(promises)
170
+ } finally {
171
+ for (const core of sessions.values()) await core.close()
172
+ }
173
+ }
174
+
175
+ async function getOplogBatch(undo, core, length, weight) {
176
+ const seq = length - 1
177
+ const block = await core.get(seq)
178
+ const oplog = encoding.decodeOplog(block)
179
+ const head = createNode(core, length, weight, oplog)
180
+
181
+ const batch = []
182
+ const result = { undo, batch }
183
+
184
+ const remaining = []
185
+ const start = seq - head.batch.start
186
+ const end = seq + head.batch.end // skip head, we have it
187
+
188
+ for (let i = start; i < end; i++) {
189
+ remaining.push(core.get(i))
190
+ }
191
+
192
+ if (remaining.length) {
193
+ const blocks = await Promise.all(remaining)
194
+ for (let i = 0; i < blocks.length; i++) {
195
+ const oplog = encoding.decodeOplog(blocks[i])
196
+ batch.push(createNode(core, i + start + 1, weight, oplog))
197
+ }
198
+ }
199
+
200
+ batch.push(head)
201
+ return result
202
+ }
203
+
204
+ function getLocalBatch(undo, writer, length, weight) {
205
+ if (length > writer.core.length) {
206
+ if (!writer.pending) throw new Error('No local nodes')
207
+
208
+ let i = 0
209
+ while (i < writer.pending.length) {
210
+ if (writer.pending[i].length === length) break
211
+ i++
212
+ }
213
+
214
+ const head = writer.pending[i]
215
+
216
+ const start = i - head.batch.start
217
+ const end = i + head.batch.end + 1 // include head
218
+
219
+ const batch = writer.pending.slice(start, end)
220
+ return { undo, batch }
221
+ }
222
+
223
+ return getOplogBatch(undo, writer.core, length, weight)
224
+ }
225
+
226
+ function getOplog(data) {
227
+ const result = { ref: null, info: null }
228
+ for (const { keys } of data.batch) {
229
+ for (const k of keys) {
230
+ const prefix = k.key[0]
231
+
232
+ if (prefix === 0) {
233
+ result.info = encoding.decodeSystemInfo(k.value)
234
+ if (result.ref) return result
235
+ }
236
+
237
+ if (prefix === 1) {
238
+ // expect inlined for now
239
+ const value = encoding.decodeSystemWriter(k.key, k.value)
240
+ if (!value.isOplog) continue
241
+
242
+ result.ref = { undo: data.tail || EMPTY_HEAD, oplog: value }
243
+ if (result.info) return result
244
+ }
245
+ }
246
+ }
247
+
248
+ asserts.bail('Bad system node')
249
+ }
250
+
251
+ function isLinking(node, link) {
252
+ if (node.length >= link.length && b4a.equals(node.key, link.key)) {
253
+ return true
254
+ }
255
+
256
+ for (const l of node.links) {
257
+ if (l.length >= link.length && b4a.equals(l.key, link.key)) {
258
+ return true
259
+ }
260
+ }
261
+
262
+ return false
263
+ }
264
+
265
+ function isLinkingAll(node, heads) {
266
+ for (const h of heads) {
267
+ if (!isLinking(node, h)) return false
268
+ }
269
+
270
+ return true
271
+ }
@@ -0,0 +1,69 @@
1
+ module.exports = class Triggers {
2
+ constructor() {
3
+ this.waiting = new Map()
4
+ }
5
+
6
+ add(id, length, writer) {
7
+ let w = this.waiting.get(id)
8
+
9
+ if (!w) {
10
+ w = []
11
+ this.waiting.set(id, w)
12
+ }
13
+
14
+ for (let i = 0; i < w.length; i++) {
15
+ const entry = w[i]
16
+ if (entry.writer !== writer) continue
17
+ entry.length = length
18
+ return entry
19
+ }
20
+
21
+ const entry = { index: w.length, id, writer, length }
22
+ w.push(entry)
23
+ return entry
24
+ }
25
+
26
+ remove(entry) {
27
+ if (!entry.writer) return
28
+
29
+ const w = this.waiting.get(entry.id)
30
+ if (w) this._remove(w, entry)
31
+ }
32
+
33
+ has(id) {
34
+ return this.waiting.get(id) !== undefined
35
+ }
36
+
37
+ get(id) {
38
+ return this.waiting.get(id) || null
39
+ }
40
+
41
+ trigger(id, length) {
42
+ const w = this.waiting.get(id)
43
+ if (!w) return
44
+
45
+ for (let i = w.length - 1; i >= 0; i--) {
46
+ const entry = w[i]
47
+ if (entry.length > length) continue
48
+
49
+ const writer = entry.writer
50
+ this._remove(w, entry)
51
+ writer.bump()
52
+ }
53
+ }
54
+
55
+ _remove(w, entry) {
56
+ const head = w.pop()
57
+
58
+ if (head !== entry) {
59
+ w[entry.index] = head
60
+ head.index = entry.index
61
+ }
62
+
63
+ entry.writer = null
64
+
65
+ if (w.length === 0) {
66
+ this.waiting.delete(entry.id)
67
+ }
68
+ }
69
+ }
package/lib/updates.js ADDED
@@ -0,0 +1,31 @@
1
+ module.exports = class UpdateChanges {
2
+ constructor(auto) {
3
+ this.auto = auto
4
+ this.byName = new Map()
5
+ }
6
+
7
+ get(key) {
8
+ return this.byName.get(key)
9
+ }
10
+
11
+ track() {
12
+ this.tracking = {
13
+ view: this.auto._workingBee,
14
+ system: this.auto.system.bee
15
+ }
16
+
17
+ for (const k in this.tracking) {
18
+ this.byName.set(k, { from: this.tracking[k].head(), to: null })
19
+ }
20
+ }
21
+
22
+ finalise() {
23
+ for (const k in this.tracking) {
24
+ const from = this.byName.get(k).from
25
+ const to = this.tracking[k].head()
26
+ this.byName.set(k, { to, from })
27
+ }
28
+
29
+ return this.byName
30
+ }
31
+ }