jazz-tools 0.15.0 → 0.15.1

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 (48) hide show
  1. package/.turbo/turbo-build.log +44 -44
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-FSIM7N33.js → chunk-VBDJM6Z5.js} +142 -31
  4. package/dist/chunk-VBDJM6Z5.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/inspector/index.js +30 -4
  7. package/dist/inspector/index.js.map +1 -1
  8. package/dist/inspector/viewer/new-app.d.ts.map +1 -1
  9. package/dist/inspector/viewer/use-open-inspector.d.ts +2 -0
  10. package/dist/inspector/viewer/use-open-inspector.d.ts.map +1 -0
  11. package/dist/inspector/viewer/use-page-path.d.ts.map +1 -1
  12. package/dist/react/index.js +0 -2
  13. package/dist/react/index.js.map +1 -1
  14. package/dist/react/testing.js +0 -2
  15. package/dist/react/testing.js.map +1 -1
  16. package/dist/react-native-core/index.js +2 -18
  17. package/dist/react-native-core/index.js.map +1 -1
  18. package/dist/react-native-core/media.d.ts.map +1 -1
  19. package/dist/testing.js +1 -1
  20. package/dist/tools/coValues/coFeed.d.ts +9 -0
  21. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  22. package/dist/tools/coValues/coMap.d.ts +98 -2
  23. package/dist/tools/coValues/coMap.d.ts.map +1 -1
  24. package/dist/tools/coValues/interfaces.d.ts +3 -0
  25. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  26. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +12 -0
  27. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  28. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  29. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +2 -1
  30. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
  31. package/dist/tools/subscribe/SubscriptionScope.d.ts +2 -1
  32. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  33. package/package.json +5 -5
  34. package/src/inspector/viewer/new-app.tsx +2 -1
  35. package/src/inspector/viewer/use-open-inspector.ts +18 -0
  36. package/src/inspector/viewer/use-page-path.ts +14 -1
  37. package/src/react-native-core/media.tsx +2 -22
  38. package/src/tools/coValues/coFeed.ts +38 -0
  39. package/src/tools/coValues/coMap.ts +118 -14
  40. package/src/tools/coValues/interfaces.ts +14 -4
  41. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +38 -0
  42. package/src/tools/implementation/zodSchema/zodCo.ts +6 -0
  43. package/src/tools/subscribe/CoValueCoreSubscription.ts +12 -9
  44. package/src/tools/subscribe/SubscriptionScope.ts +31 -19
  45. package/src/tools/tests/coFeed.test.ts +69 -0
  46. package/src/tools/tests/coMap.test.ts +480 -4
  47. package/src/tools/tests/load.test.ts +2 -1
  48. package/dist/chunk-FSIM7N33.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { cojsonInternals } from "cojson";
1
2
  import { WasmCrypto } from "cojson/crypto/WasmCrypto";
