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/README.md +255 -0
- package/index.js +694 -0
- package/lib/apply-calls.js +65 -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 +327 -0
- package/lib/topo.js +271 -0
- package/lib/triggers.js +69 -0
- package/lib/updates.js +31 -0
- package/lib/writers.js +501 -0
- package/package.json +39 -6
|
@@ -0,0 +1,65 @@
|
|
|
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 clock() {
|
|
26
|
+
return this.auto.system.flushes
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get genesis() {
|
|
30
|
+
return this.auto.system.isGenesis()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addWriter(key, { isIndexer = true, weight = isIndexer ? 2 : 1 } = {}) {
|
|
34
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
35
|
+
return this.auto.system.addWriter(key, { weight })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ackWriter(key) {
|
|
39
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
40
|
+
return this.auto.system.ackWriter(key)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
removeWriter(key) {
|
|
44
|
+
if (typeof key === 'string') key = ID.decode(key)
|
|
45
|
+
return this.auto.system.removeWriter(key)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
preferFastForward() {
|
|
49
|
+
// this.auto._preferFastForward()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interrupt(reason) {
|
|
53
|
+
this.auto.interrupt(reason)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeable(key) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createAnchor() {
|
|
61
|
+
return this.auto.createAnchor()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = ApplyCalls
|
package/lib/asserts.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
exports.heads = function (heads, node) {
|
|
2
|
+
for (const h of this.heads) {
|
|
3
|
+
if (h.key.equals(node.key)) {
|
|
4
|
+
exports.bail('Same node included twice')
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
exports.systemFlush = function (w) {
|
|
10
|
+
for (const op of w.ops) {
|
|
11
|
+
if (op.applied && op.key[0] === 1) return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.bail('Invalid system batch')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.assert = function (cond, msg) {
|
|
18
|
+
if (!cond) exports.bail(msg)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.bail = function (msg) {
|
|
22
|
+
const err = new Error('ERR_ASSERTION: ' + msg)
|
|
23
|
+
err.code = 'ERR_ASSERTION'
|
|
24
|
+
throw err
|
|
25
|
+
}
|
package/lib/boot.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const encoding = require('./encoding.js')
|
|
2
|
+
|
|
3
|
+
module.exports = async function boot(
|
|
4
|
+
corestore,
|
|
5
|
+
key,
|
|
6
|
+
{ encrypt, encryptionKey, keyPair, exclusive = true } = {}
|
|
7
|
+
) {
|
|
8
|
+
const result = {
|
|
9
|
+
key: null,
|
|
10
|
+
local: null,
|
|
11
|
+
bootstrap: null,
|
|
12
|
+
encryptionKey: null,
|
|
13
|
+
system: null,
|
|
14
|
+
flushes: null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const manifest = keyPair
|
|
18
|
+
? { version: corestore.manifestVersion, signers: [{ publicKey: keyPair.publicKey }] }
|
|
19
|
+
: null
|
|
20
|
+
|
|
21
|
+
if (key) {
|
|
22
|
+
result.key = key
|
|
23
|
+
|
|
24
|
+
const bootstrap = corestore.get({ key })
|
|
25
|
+
await bootstrap.ready()
|
|
26
|
+
|
|
27
|
+
const localKey = await bootstrap.getUserData('autobee/local')
|
|
28
|
+
|
|
29
|
+
if (keyPair) {
|
|
30
|
+
result.local = corestore.get({
|
|
31
|
+
keyPair,
|
|
32
|
+
active: false,
|
|
33
|
+
exclusive,
|
|
34
|
+
manifest
|
|
35
|
+
})
|
|
36
|
+
} else {
|
|
37
|
+
if (bootstrap.writable && !localKey) {
|
|
38
|
+
result.local = bootstrap.session({
|
|
39
|
+
active: false,
|
|
40
|
+
exclusive
|
|
41
|
+
})
|
|
42
|
+
} else {
|
|
43
|
+
const local = localKey
|
|
44
|
+
? corestore.get({
|
|
45
|
+
key: localKey,
|
|
46
|
+
active: false,
|
|
47
|
+
exclusive
|
|
48
|
+
})
|
|
49
|
+
: corestore.get({
|
|
50
|
+
name: 'local',
|
|
51
|
+
active: false,
|
|
52
|
+
exclusive
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
await local.ready()
|
|
56
|
+
result.local = local
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
result.bootstrap = bootstrap
|
|
61
|
+
} else {
|
|
62
|
+
result.local = keyPair
|
|
63
|
+
? corestore.get({
|
|
64
|
+
keyPair,
|
|
65
|
+
manifest,
|
|
66
|
+
active: false,
|
|
67
|
+
exclusive
|
|
68
|
+
})
|
|
69
|
+
: corestore.get({
|
|
70
|
+
name: 'local',
|
|
71
|
+
active: false,
|
|
72
|
+
exclusive
|
|
73
|
+
})
|
|
74
|
+
await result.local.ready()
|
|
75
|
+
|
|
76
|
+
const key = await result.local.getUserData('referrer')
|
|
77
|
+
if (key) {
|
|
78
|
+
result.key = key
|
|
79
|
+
result.bootstrap = corestore.get({ key, active: false })
|
|
80
|
+
await result.bootstrap.ready()
|
|
81
|
+
} else {
|
|
82
|
+
result.key = result.local.key
|
|
83
|
+
result.bootstrap = result.local.session({ active: false })
|
|
84
|
+
await result.bootstrap.setUserData('autobee/local', result.local.key)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (key || keyPair) {
|
|
89
|
+
await result.bootstrap.setUserData('referrer', result.key)
|
|
90
|
+
await result.bootstrap.setUserData('autobee/local', result.local.key)
|
|
91
|
+
await result.local.setUserData('referrer', result.key)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const [systemHead, encryptionKeyBuffer] = await Promise.all([
|
|
95
|
+
result.local.getUserData('autobee/head'),
|
|
96
|
+
result.local.getUserData('autobee/encryption')
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
if (systemHead) result.system = encoding.decodeBootRecord(systemHead)
|
|
100
|
+
if (encryptionKeyBuffer) {
|
|
101
|
+
result.encryptionKey = encryptionKeyBuffer
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!result.encryptionKey && (encryptionKey || encrypt)) {
|
|
105
|
+
if (!encryptionKey) {
|
|
106
|
+
encryptionKey = (await corestore.createKeyPair('autobee/encryption')).secretKey.subarray(
|
|
107
|
+
0,
|
|
108
|
+
32
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await result.local.setUserData('autobee/encryption', encryptionKey)
|
|
113
|
+
result.encryptionKey = encryptionKey
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result
|
|
117
|
+
}
|
package/lib/constants.js
ADDED
package/lib/encoding.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const b4a = require('b4a')
|
|
2
|
+
const c = require('compact-encoding')
|
|
3
|
+
const crypto = require('hypercore-crypto')
|
|
4
|
+
const { AutobeeEncryption } = require('autobee-encryption')
|
|
5
|
+
|
|
6
|
+
const { getEncoding } = require('../encoding/spec/autobee')
|
|
7
|
+
const { LEGACY_OPLOG_VERSION, OPLOG_VERSION } = require('./constants')
|
|
8
|
+
|
|
9
|
+
const Oplog = getEncoding('@autobee/oplog')
|
|
10
|
+
const Link = getEncoding('@autobee/link')
|
|
11
|
+
const SystemInfo = getEncoding('@autobee/system-info')
|
|
12
|
+
const SystemWriter = getEncoding('@autobee/system-writer')
|
|
13
|
+
const ManifestData = getEncoding('@autobee/manifest-data')
|
|
14
|
+
|
|
15
|
+
exports.OPLOG_VERSION = OPLOG_VERSION
|
|
16
|
+
|
|
17
|
+
exports.Oplog = Oplog
|
|
18
|
+
exports.ManifestData = ManifestData
|
|
19
|
+
exports.encodeBootRecord = encodeBootRecord
|
|
20
|
+
exports.decodeBootRecord = decodeBootRecord
|
|
21
|
+
exports.encodeSystemWriter = encodeSystemWriter
|
|
22
|
+
exports.decodeSystemWriter = decodeSystemWriter
|
|
23
|
+
|
|
24
|
+
function encodeBootRecord(r) {
|
|
25
|
+
return c.encode(Link, r)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decodeBootRecord(value) {
|
|
29
|
+
return c.decode(Link, value)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function encodeSystemWriter(m) {
|
|
33
|
+
return c.encode(SystemWriter, m)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function decodeSystemWriter(key, value) {
|
|
37
|
+
return {
|
|
38
|
+
// TODO: add dummy type to schema to preset key without extra alloc
|
|
39
|
+
key: key.subarray(1),
|
|
40
|
+
...c.decode(SystemWriter, value)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exports.encodeSystemInfo = encodeSystemInfo
|
|
45
|
+
exports.decodeSystemInfo = decodeSystemInfo
|
|
46
|
+
|
|
47
|
+
function encodeSystemInfo(m) {
|
|
48
|
+
return c.encode(SystemInfo, m)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function decodeSystemInfo(buf) {
|
|
52
|
+
return c.decode(SystemInfo, buf)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.encodeOplog = encodeOplog
|
|
56
|
+
exports.decodeOplog = decodeOplog
|
|
57
|
+
|
|
58
|
+
function decodeOplog(buf) {
|
|
59
|
+
const m = c.decode(Oplog, buf)
|
|
60
|
+
|
|
61
|
+
if (m.version <= LEGACY_OPLOG_VERSION) {
|
|
62
|
+
return {
|
|
63
|
+
version: m.version,
|
|
64
|
+
timestamp: 0,
|
|
65
|
+
links: m.node.heads,
|
|
66
|
+
batch: { start: 0, end: 0 },
|
|
67
|
+
views: null,
|
|
68
|
+
optimistic: !!m.optimistic,
|
|
69
|
+
value: m.node.value
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (m.batch === null) m.batch = { start: 0, end: 0 }
|
|
74
|
+
return m
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function encodeOplog(m) {
|
|
78
|
+
if (m.batch && m.batch.start === 0 && m.batch.end === 0) m.batch = null
|
|
79
|
+
return c.encode(Oplog, m)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.encodeValue = encodeValue
|
|
83
|
+
exports.decodeValue = decodeValue
|
|
84
|
+
|
|
85
|
+
function createValue(value, opts = {}) {
|
|
86
|
+
if (opts.legacy) {
|
|
87
|
+
return {
|
|
88
|
+
version: opts.version || LEGACY_OPLOG_VERSION,
|
|
89
|
+
digest: null,
|
|
90
|
+
checkpoint: null,
|
|
91
|
+
optimistic: !!opts.optimistic,
|
|
92
|
+
node: {
|
|
93
|
+
heads: opts.heads || [],
|
|
94
|
+
batch: 1,
|
|
95
|
+
value
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const {
|
|
101
|
+
version = OPLOG_VERSION,
|
|
102
|
+
optimistic = false,
|
|
103
|
+
timestamp = Date.now(),
|
|
104
|
+
links = [],
|
|
105
|
+
views = null
|
|
106
|
+
} = opts
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
version,
|
|
110
|
+
timestamp,
|
|
111
|
+
links,
|
|
112
|
+
batch: null,
|
|
113
|
+
views,
|
|
114
|
+
optimistic,
|
|
115
|
+
value
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function encodeValue(value, opts = {}) {
|
|
120
|
+
const oplog = createValue(value, opts)
|
|
121
|
+
|
|
122
|
+
const state = { start: 0, end: 0, buffer: null }
|
|
123
|
+
|
|
124
|
+
Oplog.preencode(state, oplog)
|
|
125
|
+
|
|
126
|
+
if (opts.padding) {
|
|
127
|
+
state.start = opts.padding
|
|
128
|
+
state.end += opts.padding
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
state.buffer = b4a.alloc(state.end)
|
|
132
|
+
|
|
133
|
+
Oplog.encode(state, oplog)
|
|
134
|
+
|
|
135
|
+
if (!opts.encrypted) return state.buffer
|
|
136
|
+
|
|
137
|
+
if (!opts.optimistic) {
|
|
138
|
+
throw new Error('Encoding an encrypted value is not supported')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const padding = b4a.alloc(16) // sodium.crypto_generichash_MINBYTES
|
|
142
|
+
crypto.hash(state.buffer, padding)
|
|
143
|
+
padding[0] = 0
|
|
144
|
+
|
|
145
|
+
return b4a.concat([padding.subarray(0, AutobeeEncryption.PADDING), state.buffer])
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function decodeValue(buf, opts = {}) {
|
|
149
|
+
return decodeOplog(buf).value
|
|
150
|
+
}
|
package/lib/system.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const Hyperbee = require('hyperbee2')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
|
|
4
|
+
const { AUTOBEE_VERSION } = require('./constants')
|
|
5
|
+
const encoding = require('./encoding.js')
|
|
6
|
+
const topo = require('./topo.js')
|
|
7
|
+
|
|
8
|
+
const INFO_KEY = b4a.from([0])
|
|
9
|
+
const INFO_LEGACY_KEY = b4a.concat([b4a.from([0]), b4a.from('info')])
|
|
10
|
+
const INDEXER_GTE = b4a.from([2])
|
|
11
|
+
const INDEXER_LT = b4a.from([3])
|
|
12
|
+
const EMPTY_HEAD = { length: 0, key: null }
|
|
13
|
+
const EMPTY = b4a.from([])
|
|
14
|
+
|
|
15
|
+
module.exports = class Systembee {
|
|
16
|
+
constructor(store, name, opts = {}) {
|
|
17
|
+
this.name = name // for debuggin
|
|
18
|
+
this.store = store
|
|
19
|
+
this.bee = new Hyperbee(store, opts)
|
|
20
|
+
this.view = null
|
|
21
|
+
this.heads = []
|
|
22
|
+
this.version = 0
|
|
23
|
+
this.timestamp = 0
|
|
24
|
+
this.flushes = 0
|
|
25
|
+
this.indexers = null // legacy
|
|
26
|
+
this.updates = new Map()
|
|
27
|
+
this.writers = new Map()
|
|
28
|
+
this.encrypted = opts.encrypted === true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async addWriter(key, { length = 0, weight = 1 } = {}) {
|
|
32
|
+
if (length === 0) {
|
|
33
|
+
const info = await this.get(key, { unflushed: false })
|
|
34
|
+
length = info ? info.length : 0
|
|
35
|
+
}
|
|
36
|
+
this.update(key, length, weight, false, true, false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async ackWriter(key, { length = 0 } = {}) {
|
|
40
|
+
if (length === 0) {
|
|
41
|
+
const info = await this.get(key, { unflushed: false })
|
|
42
|
+
length = info ? info.length : 0
|
|
43
|
+
}
|
|
44
|
+
this.update(key, length, 0, false, false, true)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async removeWriter(key, { length = 0 } = {}) {
|
|
48
|
+
if (length === 0) {
|
|
49
|
+
const info = await this.get(key, { unflushed: true })
|
|
50
|
+
length = info ? info.length : 0
|
|
51
|
+
}
|
|
52
|
+
this.update(key, length, 0, true, false, false)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isGenesis() {
|
|
56
|
+
return this.bee.head() === null || this.bee.head().length === 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async boot(view) {
|
|
60
|
+
await this.bee.ready()
|
|
61
|
+
this.bee.move(view)
|
|
62
|
+
await this.reset()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getInfo() {
|
|
66
|
+
const node = await this.bee.get(INFO_KEY)
|
|
67
|
+
if (node) return encoding.decodeSystemInfo(node.value)
|
|
68
|
+
const legacy = await this.bee.get(INFO_LEGACY_KEY)
|
|
69
|
+
if (legacy) return encoding.decodeSystemInfo(legacy.value)
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async *getIndexers() {
|
|
74
|
+
for await (const data of this.bee.createReadStream({ gte: INDEXER_GTE, lt: INDEXER_LT })) {
|
|
75
|
+
const key = data.key.subarray(1)
|
|
76
|
+
yield key
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async reset() {
|
|
81
|
+
const info = await this.getInfo()
|
|
82
|
+
|
|
83
|
+
this.version = info ? info.version : 0
|
|
84
|
+
this.view = info ? info.view : EMPTY_HEAD
|
|
85
|
+
this.heads = info ? info.heads : []
|
|
86
|
+
this.timestamp = info ? info.timestamp : 0
|
|
87
|
+
this.flushes = info ? info.flushes : 0
|
|
88
|
+
this.indexers = info ? info.indexers : null
|
|
89
|
+
this.updates.clear()
|
|
90
|
+
this.writers.clear()
|
|
91
|
+
|
|
92
|
+
if (this.version > AUTOBEE_VERSION) {
|
|
93
|
+
throw new Error('Autobee signals newer version than locally supported')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
bootRecord() {
|
|
98
|
+
const system = this.bee.head()
|
|
99
|
+
return system.length ? system : null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async close() {
|
|
103
|
+
await this.bee.close()
|
|
104
|
+
await this.store.close()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async undo(head) {
|
|
108
|
+
this.bee.move(head)
|
|
109
|
+
await this.reset()
|
|
110
|
+
return this.view || EMPTY_HEAD
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getLinks(key) {
|
|
114
|
+
const links = []
|
|
115
|
+
for (const h of this.heads) {
|
|
116
|
+
if (key && b4a.equals(h.key, key)) continue
|
|
117
|
+
links.push(h)
|
|
118
|
+
}
|
|
119
|
+
return links
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
addNode(node) {
|
|
123
|
+
for (let i = 0; i < this.heads.length; i++) {
|
|
124
|
+
const h = this.heads[i]
|
|
125
|
+
if (topo.isLinking(node, h)) {
|
|
126
|
+
this.heads.splice(i--, 1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Can enable if needed during debuggin
|
|
131
|
+
// asserts.heads(heads, node)
|
|
132
|
+
|
|
133
|
+
this.heads.push({ key: node.key, length: node.length })
|
|
134
|
+
if (node.timestamp > this.timestamp) this.timestamp = node.timestamp // TODO: support smoothing
|
|
135
|
+
|
|
136
|
+
this.update(node.key, node.length, 0, false, false, false)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async canApply(key, optimistic) {
|
|
140
|
+
const id = b4a.toString(key, 'hex')
|
|
141
|
+
const w = this.writers.get(id)
|
|
142
|
+
if (w) return w.added
|
|
143
|
+
const info = await this.get(key)
|
|
144
|
+
return info ? !info.isRemoved : !!optimistic
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async get(key, { unflushed = false } = {}) {
|
|
148
|
+
const node = await this.bee.get(encodeWriterKey(key))
|
|
149
|
+
const info = node !== null ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
150
|
+
if (!unflushed) return info
|
|
151
|
+
|
|
152
|
+
const upd = this.updates.get(b4a.toString(key, 'hex'))
|
|
153
|
+
if (!upd) return info
|
|
154
|
+
if (!info) return upd
|
|
155
|
+
|
|
156
|
+
if (upd.isAdded) info.isAdded = true
|
|
157
|
+
if (upd.isRemoved) info.isRemoved = true
|
|
158
|
+
if (upd.isIndexer) info.isIndexer = true
|
|
159
|
+
if (upd.isAcked) info.isAcked = true
|
|
160
|
+
if (upd.isOplog) info.isOplog = true
|
|
161
|
+
|
|
162
|
+
info.length = upd.length
|
|
163
|
+
|
|
164
|
+
return info
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async *list() {
|
|
168
|
+
for await (const data of this.bee.createReadStream()) {
|
|
169
|
+
if (data.key[0] === 1) {
|
|
170
|
+
yield encoding.decodeSystemWriter(data.key, data.value)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async has(link) {
|
|
176
|
+
// fast path
|
|
177
|
+
for (let i = 0; i < this.heads.length; i++) {
|
|
178
|
+
const h = this.heads[i]
|
|
179
|
+
if (b4a.equals(h.key, link.key)) return h.length >= link.length
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const node = await this.get(link.key)
|
|
183
|
+
if (!node || node.length < link.length) return false
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
update(key, length, weight, isRemoved, isAdded, isAcked) {
|
|
188
|
+
const id = b4a.toString(key, 'hex')
|
|
189
|
+
|
|
190
|
+
let upd = this.updates.get(id)
|
|
191
|
+
|
|
192
|
+
if (isAdded) {
|
|
193
|
+
this.writers.set(id, { key, added: true })
|
|
194
|
+
} else if (isRemoved) {
|
|
195
|
+
this.writers.set(id, { key, added: false })
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!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
|
+
}
|
|
210
|
+
this.updates.set(id, upd)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isAdded) {
|
|
215
|
+
upd.weight = weight
|
|
216
|
+
upd.isRemoved = false
|
|
217
|
+
}
|
|
218
|
+
if (isRemoved) {
|
|
219
|
+
upd.weight = 0
|
|
220
|
+
upd.isRemoved = true
|
|
221
|
+
}
|
|
222
|
+
if (isAcked) {
|
|
223
|
+
upd.isAcked = true
|
|
224
|
+
}
|
|
225
|
+
if (length > upd.length) {
|
|
226
|
+
upd.length = length
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async _updateWriter(upd, oplog) {
|
|
231
|
+
const changed = upd.isAdded || upd.isRemoved
|
|
232
|
+
const k = encodeWriterKey(upd.key)
|
|
233
|
+
|
|
234
|
+
if (upd.isAcked || !changed) {
|
|
235
|
+
const node = await this.bee.get(k)
|
|
236
|
+
const v = node ? encoding.decodeSystemWriter(node.key, node.value) : null
|
|
237
|
+
|
|
238
|
+
if (v) {
|
|
239
|
+
if (!changed) {
|
|
240
|
+
upd.weight = v.weight
|
|
241
|
+
upd.isRemoved = v.isRemoved
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (v.length > upd.length) upd.length = v.length
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
upd.clock = this.flushes
|
|
249
|
+
|
|
250
|
+
// TODO: we can optimise this a bit, if the above node hasnt changed, do not set this
|
|
251
|
+
// less writes, but future thing
|
|
252
|
+
upd.isOplog = b4a.equals(upd.key, oplog)
|
|
253
|
+
|
|
254
|
+
return [
|
|
255
|
+
k,
|
|
256
|
+
encoding.encodeSystemWriter(upd),
|
|
257
|
+
upd.isIndexer && upd.isAdded ? encodeIndexerWriterKey(upd.key) : null,
|
|
258
|
+
upd.isRemoved ? encodeIndexerWriterKey(upd.key) : null
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async flush(batch, bee) {
|
|
263
|
+
if (batch.length === 0) return []
|
|
264
|
+
|
|
265
|
+
const oplog = batch[batch.length - 1].key
|
|
266
|
+
|
|
267
|
+
const promises = []
|
|
268
|
+
for (const upd of this.updates.values()) {
|
|
269
|
+
promises.push(this._updateWriter(upd, oplog))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const w = this.bee.write()
|
|
273
|
+
|
|
274
|
+
// migrate
|
|
275
|
+
if (this.indexers) {
|
|
276
|
+
for (const key of this.indexers) w.tryPut(encodeIndexerWriterKey(key), EMPTY)
|
|
277
|
+
this.indexers = null
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const [key, value, add, remove] of await Promise.all(promises)) {
|
|
281
|
+
w.tryPut(key, value)
|
|
282
|
+
if (add) w.tryPut(add, EMPTY)
|
|
283
|
+
if (remove) w.tryDelete(remove)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.flushes++
|
|
287
|
+
|
|
288
|
+
const info = {
|
|
289
|
+
version: AUTOBEE_VERSION,
|
|
290
|
+
view: bee.head(),
|
|
291
|
+
heads: this.heads,
|
|
292
|
+
timestamp: this.timestamp,
|
|
293
|
+
flushes: this.flushes,
|
|
294
|
+
indexers: null // legacy
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
w.tryPut(INFO_KEY, encoding.encodeSystemInfo(info))
|
|
298
|
+
|
|
299
|
+
await w.flush()
|
|
300
|
+
|
|
301
|
+
// Can enable if needed during debuggin
|
|
302
|
+
// asserts.systemFlush(w)
|
|
303
|
+
|
|
304
|
+
this.updates.clear()
|
|
305
|
+
|
|
306
|
+
if (this.writers.size === 0) return []
|
|
307
|
+
|
|
308
|
+
const changes = [...this.writers.values()]
|
|
309
|
+
|
|
310
|
+
this.writers.clear()
|
|
311
|
+
return changes
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function encodeWriterKey(key) {
|
|
316
|
+
const buf = b4a.allocUnsafe(33)
|
|
317
|
+
buf[0] = 1
|
|
318
|
+
buf.set(key, 1)
|
|
319
|
+
return buf
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function encodeIndexerWriterKey(key) {
|
|
323
|
+
const buf = b4a.allocUnsafe(33)
|
|
324
|
+
buf[0] = 2
|
|
325
|
+
buf.set(key, 1)
|
|
326
|
+
return buf
|
|
327
|
+
}
|