@web3-storage/pail 0.6.0 → 0.6.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.
Files changed (106) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +208 -0
  4. package/dist/scripts/randomcid.d.ts +2 -0
  5. package/dist/scripts/randomcid.d.ts.map +1 -0
  6. package/dist/scripts/randomcid.js +10 -0
  7. package/dist/scripts/words/gen.d.ts +2 -0
  8. package/dist/scripts/words/gen.d.ts.map +1 -0
  9. package/dist/scripts/words/gen.js +51 -0
  10. package/dist/src/api.d.ts +4 -2
  11. package/dist/src/api.d.ts.map +1 -1
  12. package/dist/src/api.js +1 -0
  13. package/dist/src/batch/api.d.ts +2 -2
  14. package/dist/src/batch/api.d.ts.map +1 -1
  15. package/dist/src/batch/api.js +1 -0
  16. package/dist/src/batch/index.d.ts +1 -53
  17. package/dist/src/batch/index.d.ts.map +1 -1
  18. package/dist/src/batch/index.js +241 -0
  19. package/dist/src/batch/shard.d.ts +1 -1
  20. package/dist/src/batch/shard.d.ts.map +1 -1
  21. package/dist/src/batch/shard.js +12 -0
  22. package/dist/src/block.d.ts +2 -2
  23. package/dist/src/block.d.ts.map +1 -1
  24. package/dist/src/block.js +66 -0
  25. package/dist/src/clock/api.d.ts +1 -1
  26. package/dist/src/clock/api.d.ts.map +1 -1
  27. package/dist/src/clock/api.js +1 -0
  28. package/dist/src/clock/index.d.ts +2 -2
  29. package/dist/src/clock/index.d.ts.map +1 -1
  30. package/dist/src/clock/index.js +199 -0
  31. package/dist/src/crdt/api.d.ts +1 -1
  32. package/dist/src/crdt/api.d.ts.map +1 -1
  33. package/dist/src/crdt/api.js +1 -0
  34. package/dist/src/crdt/batch/api.d.ts +1 -1
  35. package/dist/src/crdt/batch/api.d.ts.map +1 -1
  36. package/dist/src/crdt/batch/api.js +1 -0
  37. package/dist/src/crdt/batch/index.d.ts.map +1 -1
  38. package/dist/src/crdt/batch/index.js +140 -0
  39. package/dist/src/crdt/index.d.ts +2 -2
  40. package/dist/src/crdt/index.d.ts.map +1 -1
  41. package/dist/src/crdt/index.js +344 -0
  42. package/dist/src/diff.d.ts +3 -3
  43. package/dist/src/diff.d.ts.map +1 -1
  44. package/dist/src/diff.js +151 -0
  45. package/dist/src/index.d.ts +1 -1
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +356 -0
  48. package/dist/src/merge.d.ts.map +1 -1
  49. package/dist/src/merge.js +42 -0
  50. package/dist/src/shard.d.ts +4 -4
  51. package/dist/src/shard.d.ts.map +1 -1
  52. package/dist/src/shard.js +166 -0
  53. package/dist/test/batch.test.d.ts +2 -0
  54. package/dist/test/batch.test.d.ts.map +1 -0
  55. package/dist/test/batch.test.js +126 -0
  56. package/dist/test/clock.test.d.ts +2 -0
  57. package/dist/test/clock.test.d.ts.map +1 -0
  58. package/dist/test/clock.test.js +244 -0
  59. package/dist/test/crdt.test.d.ts +2 -0
  60. package/dist/test/crdt.test.d.ts.map +1 -0
  61. package/dist/test/crdt.test.js +271 -0
  62. package/dist/test/del.test.d.ts +2 -0
  63. package/dist/test/del.test.d.ts.map +1 -0
  64. package/dist/test/del.test.js +21 -0
  65. package/dist/test/diff.test.d.ts +2 -0
  66. package/dist/test/diff.test.d.ts.map +1 -0
  67. package/dist/test/diff.test.js +38 -0
  68. package/dist/test/entries.test.d.ts +2 -0
  69. package/dist/test/entries.test.d.ts.map +1 -0
  70. package/dist/test/entries.test.js +210 -0
  71. package/dist/test/get.test.d.ts +2 -0
  72. package/dist/test/get.test.d.ts.map +1 -0
  73. package/dist/test/get.test.js +26 -0
  74. package/dist/test/helpers.d.ts +35 -0
  75. package/dist/test/helpers.d.ts.map +1 -0
  76. package/dist/test/helpers.js +168 -0
  77. package/dist/test/put.test.d.ts +2 -0
  78. package/dist/test/put.test.d.ts.map +1 -0
  79. package/dist/test/put.test.js +165 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/dist/vitest.config.d.ts +3 -0
  82. package/dist/vitest.config.d.ts.map +1 -0
  83. package/dist/vitest.config.js +13 -0
  84. package/package.json +49 -94
  85. package/scripts/propernames/gen.sh +3 -0
  86. package/cli.js +0 -218
  87. package/src/api.js +0 -1
  88. package/src/api.ts +0 -90
  89. package/src/batch/api.js +0 -1
  90. package/src/batch/api.ts +0 -59
  91. package/src/batch/index.js +0 -258
  92. package/src/batch/shard.js +0 -13
  93. package/src/block.js +0 -75
  94. package/src/clock/api.js +0 -1
  95. package/src/clock/api.ts +0 -12
  96. package/src/clock/index.js +0 -182
  97. package/src/crdt/api.js +0 -1
  98. package/src/crdt/api.ts +0 -33
  99. package/src/crdt/batch/api.js +0 -1
  100. package/src/crdt/batch/api.ts +0 -30
  101. package/src/crdt/batch/index.js +0 -155
  102. package/src/crdt/index.js +0 -354
  103. package/src/diff.js +0 -151
  104. package/src/index.js +0 -406
  105. package/src/merge.js +0 -43
  106. package/src/shard.js +0 -180
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.js"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import { join } from 'path';
5
+ import { Readable, Writable } from 'stream';
6
+ import sade from 'sade';
7
+ import { CID } from 'multiformats/cid';
8
+ import { CARReaderStream, CARWriterStream } from 'carstream';
9
+ import clc from 'cli-color';
10
+ import archy from 'archy';
11
+ // eslint-disable-next-line no-unused-vars
12
+ import * as API from './src/api.js';
13
+ import { put, get, del, entries } from './src/index.js';
14
+ import { ShardFetcher, ShardBlock, isShardLink } from './src/shard.js';
15
+ import { difference } from './src/diff.js';
16
+ import { merge } from './src/merge.js';
17
+ import { MemoryBlockstore, MultiBlockFetcher } from './src/block.js';
18
+ const cli = sade('pail')
19
+ .option('--path', 'Path to data store.', './pail.car');
20
+ cli.command('put <key> <value>')
21
+ .describe('Put a value (a CID) for the given key. If the key exists it\'s value is overwritten.')
22
+ .alias('set')
23
+ .action(async (key, value, opts) => {
24
+ const { root: prevRoot, blocks } = await openPail(opts.path);
25
+ const { root, additions, removals } = await put(blocks, prevRoot, key, CID.parse(value));
26
+ await updatePail(opts.path, blocks, root, { additions, removals });
27
+ console.log(clc.red(`--- ${prevRoot}`));
28
+ console.log(clc.green(`+++ ${root}`));
29
+ console.log(clc.magenta('@@ -1 +1 @@'));
30
+ additions.forEach(b => console.log(clc.green(`+${b.cid}`)));
31
+ removals.forEach(b => console.log(clc.red(`-${b.cid}`)));
32
+ });
33
+ cli.command('get <key>')
34
+ .describe('Get the stored value for the given key from the pail. If the key is not found, `undefined` is returned.')
35
+ .action(async (key, opts) => {
36
+ const { root, blocks } = await openPail(opts.path);
37
+ const value = await get(blocks, root, key);
38
+ if (value)
39
+ console.log(value.toString());
40
+ });
41
+ cli.command('del <key>')
42
+ .describe('Delete the value for the given key from the pail. If the key is not found no operation occurs.')
43
+ .alias('delete', 'rm', 'remove')
44
+ .action(async (key, opts) => {
45
+ const { root: prevRoot, blocks } = await openPail(opts.path);
46
+ const { root, additions, removals } = await del(blocks, prevRoot, key);
47
+ await updatePail(opts.path, blocks, root, { additions, removals });
48
+ console.log(clc.red(`--- ${prevRoot}`));
49
+ console.log(clc.green(`+++ ${root}`));
50
+ console.log(clc.magenta('@@ -1 +1 @@'));
51
+ additions.forEach(b => console.log(clc.green(`+ ${b.cid}`)));
52
+ removals.forEach(b => console.log(clc.red(`- ${b.cid}`)));
53
+ });
54
+ cli.command('ls')
55
+ .describe('List entries in the pail.')
56
+ .alias('list')
57
+ .option('-p, --prefix', 'Key prefix to filter by.')
58
+ .option('--gt', 'Filter results by keys greater than this string.')
59
+ .option('--lt', 'Filter results by keys less than this string.')
60
+ .option('--json', 'Format output as newline delimted JSON.')
61
+ .action(async (opts) => {
62
+ const { root, blocks } = await openPail(opts.path);
63
+ let n = 0;
64
+ for await (const [k, v] of entries(blocks, root, { prefix: opts.prefix, gt: opts.gt, lt: opts.lt })) {
65
+ console.log(opts.json ? JSON.stringify({ key: k, value: v.toString() }) : `${k}\t${v}`);
66
+ n++;
67
+ }
68
+ if (!opts.json)
69
+ console.log(`total ${n}`);
70
+ });
71
+ cli.command('tree')
72
+ .describe('Visualise the pail.')
73
+ .alias('vis')
74
+ .action(async (opts) => {
75
+ const { root, blocks } = await openPail(opts.path);
76
+ const shards = new ShardFetcher(blocks);
77
+ const rshard = await shards.get(root);
78
+ /** @type {archy.Data} */
79
+ const archyRoot = { label: `Shard(${clc.yellow(rshard.cid.toString())}) ${rshard.bytes.length + 'b'}`, nodes: [] };
80
+ /** @param {API.ShardEntry} entry */
81
+ const getData = async ([k, v]) => {
82
+ if (!Array.isArray(v)) {
83
+ return { label: `Key(${clc.magenta(k)})`, nodes: [{ label: `Value(${clc.cyan(v)})` }] };
84
+ }
85
+ /** @type {archy.Data} */
86
+ const data = { label: `Key(${clc.magenta(k)})`, nodes: [] };
87
+ if (v[1])
88
+ data.nodes?.push({ label: `Value(${clc.cyan(v[1])})` });
89
+ const blk = await shards.get(v[0]);
90
+ data.nodes?.push({
91
+ label: `Shard(${clc.yellow(v[0])}) ${blk.bytes.length + 'b'}`,
92
+ nodes: await Promise.all(blk.value.entries.map(e => getData(e)))
93
+ });
94
+ return data;
95
+ };
96
+ for (const entry of rshard.value.entries) {
97
+ archyRoot.nodes?.push(await getData(entry));
98
+ }
99
+ console.log(archy(archyRoot));
100
+ });
101
+ cli.command('diff <path>')
102
+ .describe('Find the differences between this pail and the passed pail.')
103
+ .option('-k, --keys', 'Output key/value diff.')
104
+ .action(async (path, opts) => {
105
+ const [{ root: aroot, blocks: ablocks }, { root: broot, blocks: bblocks }] = await Promise.all([openPail(opts.path), openPail(path)]);
106
+ if (aroot.toString() === broot.toString())
107
+ return;
108
+ const fetcher = new MultiBlockFetcher(ablocks, bblocks);
109
+ const { shards: { additions, removals }, keys } = await difference(fetcher, aroot, broot);
110
+ console.log(clc.red(`--- ${aroot}`));
111
+ console.log(clc.green(`+++ ${broot}`));
112
+ console.log(clc.magenta('@@ -1 +1 @@'));
113
+ if (opts.keys) {
114
+ keys.forEach(([k, v]) => {
115
+ if (v[0] != null)
116
+ console.log(clc.red(`- ${k}\t${v[0]}`));
117
+ if (v[1] != null)
118
+ console.log(clc.green(`+ ${k}\t${v[1]}`));
119
+ });
120
+ }
121
+ else {
122
+ additions.forEach(b => console.log(clc.green(`+ ${b.cid}`)));
123
+ removals.forEach(b => console.log(clc.red(`- ${b.cid}`)));
124
+ }
125
+ });
126
+ cli.command('merge <path>')
127
+ .describe('Merge the passed pail into this pail.')
128
+ .action(async (path, opts) => {
129
+ const [{ root: aroot, blocks: ablocks }, { root: broot, blocks: bblocks }] = await Promise.all([openPail(opts.path), openPail(path)]);
130
+ if (aroot.toString() === broot.toString())
131
+ return;
132
+ const fetcher = new MultiBlockFetcher(ablocks, bblocks);
133
+ const { root, additions, removals } = await merge(fetcher, aroot, [broot]);
134
+ await updatePail(opts.path, ablocks, root, { additions, removals });
135
+ console.log(clc.red(`--- ${aroot}`));
136
+ console.log(clc.green(`+++ ${root}`));
137
+ console.log(clc.magenta('@@ -1 +1 @@'));
138
+ additions.forEach(b => console.log(clc.green(`+ ${b.cid}`)));
139
+ removals.forEach(b => console.log(clc.red(`- ${b.cid}`)));
140
+ });
141
+ cli.parse(process.argv);
142
+ /**
143
+ * @param {string} path
144
+ * @returns {Promise<{ root: API.ShardLink, blocks: MemoryBlockstore }>}
145
+ */
146
+ async function openPail(path) {
147
+ const blocks = new MemoryBlockstore();
148
+ try {
149
+ const carReader = new CARReaderStream();
150
+ const readable = /** @type {ReadableStream<Uint8Array>} */ (Readable.toWeb(fs.createReadStream(path)));
151
+ await readable.pipeThrough(carReader).pipeTo(new WritableStream({ write: b => blocks.put(b.cid, b.bytes) }));
152
+ const header = await carReader.getHeader();
153
+ if (!isShardLink(header.roots[0]))
154
+ throw new Error(`not a shard: ${header.roots[0]}`);
155
+ return { root: header.roots[0], blocks };
156
+ }
157
+ catch (err) {
158
+ // @ts-ignore
159
+ if (err.code !== 'ENOENT')
160
+ throw new Error('failed to open bucket', { cause: err });
161
+ const rootblk = await ShardBlock.create();
162
+ blocks.put(rootblk.cid, rootblk.bytes);
163
+ return { root: rootblk.cid, blocks };
164
+ }
165
+ }
166
+ /**
167
+ * @param {string} path
168
+ * @param {MemoryBlockstore} blocks
169
+ * @param {API.ShardLink} root
170
+ * @param {API.ShardDiff} diff
171
+ */
172
+ async function updatePail(path, blocks, root, { additions, removals }) {
173
+ const tmp = join(os.tmpdir(), `pail${Date.now()}.car`);
174
+ const iterator = blocks.entries();
175
+ const readable = new ReadableStream({
176
+ start(controller) {
177
+ for (const b of additions)
178
+ controller.enqueue(b);
179
+ },
180
+ pull(controller) {
181
+ for (const b of iterator) {
182
+ if (removals.some(r => b.cid.toString() === r.cid.toString()))
183
+ continue;
184
+ return controller.enqueue(b);
185
+ }
186
+ controller.close();
187
+ }
188
+ });
189
+ await readable.pipeThrough(new CARWriterStream([root])).pipeTo(Writable.toWeb(fs.createWriteStream(tmp)));
190
+ const old = `${path}-${new Date().toISOString()}`;
191
+ try {
192
+ await fs.promises.rename(path, old);
193
+ }
194
+ catch (err) {
195
+ // @ts-ignore
196
+ if (err.code !== 'ENOENT')
197
+ throw err;
198
+ }
199
+ await fs.promises.rename(tmp, path);
200
+ try {
201
+ await fs.promises.rm(old);
202
+ }
203
+ catch (err) {
204
+ // @ts-ignore
205
+ if (err.code !== 'ENOENT')
206
+ throw err;
207
+ }
208
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=randomcid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"randomcid.d.ts","sourceRoot":"","sources":["../../scripts/randomcid.js"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { CID } from 'multiformats/cid';
2
+ import * as raw from 'multiformats/codecs/raw';
3
+ import { sha256 } from 'multiformats/hashes/sha2';
4
+ import { randomBytes } from '../test/helpers.js';
5
+ async function main() {
6
+ const bytes = await randomBytes(32);
7
+ const hash = await sha256.digest(bytes);
8
+ process.stdout.write(CID.create(1, raw.code, hash).toString());
9
+ }
10
+ main();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gen.d.ts","sourceRoot":"","sources":["../../../scripts/words/gen.js"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import { Readable } from 'node:stream';
3
+ import { CarWriter } from '@ipld/car';
4
+ import { CID } from 'multiformats/cid';
5
+ import * as raw from 'multiformats/codecs/raw';
6
+ import { sha256 } from 'multiformats/hashes/sha2';
7
+ import { put } from '../../src/index.js';
8
+ import { ShardBlock } from '../../src/shard.js';
9
+ import { MemoryBlockstore } from '../../src/block.js';
10
+ /** @param {string} str */
11
+ async function stringToCID(str) {
12
+ const hash = await sha256.digest(new TextEncoder().encode(str));
13
+ return CID.create(1, raw.code, hash);
14
+ }
15
+ async function main() {
16
+ const data = await fs.promises.readFile('/usr/share/dict/words', 'utf8');
17
+ const words = data.split(/\n/);
18
+ const cids = await Promise.all(words.map(stringToCID));
19
+ const blocks = new MemoryBlockstore();
20
+ const rootblk = await ShardBlock.create();
21
+ blocks.putSync(rootblk.cid, rootblk.bytes);
22
+ console.time(`put x${words.length}`);
23
+ /** @type {import('../../src/api.ts').ShardLink} */
24
+ let root = rootblk.cid;
25
+ for (const [i, word] of words.entries()) {
26
+ const res = await put(blocks, root, word, cids[i]);
27
+ root = res.root;
28
+ for (const b of res.additions) {
29
+ blocks.putSync(b.cid, b.bytes);
30
+ }
31
+ for (const b of res.removals) {
32
+ blocks.deleteSync(b.cid);
33
+ }
34
+ if (i % 1000 === 0) {
35
+ console.log(`${Math.floor(i / words.length * 100)}%`);
36
+ }
37
+ }
38
+ console.timeEnd(`put x${words.length}`);
39
+ // @ts-expect-error
40
+ const { writer, out } = CarWriter.create(root);
41
+ const finishPromise = new Promise(resolve => {
42
+ Readable.from(out).pipe(fs.createWriteStream('./pail.car')).on('finish', () => resolve(true));
43
+ });
44
+ for (const b of blocks.entries()) {
45
+ // @ts-expect-error
46
+ await writer.put(b);
47
+ }
48
+ await writer.close();
49
+ await finishPromise;
50
+ }
51
+ main();
package/dist/src/api.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Link, UnknownLink, BlockView, Block, Version } from 'multiformats';
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 };
@@ -20,7 +22,7 @@ export interface ShardDiff {
20
22
  removals: ShardBlockView[];
21
23
  }
