@fireproof/core 0.7.3-dev.2 → 0.8.0-dev.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.
@@ -0,0 +1,144 @@
1
+ import * as CBW from '@ipld/car/buffer-writer';
2
+ import * as raw from 'multiformats/codecs/raw';
3
+ import { encrypt, decrypt } from '../crypto.js';
4
+ import { parse } from 'multiformats/link';
5
+ import { sha256 } from 'multiformats/hashes/sha2';
6
+ import * as Block from 'multiformats/block';
7
+ import { Buffer } from 'buffer';
8
+ // @ts-ignore
9
+ import { bf } from 'prolly-trees/utils';
10
+ // @ts-ignore
11
+ import { nocache as cache } from 'prolly-trees/cache';
12
+ const chunker = bf(30);
13
+ export async function getEmptyLoader() {
14
+ const theseWriteableBlocks = new VMemoryBlockstore();
15
+ return {
16
+ blocks: theseWriteableBlocks,
17
+ put: async (cid, bytes) => {
18
+ return await theseWriteableBlocks.put(cid, bytes);
19
+ },
20
+ get: async (cid) => {
21
+ const got = await theseWriteableBlocks.get(cid);
22
+ return got.bytes;
23
+ }
24
+ };
25
+ }
26
+ export class VMemoryBlockstore {
27
+ /** @type {Map<string, Uint8Array>} */
28
+ blocks = new Map();
29
+ instanceId = Math.random().toString(36).slice(2);
30
+ async get(cid) {
31
+ const bytes = this.blocks.get(cid.toString());
32
+ if (!bytes)
33
+ throw new Error('block not found ' + cid.toString());
34
+ return { cid, bytes };
35
+ }
36
+ /**
37
+ * @param {any} cid
38
+ * @param {Uint8Array} bytes
39
+ */
40
+ async put(cid, bytes) {
41
+ this.blocks.set(cid.toString(), bytes);
42
+ }
43
+ *entries() {
44
+ for (const [str, bytes] of this.blocks) {
45
+ yield { cid: parse(str), bytes };
46
+ }
47
+ }
48
+ }
49
+ export const blocksToCarBlock = async (rootCids, blocks) => {
50
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
51
+ let size = 0;
52
+ if (!Array.isArray(rootCids)) {
53
+ rootCids = [rootCids];
54
+ }
55
+ const headerSize = CBW.headerLength({ roots: rootCids });
56
+ size += headerSize;
57
+ if (!Array.isArray(blocks)) {
58
+ blocks = Array.from(blocks.entries());
59
+ }
60
+ for (const { cid, bytes } of blocks) {
61
+ // console.log(cid, bytes)
62
+ size += CBW.blockLength({ cid, bytes });
63
+ }
64
+ const buffer = new Uint8Array(size);
65
+ const writer = await CBW.createWriter(buffer, { headerSize });
66
+ for (const cid of rootCids) {
67
+ writer.addRoot(cid);
68
+ }
69
+ for (const { cid, bytes } of blocks) {
70
+ writer.write({ cid, bytes });
71
+ }
72
+ await writer.close();
73
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
74
+ };
75
+ export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial, cids) => {
76
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
77
+ const encryptedBlocks = [];
78
+ const theCids = cids;
79
+ // console.trace('blocksToEncryptedCarBlock', blocks)
80
+ // for (const { cid } of blocks.entries()) {
81
+ // theCids.push(cid.toString())
82
+ // }
83
+ // console.log(
84
+ // 'encrypting',
85
+ // theCids.length,
86
+ // 'blocks',
87
+ // theCids.includes(innerBlockStoreClockRootCid.toString()),
88
+ // keyMaterial
89
+ // )
90
+ // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
91
+ let last;
92
+ for await (const block of encrypt({
93
+ cids: theCids,
94
+ get: async (cid) => {
95
+ // console.log('getencrypt', cid)
96
+ const got = blocks.get(cid);
97
+ // console.log('got', got)
98
+ return got.block ? ({ cid, bytes: got.block }) : got;
99
+ },
100
+ key: encryptionKey,
101
+ hasher: sha256,
102
+ chunker,
103
+ cache,
104
+ // codec: dagcbor, // should be crypto?
105
+ root: innerBlockStoreClockRootCid
106
+ })) {
107
+ encryptedBlocks.push(block);
108
+ last = block;
109
+ }
110
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
111
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
112
+ return encryptedCar;
113
+ };
114
+ // { root, get, key, cache, chunker, hasher }
115
+ const memoizeDecryptedCarBlocks = new Map();
116
+ export const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
117
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
118
+ return memoizeDecryptedCarBlocks.get(cid.toString());
119
+ }
120
+ else {
121
+ const blocksPromise = (async () => {
122
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
123
+ // console.log('decrypting', keyMaterial, cid.toString())
124
+ const cids = new Set();
125
+ const decryptedBlocks = [];
126
+ for await (const block of decrypt({
127
+ root: cid,
128
+ get,
129
+ key: decryptionKey,
130
+ chunker,
131
+ hasher: sha256,
132
+ cache
133
+ // codec: dagcbor
134
+ })) {
135
+ // console.log('decrypted', block.cid.toString())
136
+ decryptedBlocks.push(block);
137
+ cids.add(block.cid.toString());
138
+ }
139
+ return { blocks: decryptedBlocks, cids };
140
+ })();
141
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
142
+ return blocksPromise;
143
+ }
144
+ };
@@ -52,7 +52,7 @@ export class Browser extends Base {
52
52
  if (this.config.readonly)
53
53
  return;
54
54
  try {
55
- return localStorage.setItem(this.headerKey(branch), this.prepareHeader(header));
55
+ return localStorage.setItem(this.headerKey(branch), header);
56
56
  }
57
57
  catch (e) { }
58
58
  }
