autobee 2.0.0-rc.7 → 2.0.0-rc.9

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.
@@ -169,7 +169,7 @@ module.exports = function buildAutobaseSchema(schema, DIR) {
169
169
  },
170
170
  {
171
171
  name: 'value',
172
- type: 'buffer',
172
+ type: 'optionalBuffer',
173
173
  required: true
174
174
  }
175
175
  ]
@@ -153,18 +153,18 @@ const Node = {
153
153
  preencode(state, m) {
154
154
  Clock.preencode(state, m.heads)
155
155
  c.uint.preencode(state, m.batch)
156
- c.buffer.preencode(state, m.value)
156
+ c.optionalBuffer.preencode(state, m.value)
157
157
  },
158
158
  encode(state, m) {
159
159
  Clock.encode(state, m.heads)
160
160
  c.uint.encode(state, m.batch)
161
- c.buffer.encode(state, m.value)
161
+ c.optionalBuffer.encode(state, m.value)
162
162
  },
163
163
  decode(state, m) {
164
164
  return {
165
165
  heads: Clock.decode(state),
166
166
  batch: c.uint.decode(state),
167
- value: c.buffer.decode(state)
167
+ value: c.optionalBuffer.decode(state)
168
168
  }
169
169
  }
170
170
  }
@@ -97,8 +97,9 @@ const encoding4 = {
97
97
  // @autobase-compat/boot-record
98
98
  const encoding5 = {
99
99
  preencode(state, m) {
100
- c.uint.preencode(state, m.version)
101
- switch (m.version) {
100
+ const v = m.version ?? 3
101
+ c.uint.preencode(state, v)
102
+ switch (v) {
102
103
  case 0:
103
104
  encoding3.preencode(state, m)
104
105
  break
@@ -112,8 +113,9 @@ const encoding5 = {
112
113
  }
113
114
  },
114
115
  encode(state, m) {
115
- c.uint.encode(state, m.version)
116
- switch (m.version) {
116
+ const v = m.version ?? 3
117
+ c.uint.encode(state, v)
118
+ switch (v) {
117
119
  case 0:
118
120
  encoding3.encode(state, m)
119
121
  break
@@ -236,17 +238,17 @@ const encoding9 = {
236
238
  preencode(state, m) {
237
239
  encoding1.preencode(state, m.heads)
238
240
  c.uint.preencode(state, m.batch)
239
- c.buffer.preencode(state, m.value)
241
+ c.optionalBuffer.preencode(state, m.value)
240
242
  },
241
243
  encode(state, m) {
242
244
  encoding1.encode(state, m.heads)
243
245
  c.uint.encode(state, m.batch)
244
- c.buffer.encode(state, m.value)
246
+ c.optionalBuffer.encode(state, m.value)
245
247
  },
246
248
  decode(state) {
247
249
  const r0 = encoding1.decode(state)
248
250
  const r1 = c.uint.decode(state)
249
- const r2 = c.buffer.decode(state)
251
+ const r2 = c.optionalBuffer.decode(state)
250
252
 
251
253
  return {
252
254
  heads: r0,
@@ -496,8 +498,9 @@ const encoding18 = {
496
498
  // @autobee/system-info
497
499
  const encoding19 = {
498
500
  preencode(state, m) {
499
- c.uint.preencode(state, m.version)
500
- switch (m.version) {
501
+ const v = m.version ?? 3
502
+ c.uint.preencode(state, v)
503
+ switch (v) {
501
504
  case 0:
502
505
  case 1:
503
506
  encoding15.preencode(state, m)
@@ -513,8 +516,9 @@ const encoding19 = {
513
516
  }
514
517
  },
515
518
  encode(state, m) {
516
- c.uint.encode(state, m.version)
517
- switch (m.version) {
519
+ const v = m.version ?? 3
520
+ c.uint.encode(state, v)
521
+ switch (v) {
518
522
  case 0:
519
523
  case 1:
520
524
  encoding15.encode(state, m)
@@ -603,8 +607,9 @@ const encoding20 = {
603
607
  // @autobee/system-writer
604
608
  const encoding21 = {
605
609
  preencode(state, m) {
606
- c.uint.preencode(state, m.version)
607
- switch (m.version) {
610
+ const v = m.version ?? 4
611
+ c.uint.preencode(state, v)
612
+ switch (v) {
608
613
  case 0:
609
614
  case 1:
610
615
  case 2:
@@ -619,8 +624,9 @@ const encoding21 = {
619
624
  }
620
625
  },
621
626
  encode(state, m) {
622
- c.uint.encode(state, m.version)
623
- switch (m.version) {
627
+ const v = m.version ?? 4
628
+ c.uint.encode(state, v)
629
+ switch (v) {
624
630
  case 0:
625
631
  case 1:
626
632
  case 2:
@@ -905,8 +911,9 @@ const encoding29 = {
905
911
  // @autobee/oplog
906
912
  const encoding30 = {
907
913
  preencode(state, m) {
908
- c.uint.preencode(state, m.version)
909
- switch (m.version) {
914
+ const v = m.version ?? 3
915
+ c.uint.preencode(state, v)
916
+ switch (v) {
910
917
  case 0:
911
918
  encoding12.preencode(state, m)
912
919
  break
@@ -924,8 +931,9 @@ const encoding30 = {
924
931
  }
925
932
  },
926
933
  encode(state, m) {
927
- c.uint.encode(state, m.version)
928
- switch (m.version) {
934
+ const v = m.version ?? 3
935
+ c.uint.encode(state, v)
936
+ switch (v) {
929
937
  case 0:
930
938
  encoding12.encode(state, m)
931
939
  break
@@ -194,7 +194,7 @@
194
194
  {
195
195
  "name": "value",
196
196
  "required": true,
197
- "type": "buffer",
197
+ "type": "optionalBuffer",
198
198
  "version": 1
199
199
  }
200
200
  ]
@@ -805,5 +805,15 @@
805
805
  }
806
806
  ]
807
807
  }
808
+ ],
809
+ "namespaces": [
810
+ {
811
+ "name": "autobee",
812
+ "external": "../../legacy.js"
813
+ },
814
+ {
815
+ "name": "autobase-compat",
816
+ "external": "../../legacy.js"
817
+ }
808
818
  ]
809
819
  }
package/index.js CHANGED
@@ -589,58 +589,74 @@ module.exports = class Autobee extends ReadyResource {
589
589
  }
590
590
  }
591
591
 
592
- async _getOplog(key, length) {
592
+ async _getOplog(key, length, opts = null) {
593
593
  const core = this.openCore(key)
594
- await core.ready()
595
594
 
596
- const target = length >= 0 ? length : core.length
597
- if (target === 0) return null
595
+ try {
596
+ await core.ready()
598
597
 
599
- const buf = await core.get(target - 1)
600
- let op = encoding.decodeOplog(buf)
598
+ const target = length >= 0 ? length : core.length
599
+ if (target === 0) return null
601
600
 
602
- if (op.version < 3) {
603
- op = await this._inflateLegacyOplog(buf, core, target - 1)
604
- }
601
+ const buf = await core.get(target - 1, opts)
602
+ if (buf === null) return null
605
603
 
606
- await core.close()
604
+ let op = encoding.decodeOplog(buf)
607
605
 
608
- if (buf === null) return null
606
+ // legacy nodes always inflate, but only indexers carry views - callers
607
+ // that need system info must check op.views
608
+ if (op.version < 3) {
609
+ op = await this._inflateLegacyOplog(buf, core, target - 1, opts)
610
+ }
609
611
 
610
- return {
611
- key,
612
- length: target,
613
- op
612
+ return {
613
+ key,
614
+ length: target,
615
+ op
616
+ }
617
+ } finally {
618
+ await core.close()
614
619
  }
615
620
  }
616
621
 
617
- async _inflateLegacyOplog(buf, core, seq) {
622
+ async _inflateLegacyOplog(buf, core, seq, opts = null) {
618
623
  const m = encoding.decodeRawOplog(buf)
624
+
625
+ const op = {
626
+ version: m.version,
627
+ timestamp: 0,
628
+ links: m.node.heads,
629
+ batch: { start: 0, end: m.node.batch - 1 },
630
+ views: null,
631
+ optimistic: !!m.optimistic,
632
+ value: m.node.value
633
+ }
634
+
635
+ if (m.digest === null || m.checkpoint === null) return op
636
+
619
637
  const fetches = []
620
638
 
621
- fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer) : buf)
622
- fetches.push(m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer) : buf)
639
+ fetches.push(m.digest.pointer ? core.get(seq - m.digest.pointer, opts) : buf)
640
+ fetches.push(
641
+ m.checkpoint.pointer ? core.get(seq - m.checkpoint.system.checkpointer, opts) : buf
642
+ )
623
643
 
624
644
  const [digestNode, checkpointNode] = await Promise.all(fetches)
645
+ // a wait: false get we could not satisfy locally
646
+ if (digestNode === null || checkpointNode === null) return op
625
647
 
626
648
  const { digest } = encoding.decodeRawOplog(digestNode)
627
649
  const { checkpoint } = encoding.decodeRawOplog(checkpointNode)
628
650
 
629
- return {
630
- version: m.version,
631
- timestamp: 0,
632
- links: m.node.heads,
633
- batch: { start: 0, end: m.node.batch - 1 },
634
- views: {
635
- system: {
636
- key: digest.key,
637
- length: checkpoint.system.checkpoint.length
638
- },
639
- flushes: seq
651
+ op.views = {
652
+ system: {
653
+ key: digest.key,
654
+ length: checkpoint.system.checkpoint.length
640
655
  },
641
- optimistic: !!m.optimistic,
642
- value: m.node.value
656
+ flushes: seq
643
657
  }
658
+
659
+ return op
644
660
  }
645
661
 
646
662
  async _update(changes) {
@@ -1129,14 +1145,16 @@ module.exports = class Autobee extends ReadyResource {
1129
1145
 
1130
1146
  async _rebootFromHead(head, trusted, { force = false, wait = true } = {}) {
1131
1147
  const oplog = await this._getOplog(head.key, head.length)
1132
- if (!oplog) return false
1148
+ if (!oplog) return null
1133
1149
 
1134
1150
  const verified = trusted ? await this._getOplog(trusted.key, trusted.length) : oplog
1151
+ if (!verified) return null
1135
1152
 
1136
- if (!verified.op.views) return null
1153
+ // legacy nodes from non-indexers have no system info to reboot from
1154
+ if (!oplog.op.views || !verified.op.views) return null
1137
1155
 
1138
1156
  if (!force && verified.op.views.flushes - this.system.flushes < MIN_FF_GAP) {
1139
- return false
1157
+ return null
1140
1158
  }
1141
1159
 
1142
1160
  const moved = await this._moveTo(batchToHead(verified.op.views.system), {
package/lib/topo.js CHANGED
@@ -66,17 +66,66 @@ async function rollback(auto, system, verified) {
66
66
  }
67
67
  }
68
68
 
69
+ // nodes we cannot resolve are returned as null rather than dropped, so callers
70
+ // can tell an incomplete replay from a short one
69
71
  async function replay(auto) {
70
- const ch = auto.system.bee.createChangesStream()
71
72
  const replay = []
72
73
 
73
- for await (const data of ch) {
74
- const { ref } = getOplog(data)
75
- replay.push(ref)
74
+ for await (const entry of oplogIterator(auto.system.bee)) {
75
+ // the walk was cut short, nothing below this point is reachable
76
+ if (entry === null) {
77
+ replay.push(null)
78
+ break
79
+ }
80
+
81
+ for (const ref of entry.refs) replay.push(ref)
76
82
  }
77
83
 
78
- const inflated = await inflate(auto, replay.reverse())
79
- return inflated.flatMap((b) => b.batch)
84
+ // same reasoning as the iterator - never block on what we do not have
85
+ const inflated = await inflate(auto, replay.reverse(), { wait: false })
86
+ return inflated.flatMap((b) => (b === null ? [null] : b.batch))
87
+ }
88
+
89
+ // tolerates legacy nodes instead of asserting
90
+ async function* oplogIterator(bee) {
91
+ try {
92
+ for await (const node of bee.createChangesStream({ wait: false })) {
93
+ const legacy = isLegacyNode(node)
94
+ const refs = getOplogs(node)
95
+ if (refs.length) yield { node, refs, legacy }
96
+ }
97
+ } catch {
98
+ yield null // signal data not available
99
+ }
100
+ }
101
+
102
+ // legacy nodes carry no isOplog flag so we cannot tell which writer update is
103
+ // the oplog to fetch - yield every candidate until we have a better signal
104
+ function getOplogs(data) {
105
+ const undo = data.tail || EMPTY_HEAD
106
+ const oplogs = []
107
+ const candidates = []
108
+
109
+ for (const { keys } of data.batch) {
110
+ for (const k of keys) {
111
+ if (k.key[0] !== 1) continue
112
+
113
+ let value = null
114
+ try {
115
+ value = encoding.decodeSystemWriter(k.key, k.value)
116
+ } catch {
117
+ continue
118
+ }
119
+
120
+ // writer was just added, nothing to fetch
121
+ if (!value.length) continue
122
+
123
+ if (value.isOplog) oplogs.push({ undo, oplog: value })
124
+ else candidates.push({ undo, oplog: value })
125
+ }
126
+ }
127
+
128
+ return oplogs.length ? oplogs : candidates
80
129
  }
81
130
 
82
131
  async function sort(auto, batch) {
@@ -193,12 +242,19 @@ function cmp(a, b) {
193
242
  return a.length < b.length ? -1 : 1
194
243
  }
195
244
 
196
- async function inflate(auto, reorder) {
245
+ async function inflate(auto, reorder, opts = null) {
197
246
  const promises = []
198
247
  const sessions = new Map()
199
248
 
200
249
  for (let i = 0; i < reorder.length; i++) {
201
250
  const ref = reorder[i]
251
+
252
+ // marker for a node we could not resolve, pass it through
253
+ if (ref === null) {
254
+ promises.push(null)
255
+ continue
256
+ }
257
+
202
258
  const id = b4a.toString(ref.oplog.key, 'hex')
203
259
  // only seeds the node - reapplication re-resolves from the witness
204
260
  const weight = ref.oplog.weight
@@ -215,7 +271,7 @@ async function inflate(auto, reorder) {
215
271
  sessions.set(id, core)
216
272
  }
217
273
 
218
- promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i))
274
+ promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, opts))
219
275
  }
220
276
 
221
277
  try {
@@ -225,9 +281,10 @@ async function inflate(auto, reorder) {
225
281
  }
226
282
  }
227
283
 
228
- async function getOplogBatch(undo, core, length, weight, index) {
284
+ async function getOplogBatch(undo, core, length, weight, index, opts = null) {
229
285
  const seq = length - 1
230
- const block = await core.get(seq)
286
+ const block = await core.get(seq, opts)
287
+ if (block === null) return null
231
288
  const oplog = encoding.decodeOplog(block)
232
289
  const head = createNode(core, length, weight, oplog)
233
290
 
@@ -239,12 +296,18 @@ async function getOplogBatch(undo, core, length, weight, index) {
239
296
  const end = seq + head.batch.end // skip head, we have it
240
297
 
241
298
  for (let i = start; i < end; i++) {
242
- remaining.push(core.get(i))
299
+ remaining.push(core.get(i, opts))
243
300
  }
244
301
 
245
302
  if (remaining.length) {
246
303
  const blocks = await Promise.all(remaining)
247
304
  for (let i = 0; i < blocks.length; i++) {
305
+ // keep the hole so the gap in the batch stays visible
306
+ if (blocks[i] === null) {
307
+ batch.push(null)
308
+ continue
309
+ }
310
+
248
311
  const oplog = encoding.decodeOplog(blocks[i])
249
312
  batch.push(createNode(core, i + start + 1, weight, oplog))
250
313
  }
@@ -255,8 +318,12 @@ async function getOplogBatch(undo, core, length, weight, index) {
255
318
  }
256
319
 
257
320
  function getLocalBatch(undo, writer, length, weight, index) {
321
+ if (writer.core.closed) throw new Error('Writer core is closed')
322
+
258
323
  if (length > writer.core.length) {
259
- if (!writer.pending) throw new Error('No local nodes')
324
+ if (!writer.pending) {
325
+ throw new Error(`No local nodes: looking for ${length}, found ${writer.core.length}`)
326
+ }
260
327
 
261
328
  let i = 0
262
329
  while (i < writer.pending.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0-rc.7",
3
+ "version": "2.0.0-rc.9",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",
@@ -26,7 +26,7 @@
26
26
  "autobee-encryption": "^0.1.2",
27
27
  "autobee-wakeup": "^1.1.3",
28
28
  "b4a": "^1.8.0",
29
- "compact-encoding": "^2.18.0",
29
+ "compact-encoding": "^3.3.0",
30
30
  "hyperbee2": "^2.10.3",
31
31
  "hypercore": "^11.33.2",
32
32
  "hypercore-id-encoding": "^1.3.0",