@fizzyflow/endless-vector 0.0.8 → 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.
@@ -0,0 +1,499 @@
1
+ 'use strict'
2
+
3
+ import t, { end } from 'tap';
4
+ import { SuiMaster, SuiLocalTestValidator, SuiObject } from 'suidouble';
5
+ import { fileURLToPath } from 'url';
6
+ import path from 'path';
7
+ import { equalUint8Arrays, formatBytes, randomBytesOfLength } from './helpers.js';
8
+
9
+ import { EndlessVector } from '../index.js';
10
+ // import { get } from 'http';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const { test } = t;
16
+
17
+ let suiLocalTestValidator = null;
18
+
19
+ /** @type {?SuiMaster} */
20
+ let suiMaster = null;
21
+ let signAndExecuteTransaction = null;
22
+
23
+ let contract = null;
24
+
25
+ /** @type {?SuiObject} */
26
+ let endlessVectorRaw = null; // SuiObject instance
27
+ /** @type {?EndlessVector} */
28
+ let endlessVector = null; // EndlessVector instance
29
+
30
+ test('spawn local test node', async t => {
31
+ suiLocalTestValidator = await SuiLocalTestValidator.launch({ testFallbackEnabled: true, debug: true, });
32
+ t.ok(suiLocalTestValidator.active);
33
+ // SuiLocalTestValidator runs as signle instance. So you can't start it twice with static method
34
+ const suiLocalTestValidatorCopy = await SuiLocalTestValidator.launch();
35
+ t.equal(suiLocalTestValidator, suiLocalTestValidatorCopy);
36
+ });
37
+
38
+ test('init suiMaster and connect it to local test validator', async t => {
39
+ suiMaster = new SuiMaster({client: 'local', as: 'noname_tester', debug: true});
40
+ await suiMaster.initialize();
41
+ t.ok(suiMaster.address); // there should be some address
42
+ t.ok(`${suiMaster.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
43
+
44
+ signAndExecuteTransaction = async (tx) => {
45
+ // function accepting Transaction and returning signed and submitted transaction digest
46
+ const results = await suiMaster.signAndExecuteTransaction({
47
+ transaction: tx,
48
+ });
49
+ return results.digest;
50
+ }
51
+ });
52
+
53
+ test('request sui from faucet', async t => {
54
+ const balanceBefore = await suiMaster.getBalance();
55
+ await suiMaster.requestSuiFromFaucet();
56
+ await suiMaster.requestSuiFromFaucet();
57
+ await suiMaster.requestSuiFromFaucet();
58
+ await suiMaster.requestSuiFromFaucet();
59
+ await suiMaster.requestSuiFromFaucet();
60
+ await suiMaster.requestSuiFromFaucet();
61
+ const balanceAfter = await suiMaster.getBalance();
62
+ t.ok(balanceAfter > balanceBefore);
63
+ });
64
+
65
+ test('attach a local package', async t => {
66
+ contract = suiMaster.addPackage({
67
+ path: path.join(__dirname, '../../move'),
68
+ });
69
+ await contract.build({ withUnpublishedDependencies: true, });
70
+ await contract.publish();
71
+ });
72
+
73
+
74
+ test('test raw create transaction', async t => {
75
+ const arr = randomBytesOfLength(120 * 1024); // 100KB
76
+
77
+ const tx = new suiMaster.Transaction();
78
+ const vectorTxInput = await EndlessVector.getCreateTransactionAndReturnVectorInput({
79
+ packageId: contract.id,
80
+ }, arr, tx);
81
+ tx.transferObjects([vectorTxInput], tx.pure.address(suiMaster.address));
82
+
83
+ t.ok(tx);
84
+ let digest = null;
85
+ try {
86
+ digest = await signAndExecuteTransaction(tx);
87
+ t.ok(digest);
88
+ } catch (e) {
89
+ console.error('Error preparing transaction:', e);
90
+ }
91
+
92
+ const transactionBlockResponse = await suiMaster.client.waitForTransaction({
93
+ digest: digest,
94
+ options: { showObjectChanges: true, },
95
+ });
96
+
97
+ // Find the created EndlessVector object
98
+ const objectChanges = transactionBlockResponse.objectChanges || [];
99
+ const createdVector = objectChanges.find(
100
+ change => change.type === 'created' &&
101
+ change.objectType &&
102
+ change.objectType.includes('endless_vector::EndlessVector')
103
+ );
104
+
105
+ const createdVectorId = createdVector ? createdVector.objectId : null;
106
+ t.ok(createdVectorId, 'Created EndlessVector object should have an ID');
107
+
108
+ const loadBack = new EndlessVector({
109
+ suiClient: suiMaster.client,
110
+ id: createdVectorId,
111
+ });
112
+ await loadBack.initialize();
113
+
114
+ t.ok(loadBack.length === 1, `Loaded vector should have length 1, got ${loadBack.length}`);
115
+ const getBack = await loadBack.at(0);
116
+ t.ok(equalUint8Arrays(getBack, arr), 'Data retrieved from loaded vector should match original data');
117
+ });
118
+
119
+
120
+ test('make a test EndlessVector with few chunks in a single tx', async t => {
121
+ const data = [
122
+ randomBytesOfLength(1 * 1024),
123
+ randomBytesOfLength(2 * 1024),
124
+ randomBytesOfLength(3 * 1024),
125
+ ];
126
+
127
+ const testEndlessVector = await EndlessVector.create({
128
+ array: data,
129
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
130
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
131
+ signAndExecuteTransaction: signAndExecuteTransaction,
132
+ });
133
+
134
+ const getBack0 = await testEndlessVector.at(0);
135
+ const getBack1 = await testEndlessVector.at(1);
136
+ const getBack2 = await testEndlessVector.at(2);
137
+
138
+ t.ok(equalUint8Arrays(getBack0, data[0]));
139
+ t.ok(equalUint8Arrays(getBack1, data[1]));
140
+ t.ok(equalUint8Arrays(getBack2, data[2]));
141
+
142
+ t.ok(testEndlessVector.length === 3);
143
+ t.ok(testEndlessVector.binaryLength === 6 * 1024);
144
+ });
145
+
146
+ test('make a test EndlessVector and push single Uint8Array to it', async t => {
147
+ endlessVector = await EndlessVector.create({
148
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
149
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
150
+ signAndExecuteTransaction: signAndExecuteTransaction,
151
+ });
152
+
153
+ t.ok(endlessVector);
154
+ t.ok(endlessVector.id);
155
+ t.ok(endlessVector.isWritable); // we provided packageId and signAndExecuteTransaction
156
+
157
+ await endlessVector.push(new Uint8Array([1,2,3]));
158
+
159
+ const getBack = await endlessVector.at(0);
160
+ t.ok(equalUint8Arrays(getBack, new Uint8Array([1,2,3])));
161
+
162
+ await endlessVector.initialize(); // endlessVector.at(0) calles initialize() internally, but we call it again to keep code clear
163
+
164
+ t.ok(endlessVector.length === 1);
165
+ t.ok(endlessVector.binaryLength === 3);
166
+ });
167
+
168
+ test('push Uint8Array larger than max_pure_argument_size to it', async t => {
169
+ const largeArray = randomBytesOfLength(30 * 1024); // 30KB
170
+ await endlessVector.push(largeArray);
171
+
172
+ const getBack = await endlessVector.at(1);
173
+ t.ok(equalUint8Arrays(getBack, largeArray));
174
+ t.ok(getBack.length === 30*1024);
175
+
176
+ t.ok(endlessVector.length === 2);
177
+ t.ok(endlessVector.binaryLength === 3 + 30*1024);
178
+ });
179
+
180
+ test('push Uint8Array larger than max_pure_argument_size to it', async t => {
181
+ const largeArray = randomBytesOfLength(120 * 1024); // 120KB
182
+ await endlessVector.push(largeArray);
183
+
184
+ const getBack = await endlessVector.at(2);
185
+ t.ok(equalUint8Arrays(getBack, largeArray));
186
+ t.ok(getBack.length === 120*1024);
187
+
188
+ t.ok(endlessVector.length === 3);
189
+ t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024);
190
+ });
191
+
192
+
193
+ test('throws an Error is trying to push too large Uint8Array ( split it on the higher level )', async t => {
194
+ const tooLargeSize = 120 * 1024 + 1; // 120KB + 1 byte
195
+ const largeArray = randomBytesOfLength(tooLargeSize);
196
+ await t.rejects(endlessVector.push(largeArray), Error, 'expected an Error to be thrown');
197
+ });
198
+
199
+ test('push few Uint8Array in a single transaction block', async t => {
200
+ const singleItemSize = 40 * 1024;
201
+ const largeArray1 = randomBytesOfLength(singleItemSize);
202
+ const largeArray2 = randomBytesOfLength(singleItemSize);
203
+ const largeArray3 = randomBytesOfLength(singleItemSize);
204
+ const tx = new suiMaster.Transaction();
205
+ await endlessVector.getPushTransaction(largeArray1, tx);
206
+ await endlessVector.getPushTransaction(largeArray2, tx);
207
+ await endlessVector.getPushTransaction(largeArray3, tx);
208
+
209
+ await suiMaster.signAndExecuteTransaction({
210
+ transaction: tx,
211
+ requestType: 'WaitForLocalExecution',
212
+ });
213
+
214
+ await endlessVector.reInitialize(); // force re-initialization to load new data
215
+ const getBack1 = await endlessVector.at(3);
216
+ const getBack2 = await endlessVector.at(4);
217
+ const getBack3 = await endlessVector.at(5);
218
+
219
+ t.ok(equalUint8Arrays(getBack1, largeArray1));
220
+ t.ok(getBack1.length === singleItemSize);
221
+
222
+ t.ok(equalUint8Arrays(getBack2, largeArray2));
223
+ t.ok(getBack2.length === singleItemSize);
224
+
225
+ t.ok(equalUint8Arrays(getBack3, largeArray3));
226
+ t.ok(getBack3.length === singleItemSize);
227
+
228
+ console.log(endlessVector.length);
229
+ console.log(endlessVector.binaryLength);
230
+ t.ok(endlessVector.length === 6);
231
+ t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024 + 3*singleItemSize);
232
+ });
233
+
234
+
235
+ test('test concat functionality', async t => {
236
+ // Create a second EndlessVector
237
+ const endlessVector2 = await EndlessVector.create({
238
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
239
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
240
+ signAndExecuteTransaction: signAndExecuteTransaction,
241
+ });
242
+ t.ok(endlessVector2);
243
+ t.ok(endlessVector2.id);
244
+
245
+ // Push some data to the second vector
246
+ const data1 = new Uint8Array([10, 20, 30]);
247
+ const data2 = new Uint8Array([40, 50, 60]);
248
+ await endlessVector2.push(data1);
249
+ await endlessVector2.push(data2);
250
+
251
+ await endlessVector2.initialize();
252
+ t.ok(endlessVector2.length === 2);
253
+ t.ok(endlessVector2.binaryLength === 6);
254
+
255
+ // Get lengths before concat
256
+ await endlessVector.initialize();
257
+ const v1LengthBefore = endlessVector.length;
258
+ const v1BinaryLengthBefore = endlessVector.binaryLength;
259
+ const v2Length = endlessVector2.length;
260
+ const v2BinaryLength = endlessVector2.binaryLength;
261
+
262
+ console.log('v1 before concat:', { length: v1LengthBefore, binaryLength: v1BinaryLengthBefore });
263
+ console.log('v2 before concat:', { length: v2Length, binaryLength: v2BinaryLength });
264
+
265
+ // Concat the second vector into the first
266
+ // Both of these work - passing ID string or EndlessVector instance:
267
+ // await endlessVector.concat(endlessVector2.id);
268
+ await endlessVector.concat(endlessVector2); // Passing EndlessVector instance
269
+
270
+ // Verify the first vector now contains both vectors' data
271
+ await endlessVector.initialize();
272
+ console.log('v1 after concat:', { length: endlessVector.length, binaryLength: endlessVector.binaryLength });
273
+
274
+ t.ok(endlessVector.length === v1LengthBefore + v2Length, 'length should be sum of both vectors');
275
+ t.ok(endlessVector.binaryLength === v1BinaryLengthBefore + v2BinaryLength, 'binary length should be sum of both vectors');
276
+
277
+ // Verify we can access the concatenated data
278
+ const getBackFromV2_1 = await endlessVector.at(v1LengthBefore);
279
+ const getBackFromV2_2 = await endlessVector.at(v1LengthBefore + 1);
280
+
281
+ t.ok(equalUint8Arrays(getBackFromV2_1, data1), 'first item from v2 should be accessible in v1');
282
+ t.ok(equalUint8Arrays(getBackFromV2_2, data2), 'second item from v2 should be accessible in v1');
283
+ });
284
+
285
+
286
+ test('test concat with array (append)', async t => {
287
+ // Create three new EndlessVectors
288
+ const endlessVector3 = await EndlessVector.create({
289
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
290
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
291
+ signAndExecuteTransaction: signAndExecuteTransaction,
292
+ });
293
+ const endlessVector4 = await EndlessVector.create({
294
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
295
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
296
+ signAndExecuteTransaction: signAndExecuteTransaction,
297
+ });
298
+ const endlessVector5 = await EndlessVector.create({
299
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
300
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
301
+ signAndExecuteTransaction: signAndExecuteTransaction,
302
+ });
303
+
304
+ // Push data to all vectors
305
+ const data3 = new Uint8Array([100, 101, 102]);
306
+ const data4 = new Uint8Array([200, 201, 202]);
307
+ const data5 = new Uint8Array([300, 301, 302]);
308
+
309
+ await endlessVector3.push(data3);
310
+ await endlessVector4.push(data4);
311
+ await endlessVector5.push(data5);
312
+
313
+ await endlessVector3.initialize();
314
+ await endlessVector4.initialize();
315
+ await endlessVector5.initialize();
316
+
317
+ const v3Length = endlessVector3.length;
318
+ const v3BinaryLength = endlessVector3.binaryLength;
319
+ const v4Length = endlessVector4.length;
320
+ const v4BinaryLength = endlessVector4.binaryLength;
321
+ const v5Length = endlessVector5.length;
322
+ const v5BinaryLength = endlessVector5.binaryLength;
323
+
324
+ console.log('Before append:', { v3Length, v4Length, v5Length });
325
+
326
+ // Concat with array (append) - passing array of EndlessVector instances
327
+ await endlessVector3.concat([endlessVector4, endlessVector5]);
328
+
329
+ await endlessVector3.initialize();
330
+ console.log('After append:', { length: endlessVector3.length, binaryLength: endlessVector3.binaryLength });
331
+
332
+ t.ok(endlessVector3.length === v3Length + v4Length + v5Length, 'length should be sum of all vectors');
333
+ t.ok(endlessVector3.binaryLength === v3BinaryLength + v4BinaryLength + v5BinaryLength, 'binary length should be sum of all vectors');
334
+
335
+ // Verify data integrity
336
+ const retrieved3 = await endlessVector3.at(0);
337
+ const retrieved4 = await endlessVector3.at(1);
338
+ const retrieved5 = await endlessVector3.at(2);
339
+
340
+ t.ok(equalUint8Arrays(retrieved3, data3), 'v3 data should match');
341
+ t.ok(equalUint8Arrays(retrieved4, data4), 'v4 data should match');
342
+ t.ok(equalUint8Arrays(retrieved5, data5), 'v5 data should match');
343
+ });
344
+
345
+
346
+ test('test concat large EndlessVectors', async t => {
347
+ // Create three new EndlessVectors
348
+ const endlessVectorMain = await EndlessVector.create({
349
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
350
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
351
+ signAndExecuteTransaction: signAndExecuteTransaction,
352
+ });
353
+ const endlessVectorSecond = await EndlessVector.create({
354
+ suiClient: suiMaster.client, // instance of Sui SDK SuiClient
355
+ packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
356
+ signAndExecuteTransaction: signAndExecuteTransaction,
357
+ });
358
+
359
+ const data = [
360
+ ];
361
+
362
+ for (let i = 0; i < 33; i++) {
363
+ let toMain = randomBytesOfLength(0 + Math.floor(1024 + 50 * 1024 * Math.random())); // 1KB - 50KB
364
+ if (Math.random() < 0.2) {
365
+ toMain = new Uint8Array([]); // 20% chance to push empty array
366
+ }
367
+
368
+ data.push(toMain);
369
+ }
370
+
371
+ for (const item of data) {
372
+ await endlessVectorMain.push(item);
373
+ }
374
+
375
+ for (let i = 0; i < 33; i++) {
376
+ let toSecond = randomBytesOfLength(0 + Math.floor(1024 + 50 * 1024 * Math.random())); // 1KB - 50KB
377
+ if (Math.random() < 0.2) {
378
+ toSecond = new Uint8Array([]); // 20% chance to push empty array
379
+ }
380
+
381
+ await endlessVectorSecond.push(toSecond);
382
+ data.push(toSecond);
383
+ }
384
+
385
+ await endlessVectorMain.concat(endlessVectorSecond);
386
+
387
+ await endlessVectorMain.initialize();
388
+
389
+ t.ok(endlessVectorMain.length === data.length, 'length should be sum of both vectors');
390
+
391
+ // Verify data integrity
392
+ for (let i = 0; i < data.length; i++) {
393
+ const retrieved = await endlessVectorMain.at(i);
394
+ t.ok(equalUint8Arrays(retrieved, data[i]), `data at index ${i} should match`);
395
+ }
396
+ });
397
+
398
+
399
+ test('test parallel creation, push, and append of 6 vectors', async t => {
400
+ const vectorCount = 6;
401
+
402
+ // Split gas into at least N coins to use them in parallel transactions
403
+ const splitTx = new suiMaster.Transaction();
404
+ for (let i = 0; i < vectorCount; i++) {
405
+ let coin = splitTx.splitCoins(splitTx.gas, [splitTx.pure.u64(BigInt(1000000000))]);
406
+ splitTx.transferObjects([coin], splitTx.pure.address(suiMaster.address));
407
+ }
408
+ await suiMaster.signAndExecuteTransaction({
409
+ transaction: splitTx,
410
+ requestType: 'WaitForLocalExecution',
411
+ });
412
+
413
+ // Get gas coins and prepare inputs for tx.setGasPayment(...);
414
+ const gasCoins = await suiMaster.client.getCoins({
415
+ owner: suiMaster.address,
416
+ coinType: '0x2::sui::SUI',
417
+ });
418
+ const gasCoinInputs = gasCoins.data.map((c) => {
419
+ return {
420
+ objectId: c.coinObjectId,
421
+ digest: c.digest,
422
+ version: c.version,
423
+ };
424
+ });
425
+
426
+ t.ok(gasCoinInputs.length >= vectorCount, `should have at least ${vectorCount} gas coins for parallel vector creation.`);
427
+
428
+ // Prepare test data for each vector
429
+ const testData = [];
430
+ for (let i = 0; i < vectorCount; i++) {
431
+ testData.push(randomBytesOfLength(1024));
432
+ }
433
+
434
+ // Create N EndlessVectors in parallel using static create method
435
+ const createPromises = [];
436
+ for (let i = 0; i < vectorCount; i++) {
437
+ createPromises.push(
438
+ EndlessVector.create({
439
+ suiClient: suiMaster.client,
440
+ packageId: contract.id,
441
+ array: testData[i] ? testData[i] : null,
442
+ gasCoin: gasCoinInputs[i],
443
+ signAndExecuteTransaction: async (tx) => {
444
+ console.log(`Creating vector ${i}...`);
445
+ try {
446
+ const results = await suiMaster.signAndExecuteTransaction({
447
+ transaction: tx,
448
+ });
449
+ return results.digest;
450
+ } catch (e) {
451
+ console.error(`Error creating vector ${i}:`, e);
452
+ }
453
+ },
454
+ })
455
+ );
456
+ }
457
+
458
+ const vectors = await Promise.all(createPromises);
459
+ vectors.forEach((v, i) => {
460
+ console.log(`Created vector ${i} with id: ${v.id}`);
461
+ });
462
+
463
+ t.ok(vectors.length === vectorCount, `should have created ${vectorCount} vectors`);
464
+
465
+ const ids = {};
466
+ vectors.forEach((v, i) => {
467
+ t.ok(v.id, `vector ${i} should have an id`);
468
+ t.ok(!ids[v.id], `vector id ${v.id} should be unique`);
469
+ ids[v.id] = i;
470
+ });
471
+
472
+ const mainVector = vectors[0];
473
+ // append other vectors to the first one
474
+ const vectorsToAppend = vectors.slice(1);
475
+ await mainVector.concat(vectorsToAppend);
476
+
477
+ // Verify the data after concatenation
478
+ await mainVector.initialize();
479
+
480
+ const expectedLength = vectorCount;
481
+ t.ok(mainVector.length === expectedLength, `mainVector should have ${expectedLength} items, got ${mainVector.length}`);
482
+
483
+ console.log(`Verifying ${expectedLength} items in concatenated vector...`);
484
+
485
+ // Verify each item matches the original testData
486
+ for (let vectorIdx = 0; vectorIdx < vectorCount; vectorIdx++) {
487
+ const expected = testData[vectorIdx];
488
+ const retrieved = await mainVector.at(vectorIdx);
489
+
490
+ t.ok(
491
+ equalUint8Arrays(retrieved, expected),
492
+ `Item ${vectorIdx} (vector ${vectorIdx}, item ${vectorIdx}) should match`
493
+ );
494
+ }
495
+ });
496
+
497
+ test('stops local test node', async t => {
498
+ await SuiLocalTestValidator.stop();
499
+ });
@@ -0,0 +1,93 @@
1
+ import { fileURLToPath } from 'url';
2
+ import path from 'path';
3
+ import { SuiMaster, SuiLocalTestValidator } from 'suidouble';
4
+ import LocalnodeWalrusTestState from '../../../seal_walrus_localnet/includes/LocalnodeWalrusTestState.js';
5
+ import LocalnodeWalrusTestServer from '../../../seal_walrus_localnet/includes/LocalnodeWalrusTestServer.js';
6
+ import LocalnodeSealTestState from '../../../seal_walrus_localnet/includes/LocalnodeSealTestState.js';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const MOVE_PKG_PATH = path.join(__dirname, '../../move');
10
+
11
+ /**
12
+ * Process-scoped cache — shared across all test files in the same vitest worker
13
+ * (requires singleFork: true + isolate: false in vitest.config.js).
14
+ *
15
+ * @type {Promise<{ suiMaster, walrusState, walrusServer, walrusClient, packageId }> | null}
16
+ */
17
+ let cached = null;
18
+ let exitHookInstalled = false;
19
+
20
+ function installExitHook() {
21
+ if (exitHookInstalled) return;
22
+ exitHookInstalled = true;
23
+ process.once('beforeExit', async () => {
24
+ try {
25
+ const { walrusServer } = await cached;
26
+ await walrusServer?.stop();
27
+ } catch { /* best-effort */ }
28
+ try { await SuiLocalTestValidator.stop(); } catch { /* best-effort */ }
29
+ });
30
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
31
+ process.once(sig, () => {
32
+ (cached ? cached.then(({ walrusServer }) => walrusServer?.stop()).catch(() => {}) : Promise.resolve())
33
+ .then(() => SuiLocalTestValidator.stop())
34
+ .finally(() => process.exit(0));
35
+ });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Boot the local validator, deploy the endless_vector package, and start a
41
+ * WalrusTestServer. Idempotent — the first call does the work, subsequent
42
+ * calls return the same handles.
43
+ *
44
+ * @param {{ debug?: boolean }} [opts]
45
+ * @returns {Promise<{ suiMaster: import('suidouble').SuiMaster, walrusState: import('../../../../seal_walrus_localnet/includes/LocalnodeWalrusTestState.js').default, walrusServer: import('../../../../seal_walrus_localnet/includes/LocalnodeWalrusTestServer.js').default, walrusClient: import('@mysten/walrus').WalrusClient, packageId: string }>}
46
+ */
47
+ export function setupEndlessVectorLocalnet(opts = {}) {
48
+ if (cached) return cached;
49
+ installExitHook();
50
+ cached = (async () => {
51
+ const debug = !!opts.debug;
52
+ const validator = await SuiLocalTestValidator.launch({ debug });
53
+ if (!validator.active) throw new Error('local test validator failed to start');
54
+
55
+ const suiMaster = new SuiMaster({ client: validator, as: 'ev_tester', debug });
56
+ await suiMaster.initialize();
57
+ await suiMaster.requestSuiFromFaucet();
58
+ await suiMaster.requestSuiFromFaucet();
59
+ await suiMaster.requestSuiFromFaucet();
60
+
61
+ const walrusState = new LocalnodeWalrusTestState({
62
+ suiMaster,
63
+ packagePath: MOVE_PKG_PATH,
64
+ epochDuration: 30_000,
65
+ });
66
+ await walrusState.deploy();
67
+
68
+ const packageId = walrusState.walrusPackageId;
69
+
70
+ const walrusServer = new LocalnodeWalrusTestServer({ state: walrusState });
71
+ await walrusServer.start();
72
+
73
+ const walrusClient = await walrusServer.getWalrusClient({ suiMaster });
74
+
75
+ // Deploy Seal mock — lets sealed-vector tests share the same validator/walrus stack.
76
+ // Cheap if unused; sealed tests pick it up via `await sealState.getSealClient(...)`.
77
+ const sealState = new LocalnodeSealTestState({ suiMaster });
78
+ await sealState.deploy({ name: 'ev-seal', url: walrusServer.url });
79
+ walrusServer.seal = sealState;
80
+ const sealClient = await walrusServer.getSealClient({ suiMaster });
81
+
82
+ return { suiMaster, walrusState, walrusServer, walrusClient, sealState, sealClient, packageId };
83
+ })().catch((err) => {
84
+ cached = null;
85
+ throw err;
86
+ });
87
+ return cached;
88
+ }
89
+
90
+ /** No-op per-suite — cleanup is handled by the beforeExit hook. */
91
+ export async function teardownEndlessVectorLocalnet() {
92
+ // intentionally empty
93
+ }
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import EndlessVectorItem from '../EndlessVectorItem.js';
3
+
4
+ describe('EndlessVectorItem.fromGrpcJson browser decoding', () => {
5
+ it('decodes base64 gRPC JSON without Node Buffer', async () => {
6
+ const originalBuffer = globalThis.Buffer;
7
+ try {
8
+ globalThis.Buffer = undefined;
9
+
10
+ const item = EndlessVectorItem.fromGrpcJson({
11
+ bytes: 'AQID',
12
+ meta: 'BAU=',
13
+ blob: null,
14
+ });
15
+
16
+ expect([...(await item.bytes())]).toEqual([1, 2, 3]);
17
+ expect([...item.meta]).toEqual([4, 5]);
18
+ } finally {
19
+ globalThis.Buffer = originalBuffer;
20
+ }
21
+ });
22
+ });
package/test/helpers.js CHANGED
@@ -28,5 +28,5 @@ function randomBytesOfLength(length) {
28
28
  export {
29
29
  equalUint8Arrays,
30
30
  formatBytes,
31
- randomBytesOfLength
32
- };
31
+ randomBytesOfLength,
32
+ };
@@ -0,0 +1,32 @@
1
+ import crypto from 'crypto';
2
+
3
+ function equalUint8Arrays(a, b) {
4
+ if (a.length !== b.length) return false;
5
+ for (let i = 0; i < a.length; i++) {
6
+ if (a[i] !== b[i]) return false;
7
+ }
8
+ return true;
9
+ }
10
+
11
+ function formatBytes(bytes, decimals = 2) {
12
+ if (bytes === 0) return '0 Bytes';
13
+
14
+ const k = 1024;
15
+ const dm = decimals < 0 ? 0 : decimals;
16
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
17
+
18
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
19
+ const size = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
20
+
21
+ return `${size} ${sizes[i]}`;
22
+ }
23
+
24
+ function randomBytesOfLength(length) {
25
+ return new Uint8Array(crypto.randomBytes(length));
26
+ }
27
+
28
+ export {
29
+ equalUint8Arrays,
30
+ formatBytes,
31
+ randomBytesOfLength
32
+ };