2
3
  import {
3
4
  assert,
@@ -11,13 +12,19 @@ import {
11
12
  } from "vitest";
12
13
  import { Group, co, subscribeToCoValue, z } from "../exports.js";
13
14
  import { Account } from "../index.js";
14
- import { Loaded, zodSchemaToCoSchema } from "../internal.js";
15
- import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
15
+ import { ID, Loaded, zodSchemaToCoSchema } from "../internal.js";
16
+ import {
17
+ createJazzTestAccount,
18
+ getPeerConnectedToTestSyncServer,
19
+ setupJazzTestSync,
20
+ } from "../testing.js";
16
21
  import { setupTwoNodes, waitFor } from "./utils.js";
17
22
 
18
23
  const Crypto = await WasmCrypto.create();
19
24
 
20
25
  beforeEach(async () => {
26
+ cojsonInternals.CO_VALUE_LOADING_CONFIG.RETRY_DELAY = 1000;
27
+
21
28
  await setupJazzTestSync();
22
29
 
23
30
  await createJazzTestAccount({
@@ -543,6 +550,115 @@ describe("CoMap resolution", async () => {
543
550
  expect(loadedPerson.dog?.name).toEqual("Rex");
544
551
  });
545
552
 
553
+ test("loading a remotely available map with skipRetry set to true", async () => {
554
+ // Make the retry delay extra long to ensure that it's not used
555
+ cojsonInternals.CO_VALUE_LOADING_CONFIG.RETRY_DELAY = 100_000_000;
556
+
557
+ const Dog = co.map({
558
+ name: z.string(),
559
+ breed: z.string(),
560
+ });
561
+
562
+ const Person = co.map({
563
+ name: z.string(),
564
+ age: z.number(),
565
+ dog: Dog,
566
+ });
567
+
568
+ const currentAccount = Account.getMe();
569
+
570
+ // Disconnect the current account
571
+ currentAccount._raw.core.node.syncManager.getPeers().forEach((peer) => {
572
+ peer.gracefulShutdown();
573
+ });
574
+
575
+ const group = Group.create();
576
+ group.addMember("everyone", "writer");
577
+
578
+ const person = Person.create(
579
+ {
580
+ name: "John",
581
+ age: 20,
582
+ dog: Dog.create({ name: "Rex", breed: "Labrador" }, group),
583
+ },
584
+ group,
585
+ );
586
+
587
+ const userB = await createJazzTestAccount();
588
+
589
+ // We expect that the test doesn't hang here and immediately returns null
590
+ const loadedPerson = await Person.load(person.id, {
591
+ loadAs: userB,
592
+ skipRetry: true,
593
+ });
594
+
595
+ expect(loadedPerson).toBeNull();
596
+ });
597
+
598
+ test("loading a remotely available map with skipRetry set to false", async () => {
599
+ // Make the retry delay extra long to avoid flakyness in the resolved checks
600
+ cojsonInternals.CO_VALUE_LOADING_CONFIG.RETRY_DELAY = 100_000_000;
601
+
602
+ const Dog = co.map({
603
+ name: z.string(),
604
+ breed: z.string(),
605
+ });
606
+
607
+ const Person = co.map({
608
+ name: z.string(),
609
+ age: z.number(),
610
+ dog: Dog,
611
+ });
612
+
613
+ const currentAccount = Account.getMe();
614
+
615
+ // Disconnect the current account
616
+ currentAccount._raw.core.node.syncManager.getPeers().forEach((peer) => {
617
+ peer.gracefulShutdown();
618
+ });
619
+
620
+ const group = Group.create();
621
+ group.addMember("everyone", "writer");
622
+
623
+ const person = Person.create(
624
+ {
625
+ name: "John",
626
+ age: 20,
627
+ dog: Dog.create({ name: "Rex", breed: "Labrador" }, group),
628
+ },
629
+ group,
630
+ );
631
+
632
+ const userB = await createJazzTestAccount();
633
+ let resolved = false;
634
+ const promise = Person.load(person.id, {
635
+ loadAs: userB,
636
+ skipRetry: false,
637
+ });
638
+ promise.then(() => {
639
+ resolved = true;
640
+ });
641
+
642
+ await new Promise((resolve) => setTimeout(resolve, 100));
643
+
644
+ expect(resolved).toBe(false);
645
+
646
+ // Reconnect the current account
647
+ currentAccount._raw.core.node.syncManager.addPeer(
648
+ getPeerConnectedToTestSyncServer(),
649
+ );
650
+
651
+ const loadedPerson = await promise;
652
+
653
+ expect(resolved).toBe(true);
654
+ assert(loadedPerson);
655
+ expect(loadedPerson.dog).toBe(null);
656
+
657
+ await waitFor(() => expect(loadedPerson.dog).toBeTruthy());
658
+
659
+ expect(loadedPerson.dog?.name).toEqual("Rex");
660
+ });
661
+
546
662
  test("accessing the value refs", async () => {
547
663
  const Dog = co.map({
548
664
  name: z.string(),
@@ -1197,8 +1313,368 @@ describe("Creating and finding unique CoMaps", async () => {
1197
1313
  { owner: group, unique: { name: "Alice" } },
1198
1314
  );
1199
1315
 
1200
- const foundAlice = Person.findUnique({ name: "Alice" }, group.id);
1201
- expect(foundAlice).toEqual(alice.id);
1316
+ const foundAlice = await Person.loadUnique({ name: "Alice" }, group.id);
1317
+ expect(foundAlice).toEqual(alice);
1318
+ });
1319
+
1320
+ test("manual upserting pattern", async () => {
1321
+ // Schema
1322
+ const Event = co.map({
1323
+ title: z.string(),
1324
+ identifier: z.string(),
1325
+ external_id: z.string(),
1326
+ });
1327
+
1328
+ // Data
1329
+ const sourceData = {
1330
+ title: "Test Event Title",
1331
+ identifier: "test-event-identifier",
1332
+ _id: "test-event-external-id",
1333
+ };
1334
+ const workspace = Group.create();
1335
+
1336
+ // Pattern
1337
+ let activeEvent = await Event.loadUnique(
1338
+ { identifier: sourceData.identifier },
1339
+ workspace.id,
1340
+ );
1341
+ if (!activeEvent) {
1342
+ activeEvent = Event.create(
1343
+ {
1344
+ title: sourceData.title,
1345
+ identifier: sourceData.identifier,
1346
+ external_id: sourceData._id,
1347
+ },
1348
+ workspace,
1349
+ );
1350
+ } else {
1351
+ activeEvent.applyDiff({
1352
+ title: sourceData.title,
1353
+ identifier: sourceData.identifier,
1354
+ external_id: sourceData._id,
1355
+ });
1356
+ }
1357
+ expect(activeEvent).toEqual({
1358
+ title: sourceData.title,
1359
+ identifier: sourceData.identifier,
1360
+ external_id: sourceData._id,
1361
+ });
1362
+ });
1363
+
1364
+ test("upserting a non-existent value", async () => {
1365
+ // Schema
1366
+ const Event = co.map({
1367
+ title: z.string(),
1368
+ identifier: z.string(),
1369
+ external_id: z.string(),
1370
+ });
1371
+
1372
+ // Data
1373
+ const sourceData = {
1374
+ title: "Test Event Title",
1375
+ identifier: "test-event-identifier",
1376
+ _id: "test-event-external-id",
1377
+ };
1378
+ const workspace = Group.create();
1379
+
1380
+ // Upserting
1381
+ const activeEvent = await Event.upsertUnique({
1382
+ value: {
1383
+ title: sourceData.title,
1384
+ identifier: sourceData.identifier,
1385
+ external_id: sourceData._id,
1386
+ },
1387
+ unique: sourceData.identifier,
1388
+ owner: workspace,
1389
+ });
1390
+ expect(activeEvent).toEqual({
1391
+ title: sourceData.title,
1392
+ identifier: sourceData.identifier,
1393
+ external_id: sourceData._id,
1394
+ });
1395
+ });
1396
+
1397
+ test("upserting an existing value", async () => {
1398
+ // Schema
1399
+ const Event = co.map({
1400
+ title: z.string(),
1401
+ identifier: z.string(),
1402
+ external_id: z.string(),
1403
+ });
1404
+
1405
+ // Data
1406
+ const oldSourceData = {
1407
+ title: "Old Event Title",
1408
+ identifier: "test-event-identifier",
1409
+ _id: "test-event-external-id",
1410
+ };
1411
+ const newSourceData = {
1412
+ title: "New Event Title",
1413
+ identifier: "test-event-identifier",
1414
+ _id: "test-event-external-id",
1415
+ };
1416
+ expect(oldSourceData.identifier).toEqual(newSourceData.identifier);
1417
+ const workspace = Group.create();
1418
+ const oldActiveEvent = Event.create(
1419
+ {
1420
+ title: oldSourceData.title,
1421
+ identifier: oldSourceData.identifier,
1422
+ external_id: oldSourceData._id,
1423
+ },
1424
+ workspace,
1425
+ );
1426
+
1427
+ // Upserting
1428
+ const activeEvent = await Event.upsertUnique({
1429
+ value: {
1430
+ title: newSourceData.title,
1431
+ identifier: newSourceData.identifier,
1432
+ external_id: newSourceData._id,
1433
+ },
1434
+ unique: newSourceData.identifier,
1435
+ owner: workspace,
1436
+ });
1437
+ expect(activeEvent).toEqual({
1438
+ title: newSourceData.title,
1439
+ identifier: newSourceData.identifier,
1440
+ external_id: newSourceData._id,
1441
+ });
1442
+ expect(activeEvent).not.toEqual(oldActiveEvent);
1443
+ });
1444
+
1445
+ test("upserting a non-existent value with resolve", async () => {
1446
+ const Project = co.map({
1447
+ name: z.string(),
1448
+ });
1449
+ const Organisation = co.map({
1450
+ name: z.string(),
1451
+ projects: co.list(Project),
1452
+ });
1453
+ const workspace = Group.create();
1454
+
1455
+ const myOrg = await Organisation.upsertUnique({
1456
+ value: {
1457
+ name: "My organisation",
1458
+ projects: co.list(Project).create(
1459
+ [
1460
+ Project.create(
1461
+ {
1462
+ name: "My project",
1463
+ },
1464
+ workspace,
1465
+ ),
1466
+ ],
1467
+ workspace,
1468
+ ),
1469
+ },
1470
+ unique: { name: "My organisation" },
1471
+ owner: workspace,
1472
+ resolve: {
1473
+ projects: {
1474
+ $each: true,
1475
+ },
1476
+ },
1477
+ });
1478
+ assert(myOrg);
1479
+ expect(myOrg).not.toBeNull();
1480
+ expect(myOrg.name).toEqual("My organisation");
1481
+ expect(myOrg.projects.length).toBe(1);
1482
+ expect(myOrg.projects[0]).toMatchObject({
1483
+ name: "My project",
1484
+ });
1485
+ });
1486
+
1487
+ test("upserting an existing value with resolve", async () => {
1488
+ const Project = co.map({
1489
+ name: z.string(),
1490
+ });
1491
+ const Organisation = co.map({
1492
+ name: z.string(),
1493
+ projects: co.list(Project),
1494
+ });
1495
+ const workspace = Group.create();
1496
+ const initialProject = await Project.upsertUnique({
1497
+ value: {
1498
+ name: "My project",
1499
+ },
1500
+ unique: { unique: "First project" },
1501
+ owner: workspace,
1502
+ });
1503
+ assert(initialProject);
1504
+ expect(initialProject).not.toBeNull();
1505
+ expect(initialProject.name).toEqual("My project");
1506
+
1507
+ const myOrg = await Organisation.upsertUnique({
1508
+ value: {
1509
+ name: "My organisation",
1510
+ projects: co.list(Project).create([initialProject], workspace),
1511
+ },
1512
+ unique: { name: "My organisation" },
1513
+ owner: workspace,
1514
+ resolve: {
1515
+ projects: {
1516
+ $each: true,
1517
+ },
1518
+ },
1519
+ });
1520
+ assert(myOrg);
1521
+ expect(myOrg).not.toBeNull();
1522
+ expect(myOrg.name).toEqual("My organisation");
1523
+ expect(myOrg.projects.length).toBe(1);
1524
+ expect(myOrg.projects.at(0)?.name).toEqual("My project");
1525
+
1526
+ const updatedProject = await Project.upsertUnique({
1527
+ value: {
1528
+ name: "My updated project",
1529
+ },
1530
+ unique: { unique: "First project" },
1531
+ owner: workspace,
1532
+ });
1533
+
1534
+ assert(updatedProject);
1535
+ expect(updatedProject).not.toBeNull();
1536
+ expect(updatedProject).toEqual(initialProject);
1537
+ expect(updatedProject.name).toEqual("My updated project");
1538
+ expect(myOrg.projects.length).toBe(1);
1539
+ expect(myOrg.projects.at(0)?.name).toEqual("My updated project");
1540
+ });
1541
+
1542
+ test("upserting a partially loaded value on an new value with resolve", async () => {
1543
+ const Project = co.map({
1544
+ name: z.string(),
1545
+ });
1546
+ const Organisation = co.map({
1547
+ name: z.string(),
1548
+ projects: co.list(Project),
1549
+ });
1550
+ const publicAccess = Group.create();
1551
+ publicAccess.addMember("everyone", "writer");
1552
+
1553
+ const initialProject = await Project.upsertUnique({
1554
+ value: {
1555
+ name: "My project",
1556
+ },
1557
+ unique: { unique: "First project" },
1558
+ owner: publicAccess,
1559
+ });
1560
+ assert(initialProject);
1561
+ expect(initialProject).not.toBeNull();
1562
+ expect(initialProject.name).toEqual("My project");
1563
+
1564
+ const fullProjectList = co
1565
+ .list(Project)
1566
+ .create([initialProject], publicAccess);
1567
+
1568
+ const account = await createJazzTestAccount({
1569
+ isCurrentActiveAccount: true,
1570
+ });
1571
+
1572
+ const shallowProjectList = await co.list(Project).load(fullProjectList.id, {
1573
+ loadAs: account,
1574
+ });
1575
+ assert(shallowProjectList);
1576
+
1577
+ const publicAccessAsNewAccount = await Group.load(publicAccess.id, {
1578
+ loadAs: account,
1579
+ });
1580
+ assert(publicAccessAsNewAccount);
1581
+
1582
+ const updatedOrg = await Organisation.upsertUnique({
1583
+ value: {
1584
+ name: "My organisation",
1585
+ projects: shallowProjectList,
1586
+ },
1587
+ unique: { name: "My organisation" },
1588
+ owner: publicAccessAsNewAccount,
1589
+ resolve: {
1590
+ projects: {
1591
+ $each: true,
1592
+ },
1593
+ },
1594
+ });
1595
+
1596
+ assert(updatedOrg);
1597
+
1598
+ expect(updatedOrg.projects.id).toEqual(fullProjectList.id);
1599
+ expect(updatedOrg.projects.length).toBe(1);
1600
+ expect(updatedOrg.projects.at(0)?.name).toEqual("My project");
1601
+ });
1602
+
1603
+ test("upserting a partially loaded value on an existing value with resolve", async () => {
1604
+ const Project = co.map({
1605
+ name: z.string(),
1606
+ });
1607
+ const Organisation = co.map({
1608
+ name: z.string(),
1609
+ projects: co.list(Project),
1610
+ });
1611
+ const publicAccess = Group.create();
1612
+ publicAccess.addMember("everyone", "writer");
1613
+
1614
+ const initialProject = await Project.upsertUnique({
1615
+ value: {
1616
+ name: "My project",
1617
+ },
1618
+ unique: { unique: "First project" },
1619
+ owner: publicAccess,
1620
+ });
1621
+ assert(initialProject);
1622
+ expect(initialProject).not.toBeNull();
1623
+ expect(initialProject.name).toEqual("My project");
1624
+
1625
+ const myOrg = await Organisation.upsertUnique({
1626
+ value: {
1627
+ name: "My organisation",
1628
+ projects: co.list(Project).create([], publicAccess),
1629
+ },
1630
+ unique: { name: "My organisation" },
1631
+ owner: publicAccess,
1632
+ resolve: {
1633
+ projects: {
1634
+ $each: true,
1635
+ },
1636
+ },
1637
+ });
1638
+ assert(myOrg);
1639
+
1640
+ const fullProjectList = co
1641
+ .list(Project)
1642
+ .create([initialProject], publicAccess);
1643
+
1644
+ const account = await createJazzTestAccount({
1645
+ isCurrentActiveAccount: true,
1646
+ });
1647
+
1648
+ const shallowProjectList = await co.list(Project).load(fullProjectList.id, {
1649
+ loadAs: account,
1650
+ });
1651
+ assert(shallowProjectList);
1652
+
1653
+ const publicAccessAsNewAccount = await Group.load(publicAccess.id, {
1654
+ loadAs: account,
1655
+ });
1656
+ assert(publicAccessAsNewAccount);
1657
+
1658
+ const updatedOrg = await Organisation.upsertUnique({
1659
+ value: {
1660
+ name: "My organisation",
1661
+ projects: shallowProjectList,
1662
+ },
1663
+ unique: { name: "My organisation" },
1664
+ owner: publicAccessAsNewAccount,
1665
+ resolve: {
1666
+ projects: {
1667
+ $each: true,
1668
+ },
1669
+ },
1670
+ });
1671
+
1672
+ assert(updatedOrg);
1673
+
1674
+ expect(updatedOrg.projects.id).toEqual(fullProjectList.id);
1675
+ expect(updatedOrg.projects.length).toBe(1);
1676
+ expect(updatedOrg.projects.at(0)?.name).toEqual("My project");
1677
+ expect(updatedOrg.id).toEqual(myOrg.id);
1202
1678
  });
1203
1679
 
1204
1680
  test("complex discriminated union", () => {
@@ -30,7 +30,7 @@ test("load a value", async () => {
30
30
  expect(john?.name).toBe("John");
31
31
  });
32
32
 
33
- test("retry an unavailable a value", async () => {
33
+ test("retry an unavailable value", async () => {
34
34
  const Person = co.map({
35
35
  name: z.string(),
36
36
  });
@@ -69,6 +69,7 @@ test("retry an unavailable a value", async () => {
69
69
  );
70
70
 
71
71
  const john = await promise;
72
+ expect(resolved).toBe(true);
72
73
  expect(john).not.toBeNull();
73
74
  expect(john?.name).toBe("John");
74
75
  });