@fizzyflow/wdoublesync 0.1.0

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,244 @@
1
+ import { beforeAll, afterAll, describe, it, expect } from 'vitest';
2
+ import { EndlessVector } from 'endless_vector';
3
+ import {
4
+ DoubleSync,
5
+ DoubleSyncMemoryFolder,
6
+ } from 'doublesync';
7
+ import WDoubleSync from '../WDoubleSync.js';
8
+ import { equalUint8Arrays, seededBytes, collectTree } from './helpers.js';
9
+ import { setupLocalnet, teardownLocalnet } from './fixture.js';
10
+
11
+ const TX_TIMEOUT = 90_000;
12
+
13
+ let suiMaster;
14
+ let walrusServer;
15
+ let walrusClient;
16
+ let packageId;
17
+
18
+ beforeAll(async () => {
19
+ ({ suiMaster, walrusServer, walrusClient, packageId } = await setupLocalnet());
20
+ });
21
+
22
+ afterAll(async () => {
23
+ await teardownLocalnet();
24
+ });
25
+
26
+ function makeSignAndExecute() {
27
+ return async (tx) => {
28
+ const result = await suiMaster.signAndExecuteTransaction({ transaction: tx });
29
+ return result.digest;
30
+ };
31
+ }
32
+
33
+ async function createEV() {
34
+ return EndlessVector.create({
35
+ suiClient: suiMaster.client,
36
+ packageId,
37
+ walrusClient,
38
+ aggregatorUrl: walrusServer?.url,
39
+ senderAddress: suiMaster.address,
40
+ signAndExecuteTransaction: makeSignAndExecute(),
41
+ });
42
+ }
43
+
44
+ function makeEV(id) {
45
+ return new EndlessVector({
46
+ suiClient: suiMaster.client,
47
+ id,
48
+ packageId,
49
+ walrusClient,
50
+ aggregatorUrl: walrusServer?.url,
51
+ senderAddress: suiMaster.address,
52
+ signAndExecuteTransaction: makeSignAndExecute(),
53
+ });
54
+ }
55
+
56
+ function makeSync() {
57
+ return new DoubleSync({ avgSize: 1024 });
58
+ }
59
+
60
+ describe('file deletion', () => {
61
+ it('deleted file is absent after restore', async () => {
62
+ const ev = await createEV();
63
+ const sync = makeSync();
64
+ const w = new WDoubleSync({ endlessVector: ev, sync });
65
+
66
+ const sender = new DoubleSyncMemoryFolder('proj');
67
+ await sender.addFile('a.txt', seededBytes(2048, 1));
68
+ await sender.addFile('b.txt', seededBytes(2048, 2));
69
+ await sender.addFile('c.txt', seededBytes(2048, 3));
70
+
71
+ await w.initialize();
72
+ await w.push(sender);
73
+
74
+ await sender.removeChild('b.txt');
75
+ await w.push(sender);
76
+
77
+ const evReader = makeEV(ev.id);
78
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
79
+ const restored = await w2.restore();
80
+
81
+ const tree = await collectTree(restored);
82
+ const paths = tree.map(t => t.path.join('/'));
83
+ expect(paths).toEqual(['a.txt', 'c.txt']);
84
+ expect(await restored.findByPath(['b.txt'])).toBeNull();
85
+
86
+ expect(equalUint8Arrays(tree[0].bytes, seededBytes(2048, 1))).toBe(true);
87
+ expect(equalUint8Arrays(tree[1].bytes, seededBytes(2048, 3))).toBe(true);
88
+ }, TX_TIMEOUT);
89
+
90
+ it('delete all files then re-add produces correct tree', async () => {
91
+ const ev = await createEV();
92
+ const sync = makeSync();
93
+ const w = new WDoubleSync({ endlessVector: ev, sync });
94
+
95
+ const sender = new DoubleSyncMemoryFolder('proj');
96
+ await sender.addFile('old.txt', seededBytes(1024, 10));
97
+
98
+ await w.initialize();
99
+ await w.push(sender);
100
+
101
+ await sender.removeChild('old.txt');
102
+ await sender.addFile('new.txt', seededBytes(1024, 11));
103
+ await w.push(sender);
104
+
105
+ const evReader = makeEV(ev.id);
106
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
107
+ const restored = await w2.restore();
108
+
109
+ const tree = await collectTree(restored);
110
+ expect(tree.map(t => t.path.join('/'))).toEqual(['new.txt']);
111
+ expect(equalUint8Arrays(tree[0].bytes, seededBytes(1024, 11))).toBe(true);
112
+ expect(await restored.findByPath(['old.txt'])).toBeNull();
113
+ }, TX_TIMEOUT);
114
+ });
115
+
116
+ describe('folder deletion', () => {
117
+ it('deleted subfolder and its files are absent after restore', async () => {
118
+ const ev = await createEV();
119
+ const sync = makeSync();
120
+ const w = new WDoubleSync({ endlessVector: ev, sync });
121
+
122
+ const sender = new DoubleSyncMemoryFolder('proj');
123
+ await sender.addFile('root.txt', seededBytes(1024, 20));
124
+ const sub = await sender.addFolder('sub');
125
+ await sub.addFile('deep.txt', seededBytes(1024, 21));
126
+ await sub.addFile('other.txt', seededBytes(1024, 22));
127
+
128
+ await w.initialize();
129
+ await w.push(sender);
130
+
131
+ await sender.removeChild('sub');
132
+ await w.push(sender);
133
+
134
+ const evReader = makeEV(ev.id);
135
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
136
+ const restored = await w2.restore();
137
+
138
+ const tree = await collectTree(restored);
139
+ expect(tree.map(t => t.path.join('/'))).toEqual(['root.txt']);
140
+ expect(await restored.findByPath(['sub'])).toBeNull();
141
+ expect(await restored.findByPath(['sub', 'deep.txt'])).toBeNull();
142
+ }, TX_TIMEOUT);
143
+
144
+ it('delete folder then re-add folder with different contents', async () => {
145
+ const ev = await createEV();
146
+ const sync = makeSync();
147
+ const w = new WDoubleSync({ endlessVector: ev, sync });
148
+
149
+ const sender = new DoubleSyncMemoryFolder('proj');
150
+ const sub = await sender.addFolder('data');
151
+ await sub.addFile('v1.bin', seededBytes(2048, 30));
152
+
153
+ await w.initialize();
154
+ await w.push(sender);
155
+
156
+ await sender.removeChild('data');
157
+ const sub2 = await sender.addFolder('data');
158
+ await sub2.addFile('v2.bin', seededBytes(2048, 31));
159
+ await w.push(sender);
160
+
161
+ const evReader = makeEV(ev.id);
162
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
163
+ const restored = await w2.restore();
164
+
165
+ const tree = await collectTree(restored);
166
+ expect(tree.map(t => t.path.join('/'))).toEqual(['data/v2.bin']);
167
+ expect(await restored.findByPath(['data', 'v1.bin'])).toBeNull();
168
+ expect(equalUint8Arrays(tree[0].bytes, seededBytes(2048, 31))).toBe(true);
169
+ }, TX_TIMEOUT);
170
+ });
171
+
172
+ describe('version restore with deletions', () => {
173
+ it('restoring earlier version still has the deleted file', async () => {
174
+ const ev = await createEV();
175
+ const sync = makeSync();
176
+ const w = new WDoubleSync({ endlessVector: ev, sync });
177
+
178
+ const sender = new DoubleSyncMemoryFolder('proj');
179
+ await sender.addFile('keep.txt', seededBytes(1024, 40));
180
+ await sender.addFile('gone.txt', seededBytes(1024, 41));
181
+
182
+ await w.initialize();
183
+ await w.push(sender);
184
+
185
+ await sender.removeChild('gone.txt');
186
+ await w.push(sender);
187
+
188
+ const evReader = makeEV(ev.id);
189
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
190
+
191
+ const v1 = await w2.restore(1);
192
+ const v1tree = await collectTree(v1);
193
+ expect(v1tree.map(t => t.path.join('/'))).toEqual(['gone.txt', 'keep.txt']);
194
+ expect(equalUint8Arrays(
195
+ (await (await v1.findByPath(['gone.txt'])).getContent()),
196
+ seededBytes(1024, 41),
197
+ )).toBe(true);
198
+
199
+ const latest = await w2.restore();
200
+ const latestTree = await collectTree(latest);
201
+ expect(latestTree.map(t => t.path.join('/'))).toEqual(['keep.txt']);
202
+ expect(await latest.findByPath(['gone.txt'])).toBeNull();
203
+ }, TX_TIMEOUT);
204
+
205
+ it('three versions: add, delete, re-add with different content', async () => {
206
+ const ev = await createEV();
207
+ const sync = makeSync();
208
+ const w = new WDoubleSync({ endlessVector: ev, sync });
209
+
210
+ const sender = new DoubleSyncMemoryFolder('proj');
211
+ await sender.addFile('file.txt', seededBytes(2048, 50));
212
+
213
+ await w.initialize();
214
+
215
+ // v1: file exists
216
+ await w.push(sender);
217
+
218
+ // v2: file deleted
219
+ await sender.removeChild('file.txt');
220
+ await w.push(sender);
221
+
222
+ // v3: file re-added with new content
223
+ await sender.addFile('file.txt', seededBytes(2048, 51));
224
+ await w.push(sender);
225
+
226
+ const evReader = makeEV(ev.id);
227
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
228
+
229
+ const v1 = await w2.restore(1);
230
+ expect(equalUint8Arrays(
231
+ (await (await v1.findByPath(['file.txt'])).getContent()),
232
+ seededBytes(2048, 50),
233
+ )).toBe(true);
234
+
235
+ const v2 = await w2.restore(2);
236
+ expect(await v2.findByPath(['file.txt'])).toBeNull();
237
+
238
+ const v3 = await w2.restore(3);
239
+ expect(equalUint8Arrays(
240
+ (await (await v3.findByPath(['file.txt'])).getContent()),
241
+ seededBytes(2048, 51),
242
+ )).toBe(true);
243
+ }, TX_TIMEOUT * 2);
244
+ });
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Integration tests for WDoubleSync with large payloads that trigger
3
+ * EndlessVector's Walrus blob routing (> 120 KB) and history management.
4
+ *
5
+ * Covers 200 KB and 1 MB file trees pushed through WDoubleSync, verifying
6
+ * round-trip correctness when patches themselves are large enough to be
7
+ * stored as Walrus blobs.
8
+ */
9
+
10
+ import { beforeAll, afterAll, describe, it, expect } from 'vitest';
11
+ import { EndlessVector } from 'endless_vector';
12
+ import {
13
+ DoubleSync,
14
+ DoubleSyncMemoryFolder,
15
+ DoubleSyncFormat,
16
+ DoubleSyncDiffPatch,
17
+ } from 'doublesync';
18
+ import WDoubleSync from '../WDoubleSync.js';
19
+ import { equalUint8Arrays, randomBytesOfLength, seededBytes, collectTree, treesEqual } from './helpers.js';
20
+ import { setupLocalnet, teardownLocalnet } from './fixture.js';
21
+
22
+ const TX_TIMEOUT = 90_000;
23
+
24
+ let suiMaster;
25
+ let walrusServer;
26
+ let walrusClient;
27
+ let packageId;
28
+
29
+ // ─── setup ───────────────────────────────────────────────────────────────────
30
+
31
+ beforeAll(async () => {
32
+ ({ suiMaster, walrusServer, walrusClient, packageId } = await setupLocalnet());
33
+ console.log('package id:', packageId);
34
+ });
35
+
36
+ afterAll(async () => {
37
+ await teardownLocalnet();
38
+ });
39
+
40
+ // ─── helpers ─────────────────────────────────────────────────────────────────
41
+
42
+ function makeSignAndExecute() {
43
+ return async (tx) => {
44
+ const result = await suiMaster.signAndExecuteTransaction({
45
+ transaction: tx,
46
+ });
47
+ return result.digest;
48
+ };
49
+ }
50
+
51
+ async function createEV() {
52
+ return EndlessVector.create({
53
+ suiClient: suiMaster.client,
54
+ packageId,
55
+ walrusClient,
56
+ aggregatorUrl: walrusServer?.url,
57
+ senderAddress: suiMaster.address,
58
+ signAndExecuteTransaction: makeSignAndExecute(),
59
+ });
60
+ }
61
+
62
+ function makeEV(id) {
63
+ return new EndlessVector({
64
+ suiClient: suiMaster.client,
65
+ id,
66
+ packageId,
67
+ walrusClient,
68
+ aggregatorUrl: walrusServer?.url,
69
+ senderAddress: suiMaster.address,
70
+ signAndExecuteTransaction: makeSignAndExecute(),
71
+ });
72
+ }
73
+
74
+ function makeSync() {
75
+ return new DoubleSync({ avgSize: 8192 });
76
+ }
77
+
78
+ // ─── 200 KB files ────────────────────────────────────────────────────────────
79
+
80
+ describe('200 KB files', () => {
81
+ it('pushes a tree with a 200 KB file and restores it byte-equal', async () => {
82
+ const ev = await createEV();
83
+ const sync = makeSync();
84
+ const w = new WDoubleSync({ endlessVector: ev, sync });
85
+
86
+ const largeData = randomBytesOfLength(200 * 1024);
87
+ const smallData = seededBytes(512, 1);
88
+
89
+ const sender = new DoubleSyncMemoryFolder('proj');
90
+ await sender.addFile('large.bin', largeData);
91
+ await sender.addFile('small.txt', smallData);
92
+
93
+ await w.initialize();
94
+ await w.push(sender);
95
+
96
+ const evReader = makeEV(ev.id);
97
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
98
+ const restored = await w2.restore();
99
+
100
+ const restoredLarge = await (await restored.findByPath(['large.bin'])).getContent();
101
+ const restoredSmall = await (await restored.findByPath(['small.txt'])).getContent();
102
+ expect(restoredLarge.length).toBe(200 * 1024);
103
+ expect(equalUint8Arrays(restoredLarge, largeData)).toBe(true);
104
+ expect(equalUint8Arrays(restoredSmall, smallData)).toBe(true);
105
+ }, TX_TIMEOUT);
106
+
107
+ it('incremental edit on a 200 KB file produces a diff and restores byte-equal', async () => {
108
+ const ev = await createEV();
109
+ const sync = makeSync();
110
+ const w = new WDoubleSync({ endlessVector: ev, sync });
111
+
112
+ const sender = new DoubleSyncMemoryFolder('proj');
113
+ const bigData = randomBytesOfLength(200 * 1024);
114
+ const otherData = seededBytes(1024, 2);
115
+ await sender.addFile('big.bin', bigData);
116
+ await sender.addFile('other.txt', otherData);
117
+
118
+ await w.initialize();
119
+ await w.push(sender);
120
+
121
+ // Small edit in the middle of the large file
122
+ const big = await sender.findByPath(['big.bin']);
123
+ const old = await big.getContent();
124
+ const edited = new Uint8Array(old.length + 64);
125
+ edited.set(old.subarray(0, 100 * 1024));
126
+ edited.set(randomBytesOfLength(64), 100 * 1024);
127
+ edited.set(old.subarray(100 * 1024), 100 * 1024 + 64);
128
+ await big.setContent(edited);
129
+
130
+ await w.push(sender);
131
+
132
+ await ev.reInitialize();
133
+ await ev.initialize();
134
+ expect(ev.length).toBe(2);
135
+
136
+ const evReader = makeEV(ev.id);
137
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
138
+ const restored = await w2.restore();
139
+
140
+ const restoredBig = await (await restored.findByPath(['big.bin'])).getContent();
141
+ const restoredOther = await (await restored.findByPath(['other.txt'])).getContent();
142
+ expect(restoredBig.length).toBe(edited.length);
143
+ expect(equalUint8Arrays(restoredBig, edited)).toBe(true);
144
+ expect(equalUint8Arrays(restoredOther, otherData)).toBe(true);
145
+ }, TX_TIMEOUT * 2);
146
+
147
+ it('multiple 200 KB files across folders restored byte-equal', async () => {
148
+ const ev = await createEV();
149
+ const sync = makeSync();
150
+ const w = new WDoubleSync({ endlessVector: ev, sync });
151
+
152
+ const image1Data = randomBytesOfLength(200 * 1024);
153
+ const image2Data = randomBytesOfLength(200 * 1024);
154
+ const dumpData = randomBytesOfLength(200 * 1024);
155
+ const readmeData = seededBytes(2048, 3);
156
+
157
+ const sender = new DoubleSyncMemoryFolder('proj');
158
+ const assets = await sender.addFolder('assets');
159
+ await assets.addFile('image1.bin', image1Data);
160
+ await assets.addFile('image2.bin', image2Data);
161
+ const data = await sender.addFolder('data');
162
+ await data.addFile('dump.bin', dumpData);
163
+ await sender.addFile('readme.md', readmeData);
164
+
165
+ await w.initialize();
166
+ await w.push(sender);
167
+
168
+ const evReader = makeEV(ev.id);
169
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
170
+ const restored = await w2.restore();
171
+
172
+ const ri1 = await (await restored.findByPath(['assets', 'image1.bin'])).getContent();
173
+ const ri2 = await (await restored.findByPath(['assets', 'image2.bin'])).getContent();
174
+ const rd = await (await restored.findByPath(['data', 'dump.bin'])).getContent();
175
+ const rr = await (await restored.findByPath(['readme.md'])).getContent();
176
+
177
+ expect(ri1.length).toBe(200 * 1024);
178
+ expect(ri2.length).toBe(200 * 1024);
179
+ expect(rd.length).toBe(200 * 1024);
180
+ expect(equalUint8Arrays(ri1, image1Data)).toBe(true);
181
+ expect(equalUint8Arrays(ri2, image2Data)).toBe(true);
182
+ expect(equalUint8Arrays(rd, dumpData)).toBe(true);
183
+ expect(equalUint8Arrays(rr, readmeData)).toBe(true);
184
+ }, TX_TIMEOUT * 2);
185
+ });
186
+
187
+ // ─── 1 MB files ──────────────────────────────────────────────────────────────
188
+
189
+ describe('1 MB files', () => {
190
+ it('pushes a tree with a 1 MB file and restores it byte-equal', async () => {
191
+ const ev = await createEV();
192
+ const sync = makeSync();
193
+ const w = new WDoubleSync({ endlessVector: ev, sync });
194
+
195
+ const hugeData = randomBytesOfLength(1024 * 1024);
196
+ const configData = seededBytes(256, 10);
197
+
198
+ const sender = new DoubleSyncMemoryFolder('proj');
199
+ await sender.addFile('huge.bin', hugeData);
200
+ await sender.addFile('config.txt', configData);
201
+
202
+ await w.initialize();
203
+ await w.push(sender);
204
+
205
+ const evReader = makeEV(ev.id);
206
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
207
+ const restored = await w2.restore();
208
+
209
+ const restoredHuge = await (await restored.findByPath(['huge.bin'])).getContent();
210
+ const restoredConfig = await (await restored.findByPath(['config.txt'])).getContent();
211
+ expect(restoredHuge.length).toBe(1024 * 1024);
212
+ expect(equalUint8Arrays(restoredHuge, hugeData)).toBe(true);
213
+ expect(equalUint8Arrays(restoredConfig, configData)).toBe(true);
214
+ }, TX_TIMEOUT * 2);
215
+
216
+ it('incremental edit on a 1 MB file: diff is small and bytes match', async () => {
217
+ const ev = await createEV();
218
+ const sync = makeSync();
219
+ const w = new WDoubleSync({ endlessVector: ev, sync });
220
+
221
+ const hugeData = randomBytesOfLength(1024 * 1024);
222
+ const sender = new DoubleSyncMemoryFolder('proj');
223
+ await sender.addFile('huge.bin', hugeData);
224
+
225
+ await w.initialize();
226
+ await w.push(sender);
227
+
228
+ // Append 1 KB to the end
229
+ const huge = await sender.findByPath(['huge.bin']);
230
+ const old = await huge.getContent();
231
+ const appendChunk = randomBytesOfLength(1024);
232
+ const appended = new Uint8Array(old.length + 1024);
233
+ appended.set(old);
234
+ appended.set(appendChunk, old.length);
235
+ await huge.setContent(appended);
236
+
237
+ await w.push(sender);
238
+
239
+ await ev.reInitialize();
240
+ await ev.initialize();
241
+ expect(ev.length).toBe(2);
242
+
243
+ // Diff patch should be much smaller than the full patch
244
+ const fullSize = (await ev.at(0)).length;
245
+ const diffSize = (await ev.at(1)).length;
246
+ console.log(`1 MB file: full patch ${fullSize} bytes, diff patch ${diffSize} bytes`);
247
+ expect(diffSize).toBeLessThan(fullSize / 2);
248
+
249
+ const evReader = makeEV(ev.id);
250
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
251
+ const restored = await w2.restore();
252
+
253
+ const restoredHuge = await (await restored.findByPath(['huge.bin'])).getContent();
254
+ expect(restoredHuge.length).toBe(appended.length);
255
+ expect(equalUint8Arrays(restoredHuge, appended)).toBe(true);
256
+
257
+ // Verify the original portion is intact
258
+ expect(equalUint8Arrays(restoredHuge.subarray(0, 1024 * 1024), hugeData)).toBe(true);
259
+ // Verify the appended portion matches
260
+ expect(equalUint8Arrays(restoredHuge.subarray(1024 * 1024), appendChunk)).toBe(true);
261
+ }, TX_TIMEOUT * 3);
262
+ });
263
+
264
+ // ─── mixed large + small with multiple versions ──────────────────────────────
265
+
266
+ describe('mixed large and small files across versions', () => {
267
+ it('3-version chain with 200 KB and 1 MB files, partial restore with byte checks', async () => {
268
+ const ev = await createEV();
269
+ const sync = makeSync();
270
+ const w = new WDoubleSync({ endlessVector: ev, sync });
271
+
272
+ const readmeV1 = seededBytes(2048, 20);
273
+ const indexData = seededBytes(4096, 21);
274
+ const logoData = randomBytesOfLength(200 * 1024);
275
+ const bigData = randomBytesOfLength(1024 * 1024);
276
+ const readmeV3 = seededBytes(3072, 22);
277
+
278
+ const sender = new DoubleSyncMemoryFolder('proj');
279
+ await w.initialize();
280
+
281
+ // v1: small files only
282
+ await sender.addFile('readme.md', readmeV1);
283
+ const src = await sender.addFolder('src');
284
+ await src.addFile('index.js', indexData);
285
+ await w.push(sender);
286
+
287
+ // v2: add a 200 KB asset
288
+ const assets = await sender.addFolder('assets');
289
+ await assets.addFile('logo.bin', logoData);
290
+ await w.push(sender);
291
+
292
+ // v3: add a 1 MB data file + edit a small file
293
+ await sender.addFile('data.bin', bigData);
294
+ const readme = await sender.findByPath(['readme.md']);
295
+ await readme.setContent(readmeV3);
296
+ await w.push(sender);
297
+
298
+ await ev.reInitialize();
299
+ await ev.initialize();
300
+ expect(ev.length).toBe(3);
301
+
302
+ // Restore latest — verify every file byte-equal
303
+ const evReader = makeEV(ev.id);
304
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
305
+ const latest = await w2.restore();
306
+
307
+ const lReadme = await (await latest.findByPath(['readme.md'])).getContent();
308
+ const lIndex = await (await latest.findByPath(['src', 'index.js'])).getContent();
309
+ const lLogo = await (await latest.findByPath(['assets', 'logo.bin'])).getContent();
310
+ const lData = await (await latest.findByPath(['data.bin'])).getContent();
311
+
312
+ expect(equalUint8Arrays(lReadme, readmeV3)).toBe(true);
313
+ expect(equalUint8Arrays(lIndex, indexData)).toBe(true);
314
+ expect(lLogo.length).toBe(200 * 1024);
315
+ expect(equalUint8Arrays(lLogo, logoData)).toBe(true);
316
+ expect(lData.length).toBe(1024 * 1024);
317
+ expect(equalUint8Arrays(lData, bigData)).toBe(true);
318
+
319
+ // Restore v1 — should have original readme + index, no large files
320
+ const v1 = await w2.restore(1);
321
+ const v1readme = await (await v1.findByPath(['readme.md'])).getContent();
322
+ const v1index = await (await v1.findByPath(['src', 'index.js'])).getContent();
323
+ expect(equalUint8Arrays(v1readme, readmeV1)).toBe(true);
324
+ expect(equalUint8Arrays(v1index, indexData)).toBe(true);
325
+ expect(await v1.findByPath(['assets', 'logo.bin'])).toBeNull();
326
+ expect(await v1.findByPath(['data.bin'])).toBeNull();
327
+
328
+ // Restore v2 — should have 200 KB logo but not 1 MB data
329
+ const v2 = await w2.restore(2);
330
+ const v2logo = await (await v2.findByPath(['assets', 'logo.bin'])).getContent();
331
+ expect(v2logo.length).toBe(200 * 1024);
332
+ expect(equalUint8Arrays(v2logo, logoData)).toBe(true);
333
+ expect(await v2.findByPath(['data.bin'])).toBeNull();
334
+ // v2 still has original readme
335
+ const v2readme = await (await v2.findByPath(['readme.md'])).getContent();
336
+ expect(equalUint8Arrays(v2readme, readmeV1)).toBe(true);
337
+ }, TX_TIMEOUT * 4);
338
+ });
339
+
340
+ // ─── compressed large payloads ───────────────────────────────────────────────
341
+
342
+ describe('compressed large payloads', () => {
343
+ it('gzip-compressed 200 KB tree round-trips byte-equal', async () => {
344
+ const ev = await createEV();
345
+ const sync = makeSync();
346
+ const w = new WDoubleSync({ endlessVector: ev, sync, compress: 'gzip' });
347
+
348
+ const largeData = randomBytesOfLength(200 * 1024);
349
+ const smallData = seededBytes(512, 30);
350
+
351
+ const sender = new DoubleSyncMemoryFolder('proj');
352
+ await sender.addFile('large.bin', largeData);
353
+ await sender.addFile('small.txt', smallData);
354
+
355
+ await w.initialize();
356
+ await w.push(sender);
357
+
358
+ const raw = await ev.at(0);
359
+ expect(DoubleSyncFormat.detect(raw)).toBe('compressed');
360
+
361
+ const evReader = makeEV(ev.id);
362
+ const w2 = new WDoubleSync({ endlessVector: evReader, sync: makeSync() });
363
+ const restored = await w2.restore();
364
+
365
+ const restoredLarge = await (await restored.findByPath(['large.bin'])).getContent();
366
+ const restoredSmall = await (await restored.findByPath(['small.txt'])).getContent();
367
+ expect(restoredLarge.length).toBe(200 * 1024);
368
+ expect(equalUint8Arrays(restoredLarge, largeData)).toBe(true);
369
+ expect(equalUint8Arrays(restoredSmall, smallData)).toBe(true);
370
+ }, TX_TIMEOUT * 2);
371
+ });
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ testTimeout: 300_000,
6
+ hookTimeout: 300_000,
7
+ fileParallelism: false,
8
+ pool: 'forks',
9
+ isolate: false,
10
+ poolOptions: { forks: { singleFork: true } },
11
+ include: ['test/**/*.test.js'],
12
+ },
13
+ });