@fizzyflow/endless-vector 0.0.7 → 0.0.10
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/EndlessVector.js +401 -142
- package/EndlessVectorArchive.js +31 -36
- package/EndlessVectorHistory.js +17 -13
- package/EndlessVectorItem.js +160 -0
- package/EndlessVectorSeal.js +192 -0
- package/EndlessVectorWalrus.js +467 -0
- package/README.md +253 -198
- package/ids.js +4 -4
- package/index.d.ts +181 -157
- package/index.js +5 -1
- package/package.json +16 -6
- package/test/base.test.js +388 -427
- package/test/base.test.txt +499 -0
- package/test/fixture.js +93 -0
- package/test/grpc_json_browser.test.js +22 -0
- package/test/helpers.js +2 -2
- package/test/helpers.txt +32 -0
- package/test/seal.test.js +319 -0
- package/test/walrus_blobs.test.js +178 -0
- package/test/walrus_blobs_extend.test.js +115 -0
- package/test/walrus_blobs_history.test.js +301 -0
- package/test/walrus_blobs_sdk.test.js +148 -0
- package/tsconfig.json +13 -0
- package/vitest.config.js +16 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test: walrus blob items through history and archive lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Key invariants from the Move contract:
|
|
5
|
+
* - Blob items have an on-object storage_volume of 32 bytes (only the reference
|
|
6
|
+
* is stored; the payload lives in Walrus).
|
|
7
|
+
* - Clamp triggers when (new_item_storage + current_storage) > SAFE_INNER_SIZE (128 KB).
|
|
8
|
+
* - A single blob (32 bytes) + one 120 KB bytes item = ~122 KB < 128 KB → no clamp yet.
|
|
9
|
+
* - Adding a second small bytes item (~9 KB) tips the total over 128 KB → clamp fires.
|
|
10
|
+
* - fillToHistory() encapsulates this two-push pattern.
|
|
11
|
+
* - The FILL_SMALL item that triggers clamp goes into current (not history), because
|
|
12
|
+
* clamp(ev, some(new_item)) routes the triggering item into the fresh current segment.
|
|
13
|
+
*
|
|
14
|
+
* Run: pnpm test:walrus-blobs-history (or pnpm test to run all suites)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
|
|
18
|
+
import { EndlessVector } from '../index.js';
|
|
19
|
+
import { equalUint8Arrays, randomBytesOfLength } from './helpers.js';
|
|
20
|
+
import { setupEndlessVectorLocalnet, teardownEndlessVectorLocalnet } from './fixture.js';
|
|
21
|
+
|
|
22
|
+
const TX_TIMEOUT = 120_000;
|
|
23
|
+
|
|
24
|
+
// Clamp triggers when (new_item_storage + current_storage) > SAFE_INNER_SIZE (128 KB).
|
|
25
|
+
// A single blob costs only 32 bytes of on-object storage, so it never triggers clamp
|
|
26
|
+
// alone. We use a two-push fill: first a 120 KB bytes item (max tx path, 122 880 bytes),
|
|
27
|
+
// then a 9 KB bytes item — combined with the blob that brings total to ~132 KB > 128 KB.
|
|
28
|
+
const FILL_LARGE = 120 * 1024; // first fill push (via tx, stays under 120 KB limit)
|
|
29
|
+
const FILL_SMALL = 9 * 1024; // second fill push (tips over 128 KB threshold)
|
|
30
|
+
|
|
31
|
+
let suiMaster;
|
|
32
|
+
let walrusServer;
|
|
33
|
+
let walrusClient;
|
|
34
|
+
let packageId;
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
({ suiMaster, walrusServer, walrusClient, packageId } = await setupEndlessVectorLocalnet());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(async () => {
|
|
41
|
+
await teardownEndlessVectorLocalnet();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function makeSignAndExecute() {
|
|
45
|
+
return async (tx) => {
|
|
46
|
+
const result = await suiMaster.signAndExecuteTransaction({
|
|
47
|
+
transaction: tx,
|
|
48
|
+
// requestType: 'WaitForLocalExecution',
|
|
49
|
+
});
|
|
50
|
+
return result.digest;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeEV() {
|
|
55
|
+
return EndlessVector.create({
|
|
56
|
+
suiClient: suiMaster.client,
|
|
57
|
+
packageId,
|
|
58
|
+
walrusClient,
|
|
59
|
+
aggregatorUrl: walrusServer?.url,
|
|
60
|
+
senderAddress: suiMaster.address,
|
|
61
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Push two bytes items via tx to tip storage over 128 KB and trigger clamp.
|
|
67
|
+
* Items currently in `ev` go to history; FILL_SMALL lands in the new current segment.
|
|
68
|
+
* Net effect on length: +2 items (FILL_LARGE in history, FILL_SMALL in current).
|
|
69
|
+
*/
|
|
70
|
+
async function fillToHistory(ev) {
|
|
71
|
+
await ev.push(randomBytesOfLength(FILL_LARGE)); // storage grows but stays < 128 KB
|
|
72
|
+
await ev.push(randomBytesOfLength(FILL_SMALL)); // this one triggers clamp
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── blobs in history ─────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
describe('blob pushed into history via vector<u8> fill', () => {
|
|
78
|
+
it('blob at index 0 is readable after being pushed into history', async () => {
|
|
79
|
+
const ev = await makeEV();
|
|
80
|
+
|
|
81
|
+
const blobData = randomBytesOfLength(200 * 1024);
|
|
82
|
+
await ev.push(blobData); // blob → current (32 bytes on-object storage)
|
|
83
|
+
await fillToHistory(ev); // blob + FILL_LARGE → history[0], FILL_SMALL → current
|
|
84
|
+
|
|
85
|
+
await ev.initialize();
|
|
86
|
+
// blob (index 0) + FILL_LARGE (index 1) in history; FILL_SMALL (index 2) in current
|
|
87
|
+
expect(ev.length).toBe(3);
|
|
88
|
+
expect(ev.historyItemsCount).toBe(1);
|
|
89
|
+
|
|
90
|
+
const back = await ev.at(0);
|
|
91
|
+
expect(equalUint8Arrays(back, blobData)).toBe(true);
|
|
92
|
+
}, TX_TIMEOUT);
|
|
93
|
+
|
|
94
|
+
it('multiple blobs in history, all readable', async () => {
|
|
95
|
+
const ev = await makeEV();
|
|
96
|
+
|
|
97
|
+
// Must be > 120 KB (> 122 880 bytes) so push() routes them to walrus as blob
|
|
98
|
+
// items; walrus blobs cost only 32 bytes of on-object storage each.
|
|
99
|
+
const blobs = [
|
|
100
|
+
randomBytesOfLength(130 * 1024),
|
|
101
|
+
randomBytesOfLength(150 * 1024),
|
|
102
|
+
randomBytesOfLength(200 * 1024),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// push 3 blobs (total on-object storage = 3 × 32 = 96 bytes, well under 128 KB)
|
|
106
|
+
for (const b of blobs) await ev.push(b);
|
|
107
|
+
|
|
108
|
+
// fillToHistory: FILL_LARGE goes in alongside blobs, FILL_SMALL triggers clamp
|
|
109
|
+
// → all 4 (blobs + FILL_LARGE) go to history[0]; FILL_SMALL lands in current
|
|
110
|
+
await fillToHistory(ev);
|
|
111
|
+
|
|
112
|
+
await ev.initialize();
|
|
113
|
+
expect(ev.length).toBe(5); // 3 blobs + FILL_LARGE in history + FILL_SMALL in current
|
|
114
|
+
expect(ev.historyItemsCount).toBe(1);
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < blobs.length; i++) {
|
|
117
|
+
const back = await ev.at(i);
|
|
118
|
+
expect(equalUint8Arrays(back, blobs[i])).toBe(true);
|
|
119
|
+
}
|
|
120
|
+
}, TX_TIMEOUT * 2);
|
|
121
|
+
|
|
122
|
+
it('blobs and bytes items coexist in the same history segment', async () => {
|
|
123
|
+
const ev = await makeEV();
|
|
124
|
+
|
|
125
|
+
const bytesItem = randomBytesOfLength(1024);
|
|
126
|
+
const blobItem = randomBytesOfLength(200 * 1024);
|
|
127
|
+
|
|
128
|
+
await ev.push(bytesItem); // bytes → current (1 KB storage)
|
|
129
|
+
await ev.push(blobItem); // blob → current (32 bytes storage)
|
|
130
|
+
await fillToHistory(ev); // bytesItem + blobItem + FILL_LARGE → history; FILL_SMALL → current
|
|
131
|
+
|
|
132
|
+
await ev.initialize();
|
|
133
|
+
expect(ev.length).toBe(4);
|
|
134
|
+
expect(ev.historyItemsCount).toBe(1);
|
|
135
|
+
|
|
136
|
+
expect(equalUint8Arrays(await ev.at(0), bytesItem)).toBe(true);
|
|
137
|
+
expect(equalUint8Arrays(await ev.at(1), blobItem)).toBe(true);
|
|
138
|
+
}, TX_TIMEOUT * 2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ─── multiple history segments ────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
describe('blobs across multiple history segments', () => {
|
|
144
|
+
it('blob readable from any history segment after two fill cycles', async () => {
|
|
145
|
+
const ev = await makeEV();
|
|
146
|
+
|
|
147
|
+
const blob1 = randomBytesOfLength(130 * 1024);
|
|
148
|
+
const blob2 = randomBytesOfLength(150 * 1024);
|
|
149
|
+
|
|
150
|
+
// segment 0: blob1 + FILL_LARGE → history[0]; FILL_SMALL → current
|
|
151
|
+
await ev.push(blob1);
|
|
152
|
+
await fillToHistory(ev);
|
|
153
|
+
|
|
154
|
+
// segment 1: blob2 + prev-FILL_SMALL + FILL_LARGE → history[1]; FILL_SMALL → current
|
|
155
|
+
await ev.push(blob2);
|
|
156
|
+
await fillToHistory(ev);
|
|
157
|
+
|
|
158
|
+
await ev.initialize();
|
|
159
|
+
// exact segment count varies (leftover FILL_SMALL from cycle 1 may add an extra
|
|
160
|
+
// clamp in cycle 2), but there must be at least 2 history segments
|
|
161
|
+
expect(ev.historyItemsCount).toBeGreaterThanOrEqual(2);
|
|
162
|
+
|
|
163
|
+
expect(equalUint8Arrays(await ev.at(0), blob1)).toBe(true);
|
|
164
|
+
// blob2 lands at index 3 (blob1, FILL_LARGE, FILL_SMALL from cycle 1)
|
|
165
|
+
expect(equalUint8Arrays(await ev.at(3), blob2)).toBe(true);
|
|
166
|
+
}, TX_TIMEOUT);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ─── blobs in archive ─────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
describe('blob archived and readable', () => {
|
|
172
|
+
it('blob in history is readable after archive()', async () => {
|
|
173
|
+
const ev = await makeEV();
|
|
174
|
+
|
|
175
|
+
const blobData = randomBytesOfLength(200 * 1024);
|
|
176
|
+
await ev.push(blobData);
|
|
177
|
+
await fillToHistory(ev); // blob + FILL_LARGE → history; FILL_SMALL → current
|
|
178
|
+
|
|
179
|
+
await ev.archive(); // history → archive; clamp sweeps current → history first
|
|
180
|
+
await ev.initialize();
|
|
181
|
+
|
|
182
|
+
expect(ev.archiveItemsCount).toBe(1);
|
|
183
|
+
expect(ev.historyItemsCount).toBe(0);
|
|
184
|
+
|
|
185
|
+
const back = await ev.at(0);
|
|
186
|
+
expect(equalUint8Arrays(back, blobData)).toBe(true);
|
|
187
|
+
}, TX_TIMEOUT);
|
|
188
|
+
|
|
189
|
+
it('items pushed after archive() are readable alongside archived blob', async () => {
|
|
190
|
+
const ev = await makeEV();
|
|
191
|
+
|
|
192
|
+
const blobData = randomBytesOfLength(200 * 1024);
|
|
193
|
+
await ev.push(blobData);
|
|
194
|
+
await fillToHistory(ev);
|
|
195
|
+
|
|
196
|
+
await ev.archive();
|
|
197
|
+
|
|
198
|
+
const afterArchive = randomBytesOfLength(4 * 1024);
|
|
199
|
+
await ev.push(afterArchive);
|
|
200
|
+
|
|
201
|
+
await ev.initialize();
|
|
202
|
+
|
|
203
|
+
expect(equalUint8Arrays(await ev.at(0), blobData)).toBe(true);
|
|
204
|
+
expect(equalUint8Arrays(await ev.at(ev.length - 1), afterArchive)).toBe(true);
|
|
205
|
+
}, TX_TIMEOUT * 2);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ─── archive + burn ───────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
describe('blob archive burn lifecycle', () => {
|
|
211
|
+
it('burned archived blob throws on at(), remaining items are readable', async () => {
|
|
212
|
+
const ev = await makeEV();
|
|
213
|
+
|
|
214
|
+
const blobData = randomBytesOfLength(200 * 1024);
|
|
215
|
+
const afterData = randomBytesOfLength(4 * 1024);
|
|
216
|
+
|
|
217
|
+
await ev.push(blobData);
|
|
218
|
+
await fillToHistory(ev); // blob + FILL_LARGE → history; FILL_SMALL → current
|
|
219
|
+
await ev.archive(); // archive() clamps first → everything into archive
|
|
220
|
+
await ev.push(afterData);
|
|
221
|
+
|
|
222
|
+
await ev.burnArchive();
|
|
223
|
+
await ev.initialize();
|
|
224
|
+
|
|
225
|
+
expect(ev.burnedArchiveCount).toBe(1);
|
|
226
|
+
expect(ev.length).toBeGreaterThan(0);
|
|
227
|
+
|
|
228
|
+
// all archived items throw
|
|
229
|
+
const burnedCount = ev.archivedFromLength;
|
|
230
|
+
for (let i = 0; i < burnedCount; i++) {
|
|
231
|
+
await expect(ev.at(i)).rejects.toThrow();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// item pushed after archive is still readable
|
|
235
|
+
expect(equalUint8Arrays(await ev.at(ev.length - 1), afterData)).toBe(true);
|
|
236
|
+
}, TX_TIMEOUT);
|
|
237
|
+
|
|
238
|
+
it('multiple archive/burn cycles with blobs accumulate archivedFromLength', async () => {
|
|
239
|
+
const ev = await makeEV();
|
|
240
|
+
|
|
241
|
+
const blob1 = randomBytesOfLength(130 * 1024);
|
|
242
|
+
const blob2 = randomBytesOfLength(130 * 1024);
|
|
243
|
+
|
|
244
|
+
// cycle 1
|
|
245
|
+
await ev.push(blob1);
|
|
246
|
+
await fillToHistory(ev);
|
|
247
|
+
await ev.archive();
|
|
248
|
+
await ev.burnArchive();
|
|
249
|
+
await ev.initialize();
|
|
250
|
+
|
|
251
|
+
const burned1 = ev.archivedFromLength;
|
|
252
|
+
expect(ev.burnedArchiveCount).toBe(1);
|
|
253
|
+
expect(burned1).toBeGreaterThan(0);
|
|
254
|
+
|
|
255
|
+
for (let i = 0; i < burned1; i++) {
|
|
256
|
+
await expect(ev.at(i)).rejects.toThrow();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// cycle 2
|
|
260
|
+
await ev.push(blob2);
|
|
261
|
+
await fillToHistory(ev);
|
|
262
|
+
await ev.archive();
|
|
263
|
+
await ev.burnArchive();
|
|
264
|
+
await ev.initialize();
|
|
265
|
+
|
|
266
|
+
const burned2 = ev.archivedFromLength;
|
|
267
|
+
expect(ev.burnedArchiveCount).toBe(2);
|
|
268
|
+
expect(burned2).toBeGreaterThan(burned1);
|
|
269
|
+
|
|
270
|
+
for (let i = burned1; i < burned2; i++) {
|
|
271
|
+
await expect(ev.at(i)).rejects.toThrow();
|
|
272
|
+
}
|
|
273
|
+
}, TX_TIMEOUT);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ─── re-read by ID ────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
describe('re-reading vector by ID with blob in history', () => {
|
|
279
|
+
it('fresh instance resolves blob from history correctly', async () => {
|
|
280
|
+
const ev = await makeEV();
|
|
281
|
+
|
|
282
|
+
const blobData = randomBytesOfLength(200 * 1024);
|
|
283
|
+
await ev.push(blobData);
|
|
284
|
+
await fillToHistory(ev);
|
|
285
|
+
|
|
286
|
+
const fresh = new EndlessVector({
|
|
287
|
+
suiClient: suiMaster.client,
|
|
288
|
+
id: ev.id,
|
|
289
|
+
packageId,
|
|
290
|
+
walrusClient,
|
|
291
|
+
aggregatorUrl: walrusServer?.url,
|
|
292
|
+
senderAddress: suiMaster.address,
|
|
293
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
294
|
+
});
|
|
295
|
+
await fresh.initialize();
|
|
296
|
+
|
|
297
|
+
expect(fresh.historyItemsCount).toBe(1);
|
|
298
|
+
const back = await fresh.at(0);
|
|
299
|
+
expect(equalUint8Arrays(back, blobData)).toBe(true);
|
|
300
|
+
}, TX_TIMEOUT * 3);
|
|
301
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test: exercises ev.push() with both small and large payloads.
|
|
3
|
+
* Small arrays (≤ 120 KB) go through a regular Move tx; larger ones are
|
|
4
|
+
* transparently routed to Walrus via walrus.pushBlob().
|
|
5
|
+
*
|
|
6
|
+
* Run: pnpm test:walrus-blobs-sdk (or pnpm test to run all suites)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
|
|
10
|
+
import { EndlessVector } from '../index.js';
|
|
11
|
+
import { randomBytesOfLength } from './helpers.js';
|
|
12
|
+
import { setupEndlessVectorLocalnet, teardownEndlessVectorLocalnet } from './fixture.js';
|
|
13
|
+
|
|
14
|
+
const TX_TIMEOUT = 60_000;
|
|
15
|
+
|
|
16
|
+
let suiMaster;
|
|
17
|
+
let walrusServer;
|
|
18
|
+
let walrusClient;
|
|
19
|
+
let packageId;
|
|
20
|
+
|
|
21
|
+
/** @type {EndlessVector} */
|
|
22
|
+
let ev;
|
|
23
|
+
|
|
24
|
+
// ─── setup ────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
({ suiMaster, walrusServer, walrusClient, packageId } = await setupEndlessVectorLocalnet());
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterAll(async () => {
|
|
31
|
+
await teardownEndlessVectorLocalnet();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function makeSignAndExecute() {
|
|
37
|
+
return async (tx) => {
|
|
38
|
+
const result = await suiMaster.signAndExecuteTransaction({
|
|
39
|
+
transaction: tx,
|
|
40
|
+
requestType: 'WaitForLocalExecution',
|
|
41
|
+
});
|
|
42
|
+
return result.digest;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function makeEV({ usePublisherUrl = false } = {}) {
|
|
47
|
+
return EndlessVector.create({
|
|
48
|
+
suiClient: suiMaster.client,
|
|
49
|
+
packageId,
|
|
50
|
+
walrusClient: usePublisherUrl ? undefined : walrusClient,
|
|
51
|
+
publisherUrl: usePublisherUrl ? walrusServer.url : undefined,
|
|
52
|
+
aggregatorUrl: walrusServer.url,
|
|
53
|
+
senderAddress: suiMaster.address,
|
|
54
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── deployment ───────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe('deployment', () => {
|
|
61
|
+
it('publishes the combined package', () => {
|
|
62
|
+
expect(packageId).toMatch(/^0x[0-9a-f]+$/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('bootstraps the walrus committee (epoch == 1)', async () => {
|
|
66
|
+
const { walrusState } = await setupEndlessVectorLocalnet();
|
|
67
|
+
const epoch = await walrusState.systemEpoch();
|
|
68
|
+
expect(epoch).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── create ───────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
describe('EndlessVector creation', () => {
|
|
75
|
+
it('creates an empty EndlessVector via SDK', async () => {
|
|
76
|
+
ev = await makeEV();
|
|
77
|
+
expect(ev.id).toMatch(/^0x[0-9a-f]+$/);
|
|
78
|
+
expect(ev.isWritable).toBe(true);
|
|
79
|
+
expect(ev.walrus).toBeDefined();
|
|
80
|
+
}, TX_TIMEOUT);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── push via ev.push() ───────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
describe('push items via ev.push()', () => {
|
|
86
|
+
it('pushes small items (≤ 120 KB) via regular tx', async () => {
|
|
87
|
+
const sizes = [512, 4 * 1024, 64 * 1024];
|
|
88
|
+
|
|
89
|
+
for (const size of sizes) {
|
|
90
|
+
const data = randomBytesOfLength(size);
|
|
91
|
+
const ok = await ev.push(data);
|
|
92
|
+
expect(ok).toBe(true);
|
|
93
|
+
console.log(`pushed ${size}B via tx`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await ev.initialize();
|
|
97
|
+
expect(ev.length).toBe(3);
|
|
98
|
+
expect(ev.binaryLength).toBeGreaterThan(0);
|
|
99
|
+
}, TX_TIMEOUT);
|
|
100
|
+
|
|
101
|
+
it('pushes a large item (> 120 KB) via walrus fallback', async () => {
|
|
102
|
+
const data = randomBytesOfLength(200 * 1024);
|
|
103
|
+
const ok = await ev.push(data);
|
|
104
|
+
expect(ok).toBe(true);
|
|
105
|
+
console.log(`pushed 200 KB via walrus fallback`);
|
|
106
|
+
|
|
107
|
+
await ev.initialize();
|
|
108
|
+
expect(ev.length).toBe(4);
|
|
109
|
+
}, TX_TIMEOUT);
|
|
110
|
+
|
|
111
|
+
it('pushes 2 more small items sequentially', async () => {
|
|
112
|
+
await ev.push(randomBytesOfLength(1024));
|
|
113
|
+
await ev.push(randomBytesOfLength(2048));
|
|
114
|
+
|
|
115
|
+
await ev.initialize();
|
|
116
|
+
expect(ev.length).toBe(6);
|
|
117
|
+
}, TX_TIMEOUT);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ─── round-trip reads ─────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
describe('round-trip reads', () => {
|
|
123
|
+
/** @type {EndlessVector} */
|
|
124
|
+
let evRoundTrip;
|
|
125
|
+
|
|
126
|
+
it('creates a fresh vector for round-trip tests (publisherUrl path)', async () => {
|
|
127
|
+
evRoundTrip = await makeEV({ usePublisherUrl: true });
|
|
128
|
+
expect(evRoundTrip.id).toMatch(/^0x[0-9a-f]+$/);
|
|
129
|
+
}, TX_TIMEOUT);
|
|
130
|
+
|
|
131
|
+
it('reads back small item pushed via tx', async () => {
|
|
132
|
+
const data = randomBytesOfLength(512);
|
|
133
|
+
await evRoundTrip.push(data);
|
|
134
|
+
|
|
135
|
+
await evRoundTrip.initialize();
|
|
136
|
+
const back = await evRoundTrip.at(evRoundTrip.length - 1);
|
|
137
|
+
expect(back).toEqual(data);
|
|
138
|
+
}, TX_TIMEOUT);
|
|
139
|
+
|
|
140
|
+
it('reads back a large item pushed via walrus fallback (publisherUrl)', async () => {
|
|
141
|
+
const data = randomBytesOfLength(200 * 1024);
|
|
142
|
+
await evRoundTrip.push(data);
|
|
143
|
+
|
|
144
|
+
await evRoundTrip.initialize();
|
|
145
|
+
const back = await evRoundTrip.at(evRoundTrip.length - 1);
|
|
146
|
+
expect(back).toEqual(data);
|
|
147
|
+
}, TX_TIMEOUT);
|
|
148
|
+
});
|
package/tsconfig.json
ADDED
package/vitest.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
// singleFork + isolate:false keeps every test file in the same Node process
|
|
10
|
+
// so the fixture's module-level cached Promise is shared — the validator
|
|
11
|
+
// and package deploy happen exactly once for the whole run.
|
|
12
|
+
isolate: false,
|
|
13
|
+
poolOptions: { forks: { singleFork: true } },
|
|
14
|
+
include: ['test/**/*.test.js'],
|
|
15
|
+
},
|
|
16
|
+
});
|