22
24
  export interface BlockFetcher {
23
- get<T = unknown, C extends number = number, A extends number = number, V extends Version = 1>(link: Link<T, C, A, V>): Promise<Block<T, C, A, V> | undefined>;
25
+ get: <T = unknown, C extends number = number, A extends number = number, V extends Version = 1>(link: Link<T, C, A, V>) => Promise<Block<T, C, A, V> | undefined>;
24
26
  }
25
27
  export interface ShardConfig {
26
28
  /** Shard compatibility version. */
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -19,12 +19,12 @@ export interface Batcher {
19
19
  * Put a value (a CID) for the given key. If the key exists it's value is
20
20
  * overwritten.
21
21
  */
22
- put(key: string, value: UnknownLink): Promise<void>;
22
+ put: (key: string, value: UnknownLink) => Promise<void>;
23
23
  /**
24
24
  * Encode all altered shards in the batch and return the new root CID and
25
25
  * difference blocks.
26
26
  */
27
- commit(): Promise<{
27
+ commit: () => Promise<{
28
28
  root: ShardLink;
29
29
  } & ShardDiff>;
30
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/batch/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACb,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACb,CAAA;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM;IACX,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,2BAA2B,GAAG,oBAAoB,GAAG,4BAA4B;CACtI,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,MAAM,MAAM,4BAA4B,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;AAEtE,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,GAAG,CAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD;;;OAGG;IACH,MAAM,IAAK,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC,CAAA;CACpD"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/batch/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACb,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACb,CAAA;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM;IACX,KAAK,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,2BAA2B,GAAG,oBAAoB,GAAG,4BAA4B;CACtI,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,YAAY,CAAC,CAAA;AAEjD,MAAM,MAAM,4BAA4B,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;AAEtE,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD;;;OAGG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC,CAAA;CACvD"}
@@ -0,0 +1 @@
1
+ export {};
@@ -15,61 +15,9 @@ export class BatchCommittedError extends Error {
15
15
  * @param {string} [message]
16
16
  * @param {ErrorOptions} [options]
17
17
  */
18
- constructor(message?: string | undefined, options?: ErrorOptions | undefined);
18
+ constructor(message?: string, options?: ErrorOptions);
19
19
  code: string;
20
20
  }
21
21
  import * as API from './api.js';
22
- import * as BatcherShard from './shard.js';
23
22
  import { ShardFetcher } from '../shard.js';
24
- /** @implements {API.Batcher} */
25
- declare class Batcher implements API.Batcher {
26
- /**
27
- * @param {object} init
28
- * @param {API.BlockFetcher} init.blocks Block storage.
29
- * @param {API.ShardLink} init.link CID of the shard block.
30
- */
31
- static create({ blocks, link }: {
32
- blocks: API.BlockFetcher;
33
- link: API.ShardLink;
34
- }): Promise<Batcher>;
35
- /**
36
- * @param {object} init
37
- * @param {API.BlockFetcher} init.blocks Block storage.
38
- * @param {API.BatcherShardEntry[]} init.entries The entries in this shard.
39
- * @param {string} init.prefix Key prefix.
40
- * @param {number} init.version Shard compatibility version.
41
- * @param {string} init.keyChars Characters allowed in keys, referring to a known character set.
42
- * @param {number} init.maxKeySize Max key size in bytes.
43
- * @param {API.ShardBlockView} init.base Original shard this batcher is based on.
44
- */
45
- constructor({ blocks, entries, prefix, version, keyChars, maxKeySize, base }: {
46
- blocks: API.BlockFetcher;
47
- entries: API.BatcherShardEntry[];
48
- prefix: string;
49
- version: number;
50
- keyChars: string;
51
- maxKeySize: number;
52
- base: API.ShardBlockView;
53
- });
54
- blocks: API.BlockFetcher;
55
- prefix: string;
56
- entries: API.BatcherShardEntry[];
57
- base: API.ShardBlockView;
58
- version: number;
59
- keyChars: string;
60
- maxKeySize: number;
61
- /**
62
- * @param {string} key The key of the value to put.
63
- * @param {API.UnknownLink} value The value to put.
64
- * @returns {Promise<void>}
65
- */
66
- put(key: string, value: API.UnknownLink): Promise<void>;
67
- commit(): Promise<{
68
- root: import("multiformats").CID<import("../api.js").Shard, 113, 18, 1>;
69
- additions: API.ShardBlockView[];
70
- removals: API.ShardBlockView[];
71
- }>;
72
- #private;
73
- }
74
- export {};
75
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/batch/index.js"],"names":[],"mappings":"AAiEO,4BANI,IAAI,YAAY,SAChB,gBAAgB,OAChB,MAAM,SACN,IAAI,WAAW,GACb,QAAQ,IAAI,CAAC,CAsGzB;AAWM,iCALI,YAAY,SACZ,gBAAgB,OAChB,MAAM,GACJ,QAAQ;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB7D;AAQM,8BAFI,gBAAgB;;;;GAkC1B;AAaM,+BAJI,IAAI,YAAY,QAChB,IAAI,SAAS,GACX,QAAQ,WAAW,CAAC,CAE6C;AAE9E;IAUE,oBAAmC;IATnC;;;OAGG;IACH,8EAGC;IADC,aAAoC;CAIvC;qBAhQoB,UAAU;8BAGD,YAAY;6BAFK,aAAa;AAI5D,gCAAgC;AAChC,iCADiB,GAAG,CAAC,OAAO;IAwC1B;;;;OAIG;IACH;QAHkC,MAAM,EAA7B,IAAI,YAAY;QACI,IAAI,EAAxB,IAAI,SAAS;yBAMvB;IA7CD;;;;;;;;;OASG;IACH;QARkC,MAAM,EAA7B,IAAI,YAAY;QACc,OAAO,EAArC,uBAAuB;QACV,MAAM,EAAnB,MAAM;QACO,OAAO,EAApB,MAAM;QACO,QAAQ,EAArB,MAAM;QACO,UAAU,EAAvB,MAAM;QACmB,IAAI,EAA7B,IAAI,cAAc;OAU5B;IAPC,yBAAoB;IACpB,eAAoB;IACpB,iCAA2B;IAC3B,yBAAgB;IAChB,gBAAsB;IACtB,iBAAwB;IACxB,mBAA4B;IAG9B;;;;OAIG;IACH,SAJW,MAAM,SACN,IAAI,WAAW,GACb,QAAQ,IAAI,CAAC,CAKzB;IAED;;;;OAIC;;CAYF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/batch/index.js"],"names":[],"mappings":"AAiEO,4BANI,GAAG,CAAC,YAAY,SAChB,GAAG,CAAC,YAAY,OAChB,MAAM,SACN,GAAG,CAAC,WAAW,GACb,OAAO,CAAC,IAAI,CAAC,CAsGzB;AAWM,iCALI,YAAY,SACZ,GAAG,CAAC,YAAY,OAChB,MAAM,GACJ,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB7D;AAQM,8BAFI,GAAG,CAAC,YAAY;;;;GAkC1B;AAaM,+BAJI,GAAG,CAAC,YAAY,QAChB,GAAG,CAAC,SAAS,GACX,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAE6C;AAE9E;IAUE,oBAAmC;IATnC;;;OAGG;IACH,sBAHW,MAAM,YACN,YAAY,EAKtB;IADC,aAAoC;CAIvC;qBAhQoB,UAAU;6BACgB,aAAa"}
@@ -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
+ const 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
+ }