@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.
package/test/base.test.js CHANGED
@@ -1,499 +1,460 @@
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
-
1
+ /**
2
+ * Integration test for EndlessVector SDK methods (create, push, at, concat)
3
+ * using the seal_walrus_localnet infrastructure and suidouble v2 / gRPC client.
4
+ *
5
+ * Run: pnpm test:base (or pnpm test to run all suites)
6
+ */
7
+
8
+ import { beforeAll, afterAll, describe, it, expect } from 'vitest';
9
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);
10
+ import { equalUint8Arrays, randomBytesOfLength } from './helpers.js';
11
+ import { setupEndlessVectorLocalnet, teardownEndlessVectorLocalnet } from './fixture.js';
14
12
 
15
- const { test } = t;
13
+ const TX_TIMEOUT = 60_000;
16
14
 
17
- let suiLocalTestValidator = null;
15
+ let suiMaster;
16
+ let walrusServer;
17
+ let walrusClient;
18
+ let packageId;
18
19
 
19
- /** @type {?SuiMaster} */
20
- let suiMaster = null;
21
- let signAndExecuteTransaction = null;
20
+ // ─── setup ────────────────────────────────────────────────────────────────────
22
21
 
23
- let contract = null;
24
-
25
- /** @type {?SuiObject} */
26
- let endlessVectorRaw = null; // SuiObject instance
27
- /** @type {?EndlessVector} */
28
- let endlessVector = null; // EndlessVector instance
22
+ beforeAll(async () => {
23
+ ({ suiMaster, walrusServer, walrusClient, packageId } = await setupEndlessVectorLocalnet());
24
+ console.log('package id:', packageId);
25
+ });
29
26
 
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);
27
+ afterAll(async () => {
28
+ await teardownEndlessVectorLocalnet();
36
29
  });
37
30
 
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'
31
+ // ─── helpers ──────────────────────────────────────────────────────────────────
43
32
 
44
- signAndExecuteTransaction = async (tx) => {
45
- // function accepting Transaction and returning signed and submitted transaction digest
46
- const results = await suiMaster.signAndExecuteTransaction({
33
+ function makeSignAndExecute() {
34
+ return async (tx) => {
35
+ const result = await suiMaster.signAndExecuteTransaction({
47
36
  transaction: tx,
37
+ // requestType: 'WaitForLocalExecution',
48
38
  });
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
- });
39
+ return result.digest;
40
+ };
41
+ }
64
42
 
65
- test('attach a local package', async t => {
66
- contract = suiMaster.addPackage({
67
- path: path.join(__dirname, '../../move'),
43
+ async function createEV(array = null) {
44
+ return EndlessVector.create({
45
+ suiClient: suiMaster.client,
46
+ packageId,
47
+ walrusClient,
48
+ aggregatorUrl: walrusServer?.url,
49
+ senderAddress: suiMaster.address,
50
+ signAndExecuteTransaction: makeSignAndExecute(),
51
+ ...(array ? { array } : {}),
68
52
  });
69
- await contract.build({ withUnpublishedDependencies: true, });
70
- await contract.publish();
71
- });
72
-
53
+ }
73
54
 
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({
55
+ function makeEV(id) {
56
+ return new EndlessVector({
109
57
  suiClient: suiMaster.client,
110
- id: createdVectorId,
58
+ id,
59
+ packageId,
60
+ signAndExecuteTransaction: makeSignAndExecute(),
111
61
  });
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
- });
62
+ }
118
63
 
64
+ // ─── tests ────────────────────────────────────────────────────────────────────
119
65
 
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,
66
+ describe('deployment', () => {
67
+ it('publishes the combined package', () => {
68
+ expect(packageId).toMatch(/^0x[0-9a-f]+$/);
132
69
  });
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
70
  });
145
71
 
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
72
+ describe('raw create transaction', () => {
73
+ it('creates EndlessVector with 120KB payload via raw tx and reads it back', async () => {
74
+ const arr = randomBytesOfLength(120 * 1024);
156
75
 
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])));
76
+ const tx = new suiMaster.Transaction();
77
+ const vectorTxInput = await EndlessVector.getCreateTransactionAndReturnVectorInput(
78
+ { packageId },
79
+ arr,
80
+ tx,
81
+ );
82
+ tx.transferObjects([vectorTxInput], tx.pure.address(suiMaster.address));
161
83
 
162
- await endlessVector.initialize(); // endlessVector.at(0) calles initialize() internally, but we call it again to keep code clear
84
+ const signAndExecute = makeSignAndExecute();
85
+ const digest = await signAndExecute(tx);
163
86
 
164
- t.ok(endlessVector.length === 1);
165
- t.ok(endlessVector.binaryLength === 3);
166
- });
87
+ const txResult = await suiMaster.client.waitForTransaction({
88
+ digest,
89
+ include: { effects: true, objectTypes: true },
90
+ });
91
+ const txData = txResult.Transaction ?? txResult.FailedTransaction;
92
+ expect(txData?.status?.success).toBe(true);
167
93
 
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);
94
+ const objectTypes = txData.objectTypes ?? {};
95
+ const created = txData.effects?.changedObjects?.find(
96
+ c => c.idOperation === 'Created' &&
97
+ (objectTypes[c.objectId] ?? '').includes('EndlessWalrusVector'),
98
+ );
99
+ expect(created).toBeDefined();
171
100
 
