@fireproof/vendor 2.0.1 → 2.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/package.json +17 -16
- package/src/@web3-storage/pail/dist/src/api.d.ts +3 -1
- package/src/@web3-storage/pail/dist/src/api.d.ts.map +1 -1
- package/src/@web3-storage/pail/dist/src/api.js +1 -0
- package/src/@web3-storage/pail/dist/src/batch/api.js +1 -0
- package/src/@web3-storage/pail/dist/src/batch/index.js +241 -0
- package/src/@web3-storage/pail/dist/src/batch/shard.js +12 -0
- package/src/@web3-storage/pail/dist/src/block.js +66 -0
- package/src/@web3-storage/pail/dist/src/clock/api.js +1 -0
- package/src/@web3-storage/pail/dist/src/clock/index.js +178 -0
- package/src/@web3-storage/pail/dist/src/crdt/api.js +1 -0
- package/src/@web3-storage/pail/dist/src/crdt/batch/api.js +1 -0
- package/src/@web3-storage/pail/dist/src/crdt/batch/index.js +140 -0
- package/src/@web3-storage/pail/dist/src/crdt/index.js +344 -0
- package/src/@web3-storage/pail/dist/src/diff.js +151 -0
- package/src/@web3-storage/pail/dist/src/index.js +356 -0
- package/src/@web3-storage/pail/dist/src/merge.js +42 -0
- package/src/@web3-storage/pail/dist/src/shard.js +166 -0
- package/src/@web3-storage/pail/dist/tsconfig.tsbuildinfo +1 -1
- package/src/@web3-storage/pail/src/api.ts +3 -1
- package/src/@web3-storage/pail/src/api.js +0 -1
package/package.json
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fireproof/vendor",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "vendor patch repo to support esm",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
"./@web3-storage/pail": {
|
|
9
9
|
"types": "./src/@web3-storage/pail/dist/src/index.d.ts",
|
|
10
|
-
"import": "./src/@web3-storage/pail/src/index.js"
|
|
10
|
+
"import": "./src/@web3-storage/pail/dist/src/index.js"
|
|
11
11
|
},
|
|
12
12
|
"./@web3-storage/pail/api": {
|
|
13
13
|
"types": "./src/@web3-storage/pail/dist/src/api.d.ts",
|
|
14
|
-
"import": "./src/@web3-storage/pail/src/api.js"
|
|
14
|
+
"import": "./src/@web3-storage/pail/dist/src/api.js"
|
|
15
15
|
},
|
|
16
16
|
"./@web3-storage/pail/batch": {
|
|
17
17
|
"types": "./src/@web3-storage/pail/dist/src/batch/index.d.ts",
|
|
18
|
-
"import": "./src/@web3-storage/pail/src/batch/index.js"
|
|
18
|
+
"import": "./src/@web3-storage/pail/dist/src/batch/index.js"
|
|
19
19
|
},
|
|
20
20
|
"./@web3-storage/pail/batch/api": {
|
|
21
21
|
"types": "./src/@web3-storage/pail/dist/src/batch/api.d.ts",
|
|
22
|
-
"import": "./src/@web3-storage/pail/src/batch/api.js"
|
|
22
|
+
"import": "./src/@web3-storage/pail/dist/src/batch/api.js"
|
|
23
23
|
},
|
|
24
24
|
"./@web3-storage/pail/block": {
|
|
25
25
|
"types": "./src/@web3-storage/pail/dist/src/block.d.ts",
|
|
26
|
-
"import": "./src/@web3-storage/pail/src/block.js"
|
|
26
|
+
"import": "./src/@web3-storage/pail/dist/src/block.js"
|
|
27
27
|
},
|
|
28
28
|
"./@web3-storage/pail/clock": {
|
|
29
29
|
"types": "./src/@web3-storage/pail/dist/src/clock/index.d.ts",
|
|
30
|
-
"import": "./src/@web3-storage/pail/src/clock/index.js"
|
|
30
|
+
"import": "./src/@web3-storage/pail/dist/src/clock/index.js"
|
|
31
31
|
},
|
|
32
32
|
"./@web3-storage/pail/clock/api": {
|
|
33
33
|
"types": "./src/@web3-storage/pail/dist/src/clock/api.d.ts",
|
|
34
|
-
"import": "./src/@web3-storage/pail/src/clock/api.js"
|
|
34
|
+
"import": "./src/@web3-storage/pail/dist/src/clock/api.js"
|
|
35
35
|
},
|
|
36
36
|
"./@web3-storage/pail/crdt": {
|
|
37
37
|
"types": "./src/@web3-storage/pail/dist/src/crdt/index.d.ts",
|
|
38
|
-
"import": "./src/@web3-storage/pail/src/crdt/index.js"
|
|
38
|
+
"import": "./src/@web3-storage/pail/dist/src/crdt/index.js"
|
|
39
39
|
},
|
|
40
40
|
"./@web3-storage/pail/crdt/api": {
|
|
41
41
|
"types": "./src/@web3-storage/pail/dist/src/crdt/api.d.ts",
|
|
42
|
-
"import": "./src/@web3-storage/pail/src/crdt/api.js"
|
|
42
|
+
"import": "./src/@web3-storage/pail/dist/src/crdt/api.js"
|
|
43
43
|
},
|
|
44
44
|
"./@web3-storage/pail/crdt/batch": {
|
|
45
45
|
"types": "./src/@web3-storage/pail/dist/src/crdt/batch/index.d.ts",
|
|
46
|
-
"import": "./src/@web3-storage/pail/src/crdt/batch/index.js"
|
|
46
|
+
"import": "./src/@web3-storage/pail/dist/src/crdt/batch/index.js"
|
|
47
47
|
},
|
|
48
48
|
"./@web3-storage/pail/crdt/batch/api": {
|
|
49
49
|
"types": "./src/@web3-storage/pail/dist/src/crdt/batch/api.d.ts",
|
|
50
|
-
"import": "./src/@web3-storage/pail/src/crdt/batch/api.js"
|
|
50
|
+
"import": "./src/@web3-storage/pail/dist/src/crdt/batch/api.js"
|
|
51
51
|
},
|
|
52
52
|
"./@web3-storage/pail/diff": {
|
|
53
53
|
"types": "./src/@web3-storage/pail/dist/src/diff.d.ts",
|
|
54
|
-
"import": "./src/@web3-storage/pail/src/diff.js"
|
|
54
|
+
"import": "./src/@web3-storage/pail/dist/src/diff.js"
|
|
55
55
|
},
|
|
56
56
|
"./@web3-storage/pail/merge": {
|
|
57
57
|
"types": "./src/@web3-storage/pail/dist/src/merge.d.ts",
|
|
58
|
-
"import": "./src/@web3-storage/pail/src/merge.js"
|
|
58
|
+
"import": "./src/@web3-storage/pail/dist/src/merge.js"
|
|
59
59
|
},
|
|
60
60
|
"./@web3-storage/pail/shard": {
|
|
61
61
|
"types": "./src/@web3-storage/pail/dist/src/shard.d.ts",
|
|
62
|
-
"import": "./src/@web3-storage/pail/src/shard.js"
|
|
62
|
+
"import": "./src/@web3-storage/pail/dist/src/shard.js"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"keywords": [],
|
|
@@ -75,7 +75,8 @@
|
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@ipld/dag-cbor": "^9.2.2",
|
|
78
|
-
"
|
|
78
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
79
|
+
"multiformats": "^13.3.2"
|
|
79
80
|
},
|
|
80
81
|
"scripts": {
|
|
81
82
|
"build": "tsx merge-package.ts --prepare --verbose '@web3-storage/pail,https://github.com/mabels/pail.git,pnpm'"
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Version } from 'multiformats';
|
|
2
|
+
import type { Link, UnknownLink } from 'multiformats/link/interface';
|
|
3
|
+
import type { Block, BlockView } from 'multiformats/block/interface';
|
|
2
4
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
3
5
|
import * as dagCBOR from '@ipld/dag-cbor';
|
|
4
6
|
export { Link, UnknownLink, BlockView, Block, Version };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
import * as API from './api.js';
|
|
3
|
+
import { ShardFetcher, isPrintableASCII } from '../shard.js';
|
|
4
|
+
import * as Shard from '../shard.js';
|
|
5
|
+
import * as BatcherShard from './shard.js';
|
|
6
|
+
/** @implements {API.Batcher} */
|
|
7
|
+
class Batcher {
|
|
8
|
+
#committed = false;
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} init
|
|
11
|
+
* @param {API.BlockFetcher} init.blocks Block storage.
|
|
12
|
+
* @param {API.BatcherShardEntry[]} init.entries The entries in this shard.
|
|
13
|
+
* @param {string} init.prefix Key prefix.
|
|
14
|
+
* @param {number} init.version Shard compatibility version.
|
|
15
|
+
* @param {string} init.keyChars Characters allowed in keys, referring to a known character set.
|
|
16
|
+
* @param {number} init.maxKeySize Max key size in bytes.
|
|
17
|
+
* @param {API.ShardBlockView} init.base Original shard this batcher is based on.
|
|
18
|
+
*/
|
|
19
|
+
constructor({ blocks, entries, prefix, version, keyChars, maxKeySize, base }) {
|
|
20
|
+
this.blocks = blocks;
|
|
21
|
+
this.prefix = prefix;
|
|
22
|
+
this.entries = [...entries];
|
|
23
|
+
this.base = base;
|
|
24
|
+
this.version = version;
|
|
25
|
+
this.keyChars = keyChars;
|
|
26
|
+
this.maxKeySize = maxKeySize;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} key The key of the value to put.
|
|
30
|
+
* @param {API.UnknownLink} value The value to put.
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
async put(key, value) {
|
|
34
|
+
if (this.#committed)
|
|
35
|
+
throw new BatchCommittedError();
|
|
36
|
+
return put(this.blocks, this, key, value);
|
|
37
|
+
}
|
|
38
|
+
async commit() {
|
|
39
|
+
if (this.#committed)
|
|
40
|
+
throw new BatchCommittedError();
|
|
41
|
+
this.#committed = true;
|
|
42
|
+
return commit(this);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* @param {object} init
|
|
46
|
+
* @param {API.BlockFetcher} init.blocks Block storage.
|
|
47
|
+
* @param {API.ShardLink} init.link CID of the shard block.
|
|
48
|
+
*/
|
|
49
|
+
static async create({ blocks, link }) {
|
|
50
|
+
const shards = new ShardFetcher(blocks);
|
|
51
|
+
const base = await shards.get(link);
|
|
52
|
+
return new Batcher({ blocks, base, ...base.value });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @param {API.BlockFetcher} blocks
|
|
57
|
+
* @param {API.BatcherShard} shard
|
|
58
|
+
* @param {string} key The key of the value to put.
|
|
59
|
+
* @param {API.UnknownLink} value The value to put.
|
|
60
|
+
* @returns {Promise<void>}
|
|
61
|
+
*/
|
|
62
|
+
export const put = async (blocks, shard, key, value) => {
|
|
63
|
+
if (shard.keyChars !== Shard.KeyCharsASCII) {
|
|
64
|
+
throw new Error(`unsupported key character set: ${shard.keyChars}`);
|
|
65
|
+
}
|
|
66
|
+
if (!isPrintableASCII(key)) {
|
|
67
|
+
throw new Error('key contains non-ASCII characters');
|
|
68
|
+
}
|
|
69
|
+
// ensure utf8 encoded key is smaller than max
|
|
70
|
+
if (new TextEncoder().encode(key).length > shard.maxKeySize) {
|
|
71
|
+
throw new Error(`UTF-8 encoded key exceeds max size of ${shard.maxKeySize} bytes`);
|
|
72
|
+
}
|
|
73
|
+
const shards = new ShardFetcher(blocks);
|
|
74
|
+
const dest = await traverse(shards, shard, key);
|
|
75
|
+
if (dest.shard !== shard) {
|
|
76
|
+
shard = dest.shard;
|
|
77
|
+
key = dest.key;
|
|
78
|
+
}
|
|
79
|
+
/** @type {API.BatcherShardEntry} */
|
|
80
|
+
let entry = [dest.key, value];
|
|
81
|
+
let targetEntries = [...dest.shard.entries];
|
|
82
|
+
for (const [i, e] of targetEntries.entries()) {
|
|
83
|
+
const [k, v] = e;
|
|
84
|
+
// is this just a replace?
|
|
85
|
+
if (k === dest.key)
|
|
86
|
+
break;
|
|
87
|
+
// do we need to shard this entry?
|
|
88
|
+
const shortest = k.length < dest.key.length ? k : dest.key;
|
|
89
|
+
const other = shortest === k ? dest.key : k;
|
|
90
|
+
let common = '';
|
|
91
|
+
for (const char of shortest) {
|
|
92
|
+
const next = common + char;
|
|
93
|
+
if (!other.startsWith(next))
|
|
94
|
+
break;
|
|
95
|
+
common = next;
|
|
96
|
+
}
|
|
97
|
+
if (common.length) {
|
|
98
|
+
/** @type {API.ShardEntry[]} */
|
|
99
|
+
let entries = [];
|
|
100
|
+
// if the existing entry key or new key is equal to the common prefix,
|
|
101
|
+
// then the existing value / new value needs to persist in the parent
|
|
102
|
+
// shard. Otherwise they persist in this new shard.
|
|
103
|
+
if (common !== dest.key) {
|
|
104
|
+
entries = Shard.putEntry(entries, [dest.key.slice(common.length), value]);
|
|
105
|
+
}
|
|
106
|
+
if (common !== k) {
|
|
107
|
+
entries = Shard.putEntry(entries, asShardEntry([k.slice(common.length), v]));
|
|
108
|
+
}
|
|
109
|
+
let child = BatcherShard.create({
|
|
110
|
+
...Shard.configure(dest.shard),
|
|
111
|
+
prefix: dest.shard.prefix + common,
|
|
112
|
+
entries
|
|
113
|
+
});
|
|
114
|
+
// need to spread as access by index does not consider utf-16 surrogates
|
|
115
|
+
const commonChars = [...common];
|
|
116
|
+
// create parent shards for each character of the common prefix
|
|
117
|
+
for (let i = commonChars.length - 1; i > 0; i--) {
|
|
118
|
+
/** @type {API.ShardEntryShardValue | API.ShardEntryShardAndValueValue} */
|
|
119
|
+
let parentValue;
|
|
120
|
+
// if the first iteration and the existing entry key is equal to the
|
|
121
|
+
// common prefix, then existing value needs to persist in this parent
|
|
122
|
+
if (i === commonChars.length - 1 && common === k) {
|
|
123
|
+
if (Array.isArray(v))
|
|
124
|
+
throw new Error('found a shard link when expecting a value');
|
|
125
|
+
parentValue = [child, v];
|
|
126
|
+
}
|
|
127
|
+
else if (i === commonChars.length - 1 && common === dest.key) {
|
|
128
|
+
parentValue = [child, value];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
parentValue = [child];
|
|
132
|
+
}
|
|
133
|
+
const parent = BatcherShard.create({
|
|
134
|
+
...Shard.configure(dest.shard),
|
|
135
|
+
prefix: dest.shard.prefix + commonChars.slice(0, i).join(''),
|
|
136
|
+
entries: [[commonChars[i], parentValue]]
|
|
137
|
+
});
|
|
138
|
+
child = parent;
|
|
139
|
+
}
|
|
140
|
+
// remove the sharded entry
|
|
141
|
+
targetEntries.splice(i, 1);
|
|
142
|
+
// create the entry that will be added to target
|
|
143
|
+
if (commonChars.length === 1 && common === k) {
|
|
144
|
+
if (Array.isArray(v))
|
|
145
|
+
throw new Error('found a shard link when expecting a value');
|
|
146
|
+
entry = [commonChars[0], [child, v]];
|
|
147
|
+
}
|
|
148
|
+
else if (commonChars.length === 1 && common === dest.key) {
|
|
149
|
+
entry = [commonChars[0], [child, value]];
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
entry = [commonChars[0], [child]];
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
shard.entries = Shard.putEntry(asShardEntries(targetEntries), asShardEntry(entry));
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Traverse from the passed shard through to the correct shard for the passed
|
|
161
|
+
* key.
|
|
162
|
+
*
|
|
163
|
+
* @param {ShardFetcher} shards
|
|
164
|
+
* @param {API.BatcherShard} shard
|
|
165
|
+
* @param {string} key
|
|
166
|
+
* @returns {Promise<{ shard: API.BatcherShard, key: string }>}
|
|
167
|
+
*/
|
|
168
|
+
export const traverse = async (shards, shard, key) => {
|
|
169
|
+
for (let i = 0; i < shard.entries.length; i++) {
|
|
170
|
+
const [k, v] = shard.entries[i];
|
|
171
|
+
if (key <= k)
|
|
172
|
+
break;
|
|
173
|
+
if (key.startsWith(k) && Array.isArray(v)) {
|
|
174
|
+
if (Shard.isShardLink(v[0])) {
|
|
175
|
+
const blk = await shards.get(v[0]);
|
|
176
|
+
const batcher = BatcherShard.create({ base: blk, ...blk.value });
|
|
177
|
+
shard.entries[i] = [k, v[1] == null ? [batcher] : [batcher, v[1]]];
|
|
178
|
+
return traverse(shards, batcher, key.slice(k.length));
|
|
179
|
+
}
|
|
180
|
+
return traverse(shards, v[0], key.slice(k.length));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { shard, key };
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Encode all altered shards in the batch and return the new root CID and
|
|
187
|
+
* difference blocks.
|
|
188
|
+
*
|
|
189
|
+
* @param {API.BatcherShard} shard
|
|
190
|
+
*/
|
|
191
|
+
export const commit = async (shard) => {
|
|
192
|
+
/** @type {API.ShardBlockView[]} */
|
|
193
|
+
const additions = [];
|
|
194
|
+
/** @type {API.ShardBlockView[]} */
|
|
195
|
+
const removals = [];
|
|
196
|
+
/** @type {API.ShardEntry[]} */
|
|
197
|
+
const entries = [];
|
|
198
|
+
for (const entry of shard.entries) {
|
|
199
|
+
if (Array.isArray(entry[1]) && !Shard.isShardLink(entry[1][0])) {
|
|
200
|
+
const result = await commit(entry[1][0]);
|
|
201
|
+
entries.push([
|
|
202
|
+
entry[0],
|
|
203
|
+
entry[1][1] == null ? [result.root] : [result.root, entry[1][1]]
|
|
204
|
+
]);
|
|
205
|
+
additions.push(...result.additions);
|
|
206
|
+
removals.push(...result.removals);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
entries.push(asShardEntry(entry));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const block = await Shard.encodeBlock(Shard.withEntries(entries, shard));
|
|
213
|
+
additions.push(block);
|
|
214
|
+
if (shard.base && shard.base.cid.toString() === block.cid.toString()) {
|
|
215
|
+
return { root: block.cid, additions: [], removals: [] };
|
|
216
|
+
}
|
|
217
|
+
if (shard.base)
|
|
218
|
+
removals.push(shard.base);
|
|
219
|
+
return { root: block.cid, additions, removals };
|
|
220
|
+
};
|
|
221
|
+
/** @param {API.BatcherShardEntry[]} entries */
|
|
222
|
+
const asShardEntries = entries => /** @type {API.ShardEntry[]} */ (entries);
|
|
223
|
+
/** @param {API.BatcherShardEntry} entry */
|
|
224
|
+
const asShardEntry = entry => /** @type {API.ShardEntry} */ (entry);
|
|
225
|
+
/**
|
|
226
|
+
* @param {API.BlockFetcher} blocks Bucket block storage.
|
|
227
|
+
* @param {API.ShardLink} root CID of the root shard block.
|
|
228
|
+
* @returns {Promise<API.Batcher>}
|
|
229
|
+
*/
|
|
230
|
+
export const create = (blocks, root) => Batcher.create({ blocks, link: root });
|
|
231
|
+
export class BatchCommittedError extends Error {
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} [message]
|
|
234
|
+
* @param {ErrorOptions} [options]
|
|
235
|
+
*/
|
|
236
|
+
constructor(message, options) {
|
|
237
|
+
super(message ?? 'batch already committed', options);
|
|
238
|
+
this.code = BatchCommittedError.code;
|
|
239
|
+
}
|
|
240
|
+
static code = 'ERR_BATCH_COMMITTED';
|
|
241
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
import * as API from './api.js';
|
|
3
|
+
import { configure } from '../shard.js';
|
|
4
|
+
/**
|
|
5
|
+
* @param {API.BatcherShardInit} [init]
|
|
6
|
+
* @returns {API.BatcherShard}
|
|
7
|
+
*/
|
|
8
|
+
export const create = init => ({
|
|
9
|
+
base: init?.base,
|
|
10
|
+
entries: [...(init?.entries ?? [])],
|
|
11
|
+
...configure(init)
|
|
12
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
import * as API from './api.js';
|
|
3
|
+
import { parse } from 'multiformats/link';
|
|
4
|
+
/** @implements {API.BlockFetcher} */
|
|
5
|
+
export class MemoryBlockstore {
|
|
6
|
+
/** @type {Map<string, Uint8Array>} */
|
|
7
|
+
#blocks = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* @param {Array<import('multiformats').Block>} [blocks]
|
|
10
|
+
*/
|
|
11
|
+
constructor(blocks) {
|
|
12
|
+
if (blocks) {
|
|
13
|
+
this.#blocks = new Map(blocks.map(b => [b.cid.toString(), b.bytes]));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** @type {API.BlockFetcher['get']} */
|
|
17
|
+
async get(cid) {
|
|
18
|
+
const bytes = this.#blocks.get(cid.toString());
|
|
19
|
+
if (!bytes)
|
|
20
|
+
return;
|
|
21
|
+
return { cid, bytes };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @param {API.UnknownLink} cid
|
|
25
|
+
* @param {Uint8Array} bytes
|
|
26
|
+
*/
|
|
27
|
+
async put(cid, bytes) {
|
|
28
|
+
this.#blocks.set(cid.toString(), bytes);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @param {API.UnknownLink} cid
|
|
32
|
+
* @param {Uint8Array} bytes
|
|
33
|
+
*/
|
|
34
|
+
putSync(cid, bytes) {
|
|
35
|
+
this.#blocks.set(cid.toString(), bytes);
|
|
36
|
+
}
|
|
37
|
+
/** @param {API.UnknownLink} cid */
|
|
38
|
+
async delete(cid) {
|
|
39
|
+
this.#blocks.delete(cid.toString());
|
|
40
|
+
}
|
|
41
|
+
/** @param {API.UnknownLink} cid */
|
|
42
|
+
deleteSync(cid) {
|
|
43
|
+
this.#blocks.delete(cid.toString());
|
|
44
|
+
}
|
|
45
|
+
*entries() {
|
|
46
|
+
for (const [str, bytes] of this.#blocks) {
|
|
47
|
+
yield { cid: parse(str), bytes };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class MultiBlockFetcher {
|
|
52
|
+
/** @type {API.BlockFetcher[]} */
|
|
53
|
+
#fetchers;
|
|
54
|
+
/** @param {API.BlockFetcher[]} fetchers */
|
|
55
|
+
constructor(...fetchers) {
|
|
56
|
+
this.#fetchers = fetchers;
|
|
57
|
+
}
|
|
58
|
+
/** @type {API.BlockFetcher['get']} */
|
|
59
|
+
async get(link) {
|
|
60
|
+
for (const f of this.#fetchers) {
|
|
61
|
+
const v = await f.get(link);
|
|
62
|
+
if (v)
|
|
63
|
+
return v;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Block, encode, decode } from 'multiformats/block';
|
|
2
|
+
import { sha256 } from 'multiformats/hashes/sha2';
|
|
3
|
+
import * as cbor from '@ipld/dag-cbor';
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
import * as API from './api.js';
|
|
6
|
+
/**
|
|
7
|
+
* Advance the clock by adding an event.
|
|
8
|
+
*
|
|
9
|
+
* @template T
|
|
10
|
+
* @param {API.BlockFetcher} blocks Block storage.
|
|
11
|
+
* @param {API.EventLink<T>[]} head The head of the clock.
|
|
12
|
+
* @param {API.EventLink<T>} event The event to add.
|
|
13
|
+
*/
|
|
14
|
+
export const advance = async (blocks, head, event) => {
|
|
15
|
+
const events = new EventFetcher(blocks);
|
|
16
|
+
const headmap = new Map(head.map(cid => [cid.toString(), cid]));
|
|
17
|
+
if (headmap.has(event.toString()))
|
|
18
|
+
return head;
|
|
19
|
+
// does event contain the clock?
|
|
20
|
+
let changed = false;
|
|
21
|
+
for (const cid of head) {
|
|
22
|
+
if (await contains(events, event, cid)) {
|
|
23
|
+
headmap.delete(cid.toString());
|
|
24
|
+
headmap.set(event.toString(), event);
|
|
25
|
+
changed = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (changed) {
|
|
29
|
+
return [...headmap.values()];
|
|
30
|
+
}
|
|
31
|
+
// does clock contain the event?
|
|
32
|
+
for (const p of head) {
|
|
33
|
+
if (await contains(events, p, event)) {
|
|
34
|
+
return head;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return head.concat(event);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* @template T
|
|
41
|
+
* @extends {Block<API.EventView<T>, typeof cbor.code, typeof sha256.code, 1>}
|
|
42
|
+
* @implements {API.EventBlockView<T>}
|
|
43
|
+
*/
|
|
44
|
+
export class EventBlock extends Block {
|
|
45
|
+
/**
|
|
46
|
+
* @param {object} config
|
|
47
|
+
* @param {API.EventLink<T>} config.cid
|
|
48
|
+
* @param {Event} config.value
|
|
49
|
+
* @param {Uint8Array} config.bytes
|
|
50
|
+
* @param {string} config.prefix
|
|
51
|
+
*/
|
|
52
|
+
constructor({ cid, value, bytes, prefix }) {
|
|
53
|
+
// @ts-expect-error
|
|
54
|
+
super({ cid, value, bytes });
|
|
55
|
+
this.prefix = prefix;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @template T
|
|
59
|
+
* @param {T} data
|
|
60
|
+
* @param {API.EventLink<T>[]} [parents]
|
|
61
|
+
*/
|
|
62
|
+
static create(data, parents) {
|
|
63
|
+
return encodeEventBlock({ data, parents: parents ?? [] });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** @template T */
|
|
67
|
+
export class EventFetcher {
|
|
68
|
+
/** @param {API.BlockFetcher} blocks */
|
|
69
|
+
constructor(blocks) {
|
|
70
|
+
/** @private */
|
|
71
|
+
this._blocks = blocks;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @param {API.EventLink<T>} link
|
|
75
|
+
* @returns {Promise<API.EventBlockView<T>>}
|
|
76
|
+
*/
|
|
77
|
+
async get(link) {
|
|
78
|
+
const block = await this._blocks.get(link);
|
|
79
|
+
if (!block)
|
|
80
|
+
throw new Error(`missing block: ${link}`);
|
|
81
|
+
return decodeEventBlock(block.bytes);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @template T
|
|
86
|
+
* @param {API.EventView<T>} value
|
|
87
|
+
* @returns {Promise<API.EventBlockView<T>>}
|
|
88
|
+
*/
|
|
89
|
+
export const encodeEventBlock = async (value) => {
|
|
90
|
+
// TODO: sort parents
|
|
91
|
+
const { cid, bytes } = await encode({ value, codec: cbor, hasher: sha256 });
|
|
92
|
+
// @ts-expect-error
|
|
93
|
+
return new Block({ cid, value, bytes });
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* @template T
|
|
97
|
+
* @param {Uint8Array} bytes
|
|
98
|
+
* @returns {Promise<API.EventBlockView<T>>}
|
|
99
|
+
*/
|
|
100
|
+
export const decodeEventBlock = async (bytes) => {
|
|
101
|
+
const { cid, value } = await decode({ bytes, codec: cbor, hasher: sha256 });
|
|
102
|
+
// @ts-expect-error
|
|
103
|
+
return new Block({ cid, value, bytes });
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Returns true if event "a" contains event "b". Breadth first search.
|
|
107
|
+
* @template T
|
|
108
|
+
* @param {EventFetcher<T>} events
|
|
109
|
+
* @param {API.EventLink<T>} a
|
|
110
|
+
* @param {API.EventLink<T>} b
|
|
111
|
+
*/
|
|
112
|
+
const contains = async (events, a, b) => {
|
|
113
|
+
if (a.toString() === b.toString())
|
|
114
|
+
return true;
|
|
115
|
+
const [{ value: aevent }, { value: bevent }] = await Promise.all([events.get(a), events.get(b)]);
|
|
116
|
+
const links = [...aevent.parents];
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
while (links.length) {
|
|
119
|
+
const link = links.shift();
|
|
120
|
+
if (!link)
|
|
121
|
+
break;
|
|
122
|
+
if (link.toString() === b.toString())
|
|
123
|
+
return true;
|
|
124
|
+
// if any of b's parents are this link, then b cannot exist in any of the
|
|
125
|
+
// tree below, since that would create a cycle.
|
|
126
|
+
if (bevent.parents.some(p => link.toString() === p.toString()))
|
|
127
|
+
continue;
|
|
128
|
+
if (seen.has(link.toString()))
|
|
129
|
+
continue;
|
|
130
|
+
seen.add(link.toString());
|
|
131
|
+
const { value: event } = await events.get(link);
|
|
132
|
+
links.push(...event.parents);
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* @template T
|
|
138
|
+
* @param {API.BlockFetcher} blocks Block storage.
|
|
139
|
+
* @param {API.EventLink<T>[]} head
|
|
140
|
+
* @param {object} [options]
|
|
141
|
+
* @param {(b: API.EventBlockView<T>) => string} [options.renderNodeLabel]
|
|
142
|
+
*/
|
|
143
|
+
export const vis = async function* (blocks, head, options = {}) {
|
|
144
|
+
const renderNodeLabel = options.renderNodeLabel ?? (b => shortLink(b.cid));
|
|
145
|
+
const events = new EventFetcher(blocks);
|
|
146
|
+
yield 'digraph clock {';
|
|
147
|
+
yield ' node [shape=point fontname="Courier"]; head;';
|
|
148
|
+
const hevents = await Promise.all(head.map(link => events.get(link)));
|
|
149
|
+
/** @type {import('multiformats').Link<API.EventView<any>>[]} */
|
|
150
|
+
const links = [];
|
|
151
|
+
const nodes = new Set();
|
|
152
|
+
for (const e of hevents) {
|
|
153
|
+
nodes.add(e.cid.toString());
|
|
154
|
+
yield ` node [shape=oval fontname="Courier"]; ${e.cid} [label="${renderNodeLabel(e)}"];`;
|
|
155
|
+
yield ` head -> ${e.cid};`;
|
|
156
|
+
for (const p of e.value.parents) {
|
|
157
|
+
yield ` ${e.cid} -> ${p};`;
|
|
158
|
+
}
|
|
159
|
+
links.push(...e.value.parents);
|
|
160
|
+
}
|
|
161
|
+
while (links.length) {
|
|
162
|
+
const link = links.shift();
|
|
163
|
+
if (!link)
|
|
164
|
+
break;
|
|
165
|
+
if (nodes.has(link.toString()))
|
|
166
|
+
continue;
|
|
167
|
+
nodes.add(link.toString());
|
|
168
|
+
const block = await events.get(link);
|
|
169
|
+
yield ` node [shape=oval]; ${link} [label="${renderNodeLabel(block)}" fontname="Courier"];`;
|
|
170
|
+
for (const p of block.value.parents) {
|
|
171
|
+
yield ` ${link} -> ${p};`;
|
|
172
|
+
}
|
|
173
|
+
links.push(...block.value.parents);
|
|
174
|
+
}
|
|
175
|
+
yield '}';
|
|
176
|
+
};
|
|
177
|
+
/** @param {import('multiformats').UnknownLink} l */
|
|
178
|
+
const shortLink = l => `${String(l).slice(0, 4)}..${String(l).slice(-4)}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|