autobee 1.0.0 → 1.0.3

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 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 i autobee
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 auto = new Autobee(corestore, [key], [options])`
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
 
@@ -0,0 +1,341 @@
1
+ const path = require('path')
2
+
3
+ module.exports = function buildAutobaseSchema(schema, DIR) {
4
+ const autobase = schema.namespace('autobase-compat')
5
+
6
+ autobase.require(path.join(DIR, 'legacy.js'))
7
+
8
+ autobase.register({
9
+ name: 'checkout',
10
+ compact: true,
11
+ fields: [
12
+ {
13
+ name: 'key',
14
+ type: 'fixed32',
15
+ required: true
16
+ },
17
+ {
18
+ name: 'length',
19
+ type: 'uint',
20
+ required: true
21
+ }
22
+ ]
23
+ })
24
+
25
+ autobase.register({
26
+ name: 'clock',
27
+ array: true,
28
+ compact: true,
29
+ type: '@autobase-compat/checkout'
30
+ })
31
+
32
+ autobase.register({
33
+ name: 'index-checkpoint',
34
+ compact: true,
35
+ fields: [
36
+ {
37
+ name: 'signature',
38
+ type: 'fixed64',
39
+ required: true
40
+ },
41
+ {
42
+ name: 'length',
43
+ type: 'uint',
44
+ required: true
45
+ }
46
+ ]
47
+ })
48
+
49
+ autobase.register({
50
+ name: 'boot-record-v0',
51
+ external: 'BootRecordV0'
52
+ })
53
+
54
+ autobase.register({
55
+ name: 'boot-record-raw',
56
+ fields: [
57
+ {
58
+ name: 'key',
59
+ type: 'fixed32',
60
+ required: true
61
+ },
62
+ {
63
+ name: 'systemLength',
64
+ type: 'uint',
65
+ required: true
66
+ },
67
+ {
68
+ name: 'indexersUpdated',
69
+ type: 'bool',
70
+ required: false
71
+ },
72
+ {
73
+ name: 'fastForwarding',
74
+ type: 'bool',
75
+ required: false
76
+ },
77
+ {
78
+ name: 'recoveries',
79
+ type: 'uint',
80
+ required: false
81
+ }
82
+ ]
83
+ })
84
+
85
+ autobase.register({
86
+ name: 'boot-record',
87
+ versions: [
88
+ {
89
+ version: 0,
90
+ type: '@autobase-compat/boot-record-v0'
91
+ },
92
+ {
93
+ version: 3,
94
+ type: '@autobase-compat/boot-record-raw'
95
+ }
96
+ ]
97
+ })
98
+
99
+ autobase.register({
100
+ name: 'checkpointer',
101
+ compact: true,
102
+ fields: [
103
+ {
104
+ name: 'checkpointer',
105
+ type: 'uint',
106
+ required: false
107
+ },
108
+ {
109
+ name: 'checkpoint',
110
+ type: '@autobase-compat/index-checkpoint',
111
+ required: false
112
+ }
113
+ ]
114
+ })
115
+
116
+ autobase.register({
117
+ name: 'checkpoint',
118
+ compact: false,
119
+ fields: [
120
+ {
121
+ name: 'system',
122
+ type: '@autobase-compat/checkpointer',
123
+ required: false
124
+ },
125
+ {
126
+ name: 'encryption',
127
+ type: '@autobase-compat/checkpointer',
128
+ required: false
129
+ },
130
+ {
131
+ name: 'user',
132
+ type: '@autobase-compat/checkpointer',
133
+ array: true,
134
+ required: false
135
+ }
136
+ ]
137
+ })
138
+
139
+ autobase.register({
140
+ name: 'digest',
141
+ compact: false,
142
+ fields: [
143
+ {
144
+ name: 'pointer',
145
+ type: 'uint',
146
+ required: false
147
+ },
148
+ {
149
+ name: 'key',
150
+ type: 'fixed32',
151
+ required: false
152
+ }
153
+ ]
154
+ })
155
+
156
+ autobase.register({
157
+ name: 'node',
158
+ compact: true,
159
+ fields: [
160
+ {
161
+ name: 'heads',
162
+ type: '@autobase-compat/clock',
163
+ required: true
164
+ },
165
+ {
166
+ name: 'batch',
167
+ type: 'uint',
168
+ required: true
169
+ },
170
+ {
171
+ name: 'value',
172
+ type: 'buffer',
173
+ required: true
174
+ }
175
+ ]
176
+ })
177
+
178
+ autobase.register({
179
+ name: 'user-view-trace',
180
+ compact: true,
181
+ fields: [
182
+ {
183
+ name: 'view',
184
+ type: 'uint',
185
+ required: true
186
+ },
187
+ {
188
+ name: 'blocks',
189
+ type: 'uint',
190
+ array: true,
191
+ required: true
192
+ }
193
+ ]
194
+ })
195
+
196
+ autobase.register({
197
+ name: 'trace',
198
+ compact: false,
199
+ fields: [
200
+ {
201
+ name: 'system',
202
+ type: 'uint',
203
+ array: true,
204
+ required: true
205
+ },
206
+ {
207
+ name: 'encryption',
208
+ type: 'uint',
209
+ array: true,
210
+ required: true
211
+ },
212
+ {
213
+ name: 'user',
214
+ type: '@autobase-compat/user-view-trace',
215
+ array: true,
216
+ required: true
217
+ }
218
+ ]
219
+ })
220
+
221
+ autobase.register({
222
+ name: 'oplog-message-v0',
223
+ external: 'OplogMessageV0'
224
+ })
225
+
226
+ autobase.register({
227
+ name: 'oplog-message-v1',
228
+ external: 'OplogMessageV1'
229
+ })
230
+
231
+ autobase.register({
232
+ name: 'oplog-message-v2',
233
+ compact: false,
234
+ fields: [
235
+ {
236
+ name: 'node',
237
+ type: '@autobase-compat/node',
238
+ required: true
239
+ },
240
+ {
241
+ name: 'checkpoint',
242
+ type: '@autobase-compat/checkpoint',
243
+ required: false
244
+ },
245
+ {
246
+ name: 'digest',
247
+ type: '@autobase-compat/digest',
248
+ required: false
249
+ },
250
+ {
251
+ name: 'optimistic',
252
+ type: 'bool',
253
+ required: false
254
+ },
255
+ {
256
+ name: 'trace',
257
+ type: '@autobase-compat/trace',
258
+ required: false
259
+ }
260
+ ]
261
+ })
262
+
263
+ autobase.register({
264
+ name: 'info-v1',
265
+ fields: [
266
+ {
267
+ name: 'members',
268
+ type: 'uint',
269
+ required: true
270
+ },
271
+ {
272
+ name: 'pendingIndexers',
273
+ type: 'fixed32',
274
+ array: true,
275
+ required: true
276
+ },
277
+ {
278
+ name: 'indexers',
279
+ type: '@autobase-compat/clock',
280
+ required: true
281
+ },
282
+ {
283
+ name: 'heads',
284
+ type: '@autobase-compat/clock',
285
+ required: true
286
+ },
287
+ {
288
+ name: 'views',
289
+ type: '@autobase-compat/clock',
290
+ required: true
291
+ }
292
+ ]
293
+ })
294
+
295
+ autobase.register({
296
+ name: 'info-v2',
297
+ fields: [
298
+ {
299
+ name: 'members',
300
+ type: 'uint',
301
+ required: true
302
+ },
303
+ {
304
+ name: 'pendingIndexers',
305
+ type: 'fixed32',
306
+ array: true,
307
+ required: true
308
+ },
309
+ {
310
+ name: 'indexers',
311
+ type: '@autobase-compat/clock',
312
+ required: true
313
+ },
314
+ {
315
+ name: 'heads',
316
+ type: '@autobase-compat/clock',
317
+ required: true
318
+ },
319
+ {
320
+ name: 'views',
321
+ type: '@autobase-compat/clock',
322
+ required: true
323
+ },
324
+ {
325
+ name: 'encryptionLength',
326
+ type: 'uint',
327
+ required: true
328
+ },
329
+ {
330
+ name: 'entropy',
331
+ type: 'fixed32',
332
+ required: false
333
+ }
334
+ ]
335
+ })
336
+
337
+ autobase.register({
338
+ name: 'member',
339
+ external: 'SystemWriterV0'
340
+ })
341
+ }