172
- const getBack = await endlessVector.at(1);
173
- t.ok(equalUint8Arrays(getBack, largeArray));
174
- t.ok(getBack.length === 30*1024);
101
+ const loadBack = makeEV(created.objectId);
102
+ await loadBack.initialize();
175
103
 
176
- t.ok(endlessVector.length === 2);
177
- t.ok(endlessVector.binaryLength === 3 + 30*1024);
104
+ expect(loadBack.length).toBe(1);
105
+ const back = await loadBack.at(0);
106
+ expect(equalUint8Arrays(back, arr)).toBe(true);
107
+ }, TX_TIMEOUT);
178
108
  });
179
109
 
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);
110
+ describe('create with array of chunks', () => {
111
+ it('creates EndlessVector with 3 pre-loaded chunks and reads each back', async () => {
112
+ const data = [
113
+ randomBytesOfLength(1 * 1024),
114
+ randomBytesOfLength(2 * 1024),
115
+ randomBytesOfLength(3 * 1024),
116
+ ];
187
117
 
188
- t.ok(endlessVector.length === 3);
189
- t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024);
190
- });
118
+ const ev = await createEV(data);
191
119
 
120
+ expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
121
+ expect(equalUint8Arrays(await ev.at(1), data[1])).toBe(true);
122
+ expect(equalUint8Arrays(await ev.at(2), data[2])).toBe(true);
192
123
 
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');
124
+ expect(ev.length).toBe(3);
125
+ expect(ev.binaryLength).toBe(6 * 1024);
126
+ }, TX_TIMEOUT);
197
127
  });
