jazz-tools 0.18.27 → 0.18.29
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/.turbo/turbo-build.log +47 -47
- package/CHANGELOG.md +23 -0
- package/dist/{chunk-ZIAN4UY5.js → chunk-F55R554M.js} +171 -120
- package/dist/chunk-F55R554M.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inspector/{custom-element-A7UAELEG.js → custom-element-35MDW4SW.js} +1840 -1837
- package/dist/inspector/{custom-element-A7UAELEG.js.map → custom-element-35MDW4SW.js.map} +1 -1
- package/dist/inspector/custom-element.d.ts.map +1 -1
- package/dist/inspector/index.d.ts +1 -1
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +0 -1
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/react-core/hooks.d.ts +4 -0
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +5 -0
- package/dist/react-core/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coList.d.ts +11 -3
- package/dist/tools/coValues/coList.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +21 -5
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/group.d.ts +2 -2
- package/dist/tools/coValues/group.d.ts.map +1 -1
- package/dist/tools/coValues/inbox.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +17 -1
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/tests/coList.unique.test.d.ts +2 -0
- package/dist/tools/tests/coList.unique.test.d.ts.map +1 -0
- package/dist/tools/tests/coMap.unique.test.d.ts +2 -0
- package/dist/tools/tests/coMap.unique.test.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/inspector/custom-element.tsx +4 -0
- package/src/inspector/index.tsx +0 -2
- package/src/react-core/hooks.ts +8 -0
- package/src/react-core/tests/useAccount.test.ts +61 -1
- package/src/react-core/tests/usePassPhraseAuth.test.ts +74 -2
- package/src/tools/coValues/coList.ts +38 -35
- package/src/tools/coValues/coMap.ts +38 -38
- package/src/tools/coValues/group.ts +5 -1
- package/src/tools/coValues/inbox.ts +4 -3
- package/src/tools/coValues/interfaces.ts +119 -0
- package/src/tools/exports.ts +1 -0
- package/src/tools/tests/coList.test.ts +0 -190
- package/src/tools/tests/coList.unique.test.ts +244 -0
- package/src/tools/tests/coMap.test.ts +0 -433
- package/src/tools/tests/coMap.unique.test.ts +579 -0
- package/dist/chunk-ZIAN4UY5.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
cojsonInternals,
|
|
2
3
|
type CoValueUniqueness,
|
|
3
4
|
type CojsonInternalTypes,
|
|
4
5
|
type RawCoValue,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
inspect,
|
|
26
27
|
} from "../internal.js";
|
|
27
28
|
import type { BranchDefinition } from "../subscribe/types.js";
|
|
29
|
+
import { CoValueHeader } from "cojson/dist/coValueCore/verifiedState.js";
|
|
28
30
|
|
|
29
31
|
/** @category Abstract interfaces */
|
|
30
32
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -446,6 +448,123 @@ export function parseGroupCreateOptions(
|
|
|
446
448
|
: { owner: options.owner ?? activeAccountContext.get() };
|
|
447
449
|
}
|
|
448
450
|
|
|
451
|
+
export function getIdFromHeader(
|
|
452
|
+
header: CoValueHeader,
|
|
453
|
+
loadAs?: Account | AnonymousJazzAgent | Group,
|
|
454
|
+
) {
|
|
455
|
+
loadAs ||= activeAccountContext.get();
|
|
456
|
+
|
|
457
|
+
const node =
|
|
458
|
+
loadAs[TypeSym] === "Anonymous" ? loadAs.node : loadAs.$jazz.localNode;
|
|
459
|
+
|
|
460
|
+
return cojsonInternals.idforHeader(header, node.crypto);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export async function unstable_loadUnique<
|
|
464
|
+
S extends CoValueClassOrSchema,
|
|
465
|
+
const R extends ResolveQuery<S>,
|
|
466
|
+
>(
|
|
467
|
+
schema: S,
|
|
468
|
+
options: {
|
|
469
|
+
unique: CoValueUniqueness["uniqueness"];
|
|
470
|
+
onCreateWhenMissing?: () => void;
|
|
471
|
+
onUpdateWhenFound?: (value: Loaded<S, R>) => void;
|
|
472
|
+
owner: Account | Group;
|
|
473
|
+
resolve?: ResolveQueryStrict<S, R>;
|
|
474
|
+
},
|
|
475
|
+
): Promise<Loaded<S, R> | null> {
|
|
476
|
+
const cls = coValueClassFromCoValueClassOrSchema(schema);
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
!("_getUniqueHeader" in cls) ||
|
|
480
|
+
typeof cls._getUniqueHeader !== "function"
|
|
481
|
+
) {
|
|
482
|
+
throw new Error("CoValue class does not support unique headers");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const header = cls._getUniqueHeader(options.unique, options.owner.$jazz.id);
|
|
486
|
+
|
|
487
|
+
return internalLoadUnique(cls, {
|
|
488
|
+
header,
|
|
489
|
+
onCreateWhenMissing: options.onCreateWhenMissing,
|
|
490
|
+
// @ts-expect-error loaded is not compatible with Resolved at type level, but they are the same thing
|
|
491
|
+
onUpdateWhenFound: options.onUpdateWhenFound,
|
|
492
|
+
owner: options.owner,
|
|
493
|
+
// @ts-expect-error loaded is not compatible with Resolved at type level, but they are the same thing
|
|
494
|
+
resolve: options.resolve,
|
|
495
|
+
}) as unknown as Loaded<S, R> | null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export async function internalLoadUnique<
|
|
499
|
+
V extends CoValue,
|
|
500
|
+
R extends RefsToResolve<V>,
|
|
501
|
+
>(
|
|
502
|
+
cls: CoValueClass<V>,
|
|
503
|
+
options: {
|
|
504
|
+
header: CoValueHeader;
|
|
505
|
+
onCreateWhenMissing?: () => void;
|
|
506
|
+
onUpdateWhenFound?: (value: Resolved<V, R>) => void;
|
|
507
|
+
owner: Account | Group;
|
|
508
|
+
resolve?: RefsToResolveStrict<V, R>;
|
|
509
|
+
},
|
|
510
|
+
): Promise<Resolved<V, R> | null> {
|
|
511
|
+
const loadAs = options.owner.$jazz.loadedAs;
|
|
512
|
+
|
|
513
|
+
const node =
|
|
514
|
+
loadAs[TypeSym] === "Anonymous" ? loadAs.node : loadAs.$jazz.localNode;
|
|
515
|
+
|
|
516
|
+
const id = cojsonInternals.idforHeader(options.header, node.crypto);
|
|
517
|
+
|
|
518
|
+
// We first try to load the unique value without using resolve and without
|
|
519
|
+
// retrying failures
|
|
520
|
+
// This way when we want to upsert we are sure that, if the load failed
|
|
521
|
+
// it failed because the unique value was missing
|
|
522
|
+
await loadCoValueWithoutMe(cls, id, {
|
|
523
|
+
skipRetry: true,
|
|
524
|
+
loadAs,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const isAvailable = node.getCoValue(id).hasVerifiedContent();
|
|
528
|
+
|
|
529
|
+
// if load returns unavailable, we check the state in localNode
|
|
530
|
+
// to ward against race conditions that would happen when
|
|
531
|
+
// running the same upsert unique concurrently
|
|
532
|
+
if (options.onCreateWhenMissing && !isAvailable) {
|
|
533
|
+
options.onCreateWhenMissing();
|
|
534
|
+
|
|
535
|
+
return loadCoValueWithoutMe(cls, id, {
|
|
536
|
+
loadAs,
|
|
537
|
+
resolve: options.resolve,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!isAvailable) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (options.onUpdateWhenFound) {
|
|
546
|
+
// we deeply load the value, retrying any failures
|
|
547
|
+
const loaded = await loadCoValueWithoutMe(cls, id, {
|
|
548
|
+
loadAs,
|
|
549
|
+
resolve: options.resolve,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (loaded) {
|
|
553
|
+
// we don't return the update result because
|
|
554
|
+
// we want to run another load to backfill any possible partially loaded
|
|
555
|
+
// values that have been set in the update
|
|
556
|
+
options.onUpdateWhenFound(loaded);
|
|
557
|
+
} else {
|
|
558
|
+
return loaded;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return loadCoValueWithoutMe(cls, id, {
|
|
563
|
+
loadAs,
|
|
564
|
+
resolve: options.resolve,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
449
568
|
/**
|
|
450
569
|
* Deeply export a CoValue to a content piece.
|
|
451
570
|
*
|
package/src/tools/exports.ts
CHANGED
|
@@ -1164,196 +1164,6 @@ describe("CoList subscription", async () => {
|
|
|
1164
1164
|
});
|
|
1165
1165
|
});
|
|
1166
1166
|
|
|
1167
|
-
describe("CoList unique methods", () => {
|
|
1168
|
-
test("loadUnique returns existing list", async () => {
|
|
1169
|
-
const ItemList = co.list(z.string());
|
|
1170
|
-
const group = Group.create();
|
|
1171
|
-
|
|
1172
|
-
const originalList = ItemList.create(["item1", "item2", "item3"], {
|
|
1173
|
-
owner: group,
|
|
1174
|
-
unique: "test-list",
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
const foundList = await ItemList.loadUnique("test-list", group.$jazz.id);
|
|
1178
|
-
expect(foundList).toEqual(originalList);
|
|
1179
|
-
expect(foundList?.length).toBe(3);
|
|
1180
|
-
expect(foundList?.[0]).toBe("item1");
|
|
1181
|
-
});
|
|
1182
|
-
|
|
1183
|
-
test("loadUnique returns null for non-existent list", async () => {
|
|
1184
|
-
const ItemList = co.list(z.string());
|
|
1185
|
-
const group = Group.create();
|
|
1186
|
-
|
|
1187
|
-
const foundList = await ItemList.loadUnique("non-existent", group.$jazz.id);
|
|
1188
|
-
expect(foundList).toBeNull();
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
test("upsertUnique creates new list when none exists", async () => {
|
|
1192
|
-
const ItemList = co.list(z.string());
|
|
1193
|
-
const group = Group.create();
|
|
1194
|
-
|
|
1195
|
-
const sourceData = ["item1", "item2", "item3"];
|
|
1196
|
-
|
|
1197
|
-
const result = await ItemList.upsertUnique({
|
|
1198
|
-
value: sourceData,
|
|
1199
|
-
unique: "new-list",
|
|
1200
|
-
owner: group,
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
expect(result).not.toBeNull();
|
|
1204
|
-
expect(result?.length).toBe(3);
|
|
1205
|
-
expect(result?.[0]).toBe("item1");
|
|
1206
|
-
expect(result?.[1]).toBe("item2");
|
|
1207
|
-
expect(result?.[2]).toBe("item3");
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
test("upsertUnique without an active account", async () => {
|
|
1211
|
-
const account = activeAccountContext.get();
|
|
1212
|
-
const ItemList = co.list(z.string());
|
|
1213
|
-
|
|
1214
|
-
const sourceData = ["item1", "item2", "item3"];
|
|
1215
|
-
|
|
1216
|
-
const result = await runWithoutActiveAccount(() => {
|
|
1217
|
-
return ItemList.upsertUnique({
|
|
1218
|
-
value: sourceData,
|
|
1219
|
-
unique: "new-list",
|
|
1220
|
-
owner: account,
|
|
1221
|
-
});
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
expect(result).not.toBeNull();
|
|
1225
|
-
expect(result?.length).toBe(3);
|
|
1226
|
-
expect(result?.[0]).toBe("item1");
|
|
1227
|
-
expect(result?.[1]).toBe("item2");
|
|
1228
|
-
expect(result?.[2]).toBe("item3");
|
|
1229
|
-
|
|
1230
|
-
expect(result?.$jazz.owner).toEqual(account);
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
test("upsertUnique updates existing list", async () => {
|
|
1234
|
-
const ItemList = co.list(z.string());
|
|
1235
|
-
const group = Group.create();
|
|
1236
|
-
|
|
1237
|
-
// Create initial list
|
|
1238
|
-
const originalList = ItemList.create(["original1", "original2"], {
|
|
1239
|
-
owner: group,
|
|
1240
|
-
unique: "update-list",
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
// Upsert with new data
|
|
1244
|
-
const updatedList = await ItemList.upsertUnique({
|
|
1245
|
-
value: ["updated1", "updated2", "updated3"],
|
|
1246
|
-
unique: "update-list",
|
|
1247
|
-
owner: group,
|
|
1248
|
-
});
|
|
1249
|
-
|
|
1250
|
-
expect(updatedList).toEqual(originalList); // Should be the same instance
|
|
1251
|
-
expect(updatedList?.length).toBe(3);
|
|
1252
|
-
expect(updatedList?.[0]).toBe("updated1");
|
|
1253
|
-
expect(updatedList?.[1]).toBe("updated2");
|
|
1254
|
-
expect(updatedList?.[2]).toBe("updated3");
|
|
1255
|
-
});
|
|
1256
|
-
|
|
1257
|
-
test("upsertUnique with CoValue items", async () => {
|
|
1258
|
-
const Item = co.map({
|
|
1259
|
-
name: z.string(),
|
|
1260
|
-
value: z.number(),
|
|
1261
|
-
});
|
|
1262
|
-
const ItemList = co.list(Item);
|
|
1263
|
-
const group = Group.create();
|
|
1264
|
-
|
|
1265
|
-
const items = [
|
|
1266
|
-
Item.create({ name: "First", value: 1 }, group),
|
|
1267
|
-
Item.create({ name: "Second", value: 2 }, group),
|
|
1268
|
-
];
|
|
1269
|
-
|
|
1270
|
-
const result = await ItemList.upsertUnique({
|
|
1271
|
-
value: items,
|
|
1272
|
-
unique: "item-list",
|
|
1273
|
-
owner: group,
|
|
1274
|
-
resolve: { $each: true },
|
|
1275
|
-
});
|
|
1276
|
-
|
|
1277
|
-
expect(result).not.toBeNull();
|
|
1278
|
-
expect(result?.length).toBe(2);
|
|
1279
|
-
expect(result?.[0]?.name).toBe("First");
|
|
1280
|
-
expect(result?.[1]?.name).toBe("Second");
|
|
1281
|
-
});
|
|
1282
|
-
|
|
1283
|
-
test("upsertUnique updates list with CoValue items", async () => {
|
|
1284
|
-
const Item = co.map({
|
|
1285
|
-
name: z.string(),
|
|
1286
|
-
value: z.number(),
|
|
1287
|
-
});
|
|
1288
|
-
const ItemList = co.list(Item);
|
|
1289
|
-
const group = Group.create();
|
|
1290
|
-
|
|
1291
|
-
// Create initial list
|
|
1292
|
-
const initialItems = [Item.create({ name: "Initial", value: 0 }, group)];
|
|
1293
|
-
const originalList = ItemList.create(initialItems, {
|
|
1294
|
-
owner: group,
|
|
1295
|
-
unique: "updateable-item-list",
|
|
1296
|
-
});
|
|
1297
|
-
|
|
1298
|
-
// Upsert with new items
|
|
1299
|
-
const newItems = [
|
|
1300
|
-
Item.create({ name: "Updated", value: 1 }, group),
|
|
1301
|
-
Item.create({ name: "Added", value: 2 }, group),
|
|
1302
|
-
];
|
|
1303
|
-
|
|
1304
|
-
const updatedList = await ItemList.upsertUnique({
|
|
1305
|
-
value: newItems,
|
|
1306
|
-
unique: "updateable-item-list",
|
|
1307
|
-
owner: group,
|
|
1308
|
-
resolve: { $each: true },
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
expect(updatedList).toEqual(originalList); // Should be the same instance
|
|
1312
|
-
expect(updatedList?.length).toBe(2);
|
|
1313
|
-
expect(updatedList?.[0]?.name).toBe("Updated");
|
|
1314
|
-
expect(updatedList?.[1]?.name).toBe("Added");
|
|
1315
|
-
});
|
|
1316
|
-
|
|
1317
|
-
test("findUnique returns correct ID", async () => {
|
|
1318
|
-
const ItemList = co.list(z.string());
|
|
1319
|
-
const group = Group.create();
|
|
1320
|
-
|
|
1321
|
-
const originalList = ItemList.create(["test"], {
|
|
1322
|
-
owner: group,
|
|
1323
|
-
unique: "find-test",
|
|
1324
|
-
});
|
|
1325
|
-
|
|
1326
|
-
const foundId = ItemList.findUnique("find-test", group.$jazz.id);
|
|
1327
|
-
expect(foundId).toBe(originalList.$jazz.id);
|
|
1328
|
-
});
|
|
1329
|
-
|
|
1330
|
-
test("upsertUnique with resolve options", async () => {
|
|
1331
|
-
const Category = co.map({ title: z.string() });
|
|
1332
|
-
const Item = co.map({
|
|
1333
|
-
name: z.string(),
|
|
1334
|
-
category: Category,
|
|
1335
|
-
});
|
|
1336
|
-
const ItemList = co.list(Item);
|
|
1337
|
-
const group = Group.create();
|
|
1338
|
-
|
|
1339
|
-
const category = Category.create({ title: "Category 1" }, group);
|
|
1340
|
-
|
|
1341
|
-
const items = [Item.create({ name: "Item 1", category }, group)];
|
|
1342
|
-
|
|
1343
|
-
const result = await ItemList.upsertUnique({
|
|
1344
|
-
value: items,
|
|
1345
|
-
unique: "resolved-list",
|
|
1346
|
-
owner: group,
|
|
1347
|
-
resolve: { $each: { category: true } },
|
|
1348
|
-
});
|
|
1349
|
-
|
|
1350
|
-
expect(result).not.toBeNull();
|
|
1351
|
-
expect(result?.length).toBe(1);
|
|
1352
|
-
expect(result?.[0]?.name).toBe("Item 1");
|
|
1353
|
-
expect(result?.[0]?.category?.title).toBe("Category 1");
|
|
1354
|
-
});
|
|
1355
|
-
});
|
|
1356
|
-
|
|
1357
1167
|
describe("co.list schema", () => {
|
|
1358
1168
|
test("can access the inner schema of a co.list", () => {
|
|
1359
1169
|
const Keywords = co.list(co.plainText());
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
2
|
+
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import { Account, Group, subscribeToCoValue, z } from "../index.js";
|
|
4
|
+
import {
|
|
5
|
+
Loaded,
|
|
6
|
+
activeAccountContext,
|
|
7
|
+
co,
|
|
8
|
+
coValueClassFromCoValueClassOrSchema,
|
|
9
|
+
} from "../internal.js";
|
|
10
|
+
import {
|
|
11
|
+
createJazzTestAccount,
|
|
12
|
+
runWithoutActiveAccount,
|
|
13
|
+
setupJazzTestSync,
|
|
14
|
+
} from "../testing.js";
|
|
15
|
+
import { setupTwoNodes, waitFor } from "./utils.js";
|
|
16
|
+
|
|
17
|
+
const Crypto = await WasmCrypto.create();
|
|
18
|
+
|
|
19
|
+
let me = await Account.create({
|
|
20
|
+
creationProps: { name: "Hermes Puggington" },
|
|
21
|
+
crypto: Crypto,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
await setupJazzTestSync();
|
|
26
|
+
|
|
27
|
+
me = await createJazzTestAccount({
|
|
28
|
+
isCurrentActiveAccount: true,
|
|
29
|
+
creationProps: { name: "Hermes Puggington" },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("CoList unique methods", () => {
|
|
34
|
+
test("loadUnique returns existing list", async () => {
|
|
35
|
+
const ItemList = co.list(z.string());
|
|
36
|
+
const group = Group.create();
|
|
37
|
+
|
|
38
|
+
const originalList = ItemList.create(["item1", "item2", "item3"], {
|
|
39
|
+
owner: group,
|
|
40
|
+
unique: "test-list",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const foundList = await ItemList.loadUnique("test-list", group.$jazz.id);
|
|
44
|
+
expect(foundList).toEqual(originalList);
|
|
45
|
+
expect(foundList?.length).toBe(3);
|
|
46
|
+
expect(foundList?.[0]).toBe("item1");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("loadUnique returns null for non-existent list", async () => {
|
|
50
|
+
const ItemList = co.list(z.string());
|
|
51
|
+
const group = Group.create();
|
|
52
|
+
|
|
53
|
+
const foundList = await ItemList.loadUnique("non-existent", group.$jazz.id);
|
|
54
|
+
expect(foundList).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("upsertUnique creates new list when none exists", async () => {
|
|
58
|
+
const ItemList = co.list(z.string());
|
|
59
|
+
const group = Group.create();
|
|
60
|
+
|
|
61
|
+
const sourceData = ["item1", "item2", "item3"];
|
|
62
|
+
|
|
63
|
+
const result = await ItemList.upsertUnique({
|
|
64
|
+
value: sourceData,
|
|
65
|
+
unique: "new-list",
|
|
66
|
+
owner: group,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result).not.toBeNull();
|
|
70
|
+
expect(result?.length).toBe(3);
|
|
71
|
+
expect(result?.[0]).toBe("item1");
|
|
72
|
+
expect(result?.[1]).toBe("item2");
|
|
73
|
+
expect(result?.[2]).toBe("item3");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("upsertUnique without an active account", async () => {
|
|
77
|
+
const account = activeAccountContext.get();
|
|
78
|
+
const ItemList = co.list(z.string());
|
|
79
|
+
|
|
80
|
+
const sourceData = ["item1", "item2", "item3"];
|
|
81
|
+
|
|
82
|
+
const result = await runWithoutActiveAccount(() => {
|
|
83
|
+
return ItemList.upsertUnique({
|
|
84
|
+
value: sourceData,
|
|
85
|
+
unique: "new-list",
|
|
86
|
+
owner: account,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result).not.toBeNull();
|
|
91
|
+
expect(result?.length).toBe(3);
|
|
92
|
+
expect(result?.[0]).toBe("item1");
|
|
93
|
+
expect(result?.[1]).toBe("item2");
|
|
94
|
+
expect(result?.[2]).toBe("item3");
|
|
95
|
+
|
|
96
|
+
expect(result?.$jazz.owner).toEqual(account);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("upsertUnique updates existing list", async () => {
|
|
100
|
+
const ItemList = co.list(z.string());
|
|
101
|
+
const group = Group.create();
|
|
102
|
+
|
|
103
|
+
// Create initial list
|
|
104
|
+
const originalList = ItemList.create(["original1", "original2"], {
|
|
105
|
+
owner: group,
|
|
106
|
+
unique: "update-list",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Upsert with new data
|
|
110
|
+
const updatedList = await ItemList.upsertUnique({
|
|
111
|
+
value: ["updated1", "updated2", "updated3"],
|
|
112
|
+
unique: "update-list",
|
|
113
|
+
owner: group,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(updatedList).toEqual(originalList); // Should be the same instance
|
|
117
|
+
expect(updatedList?.length).toBe(3);
|
|
118
|
+
expect(updatedList?.[0]).toBe("updated1");
|
|
119
|
+
expect(updatedList?.[1]).toBe("updated2");
|
|
120
|
+
expect(updatedList?.[2]).toBe("updated3");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("upsertUnique with CoValue items", async () => {
|
|
124
|
+
const Item = co.map({
|
|
125
|
+
name: z.string(),
|
|
126
|
+
value: z.number(),
|
|
127
|
+
});
|
|
128
|
+
const ItemList = co.list(Item);
|
|
129
|
+
const group = Group.create();
|
|
130
|
+
|
|
131
|
+
const items = [
|
|
132
|
+
Item.create({ name: "First", value: 1 }, group),
|
|
133
|
+
Item.create({ name: "Second", value: 2 }, group),
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const result = await ItemList.upsertUnique({
|
|
137
|
+
value: items,
|
|
138
|
+
unique: "item-list",
|
|
139
|
+
owner: group,
|
|
140
|
+
resolve: { $each: true },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result).not.toBeNull();
|
|
144
|
+
expect(result?.length).toBe(2);
|
|
145
|
+
expect(result?.[0]?.name).toBe("First");
|
|
146
|
+
expect(result?.[1]?.name).toBe("Second");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("upsertUnique updates list with CoValue items", async () => {
|
|
150
|
+
const Item = co.map({
|
|
151
|
+
name: z.string(),
|
|
152
|
+
value: z.number(),
|
|
153
|
+
});
|
|
154
|
+
const ItemList = co.list(Item);
|
|
155
|
+
const group = Group.create();
|
|
156
|
+
|
|
157
|
+
// Create initial list
|
|
158
|
+
const initialItems = [Item.create({ name: "Initial", value: 0 }, group)];
|
|
159
|
+
const originalList = ItemList.create(initialItems, {
|
|
160
|
+
owner: group,
|
|
161
|
+
unique: "updateable-item-list",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Upsert with new items
|
|
165
|
+
const newItems = [
|
|
166
|
+
Item.create({ name: "Updated", value: 1 }, group),
|
|
167
|
+
Item.create({ name: "Added", value: 2 }, group),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const updatedList = await ItemList.upsertUnique({
|
|
171
|
+
value: newItems,
|
|
172
|
+
unique: "updateable-item-list",
|
|
173
|
+
owner: group,
|
|
174
|
+
resolve: { $each: true },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(updatedList).toEqual(originalList); // Should be the same instance
|
|
178
|
+
expect(updatedList?.length).toBe(2);
|
|
179
|
+
expect(updatedList?.[0]?.name).toBe("Updated");
|
|
180
|
+
expect(updatedList?.[1]?.name).toBe("Added");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("findUnique returns correct ID", async () => {
|
|
184
|
+
const ItemList = co.list(z.string());
|
|
185
|
+
const group = Group.create();
|
|
186
|
+
|
|
187
|
+
const originalList = ItemList.create(["test"], {
|
|
188
|
+
owner: group,
|
|
189
|
+
unique: "find-test",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const foundId = ItemList.findUnique("find-test", group.$jazz.id);
|
|
193
|
+
expect(foundId).toBe(originalList.$jazz.id);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("upsertUnique with resolve options", async () => {
|
|
197
|
+
const Category = co.map({ title: z.string() });
|
|
198
|
+
const Item = co.map({
|
|
199
|
+
name: z.string(),
|
|
200
|
+
category: Category,
|
|
201
|
+
});
|
|
202
|
+
const ItemList = co.list(Item);
|
|
203
|
+
const group = Group.create();
|
|
204
|
+
|
|
205
|
+
const category = Category.create({ title: "Category 1" }, group);
|
|
206
|
+
|
|
207
|
+
const items = [Item.create({ name: "Item 1", category }, group)];
|
|
208
|
+
|
|
209
|
+
const result = await ItemList.upsertUnique({
|
|
210
|
+
value: items,
|
|
211
|
+
unique: "resolved-list",
|
|
212
|
+
owner: group,
|
|
213
|
+
resolve: { $each: { category: true } },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result).not.toBeNull();
|
|
217
|
+
expect(result?.length).toBe(1);
|
|
218
|
+
expect(result?.[0]?.name).toBe("Item 1");
|
|
219
|
+
expect(result?.[0]?.category?.title).toBe("Category 1");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("concurrently upserting the same value", async () => {
|
|
223
|
+
const ItemList = co.list(z.string());
|
|
224
|
+
|
|
225
|
+
const owner = Group.create();
|
|
226
|
+
|
|
227
|
+
const promises = Array.from({ length: 3 }, (_, i) =>
|
|
228
|
+
ItemList.upsertUnique({
|
|
229
|
+
owner,
|
|
230
|
+
unique: "concurrent",
|
|
231
|
+
value: [`Item ${i}`, `Second ${i}`],
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await Promise.all(promises);
|
|
236
|
+
|
|
237
|
+
const result = await ItemList.loadUnique("concurrent", owner.$jazz.id);
|
|
238
|
+
assert(result);
|
|
239
|
+
|
|
240
|
+
expect(result.length).toBe(2);
|
|
241
|
+
expect(result[0]).toBe(`Item 2`);
|
|
242
|
+
expect(result[1]).toBe(`Second 2`);
|
|
243
|
+
});
|
|
244
|
+
});
|