jazz-tools 0.18.25 → 0.18.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +44 -44
  2. package/CHANGELOG.md +10 -0
  3. package/dist/{chunk-DOCEAUVD.js → chunk-ZIAN4UY5.js} +338 -9
  4. package/dist/chunk-ZIAN4UY5.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/testing.js +1 -1
  7. package/dist/tools/coValues/coVector.d.ts +127 -0
  8. package/dist/tools/coValues/coVector.d.ts.map +1 -0
  9. package/dist/tools/implementation/zodSchema/coExport.d.ts +2 -1
  10. package/dist/tools/implementation/zodSchema/coExport.d.ts.map +1 -1
  11. package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts.map +1 -1
  12. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts +47 -0
  13. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts.map +1 -0
  14. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts +2 -2
  15. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts.map +1 -1
  16. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts +2 -2
  17. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts.map +1 -1
  18. package/dist/tools/implementation/zodSchema/zodCo.d.ts +2 -1
  19. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  20. package/dist/tools/implementation/zodSchema/zodSchema.d.ts +3 -2
  21. package/dist/tools/implementation/zodSchema/zodSchema.d.ts.map +1 -1
  22. package/dist/tools/internal.d.ts +2 -0
  23. package/dist/tools/internal.d.ts.map +1 -1
  24. package/dist/tools/tests/coVector.test-d.d.ts +2 -0
  25. package/dist/tools/tests/coVector.test-d.d.ts.map +1 -0
  26. package/dist/tools/tests/coVector.test.d.ts +2 -0
  27. package/dist/tools/tests/coVector.test.d.ts.map +1 -0
  28. package/package.json +4 -4
  29. package/src/tools/coValues/coVector.ts +432 -0
  30. package/src/tools/implementation/zodSchema/coExport.ts +2 -0
  31. package/src/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.ts +13 -0
  32. package/src/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.ts +105 -0
  33. package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.ts +9 -5
  34. package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.ts +15 -9
  35. package/src/tools/implementation/zodSchema/zodCo.ts +15 -0
  36. package/src/tools/implementation/zodSchema/zodSchema.ts +15 -6
  37. package/src/tools/internal.ts +2 -0
  38. package/src/tools/tests/coVector.test-d.ts +40 -0
  39. package/src/tools/tests/coVector.test.ts +891 -0
  40. package/dist/chunk-DOCEAUVD.js.map +0 -1