198
128
 
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',
129
+ describe('push and at', () => {
130
+ let ev;
131
+
132
+ it('creates empty EndlessVector', async () => {
133
+ ev = await createEV();
134
+ expect(ev).toBeDefined();
135
+ expect(ev.id).toMatch(/^0x[0-9a-f]+$/);
136
+ expect(ev.isWritable).toBe(true);
137
+ }, TX_TIMEOUT);
138
+
139
+ it('pushes small Uint8Array and reads it back', async () => {
140
+ await ev.push(new Uint8Array([1, 2, 3]));
141
+ const back = await ev.at(0);
142
+ expect(equalUint8Arrays(back, new Uint8Array([1, 2, 3]))).toBe(true);
143
+ await ev.initialize();
144
+ expect(ev.length).toBe(1);
145
+ expect(ev.binaryLength).toBe(3);
146
+ }, TX_TIMEOUT);
147
+
148
+ it('pushes 30KB array', async () => {
149
+ const large = randomBytesOfLength(30 * 1024);
150
+ await ev.push(large);
151
+ const back = await ev.at(1);
152
+ expect(equalUint8Arrays(back, large)).toBe(true);
153
+ expect(ev.length).toBe(2);
154
+ expect(ev.binaryLength).toBe(3 + 30 * 1024);
155
+ }, TX_TIMEOUT);
156
+
157
+ it('pushes 120KB array', async () => {
158
+ const large = randomBytesOfLength(120 * 1024);
159
+ await ev.push(large);
160
+ const back = await ev.at(2);
161
+ expect(equalUint8Arrays(back, large)).toBe(true);
162
+ expect(ev.length).toBe(3);
163
+ expect(ev.binaryLength).toBe(3 + 30 * 1024 + 120 * 1024);
164
+ }, TX_TIMEOUT);
165
+
166
+ it('rejects arrays larger than 120KB when no walrus is configured', async () => {
167
+ const evNoWalrus = new EndlessVector({
168
+ suiClient: suiMaster.client,
169
+ id: ev.id,
170
+ packageId,
171
+ signAndExecuteTransaction: makeSignAndExecute(),
212
172
  });
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,
173
+ const tooLarge = randomBytesOfLength(120 * 1024 + 1);
174
+ await expect(evNoWalrus.push(tooLarge)).rejects.toThrow();
302
175
  });
303
176
 
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]);
177
+ let walrusLargeData;
308
178
 
309
- await endlessVector3.push(data3);
310
- await endlessVector4.push(data4);
311
- await endlessVector5.push(data5);
179
+ it('auto-routes arrays > 120 KB to walrus when configured', async () => {
180
+ walrusLargeData = randomBytesOfLength(200 * 1024);
181
+ const ok = await ev.push(walrusLargeData);
182
+ expect(ok).toBe(true);
312
183
 
313
- await endlessVector3.initialize();
314
- await endlessVector4.initialize();
315
- await endlessVector5.initialize();
184
+ await ev.initialize();
185
+ expect(ev.length).toBe(4);
186
+ }, TX_TIMEOUT);
316
187
 
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;
188
+ it('reads back the walrus-stored item via at()', async () => {
189
+ const back = await ev.at(3);
190
+ expect(equalUint8Arrays(back, walrusLargeData)).toBe(true);
191
+ }, TX_TIMEOUT);
323
192
 
324
- console.log('Before append:', { v3Length, v4Length, v5Length });
193
+ it('pushes 3 × 40KB in a single transaction block', async () => {
194
+ const size = 40 * 1024;
195
+ const a1 = randomBytesOfLength(size);
196
+ const a2 = randomBytesOfLength(size);
197
+ const a3 = randomBytesOfLength(size);
325
198
 
326
- // Concat with array (append) - passing array of EndlessVector instances
327
- await endlessVector3.concat([endlessVector4, endlessVector5]);
199
+ const tx = new suiMaster.Transaction();
200
+ ev.getPushTransaction(a1, tx);
201
+ ev.getPushTransaction(a2, tx);
202
+ ev.getPushTransaction(a3, tx);
328
203
 
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');
204
+ await suiMaster.signAndExecuteTransaction({
205
+ transaction: tx,
206
+ requestType: 'WaitForLocalExecution',
207
+ });
334
208
 
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);
209
+ await ev.reInitialize();
210
+ await ev.initialize();
339
211
 
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');
212
+ expect(equalUint8Arrays(await ev.at(4), a1)).toBe(true);
213
+ expect(equalUint8Arrays(await ev.at(5), a2)).toBe(true);
214
+ expect(equalUint8Arrays(await ev.at(6), a3)).toBe(true);
215
+ expect(ev.length).toBe(7);
216
+ expect(ev.binaryLength).toBe(3 + 30 * 1024 + 120 * 1024 + 200 * 1024 + 3 * size);
217
+ }, TX_TIMEOUT);
343
218
  });
344
219
 