@@ -40,12 +40,11 @@ export class Filesystem extends Base {
40
40
  return JSON.parse(header);
41
41
  }
42
42
  async writeHeader(branch, header) {
43
- // console.log('saveHeader', this.isBrowser)
43
+ // console.log('saveHeader fs', header)
44
44
  if (this.config.readonly)
45
45
  return;
46
- const pHeader = this.prepareHeader(header);
47
46
  // console.log('writeHeader fs', branch, pHeader)
48
- await writeSync(this.headerFilename(branch), pHeader);
47
+ await writeSync(this.headerFilename(branch), header);
49
48
  }
50
49
  headerFilename(branch = 'main') {
51
50
  // console.log('headerFilename', this.config.dataDir, this.name)
@@ -45,11 +45,10 @@ export class Rest extends Base {
45
45
  async writeHeader(branch, header) {
46
46
  if (this.config.readonly)
47
47
  return;
48
- const pHeader = this.prepareHeader(header);
49
48
  // console.log('writeHeader rt', branch, pHeader)
50
49
  const response = await fetch(this.headerURL(branch), {
51
50
  method: 'PUT',
52
- body: pHeader,
51
+ body: header,
53
52
  headers: { 'Content-Type': 'application/json' }
54
53
  });
55
54
  if (!response.ok)
@@ -1,40 +0,0 @@
1
- import fetch from 'cross-fetch';
2
- import { Base } from './base.js';
3
- const defaultConfig = {
4
- upload: () => { },
5
- url: (cid) => `https://${cid}.ipfs.w3s.link/`
6
- };
7
- export class UCAN extends Base {
8
- constructor(name, config = {}) {
9
- super(name, Object.assign({}, defaultConfig, config));
10
- }
11
- async writeCars(cars) {
12
- if (this.config.readonly)
13
- return;
14
- for (const { cid, bytes } of cars) {
15
- console.log(`write UCAN ${cid}, ${bytes.length} bytes`);
16
- const upCid = await this.config.upload(bytes);
17
- console.log(`wrote UCAN ${cid}, ${upCid}`);
18
- // if (!response.ok) throw new Error(`An error occurred: ${response.statusText}`)
19
- }
20
- }
21
- async readCar(carCid) {
22
- const carURL = this.config.url(carCid);
23
- const response = await fetch(carURL);
24
- if (!response.ok)
25
- throw new Error(`An error occurred: ${response.statusText}`);
26
- const got = await response.arrayBuffer();
27
- return new Uint8Array(got);
28
- }
29
- async loadHeader(branch = 'main') {
30
- return headerMock.get(branch);
31
- }
32
- async writeHeader(branch, header) {
33
- if (this.config.readonly)
34
- return;
35
- const pHeader = this.prepareHeader(header);
36
- // console.log('writeHeader rt', branch, pHeader)
37
- headerMock.set(branch, pHeader);
38
- }
39
- }
40
- const headerMock = new Map();
@@ -0,0 +1,144 @@
1
+ import * as CBW from '@ipld/car/buffer-writer';
2
+ import * as raw from 'multiformats/codecs/raw';
3
+ import { encrypt, decrypt } from '../crypto.js';
4
+ import { parse } from 'multiformats/link';
5
+ import { sha256 } from 'multiformats/hashes/sha2';
6
+ import * as Block from 'multiformats/block';
7
+ import { Buffer } from 'buffer';
8
+ // @ts-ignore
9
+ import { bf } from 'prolly-trees/utils';
10
+ // @ts-ignore
11
+ import { nocache as cache } from 'prolly-trees/cache';
12
+ const chunker = bf(30);
13
+ export async function getEmptyLoader() {
14
+ const theseWriteableBlocks = new VMemoryBlockstore();
15
+ return {
16
+ blocks: theseWriteableBlocks,
17
+ put: async (cid, bytes) => {
18
+ return await theseWriteableBlocks.put(cid, bytes);
19
+ },
20
+ get: async (cid) => {
21
+ const got = await theseWriteableBlocks.get(cid);
22
+ return got.bytes;
23
+ }
24
+ };
25
+ }
26
+ export class VMemoryBlockstore {
27
+ /** @type {Map<string, Uint8Array>} */
28
+ blocks = new Map();
29
+ instanceId = Math.random().toString(36).slice(2);
30
+ async get(cid) {
31
+ const bytes = this.blocks.get(cid.toString());
32
+ if (!bytes)
33
+ throw new Error('block not found ' + cid.toString());
34
+ return { cid, bytes };
35
+ }
36
+ /**
37
+ * @param {any} cid
38
+ * @param {Uint8Array} bytes
39
+ */
40
+ async put(cid, bytes) {
41
+ this.blocks.set(cid.toString(), bytes);
42
+ }
43
+ *entries() {
44
+ for (const [str, bytes] of this.blocks) {
45
+ yield { cid: parse(str), bytes };
46
+ }
47
+ }
48
+ }
49
+ export const blocksToCarBlock = async (rootCids, blocks) => {
50
+ // console.log('blocksToCarBlock', rootCids, blocks.constructor.name)
51
+ let size = 0;
52
+ if (!Array.isArray(rootCids)) {
53
+ rootCids = [rootCids];
54
+ }
55
+ const headerSize = CBW.headerLength({ roots: rootCids });
56
+ size += headerSize;
57
+ if (!Array.isArray(blocks)) {
58
+ blocks = Array.from(blocks.entries());
59
+ }
60
+ for (const { cid, bytes } of blocks) {
61
+ // console.log(cid, bytes)
62
+ size += CBW.blockLength({ cid, bytes });
63
+ }
64
+ const buffer = new Uint8Array(size);
65
+ const writer = await CBW.createWriter(buffer, { headerSize });
66
+ for (const cid of rootCids) {
67
+ writer.addRoot(cid);
68
+ }
69
+ for (const { cid, bytes } of blocks) {
70
+ writer.write({ cid, bytes });
71
+ }
72
+ await writer.close();
73
+ return await Block.encode({ value: writer.bytes, hasher: sha256, codec: raw });
74
+ };
75
+ export const blocksToEncryptedCarBlock = async (innerBlockStoreClockRootCid, blocks, keyMaterial, cids) => {
76
+ const encryptionKey = Buffer.from(keyMaterial, 'hex');
77
+ const encryptedBlocks = [];
78
+ const theCids = cids;
79
+ // console.trace('blocksToEncryptedCarBlock', blocks)
80
+ // for (const { cid } of blocks.entries()) {
81
+ // theCids.push(cid.toString())
82
+ // }
83
+ // console.log(
84
+ // 'encrypting',
85
+ // theCids.length,
86
+ // 'blocks',
87
+ // theCids.includes(innerBlockStoreClockRootCid.toString()),
88
+ // keyMaterial
89
+ // )
90
+ // console.log('cids', theCids, innerBlockStoreClockRootCid.toString())
91
+ let last;
92
+ for await (const block of encrypt({
93
+ cids: theCids,
94
+ get: async (cid) => {
95
+ // console.log('getencrypt', cid)
96
+ const got = blocks.get(cid);
97
+ // console.log('got', got)
98
+ return got.block ? ({ cid, bytes: got.block }) : got;
99
+ },
100
+ key: encryptionKey,
101
+ hasher: sha256,
102
+ chunker,
103
+ cache,
104
+ // codec: dagcbor, // should be crypto?
105
+ root: innerBlockStoreClockRootCid
106
+ })) {
107
+ encryptedBlocks.push(block);
108
+ last = block;
109
+ }
110
+ // console.log('last', last.cid.toString(), 'for clock', innerBlockStoreClockRootCid.toString())
111
+ const encryptedCar = await blocksToCarBlock(last.cid, encryptedBlocks);
112
+ return encryptedCar;
113
+ };
114
+ // { root, get, key, cache, chunker, hasher }
115
+ const memoizeDecryptedCarBlocks = new Map();
116
+ export const blocksFromEncryptedCarBlock = async (cid, get, keyMaterial) => {
117
+ if (memoizeDecryptedCarBlocks.has(cid.toString())) {
118
+ return memoizeDecryptedCarBlocks.get(cid.toString());
119
+ }
120
+ else {
121
+ const blocksPromise = (async () => {
122
+ const decryptionKey = Buffer.from(keyMaterial, 'hex');
123
+ // console.log('decrypting', keyMaterial, cid.toString())
124
+ const cids = new Set();
125
+ const decryptedBlocks = [];
126
+ for await (const block of decrypt({
127
+ root: cid,
128
+ get,
129
+ key: decryptionKey,
130
+ chunker,
131
+ hasher: sha256,
132
+ cache
133
+ // codec: dagcbor
134
+ })) {
135
+ // console.log('decrypted', block.cid.toString())
136
+ decryptedBlocks.push(block);
137
+ cids.add(block.cid.toString());
138
+ }
139
+ return { blocks: decryptedBlocks, cids };
140
+ })();
141
+ memoizeDecryptedCarBlocks.set(cid.toString(), blocksPromise);
142
+ return blocksPromise;
143
+ }
144
+ };
package/dist/sync.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import SimplePeer from 'simple-peer';
2
2
  import { parseCID } from './database.js';
3
3
  import { decodeEventBlock } from './clock.js';
4
- import { blocksToCarBlock, blocksToEncryptedCarBlock } from './storage/base.js';
4
+ import { blocksToCarBlock, blocksToEncryptedCarBlock } from './storage/utils.js';
5
5
  import { CarReader } from '@ipld/car';
6
6
  /**
7
7
  * @typedef {import('./database.js').Database} Database
package/dist/valet.js CHANGED
@@ -75,16 +75,22 @@ export class Valet {
75
75
  if (this.secondary) {
76
76
  // console.log('getValetBlock secondary', dataCID)
77
77
  try {
78
+ // eslint-disable-next-line
78
79
  const { block, reader } = await this.secondary.getLoaderBlock(dataCID);
80
+ const writeableCarReader = await this.primary.getWriteableCarReader(reader);
81
+ // console.log('getValetBlock secondary', dataCID, block.length)
82
+ // eslint-disable-next-line
79
83
  const cids = new Set();
80
84
  for await (const { cid } of reader.entries()) {
81
85
  // console.log(cid, bytes)
82
86
  cids.add(cid.toString());
83
87
  }
84
- reader.get = reader.gat; // some consumers prefer get
88
+ // reader.get = reader.gat // some consumers prefer get
85
89
  // console.log('replicating', reader.root)
86
- reader.lastCid = reader.root.cid;
87
- await this.primary.parkCar(reader, [...cids]);
90
+ writeableCarReader.lastCid = reader.root.cid;
91
+ writeableCarReader.head = [];
92
+ await this.primary.parkCar(writeableCarReader, cids).catch(e => console.error('parkCar error', e));
93
+ // console.log('FIX THIS', did)
88
94
  return block;
89
95
  }
90
96
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.7.3-dev.2",
3
+ "version": "0.8.0-dev.2",
4
4
  "description": "Live data for React, accelerated by proofs, powered by IPFS",
5
5
  "main": "dist/src/fireproof.js",
6
6
  "module": "dist/src/fireproof.mjs",
@@ -42,10 +42,8 @@
42
42
  "@ipld/car": "^5.1.0",
43
43
  "@ipld/dag-cbor": "^9.0.0",
44
44
  "@jsonlines/core": "^1.0.2",
45
- "@ucanto/principal": "^8.0.0",
46
- "@ucanto/server": "^8.0.1",
47
- "@ucanto/transport": "^8.0.0",
48
- "@ucanto/validator": "^8.0.0",
45
+ "@web3-storage/clock": "^0.3.0",
46
+ "@web3-storage/w3up-client": "^7.0.0",
49
47
  "async": "^3.2.4",
50
48
  "charwise": "^3.0.1",
51
49
  "cross-fetch": "^3.1.6",
package/src/blockstore.js CHANGED
@@ -221,6 +221,9 @@ export const doTransaction = async (label, blockstore, doFun, doSync = true) =>
221
221
  const innerBlockstore = blockstore.begin(label)
222
222
  try {
223
223
  const result = await doFun(innerBlockstore)
224
+ // console.log('doTransaction', label, 'result', result.head)
225
+ if (result && result.head) { innerBlockstore.head = result.head }
226
+ // pass the latest clock head for writing to the valet
224
227
  // @ts-ignore
225
228
  await blockstore.commit(innerBlockstore, doSync)
226
229
  return result
@@ -236,6 +239,7 @@ export const doTransaction = async (label, blockstore, doFun, doSync = true) =>
236
239
  export class InnerBlockstore {
237
240
  /** @type {Map<string, Uint8Array>} */
238
241
  blocks = new Map()
242
+ head = []
239
243
  lastCid = null
240
244
  label = ''
241
245
  parentBlockstore = null
package/src/crypto.js CHANGED
@@ -16,7 +16,11 @@ const encrypt = async function * ({ get, cids, hasher, key, cache, chunker, root
16
16
  let eroot
17
17
  for (const string of cids) {
18
18
  const cid = CID.parse(string)
19
- const unencrypted = await get(cid)
19
+ let unencrypted = await get(cid)
20
+ if (!unencrypted.cid) {
21
+ unencrypted = { cid, bytes: unencrypted }
22
+ }
23
+ // console.log('unencrypted', unencrypted)
20
24
  const block = await encode({ ...await codec.encrypt({ ...unencrypted, key }), codec, hasher })
21
25
  // console.log(`encrypting ${string} as ${block.cid}`)
22
26
  yield block
package/src/database.js CHANGED
@@ -5,6 +5,8 @@ import charwise from 'charwise'
5
5
  import { CID } from 'multiformats'
6
6
  import { DbIndex as Index } from './db-index.js'
7
7
 
8
+ import { Remote } from './remote.js'
9
+
8
10
  // TypeScript Types
9
11
  // eslint-disable-next-line no-unused-vars
10
12
  // import { CID } from 'multiformats/dist/types/src/cid.js'
@@ -29,14 +31,15 @@ export class Database {
29
31
  indexes = new Map()
30
32
  rootCache = null
31
33
  eventsCache = new Map()
32
-
34
+ remote = null
35
+ name = ''
33
36
  constructor (name, config = {}) {
34
37
  this.name = name
35
38
  this.clock = []
36
39
  this.instanceId = `fp.${this.name}.${Math.random().toString(36).substring(2, 7)}`
37
40
  this.blocks = new TransactionBlockstore(name, config)
38
41
  this.indexBlocks = new TransactionBlockstore(name ? name + '.indexes' : null, { primary: config.index })
39
-
42
+ this.remote = new Remote(this, name, config)
40
43
  this.config = config
41
44
  // todo we can wait for index blocks elsewhere
42
45
  this.ready = Promise.all([this.blocks.ready, this.indexBlocks.ready]).then(([blocksReady, indexReady]) => {
@@ -52,7 +55,7 @@ export class Database {
52
55
  clock.add(cid)
53
56
  }
54
57
  if (header.index) {
55
- this.indexBlocks.valet.primary.setCarCidMapCarCid(header.index.car)
58
+ this.indexBlocks.valet.primary.setLastCar(header.index.car)
56
59
  this.indexBlocks.valet.primary.setKeyMaterial(header.index.key)
57
60
  }
58
61
  if (header.indexes) {
@@ -92,11 +95,11 @@ export class Database {
92
95
 
93
96
  toHeader () {
94
97
  return {
95
- clock: this.clockToJSON(),
98
+ // clock: this.clockToJSON(),
96
99
  name: this.name,
97
100
  index: {
98
101
  key: this.indexBlocks.valet?.primary.keyMaterial,
99
- car: this.indexBlocks.valet?.primary.valetRootCarCid?.toString()
102
+ car: this.indexBlocks.valet?.primary.lastCar?.toString()
100
103
  },
101
104
  indexes: [...this.indexes.values()].map(index => index.toJSON())
102
105
  }
@@ -112,9 +115,9 @@ export class Database {
112
115
  return (clock || this.clock).map(cid => cid.toString())
113
116
  }
114
117
 
115
- maybeSaveClock () {
118
+ async maybeSaveClock () {
116
119
  if (this.name && this.blocks.valet) {
117
- this.blocks.valet.saveHeader(this.toHeader())
120
+ await this.blocks.valet.saveHeader(this.toHeader())
118
121
  }
119
122
  }
120
123
 
@@ -277,11 +280,13 @@ export class Database {
277
280
  * @memberof Fireproof
278
281
  * @instance
279
282
  */
280
- async put ({ _id, _proof, ...doc }) {
283
+ async put ({ _id, _proof, _clock, ...doc }) {
281
284
  await this.ready
282
285
  const id = _id || 'f' + Math.random().toString(36).slice(2)
286
+ doc = JSON.parse(JSON.stringify(doc))
287
+ if (_clock) doc._clock = _clock
283
288
  await this.runValidation({ _id: id, ...doc })
284
- return await this.putToProllyTree({ key: id, value: doc }, doc._clock)
289
+ return await this.putToProllyTree({ key: id, value: doc }, _clock)
285
290
  }
286
291
 
287
292
  /**
package/src/fireproof.js CHANGED
@@ -62,6 +62,7 @@ export class Fireproof {
62
62
 
63
63
  static snapshot (database, clock) {
64
64
  const definition = database.toJSON()
65
+ definition.clock = database.clockToJSON()
65
66
  if (clock) {
66
67
  definition.clock = clock.map(c => parseCID(c))
67
68
  definition.indexes.forEach(index => {
package/src/loader.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Browser } from './storage/browser.js'
2
2
  import { Rest } from './storage/rest.js'
3
- import { UCAN } from './storage/ucan.js'
4
3
 
5
4
  export const Loader = {
6
5
  appropriate: (name, config = {}) => {
@@ -12,10 +11,6 @@ export const Loader = {
12
11
  return new Rest(name, config)
13
12
  }
14
13
 
15
- if (config.type === 'ucan') {
16
- return new UCAN(name, config)
17
- }
18
-
19
14
  return new Browser(name, config)
20
15
  }
21
16
  }
package/src/prolly.js CHANGED
@@ -307,7 +307,7 @@ export async function root (inBlocks, head, doFull = false) {
307
307
  bigPut(nb)
308
308
  }
309
309
  // console.log('root root', newProllyRootNode.constructor.name, newProllyRootNode)
310
- return { clockCIDs, node: newProllyRootNode }
310
+ return { clockCIDs, node: newProllyRootNode, head }
311
311
  },
312
312
  false
313
313
  )