autobee 0.0.0 → 1.0.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/lib/topo.js ADDED
@@ -0,0 +1,196 @@
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.sort = sort
9
+
10
+ exports.isLinking = isLinking
11
+ exports.isLinkingAll = isLinkingAll
12
+
13
+ async function sort(auto, batch) {
14
+ const node = batch[0]
15
+ const ch = auto.system.bee.createChangesStream()
16
+ const reorder = []
17
+
18
+ for await (const data of ch) {
19
+ const { ref, info } = getOplog(data)
20
+ const link = { key: ref.oplog.key, length: ref.oplog.length }
21
+
22
+ if (isLinking(node, link) || node.timestamp > info.timestamp) break
23
+ reorder.push(ref)
24
+ }
25
+
26
+ const inflated = await inflate(auto, reorder.reverse())
27
+
28
+ for (let i = 0; i < inflated.length; i++) {
29
+ const b = inflated[i]
30
+ const op = b.batch[0] // just use first op, they are all the same writer
31
+ const c = cmp(op, node)
32
+
33
+ if (c === -1) continue
34
+
35
+ const tip = new Array(inflated.length - i + 1)
36
+ tip[0] = batch
37
+ for (let j = i; j < inflated.length; j++) {
38
+ tip[j - i + 1] = inflated[j].batch
39
+ }
40
+
41
+ return {
42
+ undo: b.undo,
43
+ view: null,
44
+ tip
45
+ }
46
+ }
47
+
48
+ return {
49
+ undo: null,
50
+ view: null,
51
+ tip: [batch]
52
+ }
53
+ }
54
+
55
+ function cmp(a, b) {
56
+ const c = b4a.compare(a.key, b.key)
57
+
58
+ if (c === 0) {
59
+ return a.length - b.length
60
+ }
61
+
62
+ const t = a.timestamp - b.timestamp
63
+
64
+ if (t === 0) return c
65
+ return t < 0 ? -1 : 1
66
+ }
67
+
68
+ async function inflate(auto, reorder) {
69
+ const promises = []
70
+ const sessions = new Map()
71
+
72
+ for (const ref of reorder) {
73
+ const id = b4a.toString(ref.oplog.key, 'hex')
74
+
75
+ const local = auto.writers.localWriter
76
+ if (local && local.id === id) {
77
+ promises.push(getLocalBatch(ref.undo, local, ref.oplog.length))
78
+ continue
79
+ }
80
+
81
+ let core = sessions.get(id)
82
+ if (!core) {
83
+ core = auto.openCore(ref.oplog.key)
84
+ sessions.set(id, core)
85
+ }
86
+
87
+ promises.push(getOplogBatch(ref.undo, core, ref.oplog.length))
88
+ }
89
+
90
+ try {
91
+ return await Promise.all(promises)
92
+ } finally {
93
+ for (const core of sessions.values()) await core.close()
94
+ }
95
+ }
96
+
97
+ async function getOplogBatch(undo, core, length) {
98
+ const seq = length - 1
99
+ const block = await core.get(seq)
100
+ const oplog = encoding.decodeOplog(block)
101
+ const head = createNode(core, length, oplog)
102
+
103
+ const batch = []
104
+ const result = { undo, batch }
105
+
106
+ const remaining = []
107
+ const start = seq - head.batch.start
108
+ const end = seq + head.batch.end // skip head, we have it
109
+
110
+ for (let i = start; i < end; i++) {
111
+ remaining.push(core.get(i))
112
+ }
113
+
114
+ if (remaining.length) {
115
+ const blocks = await Promise.all(remaining)
116
+ for (let i = 0; i < blocks.length; i++) {
117
+ const oplog = encoding.decodeOplog(blocks[i])
118
+ batch.push(createNode(core, i + start + 1, oplog))
119
+ }
120
+ }
121
+
122
+ batch.push(head)
123
+ return result
124
+ }
125
+
126
+ async function getLocalBatch(undo, writer, length) {
127
+ if (length > writer.core.length) {
128
+ if (!writer.pending) throw new Error('No local nodes')
129
+
130
+ let i = 0
131
+ while (i < writer.pending.length) {
132
+ if (writer.pending[i].length === length) break
133
+ i++
134
+ }
135
+
136
+ const head = writer.pending[i]
137
+
138
+ const start = i - head.batch.start
139
+ const end = i + head.batch.end + 1 // include head
140
+
141
+ const batch = writer.pending.slice(start, end)
142
+ return { undo, batch }
143
+ }
144
+
145
+ return getOplogBatch(undo, writer.core, length)
146
+ }
147
+
148
+ function getOplog(data) {
149
+ const result = { ref: null, info: null }
150
+ for (let i = 0; i < data.batch.length; i++) {
151
+ const keys = data.batch[i].keys
152
+
153
+ for (let j = 0; j < keys.length; j++) {
154
+ const k = keys[j]
155
+ const prefix = k.key[0]
156
+
157
+ if (prefix === 0) {
158
+ result.info = encoding.decodeSystemInfo(k.value)
159
+ if (result.ref) return result
160
+ }
161
+
162
+ if (prefix === 1) {
163
+ // expect inlined for now
164
+ const value = encoding.decodeSystemWriter(k.key, k.value)
165
+ if (!value.isOplog) continue
166
+
167
+ result.ref = { undo: data.tail || EMPTY_HEAD, oplog: value }
168
+ if (result.info) return result
169
+ }
170
+ }
171
+ }
172
+
173
+ asserts.bail('Bad system node')
174
+ }
175
+
176
+ function isLinking(node, link) {
177
+ if (node.length >= link.length && b4a.equals(node.key, link.key)) {
178
+ return true
179
+ }
180
+
181
+ for (const l of node.links) {
182
+ if (l.length >= link.length && b4a.equals(l.key, link.key)) {
183
+ return true
184
+ }
185
+ }
186
+
187
+ return false
188
+ }
189
+
190
+ function isLinkingAll(node, heads) {
191
+ for (const h of heads) {
192
+ if (!isLinking(node, h)) return false
193
+ }
194
+
195
+ return true
196
+ }
@@ -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
+ }