220
+ describe('concat', () => {
221
+ it('appends a second vector into the first and verifies data', async () => {
222
+ const ev1 = await createEV();
223
+ const ev2 = await createEV();
345
224
 
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
- });
225
+ const d1 = new Uint8Array([10, 20, 30]);
226
+ const d2 = new Uint8Array([40, 50, 60]);
227
+ await ev2.push(d1);
228
+ await ev2.push(d2);
358
229
 
359
- const data = [
360
- ];
230
+ await ev2.initialize();
231
+ expect(ev2.length).toBe(2);
361
232
 
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
- }
233
+ await ev1.initialize();
234
+ const len1Before = ev1.length;
235
+ const binLen1Before = ev1.binaryLength;
367
236
 
368
- data.push(toMain);
369
- }
237
+ await ev1.concat(ev2);
238
+ await ev1.initialize();
370
239
 
371
- for (const item of data) {
372
- await endlessVectorMain.push(item);
373
- }
240
+ expect(ev1.length).toBe(len1Before + 2);
241
+ expect(ev1.binaryLength).toBe(binLen1Before + 6);
374
242
 
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
- }
243
+ expect(equalUint8Arrays(await ev1.at(len1Before), d1)).toBe(true);
244
+ expect(equalUint8Arrays(await ev1.at(len1Before + 1), d2)).toBe(true);
245
+ }, TX_TIMEOUT);
384
246
 
385
- await endlessVectorMain.concat(endlessVectorSecond);
247
+ it('appends array of vectors via concat([...])', async () => {
248
+ const ev3 = await createEV();
249
+ const ev4 = await createEV();
250
+ const ev5 = await createEV();
386
251
 
387
- await endlessVectorMain.initialize();
252
+ const d3 = new Uint8Array([100, 101, 102]);
253
+ const d4 = new Uint8Array([200, 201, 202]);
254
+ const d5 = new Uint8Array([44, 55, 66]);
388
255
 
389
- t.ok(endlessVectorMain.length === data.length, 'length should be sum of both vectors');
256
+ await ev3.push(d3);
257
+ await ev4.push(d4);
258
+ await ev5.push(d5);
390
259
 
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
- });
260
+ await ev3.initialize();
261
+ await ev4.initialize();
262
+ await ev5.initialize();
397
263
 
264
+ const len3 = ev3.length;
265
+ const bin3 = ev3.binaryLength;
398
266
 
399
- test('test parallel creation, push, and append of 6 vectors', async t => {
400
- const vectorCount = 6;
267
+ await ev3.concat([ev4, ev5]);
268
+ await ev3.initialize();
401
269
 
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
- });
270
+ expect(ev3.length).toBe(len3 + 2);
271
+ expect(ev3.binaryLength).toBe(bin3 + 6);
412
272
 
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
- }
273
+ expect(equalUint8Arrays(await ev3.at(0), d3)).toBe(true);
274
+ expect(equalUint8Arrays(await ev3.at(1), d4)).toBe(true);
275
+ expect(equalUint8Arrays(await ev3.at(2), d5)).toBe(true);
276
+ }, TX_TIMEOUT);
277
+ });
457
278
 
