autobee 1.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/README.md +242 -2
- package/index.js +73 -15
- package/lib/apply-calls.js +7 -4
- package/lib/encoding.js +1 -1
- package/lib/system.js +23 -10
- package/lib/topo.js +114 -39
- package/lib/writers.js +56 -10
- package/package.json +6 -2
- package/spec/hyperschema/index.js +0 -644
- package/spec/hyperschema/schema.json +0 -456
package/README.md
CHANGED
|
@@ -2,13 +2,253 @@
|
|
|
2
2
|
|
|
3
3
|
Unstoppable, scalable multiwriter Hyperbee.
|
|
4
4
|
|
|
5
|
+
> **Still experimental and under heavy development. Expect breaking changes.**
|
|
6
|
+
|
|
5
7
|
```sh
|
|
6
|
-
npm
|
|
8
|
+
npm install autobee
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Multiple peers each write to their own local Hypercore. An `apply` function you provide merges those writes into a shared Hyperbee view deterministically. The view is consistent across all peers once they replicate.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const Autobee = require('autobee')
|
|
17
|
+
const Corestore = require('corestore')
|
|
18
|
+
|
|
19
|
+
const store = new Corestore('./my-db')
|
|
20
|
+
|
|
21
|
+
const db = new Autobee(store, null, { apply })
|
|
22
|
+
await db.ready()
|
|
23
|
+
|
|
24
|
+
// append some data
|
|
25
|
+
await db.append(Buffer.from(JSON.stringify({ hello: 'world' })))
|
|
26
|
+
|
|
27
|
+
// read it back from the view
|
|
28
|
+
const node = await db.view.get(Buffer.from('latest'))
|
|
29
|
+
console.log(JSON.parse(node.value))
|
|
30
|
+
|
|
31
|
+
async function apply(nodes, view, host) {
|
|
32
|
+
for (const node of nodes) {
|
|
33
|
+
const op = JSON.parse(node.value)
|
|
34
|
+
|
|
35
|
+
if (op.addWriter) host.addWriter(op.addWriter)
|
|
36
|
+
if (op.removeWriter) host.removeWriter(op.removeWriter)
|
|
37
|
+
|
|
38
|
+
const w = view.write()
|
|
39
|
+
w.tryPut(Buffer.from('latest'), node.value)
|
|
40
|
+
await w.flush()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To add a second writer and replicate:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const db1 = new Autobee(store1, null, { apply })
|
|
49
|
+
await db1.ready()
|
|
50
|
+
|
|
51
|
+
// share db1.key with others so they can join
|
|
52
|
+
const db2 = new Autobee(store2, db1.key, { apply })
|
|
53
|
+
await db2.ready()
|
|
54
|
+
|
|
55
|
+
// db1 adds db2 as a writer
|
|
56
|
+
await db1.append(Buffer.from(JSON.stringify({ addWriter: db2.local.id })))
|
|
57
|
+
|
|
58
|
+
// replicate using any stream
|
|
59
|
+
const s1 = db1.replicate(true)
|
|
60
|
+
const s2 = db2.replicate(false)
|
|
61
|
+
s1.pipe(s2).pipe(s1)
|
|
7
62
|
```
|
|
8
63
|
|
|
9
64
|
## API
|
|
10
65
|
|
|
11
|
-
#### `const
|
|
66
|
+
#### `const db = new Autobee(store, [key], [options])`
|
|
67
|
+
|
|
68
|
+
Create a new Autobee. `store` is a Corestore. `key` is the public key of an existing Autobee to join — omit or pass `null` to create a new one.
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
{
|
|
74
|
+
apply (nodes, view, host) {}, // called with batches of new nodes to apply to the view
|
|
75
|
+
open (bee, db) {}, // called to create a custom view, return it
|
|
76
|
+
close (view) {}, // called when the db closes
|
|
77
|
+
update (view, changes) {}, // called after apply when the view has been updated
|
|
78
|
+
encryptionKey: Buffer, // 32-byte key to encrypt all data at rest
|
|
79
|
+
encrypted: false, // set true if using encryptionKey
|
|
80
|
+
keyPair: { publicKey, secretKey }, // custom signing key pair for the local writer
|
|
81
|
+
optimistic: true // allow optimistic writes from unknown writers
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### `db.key`
|
|
86
|
+
|
|
87
|
+
The public key of this Autobee. Share this with peers so they can join.
|
|
88
|
+
|
|
89
|
+
#### `db.discoveryKey`
|
|
90
|
+
|
|
91
|
+
The discovery key. Use this to find peers on the network.
|
|
92
|
+
|
|
93
|
+
#### `db.id`
|
|
94
|
+
|
|
95
|
+
The public key encoded as a hex string.
|
|
96
|
+
|
|
97
|
+
#### `db.local`
|
|
98
|
+
|
|
99
|
+
The local writer Hypercore. Use `db.local.key` or `db.local.id` to identify this writer to others.
|
|
100
|
+
|
|
101
|
+
#### `db.view`
|
|
102
|
+
|
|
103
|
+
A read-only snapshot of the Hyperbee view. Updated after each apply cycle. Use the standard [Hyperbee](https://github.com/holepunks/hyperbee) API to read from it.
|
|
104
|
+
|
|
105
|
+
#### `db.bee`
|
|
106
|
+
|
|
107
|
+
Alias for `db.view`.
|
|
108
|
+
|
|
109
|
+
#### `db.writable`
|
|
110
|
+
|
|
111
|
+
`true` if this instance has been added as a writer.
|
|
112
|
+
|
|
113
|
+
#### `db.isIndexer`
|
|
114
|
+
|
|
115
|
+
`true` if this writer is an indexer.
|
|
116
|
+
|
|
117
|
+
#### `await db.append(value | values)`
|
|
118
|
+
|
|
119
|
+
Append one or more values to the local writer. Triggers an apply cycle.
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
await db.append(Buffer.from('hello'))
|
|
123
|
+
await db.append([buf1, buf2, buf3])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Optionally pass `{ optimistic: true }` to write without waiting to be a confirmed writer.
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
await db.append(buf, { optimistic: true })
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### `await db.update()`
|
|
133
|
+
|
|
134
|
+
Trigger a new apply cycle. Useful after replication to process new data.
|
|
135
|
+
|
|
136
|
+
#### `await db.updated()`
|
|
137
|
+
|
|
138
|
+
Wait until the current apply cycle has finished.
|
|
139
|
+
|
|
140
|
+
#### `await db.flush()`
|
|
141
|
+
|
|
142
|
+
Wait until all known writers have been fully indexed.
|
|
143
|
+
|
|
144
|
+
#### `stream = db.replicate(isInitiator)`
|
|
145
|
+
|
|
146
|
+
Create a replication stream. Pass `true` for the initiating side, `false` for the other.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const s1 = db1.replicate(true)
|
|
150
|
+
const s2 = db2.replicate(false)
|
|
151
|
+
s1.pipe(s2).pipe(s1)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `db.wakeup({ key, length })`
|
|
155
|
+
|
|
156
|
+
Hint that a new writer core is available at `key` with at least `length` entries. Used to wake up replication when you learn about a peer out of band.
|
|
157
|
+
|
|
158
|
+
#### `await db.setLocal(key, [options])`
|
|
159
|
+
|
|
160
|
+
Rotate the local writer to a different key. The new writer takes over as the active oplog.
|
|
161
|
+
|
|
162
|
+
#### `views = db.views()`
|
|
163
|
+
|
|
164
|
+
Returns the current system and view core positions. Used for replication coordination.
|
|
165
|
+
|
|
166
|
+
#### `Autobee.isAutobee(val)`
|
|
167
|
+
|
|
168
|
+
Returns `true` if `val` is an Autobee instance.
|
|
169
|
+
|
|
170
|
+
### Apply
|
|
171
|
+
|
|
172
|
+
The `apply` function is called with a batch of nodes from writers, a writable `view` (Hyperbee batch), and a `host` object.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
async function apply(nodes, view, host) {
|
|
176
|
+
for (const node of nodes) {
|
|
177
|
+
// node.key — writer public key (Buffer)
|
|
178
|
+
// node.value — the value appended (Buffer)
|
|
179
|
+
// node.length — position in the writer's core
|
|
180
|
+
|
|
181
|
+
const op = JSON.parse(node.value)
|
|
182
|
+
|
|
183
|
+
// manage writers
|
|
184
|
+
if (op.addWriter) host.addWriter(op.addWriter)
|
|
185
|
+
if (op.removeWriter) host.removeWriter(op.removeWriter)
|
|
186
|
+
|
|
187
|
+
// write to the view
|
|
188
|
+
const w = view.write()
|
|
189
|
+
w.tryPut(Buffer.from('key'), node.value)
|
|
190
|
+
await w.flush()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `host.addWriter(key, [options])`
|
|
196
|
+
|
|
197
|
+
Add a writer by public key (Buffer or hex string). Options:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
{
|
|
201
|
+
isIndexer: true // default
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### `host.removeWriter(key)`
|
|
206
|
+
|
|
207
|
+
Remove a writer by public key (Buffer or hex string).
|
|
208
|
+
|
|
209
|
+
#### `host.ackWriter(key)`
|
|
210
|
+
|
|
211
|
+
Acknowledge a writer without changing their permissions.
|
|
212
|
+
|
|
213
|
+
#### `host.interrupt(reason)`
|
|
214
|
+
|
|
215
|
+
Interrupt the current apply cycle. The db emits `'interrupt'` with the reason. Useful for pausing apply while waiting on external data.
|
|
216
|
+
|
|
217
|
+
#### `anchor = await host.createAnchor()`
|
|
218
|
+
|
|
219
|
+
Create an anchor node. Returns `{ key, length }`. Anchors are used to create a verifiable checkpoint in the log that can be used by future writers to prove causal ordering.
|
|
220
|
+
|
|
221
|
+
#### `host.genesis`
|
|
222
|
+
|
|
223
|
+
`true` if the system has not yet processed any nodes. Use this to bootstrap the first writer.
|
|
224
|
+
|
|
225
|
+
### Encryption
|
|
226
|
+
|
|
227
|
+
Pass an `encryptionKey` to encrypt all writer cores and the view at rest.
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
const db = new Autobee(store, null, {
|
|
231
|
+
apply,
|
|
232
|
+
encrypted: true,
|
|
233
|
+
encryptionKey: crypto.randomBytes(32)
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
All peers must use the same encryption key.
|
|
238
|
+
|
|
239
|
+
### Static methods
|
|
240
|
+
|
|
241
|
+
#### `buf = Autobee.encodeValue(value, [opts])`
|
|
242
|
+
|
|
243
|
+
Encode a value into an Autobee block with optional metadata.
|
|
244
|
+
|
|
245
|
+
#### `value = Autobee.decodeValue(buf, [opts])`
|
|
246
|
+
|
|
247
|
+
Decode an Autobee block back to its value.
|
|
248
|
+
|
|
249
|
+
#### `Autobee.GENESIS`
|
|
250
|
+
|
|
251
|
+
`{ length: 0, key: null }`. The empty head used to represent the genesis state.
|
|
12
252
|
|
|
13
253
|
## License
|
|
14
254
|
|
package/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const { ActiveWriters } = require('./lib/writers.js')
|
|
|
18
18
|
const UpdateChanges = require('./lib/updates.js')
|
|
19
19
|
|
|
20
20
|
const EMPTY_HEAD = { length: 0, key: null }
|
|
21
|
+
const INTERRUPT = new Error('Apply interrupted')
|
|
21
22
|
|
|
22
23
|
module.exports = class Autobee extends ReadyResource {
|
|
23
24
|
constructor(store, key = null, handlers = {}) {
|
|
@@ -81,6 +82,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
81
82
|
this._updateLocalCore = null
|
|
82
83
|
this._host = new ApplyCalls(this)
|
|
83
84
|
|
|
85
|
+
this.interrupted = null
|
|
86
|
+
this._interrupting = false
|
|
87
|
+
this._onErrorBound = this._onError.bind(this)
|
|
88
|
+
|
|
84
89
|
this._wakeup = new AutobeeWakeup(this, handlers)
|
|
85
90
|
this.wakeupCapability = null
|
|
86
91
|
|
|
@@ -126,6 +131,22 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
126
131
|
this._wakeup.setCapability(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
getExternalWriters() {
|
|
135
|
+
const keys = []
|
|
136
|
+
for (const w of this.writers.active.values()) {
|
|
137
|
+
if (w === this.writers.localWriter) continue
|
|
138
|
+
keys.push(w.core.key)
|
|
139
|
+
}
|
|
140
|
+
return keys
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async getWriterViews(key) {
|
|
144
|
+
const id = b4a.toString(key, 'hex')
|
|
145
|
+
const w = this.writers.active.get(id)
|
|
146
|
+
if (!w) return []
|
|
147
|
+
return w.views()
|
|
148
|
+
}
|
|
149
|
+
|
|
129
150
|
views() {
|
|
130
151
|
const sys = this.system.bee.context.local
|
|
131
152
|
const view = this._workingBee.context.local
|
|
@@ -138,7 +159,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
138
159
|
}
|
|
139
160
|
|
|
140
161
|
async _close() {
|
|
141
|
-
|
|
162
|
+
this._interrupting = true
|
|
163
|
+
if (this._draining) await this._draining
|
|
142
164
|
|
|
143
165
|
if (this._handlers.close) await this._handlers.close(this.view)
|
|
144
166
|
|
|
@@ -254,8 +276,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
254
276
|
this.bumping++
|
|
255
277
|
|
|
256
278
|
if (!this._draining) {
|
|
257
|
-
this._draining = this._drain()
|
|
258
|
-
this._draining.catch(safetyCatch)
|
|
279
|
+
this._draining = this._drain().catch(this._onErrorBound)
|
|
259
280
|
}
|
|
260
281
|
|
|
261
282
|
return this._draining
|
|
@@ -265,11 +286,46 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
265
286
|
return this._bump()
|
|
266
287
|
}
|
|
267
288
|
|
|
268
|
-
updated() {
|
|
289
|
+
async updated() {
|
|
290
|
+
if (this.opened === false) await this.ready()
|
|
269
291
|
if (this._draining) return this._draining
|
|
270
292
|
return Promise.resolve()
|
|
271
293
|
}
|
|
272
294
|
|
|
295
|
+
interrupt(reason) {
|
|
296
|
+
asserts.assert(!!this._host.applying, 'Interrupt is only allowed in apply')
|
|
297
|
+
this._interrupting = true
|
|
298
|
+
if (reason) this.interrupted = reason
|
|
299
|
+
throw INTERRUPT
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getLastError() {
|
|
303
|
+
return this._lastError
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_onError(err) {
|
|
307
|
+
if (this.closing) return
|
|
308
|
+
|
|
309
|
+
this._lastError = err
|
|
310
|
+
|
|
311
|
+
if (err === INTERRUPT) {
|
|
312
|
+
this.emit('interrupt', this.interrupted)
|
|
313
|
+
this.emit('update')
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.close().catch(safetyCatch)
|
|
318
|
+
|
|
319
|
+
// if no one is listening we should crash! we cannot rely on the EE here
|
|
320
|
+
// as this is wrapped in a promise so instead of nextTick throw it
|
|
321
|
+
if (ReadyResource.listenerCount(this, 'error') === 0) {
|
|
322
|
+
crashSoon(err)
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.emit('error', err)
|
|
327
|
+
}
|
|
328
|
+
|
|
273
329
|
async _drain() {
|
|
274
330
|
if (this._updateLocalCore !== null) {
|
|
275
331
|
await this._rotateLocalWriter(this._updateLocalCore)
|
|
@@ -376,11 +432,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
376
432
|
this.emit('rotate-local-writer')
|
|
377
433
|
}
|
|
378
434
|
|
|
379
|
-
interrupt() {
|
|
380
|
-
this._interrupting = true
|
|
381
|
-
if (this._draining) return this._draining
|
|
382
|
-
}
|
|
383
|
-
|
|
384
435
|
async createAnchor() {
|
|
385
436
|
const node = this._host.applying[this._host.applying.length - 1]
|
|
386
437
|
|
|
@@ -424,7 +475,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
424
475
|
await core.append(block, { writable: true, maxLength: 1 })
|
|
425
476
|
}
|
|
426
477
|
|
|
427
|
-
await this.system.addWriter(core.key)
|
|
478
|
+
await this.system.addWriter(core.key, { weight: 1 })
|
|
428
479
|
|
|
429
480
|
const anchor = { key: core.key, length: core.length }
|
|
430
481
|
|
|
@@ -475,8 +526,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
475
526
|
this._workingBee.move(t.view)
|
|
476
527
|
}
|
|
477
528
|
|
|
478
|
-
asserts.assert(batch === t.tip[0], 'Batch must be first part of tip')
|
|
479
|
-
|
|
480
529
|
let failed = true
|
|
481
530
|
|
|
482
531
|
try {
|
|
@@ -594,9 +643,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
594
643
|
const value = values[i]
|
|
595
644
|
const buffer = typeof value === 'string' ? b4a.from(value) : value
|
|
596
645
|
const lnk = i === 0 ? links : []
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
const node = this.writers.appendLocal(buffer, t, b, lnk, optimistic)
|
|
646
|
+
const node = this.writers.appendLocal(buffer, t, null, lnk, optimistic)
|
|
600
647
|
batch.push(node)
|
|
601
648
|
}
|
|
602
649
|
|
|
@@ -607,6 +654,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
607
654
|
// analyze is worth the trade off adding the view here also (technically not needed)
|
|
608
655
|
await this.writers.flushLocal(this._workingBee.head())
|
|
609
656
|
}
|
|
657
|
+
|
|
658
|
+
async replay() {
|
|
659
|
+
return topo.replay(this)
|
|
660
|
+
}
|
|
610
661
|
}
|
|
611
662
|
|
|
612
663
|
function isObject(o) {
|
|
@@ -634,3 +685,10 @@ function createAnchorCore(store, prologue, manifestData) {
|
|
|
634
685
|
|
|
635
686
|
return core
|
|
636
687
|
}
|
|
688
|
+
|
|
689
|
+
function crashSoon(err) {
|
|
690
|
+
queueMicrotask(() => {
|
|
691
|
+
throw err
|
|
692
|
+
})
|
|
693
|
+
throw err
|
|
694
|
+
}
|
package/lib/apply-calls.js
CHANGED
|
@@ -22,13 +22,17 @@ class ApplyCalls {
|
|
|
22
22
|
return this.auto.name
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
get clock() {
|
|
26
|
+
return this.auto.system.flushes
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
get genesis() {
|
|
26
30
|
return this.auto.system.isGenesis()
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
addWriter(key, { isIndexer = true } = {}) {
|
|
33
|
+
addWriter(key, { isIndexer = true, weight = isIndexer ? 2 : 1 } = {}) {
|
|
30
34
|
if (typeof key === 'string') key = ID.decode(key)
|
|
31
|
-
return this.auto.system.addWriter(key, {
|
|
35
|
+
return this.auto.system.addWriter(key, { weight })
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
ackWriter(key) {
|
|
@@ -46,8 +50,7 @@ class ApplyCalls {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
interrupt(reason) {
|
|
49
|
-
|
|
50
|
-
// this.auto._interrupt(reason)
|
|
53
|
+
this.auto.interrupt(reason)
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
removeable(key) {
|
package/lib/encoding.js
CHANGED
|
@@ -3,7 +3,7 @@ const c = require('compact-encoding')
|
|
|
3
3
|
const crypto = require('hypercore-crypto')
|
|
4
4
|
const { AutobeeEncryption } = require('autobee-encryption')
|
|
5
5
|
|
|
6
|
-
const { getEncoding } = require('../spec/
|
|
6
|
+
const { getEncoding } = require('../encoding/spec/autobee')
|
|
7
7
|
const { LEGACY_OPLOG_VERSION, OPLOG_VERSION } = require('./constants')
|
|
8
8
|
|
|
9
9
|
const Oplog = getEncoding('@autobee/oplog')
|
package/lib/system.js
CHANGED
|
@@ -28,12 +28,12 @@ module.exports = class Systembee {
|
|
|
28
28
|
this.encrypted = opts.encrypted === true
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async addWriter(key, { length = 0,
|
|
31
|
+
async addWriter(key, { length = 0, weight = 1 } = {}) {
|
|
32
32
|
if (length === 0) {
|
|
33
33
|
const info = await this.get(key, { unflushed: false })
|
|
34
34
|
length = info ? info.length : 0
|
|
35
35
|
}
|
|
36
|
-
this.update(key, length,
|
|
36
|
+
this.update(key, length, weight, false, true, false)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async ackWriter(key, { length = 0 } = {}) {
|
|
@@ -41,7 +41,7 @@ module.exports = class Systembee {
|
|
|
41
41
|
const info = await this.get(key, { unflushed: false })
|
|
42
42
|
length = info ? info.length : 0
|
|
43
43
|
}
|
|
44
|
-
this.update(key, length,
|
|
44
|
+
this.update(key, length, 0, false, false, true)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async removeWriter(key, { length = 0 } = {}) {
|
|
@@ -49,7 +49,7 @@ module.exports = class Systembee {
|
|
|
49
49
|
const info = await this.get(key, { unflushed: true })
|
|
50
50
|
length = info ? info.length : 0
|
|
51
51
|
}
|
|
52
|
-
this.update(key, length,
|
|
52
|
+
this.update(key, length, 0, true, false, false)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
isGenesis() {
|
|
@@ -133,7 +133,7 @@ module.exports = class Systembee {
|
|
|
133
133
|
this.heads.push({ key: node.key, length: node.length })
|
|
134
134
|
if (node.timestamp > this.timestamp) this.timestamp = node.timestamp // TODO: support smoothing
|
|
135
135
|
|
|
136
|
-
this.update(node.key, node.length,
|
|
136
|
+
this.update(node.key, node.length, 0, false, false, false)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
async canApply(key, optimistic) {
|
|
@@ -151,6 +151,7 @@ module.exports = class Systembee {
|
|
|
151
151
|
|
|
152
152
|
const upd = this.updates.get(b4a.toString(key, 'hex'))
|
|
153
153
|
if (!upd) return info
|
|
154
|
+
if (!info) return upd
|
|
154
155
|
|
|
155
156
|
if (upd.isAdded) info.isAdded = true
|
|
156
157
|
if (upd.isRemoved) info.isRemoved = true
|
|
@@ -183,7 +184,7 @@ module.exports = class Systembee {
|
|
|
183
184
|
return true
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
update(key, length,
|
|
187
|
+
update(key, length, weight, isRemoved, isAdded, isAcked) {
|
|
187
188
|
const id = b4a.toString(key, 'hex')
|
|
188
189
|
|
|
189
190
|
let upd = this.updates.get(id)
|
|
@@ -195,17 +196,27 @@ module.exports = class Systembee {
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
if (!upd) {
|
|
198
|
-
upd = {
|
|
199
|
+
upd = {
|
|
200
|
+
version: 4,
|
|
201
|
+
key,
|
|
202
|
+
length,
|
|
203
|
+
weight,
|
|
204
|
+
clock: 0,
|
|
205
|
+
isRemoved,
|
|
206
|
+
isAdded,
|
|
207
|
+
isAcked,
|
|
208
|
+
isOplog: false
|
|
209
|
+
}
|
|
199
210
|
this.updates.set(id, upd)
|
|
200
211
|
return
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
if (isAdded) {
|
|
204
|
-
upd.
|
|
215
|
+
upd.weight = weight
|
|
205
216
|
upd.isRemoved = false
|
|
206
217
|
}
|
|
207
218
|
if (isRemoved) {
|
|
208
|
-
upd.
|
|
219
|
+
upd.weight = 0
|
|
209
220
|
upd.isRemoved = true
|
|
210
221
|
}
|
|
211
222
|
if (isAcked) {
|
|
@@ -226,7 +237,7 @@ module.exports = class Systembee {
|
|
|
226
237
|
|
|
227
238
|
if (v) {
|
|
228
239
|
if (!changed) {
|
|
229
|
-
upd.
|
|
240
|
+
upd.weight = v.weight
|
|
230
241
|
upd.isRemoved = v.isRemoved
|
|
231
242
|
}
|
|
232
243
|
|
|
@@ -234,6 +245,8 @@ module.exports = class Systembee {
|
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
247
|
|
|
248
|
+
upd.clock = this.flushes
|
|
249
|
+
|
|
237
250
|
// TODO: we can optimise this a bit, if the above node hasnt changed, do not set this
|
|
238
251
|
// less writes, but future thing
|
|
239
252
|
upd.isOplog = b4a.equals(upd.key, oplog)
|