hypercore 9.12.0 → 10.0.0-alpha.11
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/.github/workflows/test-node.yml +3 -4
- package/README.md +131 -404
- package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
- package/examples/announce.js +19 -0
- package/examples/basic.js +10 -0
- package/examples/http.js +123 -0
- package/examples/lookup.js +20 -0
- package/index.js +365 -1600
- package/lib/bitfield.js +113 -285
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +58 -0
- package/lib/core.js +468 -0
- package/lib/extensions.js +76 -0
- package/lib/merkle-tree.js +1110 -0
- package/lib/messages.js +571 -0
- package/lib/mutex.js +39 -0
- package/lib/oplog.js +224 -0
- package/lib/protocol.js +525 -0
- package/lib/random-iterator.js +46 -0
- package/lib/remote-bitfield.js +24 -0
- package/lib/replicator.js +857 -0
- package/lib/streams.js +39 -0
- package/package.json +44 -45
- package/test/basic.js +59 -471
- package/test/bitfield.js +48 -133
- package/test/core.js +290 -0
- package/test/encodings.js +18 -0
- package/test/encryption.js +123 -0
- package/test/extension.js +71 -0
- package/test/helpers/index.js +23 -0
- package/test/merkle-tree.js +518 -0
- package/test/mutex.js +137 -0
- package/test/oplog.js +399 -0
- package/test/preload.js +72 -0
- package/test/replicate.js +227 -824
- package/test/sessions.js +173 -0
- package/test/storage.js +31 -0
- package/test/streams.js +39 -146
- package/test/user-data.js +47 -0
- package/bench/all.sh +0 -65
- package/bench/copy-64kb-blocks.js +0 -51
- package/bench/helpers/read-throttled.js +0 -27
- package/bench/helpers/read.js +0 -47
- package/bench/helpers/write.js +0 -29
- package/bench/read-16kb-blocks-proof-throttled.js +0 -1
- package/bench/read-16kb-blocks-proof.js +0 -1
- package/bench/read-16kb-blocks-throttled.js +0 -1
- package/bench/read-16kb-blocks.js +0 -1
- package/bench/read-512b-blocks.js +0 -1
- package/bench/read-64kb-blocks-linear-batch.js +0 -18
- package/bench/read-64kb-blocks-linear.js +0 -18
- package/bench/read-64kb-blocks-proof.js +0 -1
- package/bench/read-64kb-blocks.js +0 -1
- package/bench/replicate-16kb-blocks.js +0 -19
- package/bench/replicate-64kb-blocks.js +0 -19
- package/bench/write-16kb-blocks.js +0 -1
- package/bench/write-512b-blocks.js +0 -1
- package/bench/write-64kb-blocks-static.js +0 -1
- package/bench/write-64kb-blocks.js +0 -1
- package/example.js +0 -23
- package/lib/cache.js +0 -26
- package/lib/crypto.js +0 -5
- package/lib/replicate.js +0 -829
- package/lib/safe-buffer-equals.js +0 -6
- package/lib/storage.js +0 -421
- package/lib/tree-index.js +0 -183
- package/test/ack.js +0 -306
- package/test/audit.js +0 -36
- package/test/cache.js +0 -93
- package/test/compat.js +0 -209
- package/test/copy.js +0 -377
- package/test/default-storage.js +0 -51
- package/test/extensions.js +0 -137
- package/test/get.js +0 -64
- package/test/head.js +0 -65
- package/test/helpers/create-tracking-ram.js +0 -27
- package/test/helpers/create.js +0 -6
- package/test/helpers/replicate.js +0 -4
- package/test/seek.js +0 -234
- package/test/selections.js +0 -95
- package/test/set-uploading-downloading.js +0 -91
- package/test/stats.js +0 -77
- package/test/timeouts.js +0 -22
- package/test/tree-index.js +0 -841
- package/test/update.js +0 -156
- package/test/value-encoding.js +0 -52
package/lib/oplog.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const cenc = require('compact-encoding')
|
|
2
|
+
const b4a = require('b4a')
|
|
3
|
+
const crc32 = require('crc32-universal')
|
|
4
|
+
|
|
5
|
+
module.exports = class Oplog {
|
|
6
|
+
constructor (storage, { pageSize = 4096, headerEncoding = cenc.raw, entryEncoding = cenc.raw } = {}) {
|
|
7
|
+
this.storage = storage
|
|
8
|
+
this.headerEncoding = headerEncoding
|
|
9
|
+
this.entryEncoding = entryEncoding
|
|
10
|
+
this.flushed = false
|
|
11
|
+
this.byteLength = 0
|
|
12
|
+
this.length = 0
|
|
13
|
+
|
|
14
|
+
this._headers = [1, 0]
|
|
15
|
+
this._pageSize = pageSize
|
|
16
|
+
this._entryOffset = pageSize * 2
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_addHeader (state, len, headerBit, partialBit) {
|
|
20
|
+
// add the uint header (frame length and flush info)
|
|
21
|
+
state.start = state.start - len - 4
|
|
22
|
+
cenc.uint32.encode(state, (len << 2) | headerBit | partialBit)
|
|
23
|
+
|
|
24
|
+
// crc32 the length + header-bit + content and prefix it
|
|
25
|
+
state.start -= 8
|
|
26
|
+
cenc.uint32.encode(state, crc32(state.buffer.subarray(state.start + 4, state.start + 8 + len)))
|
|
27
|
+
state.start += len + 4
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_decodeEntry (state, enc) {
|
|
31
|
+
if (state.end - state.start < 8) return null
|
|
32
|
+
const cksum = cenc.uint32.decode(state)
|
|
33
|
+
const l = cenc.uint32.decode(state)
|
|
34
|
+
const length = l >>> 2
|
|
35
|
+
const headerBit = l & 1
|
|
36
|
+
const partialBit = l & 2
|
|
37
|
+
|
|
38
|
+
if (state.end - state.start < length) return null
|
|
39
|
+
|
|
40
|
+
const end = state.start + length
|
|
41
|
+
|
|
42
|
+
if (crc32(state.buffer.subarray(state.start - 4, end)) !== cksum) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
result.message = enc.decode({ start: state.start, end, buffer: state.buffer })
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
state.start = end
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async open () {
|
|
60
|
+
const buffer = await this._readAll() // TODO: stream the oplog in on load maybe?
|
|
61
|
+
const state = { start: 0, end: buffer.byteLength, buffer }
|
|
62
|
+
const result = { header: null, entries: [] }
|
|
63
|
+
|
|
64
|
+
this.byteLength = 0
|
|
65
|
+
this.length = 0
|
|
66
|
+
|
|
67
|
+
const h1 = this._decodeEntry(state, this.headerEncoding)
|
|
68
|
+
state.start = this._pageSize
|
|
69
|
+
|
|
70
|
+
const h2 = this._decodeEntry(state, this.headerEncoding)
|
|
71
|
+
state.start = this._entryOffset
|
|
72
|
+
|
|
73
|
+
if (!h1 && !h2) {
|
|
74
|
+
// reset state...
|
|
75
|
+
this.flushed = false
|
|
76
|
+
this._headers[0] = 1
|
|
77
|
+
this._headers[1] = 0
|
|
78
|
+
|
|
79
|
+
if (buffer.byteLength >= this._entryOffset) {
|
|
80
|
+
throw new Error('Oplog file appears corrupt or out of date')
|
|
81
|
+
}
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.flushed = true
|
|
86
|
+
|
|
87
|
+
if (h1 && !h2) {
|
|
88
|
+
this._headers[0] = h1.header
|
|
89
|
+
this._headers[1] = h1.header
|
|
90
|
+
} else if (!h1 && h2) {
|
|
91
|
+
this._headers[0] = (h2.header + 1) & 1
|
|
92
|
+
this._headers[1] = h2.header
|
|
93
|
+
} else {
|
|
94
|
+
this._headers[0] = h1.header
|
|
95
|
+
this._headers[1] = h2.header
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const header = (this._headers[0] + this._headers[1]) & 1
|
|
99
|
+
const decoded = []
|
|
100
|
+
|
|
101
|
+
result.header = header ? h2.message : h1.message
|
|
102
|
+
|
|
103
|
+
while (true) {
|
|
104
|
+
const entry = this._decodeEntry(state, this.entryEncoding)
|
|
105
|
+
if (!entry) break
|
|
106
|
+
if (entry.header !== header) break
|
|
107
|
+
|
|
108
|
+
decoded.push(entry)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
while (decoded.length > 0 && decoded[decoded.length - 1].partial) decoded.pop()
|
|
112
|
+
|
|
113
|
+
for (const e of decoded) {
|
|
114
|
+
result.entries.push(e.message)
|
|
115
|
+
this.byteLength += e.byteLength
|
|
116
|
+
this.length++
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const size = this.byteLength + this._entryOffset
|
|
120
|
+
|
|
121
|
+
if (size === buffer.byteLength) return result
|
|
122
|
+
|
|
123
|
+
await new Promise((resolve, reject) => {
|
|
124
|
+
this.storage.del(size, Infinity, err => {
|
|
125
|
+
if (err) return reject(err)
|
|
126
|
+
resolve()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_readAll () {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
this.storage.open(err => {
|
|
136
|
+
if (err && err.code !== 'ENOENT') return reject(err)
|
|
137
|
+
if (err) return resolve(b4a.alloc(0))
|
|
138
|
+
this.storage.stat((err, stat) => {
|
|
139
|
+
if (err && err.code !== 'ENOENT') return reject(err)
|
|
140
|
+
this.storage.read(0, stat.size, (err, buf) => {
|
|
141
|
+
if (err) return reject(err)
|
|
142
|
+
resolve(buf)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
flush (header) {
|
|
150
|
+
const state = { start: 8, end: 8, buffer: null }
|
|
151
|
+
const i = this._headers[0] === this._headers[1] ? 1 : 0
|
|
152
|
+
const bit = (this._headers[i] + 1) & 1
|
|
153
|
+
|
|
154
|
+
this.headerEncoding.preencode(state, header)
|
|
155
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
156
|
+
this.headerEncoding.encode(state, header)
|
|
157
|
+
this._addHeader(state, state.end - 8, bit, 0)
|
|
158
|
+
|
|
159
|
+
return this._writeHeaderAndTruncate(i, bit, state.buffer)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_writeHeaderAndTruncate (i, bit, buf) {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
this.storage.write(i === 0 ? 0 : this._pageSize, buf, err => {
|
|
165
|
+
if (err) return reject(err)
|
|
166
|
+
|
|
167
|
+
this.storage.del(this._entryOffset, Infinity, err => {
|
|
168
|
+
if (err) return reject(err)
|
|
169
|
+
|
|
170
|
+
this._headers[i] = bit
|
|
171
|
+
this.byteLength = 0
|
|
172
|
+
this.length = 0
|
|
173
|
+
this.flushed = true
|
|
174
|
+
|
|
175
|
+
resolve()
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
append (batch, atomic = true) {
|
|
182
|
+
if (!Array.isArray(batch)) batch = [batch]
|
|
183
|
+
|
|
184
|
+
const state = { start: 0, end: batch.length * 8, buffer: null }
|
|
185
|
+
const bit = (this._headers[0] + this._headers[1]) & 1
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < batch.length; i++) {
|
|
188
|
+
this.entryEncoding.preencode(state, batch[i])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < batch.length; i++) {
|
|
194
|
+
const start = state.start += 8 // space for header
|
|
195
|
+
const partial = (atomic && i < batch.length - 1) ? 2 : 0
|
|
196
|
+
this.entryEncoding.encode(state, batch[i])
|
|
197
|
+
this._addHeader(state, state.start - start, bit, partial)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return this._append(state.buffer, batch.length)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
close () {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
this.storage.close(err => {
|
|
206
|
+
if (err) return reject(err)
|
|
207
|
+
resolve()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_append (buf, count) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
this.storage.write(this._entryOffset + this.byteLength, buf, err => {
|
|
215
|
+
if (err) return reject(err)
|
|
216
|
+
|
|
217
|
+
this.byteLength += buf.byteLength
|
|
218
|
+
this.length += count
|
|
219
|
+
|
|
220
|
+
resolve()
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
}
|