@@ -0,0 +1,891 @@
1
+ import { assert, beforeEach, describe, expect, test, vi } from "vitest";
2
+ import { cojsonInternals, Group, isControlledAccount, z } from "../index.js";
3
+ import {
4
+ CoVector,
5
+ CoVectorSchema,
6
+ ControlledAccount,
7
+ Loaded,
8
+ co,
9
+ } from "../internal.js";
10
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
11
+ import { setupTwoNodes, waitFor } from "./utils.js";
12
+
13
+ let me: ControlledAccount;
14
+
15
+ beforeEach(async () => {
16
+ await setupJazzTestSync();
17
+ const account = await createJazzTestAccount({
18
+ isCurrentActiveAccount: true,
19
+ creationProps: { name: "Hermes Puggington" },
20
+ });
21
+ if (!isControlledAccount(account)) {
22
+ throw new Error("account is not a controlled account");
23
+ }
24
+ me = account;
25
+ });
26
+
27
+ const initNodeAndVector = async (
28
+ coVectorSchema: CoVectorSchema,
29
+ vectorInput: number[] | Float32Array = [1, 2, 3],
30
+ ) => {
31
+ const me = await createJazzTestAccount({
32
+ isCurrentActiveAccount: true,
33
+ });
34
+
35
+ const group = Group.create(me);
36
+ group.addMember("everyone", "reader");
37
+
38
+ const coVector = coVectorSchema.create(vectorInput, {
39
+ owner: group,
40
+ });
41
+
42
+ return { me, coVector };
43
+ };
44
+
45
+ const kbToBytes = (kb: number) => kb * 1024;
46
+ const elementsForKb = (kb: number) => kbToBytes(kb) / 4;
47
+
48
+ describe("Defining a co.vector()", () => {
49
+ test("given a non-positive dimensions count • throws an error", () => {
50
+ expect(() => co.vector(-1)).toThrow(
51
+ "co.vector() expects the vector dimensions count to be a positive integer",
52
+ );
53
+ expect(() => co.vector(0)).toThrow(
54
+ "co.vector() expects the vector dimensions count to be a positive integer",
55
+ );
56
+ });
57
+
58
+ test("given a non-integer dimensions count • throws an error", () => {
59
+ expect(() => co.vector(384.5)).toThrow(
60
+ "co.vector() expects the vector dimensions count to be a positive integer",
61
+ );
62
+ });
63
+
64
+ test("given a valid dimensions count • creates a CoVector schema", () => {
65
+ const EmbeddingSchema = co.vector(384);
66
+ expect(EmbeddingSchema.builtin).toBe("CoVector");
67
+ expect(EmbeddingSchema.dimensions).toBe(384);
68
+ expect(EmbeddingSchema.create).toBeDefined();
69
+ });
70
+ });
71
+
72
+ describe("Creating a CoVector", async () => {
73
+ const EmbeddingSchema = co.vector(3);
74
+ const { me } = await initNodeAndVector(EmbeddingSchema);
75
+
76
+ test("given a vector of mismatched dimensions count • throws an error", () => {
77
+ expect(() => EmbeddingSchema.create([1, 2, 3, 4, 5])).toThrow(
78
+ "Vector dimension mismatch! Expected 3 dimensions, got 5",
79
+ );
80
+ });
81
+
82
+ test("directly instantiating CoVector.create • throws an error", () => {
83
+ expect(() => CoVector.create([1, 2, 3])).toThrow(
84
+ "Instantiating CoVector without a dimensions count is not allowed. Use co.vector(...).create() instead.",
85
+ );
86
+ });
87
+
88
+ test("given a Array<number> with valid dimensions count • creates a CoVector", () => {
89
+ const embedding = EmbeddingSchema.create([1, 2, 3]);
90
+ expect(embedding).toBeInstanceOf(CoVector);
91
+ expect(embedding[0]).toBe(1);
92
+ expect(embedding[1]).toBe(2);
93
+ expect(embedding[2]).toBe(3);
94
+ });
95
+
96
+ test("given a Float32Array with valid dimensions count • creates a CoVector", () => {
97
+ const embedding = EmbeddingSchema.create(new Float32Array([1, 2, 3]));
98
+ expect(embedding).toBeInstanceOf(CoVector);
99
+ expect(embedding[0]).toBe(1);
100
+ expect(embedding[1]).toBe(2);
101
+ expect(embedding[2]).toBe(3);
102
+ });
103
+
104
+ test("given Account as owner • creates a CoVector", () => {
105
+ const embedding = EmbeddingSchema.create([1, 2, 3], me);
106
+ expect(embedding.$jazz.owner.myRole()).toEqual("admin");
107
+ });
108
+
109
+ test("given Group as owner • creates a CoVector", () => {
110
+ const group = Group.create(me);
111
+ const embedding = EmbeddingSchema.create([1, 2, 3], group);
112
+
113
+ expect(embedding.$jazz.owner).toEqual(group);
114
+ });
115
+ });
116
+
117
+ describe("CoVector structure", async () => {
118
+ const EmbeddingSchema = co.vector(5);
119
+ const vector = new Float32Array([1, 2, 3, 4, 5]);
120
+ const coVector = EmbeddingSchema.create(vector);
121
+
122
+ test("CoVector keys can be iterated over just like an Float32Array's", () => {
123
+ const keys = [];
124
+
125
+ for (const key in coVector) {
126
+ keys.push(key);
127
+ }
128
+
129
+ expect(keys).toEqual(Object.keys(vector));
130
+ expect(Object.keys(coVector)).toEqual(Object.keys(vector));
131
+ });
132
+
133
+ test("Float32Array can be constructed from a CoVector", () => {
134
+ expect(new Float32Array(coVector)).toEqual(vector);
135
+ });
136
+
137
+ test("a CoVector is structurally equal to a Float32Array", () => {
138
+ expect([...coVector]).toEqual([...vector]);
139
+ });
140
+
141
+ test("a CoVector identity is NOT equal to a Float32Array", () => {
142
+ expect(coVector).not.toEqual(vector);
143
+ });
144
+ });
145
+
146
+ describe("CoVector reader methods & properties (Float32Array-like)", async () => {
147
+ const EmbeddingSchema = co.vector(5);
148
+
149
+ const { me, coVector } = await initNodeAndVector(
150
+ EmbeddingSchema,
151
+ new Float32Array([1, 2, 3, 4, 5]),
152
+ );
153
+
154
+ // Properties
155
+ test("has .length", () => {
156
+ expect(coVector.length).toBe(5);
157
+ });
158
+ test("has .buffer", () => {
159
+ expect(coVector.buffer).toEqual(new Float32Array([1, 2, 3, 4, 5]).buffer);
160
+ });
161
+ test("has .byteLength", () => {
162
+ expect(coVector.byteLength).toBe(20);
163
+ });
164
+ test("has .byteOffset", () => {
165
+ expect(coVector.byteOffset).toBe(0);
166
+ });
167
+
168
+ test("has index access", () => {
169
+ expect(coVector[0]).toBe(1);
170
+ expect(coVector[1]).toBe(2);
171
+ expect(coVector[2]).toBe(3);
172
+ expect(coVector[3]).toBe(4);
173
+ expect(coVector[4]).toBe(5);
174
+ });
175
+
176
+ // Methods
177
+ test("supports .at", () => {
178
+ expect(coVector.at(0)).toBe(1);
179
+ expect(coVector.at(1)).toBe(2);
180
+ expect(coVector.at(2)).toBe(3);
181
+ expect(coVector.at(3)).toBe(4);
182
+ expect(coVector.at(4)).toBe(5);
183
+ });
184
+
185
+ test("supports .entries", () => {
186
+ expect(Array.from(coVector.entries())).toEqual([
187
+ [0, 1],
188
+ [1, 2],
189
+ [2, 3],
190
+ [3, 4],
191
+ [4, 5],
192
+ ]);
193
+ });
194
+
195
+ test("supports .every", () => {
196
+ expect(coVector.every((item) => item > 0)).toEqual(true);
197
+ expect(coVector.every((item) => item > 3)).toEqual(false);
198
+ });
199
+
200
+ test("supports .filter", () => {
201
+ expect(coVector.filter((item) => item > 3)).toEqual(
202
+ new Float32Array([4, 5]),
203
+ );
204
+ });
205
+
206
+ test("supports .find", () => {
207
+ expect(coVector.find((item) => item > 3)).toEqual(4);
208
+ });
209
+
210
+ test("supports .findIndex", () => {
211
+ expect(coVector.findIndex((item) => item > 3)).toEqual(3);
212
+ });
213
+
214
+ test("supports .findLast", () => {
215
+ expect(coVector.findLast((item) => item > 3)).toEqual(5);
216
+ });
217
+
218
+ test("supports .findLastIndex", () => {
219
+ expect(coVector.findLastIndex((item) => item > 3)).toEqual(4);
220
+ });
221
+
222
+ test("supports .forEach", () => {
223
+ const iterationFn = vi.fn();
224
+ coVector.forEach(iterationFn);
225
+
226
+ expect(iterationFn).toHaveBeenCalledTimes(5);
227
+
228
+ const f32 = new Float32Array([1, 2, 3, 4, 5]);
229
+ const calls = iterationFn.mock.calls;
230
+ expect(calls.length).toBe(5);
231
+ const expectedValue = [1, 2, 3, 4, 5];
232
+ for (let i = 0; i < 5; i++) {
233
+ const [value, index, arrayArg] = calls[i]!;
234
+ expect(value).toBe(expectedValue[i]);
235
+ expect(index).toBe(i);
236
+ expect(Array.from(arrayArg as any)).toEqual(Array.from(f32));
237
+ }
238
+ });
239
+
240
+ test("supports .includes", () => {
241
+ expect(coVector.includes(3)).toBe(true);
242
+ expect(coVector.includes(6)).toBe(false);
243
+ });
244
+
245
+ test("supports .indexOf", () => {
246
+ expect(coVector.indexOf(3)).toBe(2);
247
+ expect(coVector.indexOf(6)).toBe(-1);
248
+ });
249
+
250
+ test("supports .join", () => {
251
+ expect(coVector.join(":")).toEqual("1:2:3:4:5");
252
+ });
253
+
254
+ test("supports .keys", () => {
255
+ expect(Array.from(coVector.keys())).toEqual([0, 1, 2, 3, 4]);
256
+ });
257
+
258
+ test("supports .lastIndexOf", () => {
259
+ const coVector = EmbeddingSchema.create([1, 2, 3, 4, 3]);
260
+ expect(coVector.lastIndexOf(3)).toBe(4);
261
+ expect(coVector.lastIndexOf(6)).toBe(-1);
262
+ });
263
+
264
+ test("supports .map", () => {
265
+ expect(coVector.map((item) => item * 2)).toEqual(
266
+ new Float32Array([2, 4, 6, 8, 10]),
267
+ );
268
+ });
269
+
270
+ test("supports .reduce", () => {
271
+ const reducerFn = vi.fn((acc, item) => acc + item);
272
+
273
+ expect(coVector.reduce(reducerFn, 0)).toEqual(15);
274
+
275
+ const f32 = new Float32Array([1, 2, 3, 4, 5]);
276
+ const calls = (reducerFn as any).mock.calls as any[];
277
+ expect(calls.length).toBe(5);
278
+ const expectedAcc = [0, 1, 3, 6, 10];
279
+ const expectedVal = [1, 2, 3, 4, 5];
280
+ const expectedIdx = [0, 1, 2, 3, 4];
281
+ for (let i = 0; i < 5; i++) {
282
+ const call = calls[i] as any[];
283
+ expect(call[0]).toBe(expectedAcc[i]);
284
+ expect(call[1]).toBe(expectedVal[i]);
285
+ expect(call[2]).toBe(expectedIdx[i]);
286
+ expect(Array.from(call[3] as any)).toEqual(Array.from(f32));
287
+ }
288
+ });
289
+
290
+ test("supports .reduceRight", () => {
291
+ const reducerFn = vi.fn((acc, item) => acc + item);
292
+
293
+ expect(coVector.reduceRight(reducerFn, 0)).toEqual(15);
294
+
295
+ const f32 = new Float32Array([1, 2, 3, 4, 5]);
296
+ const calls = (reducerFn as any).mock.calls as any[];
297
+ expect(calls.length).toBe(5);
298
+ const expectedAcc = [0, 5, 9, 12, 14];
299
+ const expectedVal = [5, 4, 3, 2, 1];
300
+ const expectedIdx = [4, 3, 2, 1, 0];
301
+ for (let i = 0; i < 5; i++) {
302
+ const call = calls[i] as any[];
303
+ expect(call[0]).toBe(expectedAcc[i]);
304
+ expect(call[1]).toBe(expectedVal[i]);
305
+ expect(call[2]).toBe(expectedIdx[i]);
306
+ expect(Array.from(call[3] as any)).toEqual(Array.from(f32));
307
+ }
308
+ });
309
+
310
+ test("supports .slice", () => {
311
+ expect(coVector.slice()).toEqual(new Float32Array([1, 2, 3, 4, 5]));
312
+ expect(coVector.slice(2)).toEqual(new Float32Array([3, 4, 5]));
313
+ expect(coVector.slice(2, 4)).toEqual(new Float32Array([3, 4]));
314
+ });
315
+
316
+ test("supports .some", () => {
317
+ expect(coVector.some((item) => item > 3)).toBe(true);
318
+ expect(coVector.some((item) => item > 5)).toBe(false);
319
+ });
320
+
321
+ test("supports .subarray", () => {
322
+ expect(coVector.subarray().toString()).toEqual("1,2,3,4,5");
323
+ expect(coVector.subarray(2).toString()).toEqual("3,4,5");
324
+ expect(coVector.subarray(2, 4).toString()).toEqual("3,4");
325
+ });
326
+
327
+ test("supports .toJSON (JSON.stringify)", () => {
328
+ expect(JSON.stringify(coVector)).toEqual(`[1,2,3,4,5]`);
329
+ });
330
+
331
+ test("supports .toLocaleString", () => {
332
+ expect(coVector.toLocaleString()).toEqual("1,2,3,4,5");
333
+ expect(
334
+ coVector.toLocaleString("ja-JP", { style: "currency", currency: "JPY" }),
335
+ ).toEqual("¥1,¥2,¥3,¥4,¥5");
336
+ });
337
+
338
+ test("supports .toReversed", () => {
339
+ expect(coVector.toReversed()).toEqual(new Float32Array([5, 4, 3, 2, 1]));
340
+ });
341
+
342
+ test("supports .toSorted", () => {
343
+ const coVector = EmbeddingSchema.create([2, 3, 4, 5, 1]);
344
+ expect(coVector.toSorted()).toEqual(new Float32Array([1, 2, 3, 4, 5]));
345
+ });
346
+
347
+ test("supports .toString", () => {
348
+ expect(coVector.toString()).toEqual("1,2,3,4,5");
349
+ });
350
+
351
+ test("supports .values", () => {
352
+ expect(Array.from(coVector.values())).toEqual([1, 2, 3, 4, 5]);
353
+ });
354
+
355
+ test("supports .with", () => {
356
+ expect(coVector.with(4, 2).toString()).toEqual("1,2,3,4,2");
357
+ });
358
+ });
359
+
360
+ describe("CoVector mutation methods", async () => {
361
+ const EmbeddingSchema = co.vector(5);
362
+ const { coVector } = await initNodeAndVector(
363
+ EmbeddingSchema,
364
+ new Float32Array([1, 2, 3, 4, 5]),
365
+ );
366
+
367
+ const expectedErrorMessage = /Cannot mutate a CoVector/i;
368
+
369
+ test("calling .copyWithin • throws an error", () => {
370
+ expect(() => coVector.copyWithin(1, 2)).toThrow(expectedErrorMessage);
371
+ });
372
+ test("calling .fill • throws an error", () => {
373
+ expect(() => coVector.fill(1)).toThrow(expectedErrorMessage);
374
+ });
375
+ test("calling .reverse • throws an error", () => {
376
+ expect(() => coVector.reverse()).toThrow(expectedErrorMessage);
377
+ });
378
+ test("calling .set • throws an error", () => {
379
+ expect(() => coVector.set(new Float32Array([6, 7, 8]))).toThrow(
380
+ expectedErrorMessage,
381
+ );
382
+ });
383
+ test("calling .sort • throws an error", () => {
384
+ expect(() => coVector.sort()).toThrow(expectedErrorMessage);
385
+ });
386
+ });
387
+
388
+ describe("Vector calculations on .$jazz", async () => {
389
+ const VectorSchema = co.vector(5);
390
+
391
+ const vecA = new Float32Array([1, 2, 3, 4, 5]);
392
+ const vecB = new Float32Array([5, 4, 3, 2, 1]);
393
+ const arrB = [5, 4, 3, 2, 1];
394
+
395
+ const coVectorA = VectorSchema.create(vecA);
396
+ const coVectorB = VectorSchema.create(vecB);
397
+
398
+ describe("magnitude", () => {
399
+ const magnitudeApprox: [number, number] = [7.4162, 4];
400
+
401
+ test("returns the magnitude of the vector", () => {
402
+ expect(coVectorA.$jazz.magnitude()).toBeCloseTo(...magnitudeApprox);
403
+ });
404
+ });
405
+
406
+ describe("normalize", () => {
407
+ const normalized = new Float32Array([
408
+ 0.1348399668931961, 0.2696799337863922, 0.4045199155807495,
409
+ 0.5393598675727844, 0.6741998791694641,
410
+ ]);
411
+
412
+ test("returns the normalized vector", () => {
413
+ expect(coVectorA.$jazz.normalize()).toEqual(normalized);
414
+ });
415
+ });
416
+
417
+ describe("dot product", () => {
418
+ const dotProduct = 35;
419
+
420
+ test("returns the dot product of the 2 vectors", () => {
421
+ expect(coVectorA.$jazz.dotProduct(arrB)).toBe(dotProduct);
422
+ expect(coVectorA.$jazz.dotProduct(vecB)).toBe(dotProduct);
423
+ expect(coVectorA.$jazz.dotProduct(coVectorB)).toBe(dotProduct);
424
+ });
425
+ });
426
+
427
+ describe("cosine similarity", () => {
428
+ const similarityApprox: [number, number] = [0.6364, 4];
429
+
430
+ test("returns the cosine similarity of the 2 vectors", () => {
431
+ expect(coVectorA.$jazz.cosineSimilarity(arrB)).toBeCloseTo(
432
+ ...similarityApprox,
433
+ );
434
+ expect(coVectorA.$jazz.cosineSimilarity(vecB)).toBeCloseTo(
435
+ ...similarityApprox,
436
+ );
437
+ expect(coVectorA.$jazz.cosineSimilarity(coVectorB)).toBeCloseTo(
438
+ ...similarityApprox,
439
+ );
440
+ });
441
+ });
442
+ });
443
+
444
+ describe("CoVector loading & availability", async () => {
445
+ const EmbeddingSchema = co.vector(3);
446
+
447
+ test("when .waitForSync is called • the entire vector is uploaded", async () => {
448
+ const { clientNode, serverNode, clientAccount } = await setupTwoNodes();
449
+
450
+ const embedding = EmbeddingSchema.create([1, 2, 3], {
451
+ owner: clientAccount,
452
+ });
453
+ await embedding.$jazz.waitForSync({ timeout: 1000 });
454
+
455
+ // Killing the client node so the serverNode can't load the vector from it
456
+ clientNode.gracefulShutdown();
457
+
458
+ const loadedEmbedding = await serverNode.load(embedding.$jazz.raw.id);
459
+
460
+ expect(loadedEmbedding).not.toBe("unavailable");
461
+
462
+ if (loadedEmbedding !== "unavailable") {
463
+ // check that the data was uploaded (3 transactions for smaller vectors: start, data, end)
464
+ expect(loadedEmbedding?.core?.verifiedTransactions.length).toBe(3);
465
+ }
466
+ });
467
+
468
+ test("when .waitForSync is called on 1024 kB vector • the entire vector is uploaded", async () => {
469
+ const { clientNode, serverNode, clientAccount } = await setupTwoNodes();
470
+
471
+ const kB = 1024;
472
+
473
+ const EmbeddingSchema = co.vector(elementsForKb(kB));
474
+ const embedding = EmbeddingSchema.create(
475
+ new Float32Array(elementsForKb(kB)).fill(0.5),
476
+ { owner: clientAccount },
477
+ );
478
+ await embedding.$jazz.waitForSync({ timeout: 1000 });
479
+
480
+ // Killing the client node so the serverNode can't load the vector from it
481
+ clientNode.gracefulShutdown();
482
+
483
+ const loadedEmbedding = await serverNode.load(embedding.$jazz.raw.id);
484
+
485
+ expect(loadedEmbedding).not.toBe("unavailable");
486
+
487
+ // check that the data was uploaded
488
+ if (loadedEmbedding !== "unavailable") {
489
+ const expectedTransactionsCount = Math.ceil(
490
+ kbToBytes(kB) /
491
+ cojsonInternals.TRANSACTION_CONFIG.MAX_RECOMMENDED_TX_SIZE,
492
+ );
493
+ expect(loadedEmbedding?.core?.verifiedTransactions.length).toBe(
494
+ expectedTransactionsCount + 2, // +2 for the start and end transactions
495
+ );
496
+ }
497
+ });
498
+
499
+ test("when calling .load from different account • syncs across peers", async () => {
500
+ const { coVector } = await initNodeAndVector(
501
+ EmbeddingSchema,
502
+ new Float32Array([9, 8, 7]),
503
+ );
504
+
505
+ const alice = await createJazzTestAccount();
506
+
507
+ const loadedVector = await EmbeddingSchema.load(coVector.$jazz.id, {
508
+ loadAs: alice,
509
+ });
510
+
511
+ assert(loadedVector);
512
+ expect(loadedVector.length).toBe(3);
513
+ expect(loadedVector[0]).toBe(9);
514
+ expect(loadedVector[1]).toBe(8);
515
+ expect(loadedVector[2]).toBe(7);
516
+
517
+ // Check that the cores are not the same...
518
+ expect(loadedVector.$jazz.raw.core).not.toEqual(coVector.$jazz.raw.core);
519
+
520
+ // ...but the known states are the same
521
+ expect(loadedVector.$jazz.raw.core.knownState()).toEqual(
522
+ coVector.$jazz.raw.core.knownState(),
523
+ );
524
+ expect(loadedVector.$jazz.raw.core.verifiedTransactions.length).toBe(
525
+ coVector.$jazz.raw.core.verifiedTransactions.length,
526
+ );
527
+ });
528
+
529
+ test("when calling .load on a 1024 kB vector from different account • syncs across peers", async () => {
530
+ const kb = 1024;
531
+
532
+ const EmbeddingSchema = co.vector(elementsForKb(kb));
533
+ const { coVector } = await initNodeAndVector(
534
+ EmbeddingSchema,
535
+ new Float32Array(elementsForKb(kb)).fill(0.5),
536
+ );
537
+
538
+ const alice = await createJazzTestAccount();
539
+
540
+ const loadedVector = await EmbeddingSchema.load(coVector.$jazz.id, {
541
+ loadAs: alice,
542
+ });
543
+
544
+ assert(loadedVector);
545
+ expect(loadedVector).toBeInstanceOf(CoVector);
546
+ expect(loadedVector.length).toBe(elementsForKb(kb));
547
+ expect(loadedVector.every((item) => item === 0.5)).toBe(true);
548
+
549
+ // Check that the cores are not the same...
550
+ expect(loadedVector.$jazz.raw.core).not.toEqual(coVector.$jazz.raw.core);
551
+
552
+ // ...but the known states are the same
553
+ expect(loadedVector.$jazz.raw.core.knownState()).toEqual(
554
+ coVector.$jazz.raw.core.knownState(),
555
+ );
556
+ expect(loadedVector.$jazz.raw.core.verifiedTransactions.length).toBe(
557
+ coVector.$jazz.raw.core.verifiedTransactions.length,
558
+ );
559
+ });
560
+ });
561
+
562
+ describe("CoVector in subscription", async () => {
563
+ const EmbeddingSchema = co.vector(3);
564
+ const DocsChunk = co.map({
565
+ content: z.string(),
566
+ embedding: EmbeddingSchema,
567
+ });
568
+ const Docs = co.list(DocsChunk);
569
+
570
+ describe("standalone CoVector", async () => {
571
+ test("subscribing to locally available CoVector • sends update holding the CoVector", async () => {
572
+ const embedding = EmbeddingSchema.create(new Float32Array([10, 20, 30]));
573
+
574
+ const updates: Loaded<typeof EmbeddingSchema>[] = [];
575
+ const updatesCallback = vi.fn((embedding) => updates.push(embedding));
576
+
577
+ EmbeddingSchema.subscribe(
578
+ embedding.$jazz.id,
579
+ { loadAs: me },
580
+ updatesCallback,
581
+ );
582
+
583
+ expect(updatesCallback).not.toHaveBeenCalled();
584
+
585
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
586
+
587
+ expect(updatesCallback).toHaveBeenCalledTimes(1);
588
+
589
+ expect(updates[0]).toBeInstanceOf(CoVector);
590
+ expect(updates[0]?.toString()).toEqual("10,20,30");
591
+ });
592
+
593
+ test("subscribing to remotely available CoVector • sends update holding the CoVector", async () => {
594
+ const group = Group.create();
595
+ group.addMember("everyone", "writer");
596
+
597
+ const embedding = EmbeddingSchema.create(
598
+ new Float32Array([10, 20, 30]),
599
+ group,
600
+ );
601
+
602
+ const userB = await createJazzTestAccount();
603
+
604
+ const updates: Loaded<typeof EmbeddingSchema>[] = [];
605
+ const updatesCallback = vi.fn((embedding) => updates.push(embedding));
606
+
607
+ EmbeddingSchema.subscribe(
608
+ embedding.$jazz.id,
609
+ { loadAs: userB },
610
+ updatesCallback,
611
+ );
612
+
613
+ expect(updatesCallback).not.toHaveBeenCalled();
614
+
615
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
616
+
617
+ expect(updatesCallback).toHaveBeenCalledTimes(1);
618
+
619
+ expect(updates[0]).toBeInstanceOf(CoVector);
620
+ expect(updates[0]?.toString()).toEqual("10,20,30");
621
+ });
622
+ });
623
+
624
+ describe("CoVector in CoList[CoMap]", async () => {
625
+ describe("locally available list", async () => {
626
+ test("subscribing with deep resolve • sends update holding the CoVectors", async () => {
627
+ const docs = Docs.create([
628
+ DocsChunk.create({
629
+ content: "Call GET to retrieve document",
630
+ embedding: EmbeddingSchema.create(new Float32Array([1, 2, 3])),
631
+ }),
632
+ DocsChunk.create({
633
+ content: "Call POST to create a new document",
634
+ embedding: EmbeddingSchema.create(new Float32Array([4, 5, 6])),
635
+ }),
636
+ ]);
637
+
638
+ const updates: Loaded<typeof Docs, { $each: true }>[] = [];
639
+ const updatesCallback = vi.fn((docs) => updates.push(docs));
640
+
641
+ Docs.subscribe(
642
+ docs.$jazz.id,
643
+ { resolve: { $each: true } },
644
+ updatesCallback,
645
+ );
646
+
647
+ expect(updatesCallback).not.toHaveBeenCalled();
648
+
649
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
650
+
651
+ expect(updatesCallback).toHaveBeenCalledTimes(1);
652
+
653
+ expect(updates[0]?.[0]?.content).toEqual(
654
+ "Call GET to retrieve document",
655
+ );
656
+ expect(updates[0]?.[1]?.content).toEqual(
657
+ "Call POST to create a new document",
658
+ );
659
+ expect(updates[0]?.[0]?.embedding?.toString()).toBe("1,2,3");
660
+ expect(updates[0]?.[1]?.embedding?.toString()).toBe("4,5,6");
661
+
662
+ // Update the second document's embedding
663
+ docs[1]!.$jazz.set(
664
+ "embedding",
665
+ EmbeddingSchema.create(new Float32Array([7, 8, 9])),
666
+ );
667
+
668
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalledTimes(2));
669
+
670
+ expect(updates[1]?.[0]?.embedding?.toString()).toBe("1,2,3");
671
+ expect(updates[1]?.[1]?.embedding?.toString()).toBe("7,8,9");
672
+
673
+ expect(updatesCallback).toHaveBeenCalledTimes(2);
674
+ });
675
+
676
+ test("subscribing with autoload • sends update holding the CoVectors", async () => {
677
+ const docs = Docs.create([
678
+ DocsChunk.create({
679
+ content: "Call GET to retrieve document",
680
+ embedding: EmbeddingSchema.create(new Float32Array([1, 2, 3])),
681
+ }),
682
+ DocsChunk.create({
683
+ content: "Call POST to create a new document",
684
+ embedding: EmbeddingSchema.create(new Float32Array([4, 5, 6])),
685
+ }),
686
+ ]);
687
+
688
+ const updates: Loaded<typeof Docs>[] = [];
689
+ const updatesCallback = vi.fn((docs) => updates.push(docs));
690
+
691
+ Docs.subscribe(docs.$jazz.id, {}, updatesCallback);
692
+
693
+ expect(updatesCallback).not.toHaveBeenCalled();
694
+
695
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
696
+
697
+ expect(updatesCallback).toHaveBeenCalledTimes(1);
698
+
699
+ expect(updates[0]?.[0]?.content).toEqual(
700
+ "Call GET to retrieve document",
701
+ );
702
+ expect(updates[0]?.[1]?.content).toEqual(
703
+ "Call POST to create a new document",
704
+ );
705
+ expect(updates[0]?.[0]?.embedding?.toString()).toBe("1,2,3");
706
+ expect(updates[0]?.[1]?.embedding?.toString()).toBe("4,5,6");
707
+
708
+ // Update the second document's embedding
709
+ docs[1]!.$jazz.set(
710
+ "embedding",
711
+ EmbeddingSchema.create(new Float32Array([7, 8, 9])),
712
+ );
713
+
714
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalledTimes(2));
715
+
716
+ expect(updates[1]?.[0]?.embedding?.toString()).toBe("1,2,3");
717
+ expect(updates[1]?.[1]?.embedding?.toString()).toBe("7,8,9");
718
+
719
+ expect(updatesCallback).toHaveBeenCalledTimes(2);
720
+ });
721
+ });
722
+
723
+ describe("remotely available list", async () => {
724
+ test("subscribing with deep resolve • sends update holding the CoVectors", async () => {
725
+ const group = Group.create({ owner: me });
726
+ group.addMember("everyone", "writer");
727
+
728
+ const docs = Docs.create(
729
+ [
730
+ DocsChunk.create(
731
+ {
732
+ content: "Call GET to retrieve document",
733
+ embedding: EmbeddingSchema.create(
734
+ new Float32Array([1, 2, 3]),
735
+ group,
736
+ ),
737
+ },
738
+ group,
739
+ ),
740
+ DocsChunk.create(
741
+ {
742
+ content: "Call POST to create a new document",
743
+ embedding: EmbeddingSchema.create(
744
+ new Float32Array([4, 5, 6]),
745
+ group,
746
+ ),
747
+ },
748
+ group,
749
+ ),
750
+ ],
751
+ group,
752
+ );
753
+
754
+ const userB = await createJazzTestAccount();
755
+
756
+ const updates: Loaded<typeof Docs, { $each: true }>[] = [];
757
+ const updatesCallback = vi.fn((docs) => updates.push(docs));
758
+
759
+ Docs.subscribe(
760
+ docs.$jazz.id,
761
+ {
762
+ resolve: { $each: true },
763
+ loadAs: userB,
764
+ },
765
+ updatesCallback,
766
+ );
767
+
768
+ expect(updatesCallback).not.toHaveBeenCalled();
769
+
770
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
771
+
772
+ expect(updatesCallback).toHaveBeenCalledTimes(1);
773
+
774
+ await waitFor(() => {
775
+ expect(updates[0]?.[0]?.embedding?.toString()).toBe("1,2,3");
776
+ expect(updates[0]?.[1]?.embedding?.toString()).toBe("4,5,6");
777
+ });
778
+
779
+ // Update the second document's embedding
780
+ docs[1]!.$jazz.set(
781
+ "embedding",
782
+ EmbeddingSchema.create(new Float32Array([7, 8, 9]), group),
783
+ );
784
+
785
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalledTimes(5));
786
+
787
+ expect(updates[1]?.[0]?.embedding?.toString()).toBe("1,2,3");
788
+ expect(updates[1]?.[1]?.embedding?.toString()).toBe("7,8,9");
789
+
790
+ expect(updatesCallback).toHaveBeenCalledTimes(5);
791
+ });
792
+
793
+ test("subscribing with autoload • sends update holding the CoVectors", async () => {
794
+ const group = Group.create({ owner: me });
795
+ group.addMember("everyone", "writer");
796
+
797
+ const docs = Docs.create(
798
+ [
799
+ DocsChunk.create(
800
+ {
801
+ content: "Call GET to retrieve document",
802
+ embedding: EmbeddingSchema.create(
803
+ new Float32Array([1, 2, 3]),
804
+ group,
805
+ ),
806
+ },
807
+ group,
808
+ ),
809
+ DocsChunk.create(
810
+ {
811
+ content: "Call POST to create a new document",
812
+ embedding: EmbeddingSchema.create(
813
+ new Float32Array([4, 5, 6]),
814
+ group,
815
+ ),
816
+ },
817
+ group,
818
+ ),
819
+ ],
820
+ group,
821
+ );
822
+
823
+ const userB = await createJazzTestAccount();
824
+
825
+ const updates: Loaded<typeof Docs>[] = [];
826
+ const updatesCallback = vi.fn((docs) => updates.push(docs));
827
+
828
+ Docs.subscribe(
829
+ docs.$jazz.id,
830
+ {
831
+ loadAs: userB,
832
+ },
833
+ updatesCallback,
834
+ );
835
+
836
+ expect(updatesCallback).not.toHaveBeenCalled();
837
+
838
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalled());
839
+
840
+ await waitFor(() => {
841
+ expect(updates[0]?.[0]?.embedding?.toString()).toBe("1,2,3");
842
+ expect(updates[0]?.[1]?.embedding?.toString()).toBe("4,5,6");
843
+ });
844
+
845
+ // Update the second document's embedding
846
+ docs[1]!.$jazz.set(
847
+ "embedding",
848
+ EmbeddingSchema.create(new Float32Array([7, 8, 9]), group),
849
+ );
850
+
851
+ await waitFor(() => expect(updatesCallback).toHaveBeenCalledTimes(7));
852
+
853
+ expect(updates[1]?.[0]?.embedding?.toString()).toBe("1,2,3");
854
+ expect(updates[1]?.[1]?.embedding?.toString()).toBe("7,8,9");
855
+
856
+ expect(updatesCallback).toHaveBeenCalledTimes(7);
857
+ });
858
+ });
859
+ });
860
+ });
861
+
862
+ describe("CoVector dimension mismatch after loading", async () => {
863
+ test("loading a CoVector with an incompatible schema • throws an error", async () => {
864
+ const { coVector } = await initNodeAndVector(
865
+ co.vector(3),
866
+ new Float32Array([1, 2, 3]),
867
+ );
868
+
869
+ await expect(co.vector(9).load(coVector.$jazz.id)).rejects.toThrow(
870
+ /Vector dimension mismatch/,
871
+ );
872
+ });
873
+
874
+ test("subscribing to a CoVector with an incompatible schema • throws an error", async () => {
875
+ const EmbeddingSchema = co.vector(3);
876
+
877
+ const { coVector } = await initNodeAndVector(
878
+ EmbeddingSchema,
879
+ new Float32Array([1, 2, 3]),
880
+ );
881
+
882
+ const updates: Loaded<typeof EmbeddingSchema>[] = [];
883
+ const updatesCallback = vi.fn((embedding) => updates.push(embedding));
884
+
885
+ expect(() =>
886
+ co.vector(5).subscribe(coVector.$jazz.id, updatesCallback),
887
+ ).toThrow(/Vector dimension mismatch/);
888
+
889
+ expect(updatesCallback).not.toHaveBeenCalled();
890
+ });
891
+ });