jazz-tools 0.15.0 → 0.15.2
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-FSIM7N33.js → chunk-VBDJM6Z5.js} +142 -31
- package/dist/chunk-VBDJM6Z5.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/index.js +147 -102
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/viewer/co-plain-text-view.d.ts +5 -0
- package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -0
- package/dist/inspector/viewer/new-app.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/inspector/viewer/type-icon.d.ts.map +1 -1
- package/dist/inspector/viewer/use-open-inspector.d.ts +2 -0
- package/dist/inspector/viewer/use-open-inspector.d.ts.map +1 -0
- package/dist/inspector/viewer/use-page-path.d.ts.map +1 -1
- package/dist/inspector/viewer/use-resolve-covalue.d.ts +1 -1
- package/dist/inspector/viewer/use-resolve-covalue.d.ts.map +1 -1
- package/dist/inspector/viewer/value-renderer.d.ts.map +1 -1
- package/dist/react-native-core/index.js +2 -18
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/media.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coFeed.d.ts +9 -0
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +98 -2
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +3 -0
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +12 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +2 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +2 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/inspector/viewer/co-plain-text-view.tsx +17 -0
- package/src/inspector/viewer/new-app.tsx +2 -1
- package/src/inspector/viewer/page.tsx +5 -0
- package/src/inspector/viewer/type-icon.tsx +1 -0
- package/src/inspector/viewer/use-open-inspector.ts +18 -0
- package/src/inspector/viewer/use-page-path.ts +14 -1
- package/src/inspector/viewer/use-resolve-covalue.ts +1 -1
- package/src/inspector/viewer/value-renderer.tsx +4 -0
- package/src/react-native-core/media.tsx +2 -22
- package/src/tools/coValues/coFeed.ts +38 -0
- package/src/tools/coValues/coMap.ts +118 -14
- package/src/tools/coValues/interfaces.ts +14 -4
- package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +38 -0
- package/src/tools/implementation/zodSchema/zodCo.ts +6 -0
- package/src/tools/subscribe/CoValueCoreSubscription.ts +12 -9
- package/src/tools/subscribe/SubscriptionScope.ts +31 -19
- package/src/tools/tests/coFeed.test.ts +69 -0
- package/src/tools/tests/coMap.test.ts +480 -4
- package/src/tools/tests/load.test.ts +2 -1
- package/dist/chunk-FSIM7N33.js.map +0 -1
@@ -44,34 +44,46 @@ export class SubscriptionScope<D extends CoValue> {
|
|
44
44
|
resolve: RefsToResolve<D>,
|
45
45
|
public id: ID<D>,
|
46
46
|
public schema: RefEncoded<D>,
|
47
|
+
public skipRetry?: boolean,
|
47
48
|
) {
|
48
49
|
this.resolve = resolve;
|
49
50
|
this.value = { type: "unloaded", id };
|
50
51
|
|
51
52
|
let lastUpdate: RawCoValue | "unavailable" | undefined;
|
52
|
-
this.subscription = new CoValueCoreSubscription(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if (!this.migrated && value !== "unavailable") {
|
62
|
-
if (this.migrating) {
|
53
|
+
this.subscription = new CoValueCoreSubscription(
|
54
|
+
node,
|
55
|
+
id,
|
56
|
+
(value) => {
|
57
|
+
lastUpdate = value;
|
58
|
+
|
59
|
+
if (skipRetry && value === "unavailable") {
|
60
|
+
this.handleUpdate(value);
|
61
|
+
this.destroy();
|
63
62
|
return;
|
64
63
|
}
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
// Need all these checks because the migration can trigger new syncronous updates
|
66
|
+
//
|
67
|
+
// We want to:
|
68
|
+
// - Run the migration only once
|
69
|
+
// - Skip all the updates until the migration is done
|
70
|
+
// - Trigger handleUpdate only with the final value
|
71
|
+
if (!this.migrated && value !== "unavailable") {
|
72
|
+
if (this.migrating) {
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
this.migrating = true;
|
77
|
+
applyCoValueMigrations(instantiateRefEncoded(this.schema, value));
|
78
|
+
this.migrated = true;
|
79
|
+
this.handleUpdate(lastUpdate);
|
80
|
+
return;
|
81
|
+
}
|
72
82
|
|
73
|
-
|
74
|
-
|
83
|
+
this.handleUpdate(value);
|
84
|
+
},
|
85
|
+
skipRetry,
|
86
|
+
);
|
75
87
|
}
|
76
88
|
|
77
89
|
updateValue(value: SubscriptionValue<D, any>) {
|
@@ -688,6 +688,75 @@ describe("FileStream.loadAsBlob", async () => {
|
|
688
688
|
});
|
689
689
|
});
|
690
690
|
|
691
|
+
describe("FileStream.loadAsBase64", async () => {
|
692
|
+
async function setup() {
|
693
|
+
const me = await Account.create({
|
694
|
+
creationProps: { name: "Hermes Puggington" },
|
695
|
+
crypto: Crypto,
|
696
|
+
});
|
697
|
+
|
698
|
+
const stream = FileStream.create({ owner: me });
|
699
|
+
|
700
|
+
stream.start({ mimeType: "text/plain" });
|
701
|
+
|
702
|
+
return { stream, me };
|
703
|
+
}
|
704
|
+
|
705
|
+
test("resolves only when the stream is ended", async () => {
|
706
|
+
const { stream, me } = await setup();
|
707
|
+
stream.push(new Uint8Array([1]));
|
708
|
+
|
709
|
+
const promise = FileStream.loadAsBase64(stream.id, { loadAs: me });
|
710
|
+
|
711
|
+
stream.push(new Uint8Array([2]));
|
712
|
+
stream.end();
|
713
|
+
|
714
|
+
const base64 = await promise;
|
715
|
+
|
716
|
+
// The promise resolves only when the stream is ended
|
717
|
+
// so we get a blob with all the chunks
|
718
|
+
expect(base64).toBe("AQI=");
|
719
|
+
});
|
720
|
+
|
721
|
+
test("resolves with a data URL if dataURL: true", async () => {
|
722
|
+
const { stream, me } = await setup();
|
723
|
+
stream.push(new Uint8Array([1]));
|
724
|
+
|
725
|
+
const promise = FileStream.loadAsBase64(stream.id, {
|
726
|
+
loadAs: me,
|
727
|
+
dataURL: true,
|
728
|
+
});
|
729
|
+
|
730
|
+
stream.push(new Uint8Array([2]));
|
731
|
+
stream.end();
|
732
|
+
|
733
|
+
const base64 = await promise;
|
734
|
+
|
735
|
+
// The promise resolves only when the stream is ended
|
736
|
+
// so we get a blob with all the chunks
|
737
|
+
expect(base64).toBe("data:text/plain;base64,AQI=");
|
738
|
+
});
|
739
|
+
|
740
|
+
test("resolves with a partial base64 if allowUnfinished: true", async () => {
|
741
|
+
const { stream, me } = await setup();
|
742
|
+
stream.push(new Uint8Array([1]));
|
743
|
+
|
744
|
+
const promise = FileStream.loadAsBase64(stream.id, {
|
745
|
+
loadAs: me,
|
746
|
+
allowUnfinished: true,
|
747
|
+
});
|
748
|
+
|
749
|
+
const base64 = await promise;
|
750
|
+
|
751
|
+
stream.push(new Uint8Array([2]));
|
752
|
+
stream.end();
|
753
|
+
|
754
|
+
// The promise resolves before the stream is ended
|
755
|
+
// so we get a blob only with the first chunk
|
756
|
+
expect(base64).toBe("AQ==");
|
757
|
+
});
|
758
|
+
});
|
759
|
+
|
691
760
|
describe("FileStream progress tracking", async () => {
|
692
761
|
test("createFromBlob should report upload progress correctly", async () => {
|
693
762
|
// Create 5MB test blob
|
@@ -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 {
|
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.
|
1201
|
-
expect(foundAlice).toEqual(alice
|
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
|
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
|
});
|