458
- const vectors = await Promise.all(createPromises);
459
- vectors.forEach((v, i) => {
460
- console.log(`Created vector ${i} with id: ${v.id}`);
461
- });
279
+ describe('parallel creation and append', () => {
280
+ it('creates 6 vectors in parallel and concatenates them', async () => {
281
+ const vectorCount = 6;
462
282
 
463
- t.ok(vectors.length === vectorCount, `should have created ${vectorCount} vectors`);
283
+ const splitTx = new suiMaster.Transaction();
284
+ for (let i = 0; i < vectorCount; i++) {
285
+ let coin = splitTx.splitCoins(splitTx.gas, [splitTx.pure.u64(BigInt(1000000000))]);
286
+ splitTx.transferObjects([coin], splitTx.pure.address(suiMaster.address));
287
+ }
288
+ await suiMaster.signAndExecuteTransaction({
289
+ transaction: splitTx,
290
+ requestType: 'WaitForLocalExecution',
291
+ });
464
292
 
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
- });
293
+ const testData = Array.from({ length: vectorCount }, () => randomBytesOfLength(1024));
471
294
 
472
- const mainVector = vectors[0];
473
- // append other vectors to the first one
474
- const vectorsToAppend = vectors.slice(1);
475
- await mainVector.concat(vectorsToAppend);
295
+ const gasCoinsResponse = await suiMaster.client.listCoins({
296
+ owner: suiMaster.address,
297
+ coinType: '0x2::sui::SUI',
298
+ });
299
+ const gasCoinInputs = gasCoinsResponse.objects.map((c) => ({
300
+ objectId: c.objectId,
301
+ digest: c.digest,
302
+ version: c.version,
303
+ }));
304
+
305
+ const createPromises = [];
306
+ for (let i = 0; i < vectorCount; i++) {
307
+ createPromises.push(
308
+ EndlessVector.create({
309
+ suiClient: suiMaster.client,
310
+ packageId,
311
+ array: testData[i] ?? null,
312
+ gasCoin: gasCoinInputs[i],
313
+ signAndExecuteTransaction: makeSignAndExecute(),
314
+ })
315
+ );
316
+ }
476
317
 
477
- // Verify the data after concatenation
478
- await mainVector.initialize();
318
+ const vectors = await Promise.all(createPromises);
479
319
 
480
- const expectedLength = vectorCount;
481
- t.ok(mainVector.length === expectedLength, `mainVector should have ${expectedLength} items, got ${mainVector.length}`);
320
+ expect(vectors).toHaveLength(vectorCount);
321
+ vectors.forEach((v, i) => {
322
+ expect(v.id).toMatch(/^0x[0-9a-f]+$/);
323
+ console.log(`vector ${i}: ${v.id}`);
324
+ });
482
325
 
483
- console.log(`Verifying ${expectedLength} items in concatenated vector...`);
326
+ const main = vectors[0];
327
+ await main.concat(vectors.slice(1));
328
+ await main.initialize();
484
329
 
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);
330
+ expect(main.length).toBe(vectorCount);
489
331
 
490
- t.ok(
491
- equalUint8Arrays(retrieved, expected),
492
- `Item ${vectorIdx} (vector ${vectorIdx}, item ${vectorIdx}) should match`
493
- );
494
- }
332
+ for (let i = 0; i < vectorCount; i++) {
333
+ const back = await main.at(i);
334
+ expect(equalUint8Arrays(back, testData[i])).toBe(true);
335
+ }
336
+ }, 300_000);
495
337
  });
496
338
 
