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/README.md +15 -0
- package/index.js +636 -0
- package/lib/apply-calls.js +62 -0
- package/lib/asserts.js +25 -0
- package/lib/boot.js +117 -0
- package/lib/constants.js +9 -0
- package/lib/encoding.js +150 -0
- package/lib/system.js +314 -0
- package/lib/topo.js +196 -0
- package/lib/triggers.js +69 -0
- package/lib/updates.js +31 -0
- package/lib/writers.js +455 -0
- package/package.json +35 -6
- package/spec/hyperschema/index.js +644 -0
- package/spec/hyperschema/schema.json +456 -0
package/README.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
const ReadyResource = require('ready-resource')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
const safetyCatch = require('safety-catch')
|
|
4
|
+
const Hyperbee = require('hyperbee2')
|
|
5
|
+
const ID = require('hypercore-id-encoding')
|
|
6
|
+
const { AutobeeEncryption, WriterEncryption } = require('autobee-encryption')
|
|
7
|
+
const AutobeeWakeup = require('autobee-wakeup')
|
|
8
|
+
const Hypercore = require('hypercore')
|
|
9
|
+
const crypto = require('hypercore-crypto')
|
|
10
|
+
const c = require('compact-encoding')
|
|
11
|
+
const asserts = require('./lib/asserts.js')
|
|
12
|
+
const boot = require('./lib/boot.js')
|
|
13
|
+
const encoding = require('./lib/encoding.js')
|
|
14
|
+
const System = require('./lib/system.js')
|
|
15
|
+
const ApplyCalls = require('./lib/apply-calls.js')
|
|
16
|
+
const topo = require('./lib/topo.js')
|
|
17
|
+
const { ActiveWriters } = require('./lib/writers.js')
|
|
18
|
+
const UpdateChanges = require('./lib/updates.js')
|
|
19
|
+
|
|
20
|
+
const EMPTY_HEAD = { length: 0, key: null }
|
|
21
|
+
|
|
22
|
+
module.exports = class Autobee extends ReadyResource {
|
|
23
|
+
constructor(store, key = null, handlers = {}) {
|
|
24
|
+
super()
|
|
25
|
+
|
|
26
|
+
if (isObject(key)) {
|
|
27
|
+
handlers = key
|
|
28
|
+
key = null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { name = null, encrypted, encryptionKey } = handlers
|
|
32
|
+
|
|
33
|
+
this.encrypted = encrypted === true || !!encryptionKey
|
|
34
|
+
|
|
35
|
+
const bee = new Hyperbee(store.namespace('view'), {
|
|
36
|
+
// defer one tick to ensure consistent state, then return state prom
|
|
37
|
+
preload: async () => {
|
|
38
|
+
await 1
|
|
39
|
+
await this._bootingState
|
|
40
|
+
},
|
|
41
|
+
getEncryptionProvider: () => this._getEncryptionProvider()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
this.store = store
|
|
45
|
+
|
|
46
|
+
this.key = key ? ID.decode(key) : null
|
|
47
|
+
this.discoveryKey = null
|
|
48
|
+
this.id = null
|
|
49
|
+
this.bootstrap = null
|
|
50
|
+
|
|
51
|
+
this.system = new System(this.store.namespace('system'), this.name, {
|
|
52
|
+
getEncryptionProvider: () => this._getEncryptionProvider(),
|
|
53
|
+
encrypted: this.encrypted
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
this.bee = bee.snapshot()
|
|
57
|
+
this.view = handlers.open ? handlers.open(this.bee, this) : this.bee
|
|
58
|
+
this.optimistic = handlers.optimistic !== false // TODO: should default to false instead
|
|
59
|
+
|
|
60
|
+
this.name = name // for debugging
|
|
61
|
+
|
|
62
|
+
this.local = null
|
|
63
|
+
this.encryptionKey = null
|
|
64
|
+
this.keyPair = null
|
|
65
|
+
this.writers = null
|
|
66
|
+
this.bumping = 0
|
|
67
|
+
|
|
68
|
+
this._workingBee = bee
|
|
69
|
+
this._workingView = handlers.open ? handlers.open(this._workingBee, this) : this._workingBee
|
|
70
|
+
|
|
71
|
+
this._appending = []
|
|
72
|
+
this._draining = null
|
|
73
|
+
|
|
74
|
+
this._bootingState = null
|
|
75
|
+
this._bootingAll = null
|
|
76
|
+
|
|
77
|
+
this._handlers = handlers
|
|
78
|
+
this._hasApply = !!handlers.apply
|
|
79
|
+
this._hasUpdate = !!handlers.update
|
|
80
|
+
this._needsUpdate = false
|
|
81
|
+
this._updateLocalCore = null
|
|
82
|
+
this._host = new ApplyCalls(this)
|
|
83
|
+
|
|
84
|
+
this._wakeup = new AutobeeWakeup(this, handlers)
|
|
85
|
+
this.wakeupCapability = null
|
|
86
|
+
|
|
87
|
+
this.ready().catch(noop)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static GENESIS = EMPTY_HEAD
|
|
91
|
+
|
|
92
|
+
static isAutobee(auto) {
|
|
93
|
+
return auto instanceof Autobee
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get isIndexer() {
|
|
97
|
+
return this.writers.localWriter.isIndexer
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get writable() {
|
|
101
|
+
return this.writers.writable
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// autobase compat
|
|
105
|
+
get activeWriters() {
|
|
106
|
+
return this.writers
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async _open() {
|
|
110
|
+
await this._preBoot()
|
|
111
|
+
|
|
112
|
+
this._bootingState = this._bootState()
|
|
113
|
+
this._bootingAll = this._bootAll()
|
|
114
|
+
|
|
115
|
+
this._bootingState.catch(safetyCatch)
|
|
116
|
+
this._bootingAll.catch(safetyCatch)
|
|
117
|
+
|
|
118
|
+
await this.bee.ready()
|
|
119
|
+
await this._bootingState
|
|
120
|
+
|
|
121
|
+
this.bumpSoon()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_registerWakeup() {
|
|
125
|
+
this._wakeup.recouple()
|
|
126
|
+
this._wakeup.setCapability(this.wakeupCapability.key, this.wakeupCapability.discoveryKey)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
views() {
|
|
130
|
+
const sys = this.system.bee.context.local
|
|
131
|
+
const view = this._workingBee.context.local
|
|
132
|
+
|
|
133
|
+
// signedLength for autobase compat
|
|
134
|
+
return [
|
|
135
|
+
{ key: sys.key, length: sys.length, signedLength: sys.length },
|
|
136
|
+
{ key: view.key, length: view.length, signedLength: view.length }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async _close() {
|
|
141
|
+
await this.interrupt()
|
|
142
|
+
|
|
143
|
+
if (this._handlers.close) await this._handlers.close(this.view)
|
|
144
|
+
|
|
145
|
+
await this.local.close()
|
|
146
|
+
await this.system.close()
|
|
147
|
+
await this._wakeup.close()
|
|
148
|
+
await this._workingBee.close()
|
|
149
|
+
await this.bee.close()
|
|
150
|
+
await this.store.close()
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await this._bootingAll
|
|
154
|
+
} catch {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
replicate(...args) {
|
|
158
|
+
const stream = this.store.replicate(...args)
|
|
159
|
+
this._wakeup.addStream(stream)
|
|
160
|
+
return stream
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async flush() {
|
|
164
|
+
await this._bootingAll
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
hintWakeup(wakeup) {
|
|
168
|
+
this._wakeup.hint(wakeup)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
openCore(key) {
|
|
172
|
+
const encryption = this.encryptionKey ? new WriterEncryption(this) : null
|
|
173
|
+
return this.store.get({ key, encryption })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_getEncryptionProvider() {
|
|
177
|
+
if (!this.encrypted) return null
|
|
178
|
+
return new WriterEncryption(this)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async _preBoot() {
|
|
182
|
+
if (this._handlers.wait) await this._handlers.wait()
|
|
183
|
+
|
|
184
|
+
await this.store.ready()
|
|
185
|
+
|
|
186
|
+
if (this._handlers.encryptionKey) {
|
|
187
|
+
this.encryptionKey = await this._handlers.encryptionKey
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this._handlers.keyPair) {
|
|
191
|
+
this.keyPair = await this._handlers.keyPair
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async _bootState() {
|
|
196
|
+
const result = await boot(this.store, this.key, {
|
|
197
|
+
encryptionKey: this.encryptionKey,
|
|
198
|
+
keyPair: this.keyPair
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
this.key = result.key
|
|
202
|
+
this.bootstrap = result.bootstrap
|
|
203
|
+
this.discoveryKey = result.bootstrap.core.discoveryKey
|
|
204
|
+
this.id = result.bootstrap.core.id
|
|
205
|
+
this.encryptionKey = result.encryptionKey
|
|
206
|
+
|
|
207
|
+
if (this.encrypted) {
|
|
208
|
+
asserts.assert(this.encryptionKey !== null, 'Encryption key is expected')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.local = result.local
|
|
212
|
+
this.local.setEncryption(this._getEncryptionProvider())
|
|
213
|
+
this.local.setActive(true)
|
|
214
|
+
|
|
215
|
+
this.writers = new ActiveWriters(this)
|
|
216
|
+
|
|
217
|
+
if (this._handlers.wakeupCapability) {
|
|
218
|
+
this.wakeupCapability = await this._handlers.wakeupCapability
|
|
219
|
+
} else {
|
|
220
|
+
this.wakeupCapability = { key: this.key, discoveryKey: this.discoveryKey }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this._registerWakeup()
|
|
224
|
+
|
|
225
|
+
const system = result.system || EMPTY_HEAD
|
|
226
|
+
|
|
227
|
+
await this.system.boot(system)
|
|
228
|
+
|
|
229
|
+
// Use the view position from the system info (authoritative, post-processing)
|
|
230
|
+
// rather than from the oplog (stale, captured at append time before _bump)
|
|
231
|
+
const view = this.system.view || EMPTY_HEAD
|
|
232
|
+
|
|
233
|
+
this._workingBee.move(view)
|
|
234
|
+
this.bee.move(view)
|
|
235
|
+
|
|
236
|
+
await this.writers.updateLocalState()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async _bootAll() {
|
|
240
|
+
await this._bootingState
|
|
241
|
+
|
|
242
|
+
for await (const node of this.system.list()) {
|
|
243
|
+
await this.writers.add(node.key)
|
|
244
|
+
}
|
|
245
|
+
await this._bump()
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bumpSoon() {
|
|
249
|
+
this._bump().catch(safetyCatch)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async _bump() {
|
|
253
|
+
await this._flushWakeup()
|
|
254
|
+
this.bumping++
|
|
255
|
+
|
|
256
|
+
if (!this._draining) {
|
|
257
|
+
this._draining = this._drain()
|
|
258
|
+
this._draining.catch(safetyCatch)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return this._draining
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
update() {
|
|
265
|
+
return this._bump()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
updated() {
|
|
269
|
+
if (this._draining) return this._draining
|
|
270
|
+
return Promise.resolve()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async _drain() {
|
|
274
|
+
if (this._updateLocalCore !== null) {
|
|
275
|
+
await this._rotateLocalWriter(this._updateLocalCore)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
279
|
+
if (changes) changes.track()
|
|
280
|
+
|
|
281
|
+
while (!this._interrupting && this.bumping > 0) {
|
|
282
|
+
if (this._interrupting) return
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
while (!this._interrupting) {
|
|
286
|
+
if (!(await this._bumpPendingWriters())) break
|
|
287
|
+
this._needsUpdate = true
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await this._flushLocal()
|
|
291
|
+
} finally {
|
|
292
|
+
if (this.bumping === 1) this.bumping = 0
|
|
293
|
+
else this.bumping = 1
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this._draining = null
|
|
298
|
+
if (this._interrupting) return
|
|
299
|
+
|
|
300
|
+
if (this._needsUpdate) {
|
|
301
|
+
this._update(changes)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async _flushWakeup() {
|
|
306
|
+
const hints = this._wakeup.flush()
|
|
307
|
+
|
|
308
|
+
for (const [hex, length] of hints) {
|
|
309
|
+
const key = b4a.from(hex, 'hex')
|
|
310
|
+
if (this.writers.has(hex)) continue
|
|
311
|
+
if (length !== -1) {
|
|
312
|
+
const info = await this.system.get(key)
|
|
313
|
+
if (info && length <= info.length) continue // stale hint
|
|
314
|
+
}
|
|
315
|
+
await this.writers.wakeup(key, length === -1 ? 0 : length)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
_update(changes) {
|
|
320
|
+
this._needsUpdate = false
|
|
321
|
+
this.bee.update(this._workingBee.root)
|
|
322
|
+
|
|
323
|
+
if (!changes) return
|
|
324
|
+
|
|
325
|
+
changes.finalise()
|
|
326
|
+
this._handlers.update(this.view, changes)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async setLocal(key, { keyPair } = {}) {
|
|
330
|
+
if (!this.opened) await this.ready()
|
|
331
|
+
|
|
332
|
+
const manifest = keyPair
|
|
333
|
+
? { version: this.store.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
|
|
334
|
+
: null
|
|
335
|
+
if (!key) key = Hypercore.key(manifest)
|
|
336
|
+
// If the keys are the same, no need to rotate
|
|
337
|
+
if (b4a.equals(key, this.local.key)) return
|
|
338
|
+
|
|
339
|
+
const encryption = this.encryptionKey ? this._getEncryptionProvider() : null
|
|
340
|
+
|
|
341
|
+
const local = this.store.get({
|
|
342
|
+
key,
|
|
343
|
+
manifest,
|
|
344
|
+
active: false,
|
|
345
|
+
exclusive: true,
|
|
346
|
+
encryption
|
|
347
|
+
})
|
|
348
|
+
await local.ready()
|
|
349
|
+
|
|
350
|
+
this._updateLocalCore = local
|
|
351
|
+
|
|
352
|
+
let runs = 0
|
|
353
|
+
while (!this._interrupting && this.appending && runs++ < 16) await this.update()
|
|
354
|
+
await this.bumpSoon()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async _rotateLocalWriter(newLocal) {
|
|
358
|
+
asserts.assert(!this.appending, 'Cannot rotate a newLocal writer if an append is in progress')
|
|
359
|
+
|
|
360
|
+
const oldLocal = this.local
|
|
361
|
+
|
|
362
|
+
this.local = newLocal
|
|
363
|
+
this.writers.rotateLocalWriter(this.local)
|
|
364
|
+
|
|
365
|
+
this._updateLocalCore = null
|
|
366
|
+
|
|
367
|
+
this.local.setUserData('referrer', this.key)
|
|
368
|
+
if (this.encryptionKey) {
|
|
369
|
+
await this.local.setUserData('autobase/encryption', this.encryptionKey)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
await this.bootstrap.setUserData('autobase/local', this.local.key)
|
|
373
|
+
await oldLocal.close()
|
|
374
|
+
|
|
375
|
+
// done, soft reboot
|
|
376
|
+
this.emit('rotate-local-writer')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
interrupt() {
|
|
380
|
+
this._interrupting = true
|
|
381
|
+
if (this._draining) return this._draining
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async createAnchor() {
|
|
385
|
+
const node = this._host.applying[this._host.applying.length - 1]
|
|
386
|
+
|
|
387
|
+
const key = node.key
|
|
388
|
+
const length = node.length
|
|
389
|
+
const legacy = node.version <= 2
|
|
390
|
+
|
|
391
|
+
const info = await this.system.get(key, { unflushed: true })
|
|
392
|
+
if (!info || info.length < length) throw new Error('Anchor node is not in system')
|
|
393
|
+
|
|
394
|
+
const state = { start: 0, end: 40, buffer: b4a.alloc(40) }
|
|
395
|
+
c.fixed32.encode(state, key)
|
|
396
|
+
c.uint64.encode(state, length)
|
|
397
|
+
|
|
398
|
+
const namespace = crypto.hash(state.buffer)
|
|
399
|
+
const manifestData = c.encode(encoding.ManifestData, { version: 0, legacyBlocks: 0, namespace })
|
|
400
|
+
|
|
401
|
+
const padding = this.encryptionKey ? AutobeeEncryption.PADDING : 0
|
|
402
|
+
const links = [{ key, length }]
|
|
403
|
+
|
|
404
|
+
const block = Autobee.encodeValue(null, {
|
|
405
|
+
legacy,
|
|
406
|
+
timestamp: 0,
|
|
407
|
+
links,
|
|
408
|
+
heads: links, // legacy compat
|
|
409
|
+
padding
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
if (this.encryptionKey) {
|
|
413
|
+
AutobeeEncryption.encryptAnchor(block, this.key, this.encryptionKey, namespace)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const root = { index: 0, size: block.byteLength, hash: crypto.data(block) }
|
|
417
|
+
const hash = crypto.tree([root])
|
|
418
|
+
const prologue = { hash, length: 1 }
|
|
419
|
+
|
|
420
|
+
const core = createAnchorCore(this.store, prologue, manifestData)
|
|
421
|
+
await core.ready()
|
|
422
|
+
|
|
423
|
+
if (core.length === 0) {
|
|
424
|
+
await core.append(block, { writable: true, maxLength: 1 })
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
await this.system.addWriter(core.key)
|
|
428
|
+
|
|
429
|
+
const anchor = { key: core.key, length: core.length }
|
|
430
|
+
|
|
431
|
+
await core.close()
|
|
432
|
+
|
|
433
|
+
return anchor
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async _bumpPendingWriters() {
|
|
437
|
+
let updated = false
|
|
438
|
+
|
|
439
|
+
const pending = this.writers.pending.slice()
|
|
440
|
+
|
|
441
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
442
|
+
const w = pending[i]
|
|
443
|
+
|
|
444
|
+
const batch = await w.next()
|
|
445
|
+
if (batch === null) continue
|
|
446
|
+
|
|
447
|
+
if (w.isAdded || (w.isRemoved && w.hasReferrals())) {
|
|
448
|
+
await this._processBatch(batch)
|
|
449
|
+
w.notify(batch)
|
|
450
|
+
updated = true
|
|
451
|
+
continue
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (this.optimistic && !w.isRemoved && batch[0].optimistic) {
|
|
455
|
+
if (!(await this._optimisticBatch(batch))) {
|
|
456
|
+
w.removePending()
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
w.notify(batch)
|
|
460
|
+
updated = true
|
|
461
|
+
continue
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return updated
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async _optimisticBatch(batch) {
|
|
469
|
+
const rollbackSystem = this.system.bee.head()
|
|
470
|
+
const rollbackView = this._workingBee.head()
|
|
471
|
+
|
|
472
|
+
const t = await this.prepareBatch(batch)
|
|
473
|
+
|
|
474
|
+
if (t.view) {
|
|
475
|
+
this._workingBee.move(t.view)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
asserts.assert(batch === t.tip[0], 'Batch must be first part of tip')
|
|
479
|
+
|
|
480
|
+
let failed = true
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
if (await this.system.canApply(batch[0].key, true)) {
|
|
484
|
+
await this._applyBatch(batch, true)
|
|
485
|
+
failed = false
|
|
486
|
+
}
|
|
487
|
+
} catch {}
|
|
488
|
+
|
|
489
|
+
const w = failed ? null : await this.system.get(batch[0].key)
|
|
490
|
+
if (!w || w.length < batch[0].length) {
|
|
491
|
+
this._workingBee.move(rollbackView)
|
|
492
|
+
this.system.bee.move(rollbackSystem)
|
|
493
|
+
await this.system.reset()
|
|
494
|
+
return false
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
for (let i = 1; i < t.tip.length; i++) {
|
|
498
|
+
await this._applyBatch(t.tip[i], t.tip[i][0].optimistic)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return true
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async prepareBatch(batch) {
|
|
505
|
+
const node = batch[0]
|
|
506
|
+
|
|
507
|
+
if (topo.isLinkingAll(node, this.system.heads)) {
|
|
508
|
+
return { undo: null, view: null, tip: [batch] }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const t = await topo.sort(this, batch)
|
|
512
|
+
|
|
513
|
+
if (t.undo) {
|
|
514
|
+
t.view = await this.system.undo(t.undo)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return t
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async _processBatch(batch) {
|
|
521
|
+
const t = await this.prepareBatch(batch)
|
|
522
|
+
|
|
523
|
+
if (t.view) {
|
|
524
|
+
this._workingBee.move(t.view)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// first writer is always added with full permissions
|
|
528
|
+
if (this.system.isGenesis()) {
|
|
529
|
+
await this._host.addWriter(t.tip[0][0].key)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
for (let i = 0; i < t.tip.length; i++) {
|
|
533
|
+
await this._applyBatch(t.tip[i], t.tip[i][0].optimistic)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async _applyBatch(batch, optimistic) {
|
|
538
|
+
const userBatch = []
|
|
539
|
+
for (const node of batch) {
|
|
540
|
+
this.system.addNode(node)
|
|
541
|
+
|
|
542
|
+
// compat: autobase nodes may be null
|
|
543
|
+
if (node.value) userBatch.push(node)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
|
|
547
|
+
this._host.applying = batch
|
|
548
|
+
await this._handlers.apply(userBatch, this._workingView, this._host)
|
|
549
|
+
this._host.applying = null
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const changed = await this.system.flush(batch, this._workingBee)
|
|
553
|
+
|
|
554
|
+
await this._storeBoot()
|
|
555
|
+
|
|
556
|
+
for (const { key, added } of changed) {
|
|
557
|
+
if (added) await this.writers.add(key)
|
|
558
|
+
else await this.writers.remove(key)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
_storeBoot() {
|
|
563
|
+
const boot = this.system.bootRecord()
|
|
564
|
+
if (!boot) return
|
|
565
|
+
return this.local.setUserData('autobee/head', encoding.encodeBootRecord(boot))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
static decodeValue(buf, opts) {
|
|
569
|
+
return encoding.decodeValue(buf, opts)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
static encodeValue(value, opts) {
|
|
573
|
+
return encoding.encodeValue(value, opts)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async wakeup({ key, length }) {
|
|
577
|
+
await this._bootingState
|
|
578
|
+
await this.writers.wakeup(key, length)
|
|
579
|
+
await this._bump()
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async append(values, { optimistic = false } = {}) {
|
|
583
|
+
if (!Array.isArray(values)) values = [values]
|
|
584
|
+
|
|
585
|
+
if (!this.opened) await this.ready()
|
|
586
|
+
|
|
587
|
+
await this.local.ready()
|
|
588
|
+
|
|
589
|
+
const links = this.system.getLinks(this.local.key)
|
|
590
|
+
const t = Date.now()
|
|
591
|
+
const batch = []
|
|
592
|
+
|
|
593
|
+
for (let i = 0; i < values.length; i++) {
|
|
594
|
+
const value = values[i]
|
|
595
|
+
const buffer = typeof value === 'string' ? b4a.from(value) : value
|
|
596
|
+
const lnk = i === 0 ? links : []
|
|
597
|
+
const b = { start: i, end: values.length - 1 - i }
|
|
598
|
+
|
|
599
|
+
const node = this.writers.appendLocal(buffer, t, b, lnk, optimistic)
|
|
600
|
+
batch.push(node)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return this._bump()
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async _flushLocal() {
|
|
607
|
+
// analyze is worth the trade off adding the view here also (technically not needed)
|
|
608
|
+
await this.writers.flushLocal(this._workingBee.head())
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function isObject(o) {
|
|
613
|
+
return typeof o === 'object' && o && !b4a.isBuffer(o)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function noop() {}
|
|
617
|
+
|
|
618
|
+
function createAnchorCore(store, prologue, manifestData) {
|
|
619
|
+
const manifest = {
|
|
620
|
+
version: 2,
|
|
621
|
+
hash: 'blake2b',
|
|
622
|
+
prologue,
|
|
623
|
+
allowPatch: false,
|
|
624
|
+
quorum: 0,
|
|
625
|
+
signers: [],
|
|
626
|
+
userData: manifestData,
|
|
627
|
+
linked: null
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const core = store.get({
|
|
631
|
+
manifest,
|
|
632
|
+
active: false
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
return core
|
|
636
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ID = require('hypercore-id-encoding')
|
|
2
|
+
|
|
3
|
+
class ApplyCalls {
|
|
4
|
+
constructor(auto) {
|
|
5
|
+
this.auto = auto
|
|
6
|
+
this.applying = null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get id() {
|
|
10
|
+
return this.auto.id
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get key() {
|
|
14
|
+
return this.auto.key
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get discoveryKey() {
|
|
18
|
+
return this.auto.discoveryKey
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get name() {
|
|
22
|
+
return this.auto.name
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get genesis() {
|
|
26
|
+
return this.auto.system.isGenesis()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addWriter(key, { isIndexer = true } = {}) {
|
|
30
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
31
|
+
return this.auto.system.addWriter(key, { isIndexer })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ackWriter(key) {
|
|
35
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
36
|
+
return this.auto.system.ackWriter(key)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
removeWriter(key) {
|
|
40
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
41
|
+
return this.auto.system.removeWriter(key)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
preferFastForward() {
|
|
45
|
+
// this.auto._preferFastForward()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interrupt(reason) {
|
|
49
|
+
throw new Error('TODO')
|
|
50
|
+
// this.auto._interrupt(reason)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
removeable(key) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
createAnchor() {
|
|
58
|
+
return this.auto.createAnchor()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = ApplyCalls
|