497
- test('stops local test node', async t => {
498
- await SuiLocalTestValidator.stop();
499
- });
339
+ describe('archive', () => {
340
+ it('archives history and data remains readable', async () => {
341
+ const data = [
342
+ randomBytesOfLength(1 * 1024),
343
+ randomBytesOfLength(2 * 1024),
344
+ randomBytesOfLength(3 * 1024),
345
+ ];
346
+
347
+ const ev = await createEV(data);
348
+ await ev.initialize();
349
+
350
+ const lengthBefore = ev.length;
351
+ const binLengthBefore = ev.binaryLength;
352
+ expect(lengthBefore).toBe(3);
353
+
354
+ const extra = randomBytesOfLength(120 * 1024);
355
+ await ev.push(extra);
356
+
357
+ await ev.archive();
358
+ await ev.initialize();
359
+
360
+ expect(ev.length).toBe(4);
361
+ expect(ev.binaryLength).toBe(binLengthBefore + 120 * 1024);
362
+ expect(ev.archiveItemsCount).toBe(1);
363
+ expect(ev.archivedAtLength).toBe(4);
364
+ expect(ev.historyItemsCount).toBe(0);
365
+ expect(ev.burnedArchiveCount).toBe(0);
366
+
367
+ expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
368
+ expect(equalUint8Arrays(await ev.at(1), data[1])).toBe(true);
369
+ expect(equalUint8Arrays(await ev.at(2), data[2])).toBe(true);
370
+ expect(equalUint8Arrays(await ev.at(3), extra)).toBe(true);
371
+
372
+ const afterArchive = randomBytesOfLength(4 * 1024);
373
+ await ev.push(afterArchive);
374
+ await ev.initialize();
375
+
376
+ expect(ev.length).toBe(5);
377
+ expect(equalUint8Arrays(await ev.at(4), afterArchive)).toBe(true);
378
+ expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
379
+ expect(equalUint8Arrays(await ev.at(3), extra)).toBe(true);
380
+
381
+ await ev.burnArchive();
382
+ await ev.initialize();
383
+
384
+ expect(ev.burnedArchiveCount).toBe(1);
385
+ expect(ev.archivedFromLength).toBe(4);
386
+ expect(ev.length).toBe(5);
387
+
388
+ expect(equalUint8Arrays(await ev.at(4), afterArchive)).toBe(true);
389
+
390
+ await expect(ev.at(0)).rejects.toThrow();
391
+ await expect(ev.at(1)).rejects.toThrow();
392
+ await expect(ev.at(2)).rejects.toThrow();
393
+ await expect(ev.at(3)).rejects.toThrow();
394
+ }, TX_TIMEOUT);
395
+
396
+ it('multiple archive/burn cycles accumulate archivedFromLength correctly', async () => {
397
+ const ev = await createEV();
398
+
399
+ const batch1 = randomBytesOfLength(120 * 1024);
400
+ await ev.push(batch1);
401
+ await ev.archive();
402
+ await ev.initialize();
403
+ expect(ev.archiveItemsCount).toBe(1);
404
+ expect(ev.archivedAtLength).toBe(1);
405
+
406
+ const batch2 = randomBytesOfLength(120 * 1024);
407
+ await ev.push(batch2);
408
+ await ev.archive();
409
+ await ev.initialize();
410
+ expect(ev.archiveItemsCount).toBe(2);
411
+ expect(ev.archivedAtLength).toBe(2);
412
+
413
+ expect(equalUint8Arrays(await ev.at(0), batch1)).toBe(true);
414
+ expect(equalUint8Arrays(await ev.at(1), batch2)).toBe(true);
415
+
416
+ await ev.burnArchive();
417
+ await ev.initialize();
418
+ expect(ev.burnedArchiveCount).toBe(1);
419
+ expect(ev.archivedFromLength).toBe(1);
420
+
421
+ await expect(ev.at(0)).rejects.toThrow();
422
+ expect(equalUint8Arrays(await ev.at(1), batch2)).toBe(true);
423
+
424
+ await ev.burnArchive();
425
+ await ev.initialize();
426
+ expect(ev.burnedArchiveCount).toBe(2);
427
+ expect(ev.archivedFromLength).toBe(2);
428
+
429
+ await expect(ev.at(1)).rejects.toThrow();
430
+ }, TX_TIMEOUT);
431
+
432
+ it('re-reading vector by ID after archive and burn reflects correct state', async () => {
433
+ const ev = await createEV();
434
+ const item = randomBytesOfLength(120 * 1024);
435
+ await ev.push(item);
436
+ await ev.archive();
437
+ await ev.burnArchive();
438
+
439
+ const fresh = makeEV(ev.id);
440
+ await fresh.initialize();
441
+
442
+ expect(fresh.burnedArchiveCount).toBe(1);
443
+ expect(fresh.archivedFromLength).toBe(1);
444
+ expect(fresh.length).toBe(1);
445
+ await expect(fresh.at(0)).rejects.toThrow();
446
+ }, TX_TIMEOUT);
447
+
448
+ it('concat rejects a source vector that has archived items', async () => {
449
+ const src = await createEV();
450
+ const dst = await createEV();
451
+
452
+ const item = randomBytesOfLength(120 * 1024);
453
+ await src.push(item);
454
+ await src.archive();
455
+ await src.initialize();
456
+ expect(src.archiveItemsCount).toBeGreaterThan(0);
457
+
458
+ await expect(dst.concat(src)).rejects.toThrow();
459
+ }, TX_TIMEOUT);
460
+ });