@evolu/common 8.12.0 → 8.14.0
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/dist/src/Callbacks.d.ts +12 -1
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +3 -0
- package/dist/src/Error.d.ts +10 -4
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +10 -4
- package/dist/src/Object.d.ts +65 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +142 -0
- package/dist/src/Resource.d.ts +0 -5
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +6 -13
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +7 -0
- package/dist/src/Task.d.ts +10 -8
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +41 -5
- package/dist/src/Worker.d.ts +3 -3
- package/dist/src/local-first/Db.d.ts +8 -3
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +56 -19
- package/dist/src/local-first/Evolu.d.ts +147 -39
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +53 -9
- package/dist/src/local-first/Owner.d.ts +9 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +9 -0
- package/dist/src/local-first/Protocol.d.ts +6 -4
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +1 -6
- package/dist/src/local-first/Schema.d.ts +7 -1
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Shared.d.ts +550 -153
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +724 -273
- package/dist/src/local-first/Storage.d.ts +16 -12
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Callbacks.test.ts +20 -0
- package/src/Callbacks.ts +17 -1
- package/src/Error.ts +10 -4
- package/src/Object.test.ts +296 -0
- package/src/Object.ts +163 -0
- package/src/Resource.test.ts +20 -16
- package/src/Resource.ts +6 -20
- package/src/Sqlite.ts +7 -0
- package/src/Task.test.ts +233 -62
- package/src/Task.ts +47 -13
- package/src/Worker.ts +3 -3
- package/src/local-first/Db.ts +88 -18
- package/src/local-first/Evolu.test.ts +381 -11
- package/src/local-first/Evolu.ts +219 -51
- package/src/local-first/Owner.ts +9 -0
- package/src/local-first/Protocol.test.ts +42 -60
- package/src/local-first/Protocol.ts +7 -9
- package/src/local-first/Schema.ts +8 -2
- package/src/local-first/Shared.test.ts +2714 -644
- package/src/local-first/Shared.ts +1245 -401
- package/src/local-first/Storage.ts +18 -19
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
type SharedWorkerInput,
|
|
62
62
|
type SharedWorkerOutput,
|
|
63
63
|
type SyncState,
|
|
64
|
+
syncStateToOwnerSyncStatus,
|
|
64
65
|
} from "./Shared.ts";
|
|
65
66
|
import {
|
|
66
67
|
acquireLeaderLock,
|
|
@@ -72,6 +73,7 @@ import { err, ok } from "../Result.ts";
|
|
|
72
73
|
import { SqliteBoolean } from "../Sqlite.ts";
|
|
73
74
|
import { explicitAbortReason, testCreateDeps, testCreateRun } from "../Task.ts";
|
|
74
75
|
import { testCreateId } from "../Test.ts";
|
|
76
|
+
import { Millis } from "../Time.ts";
|
|
75
77
|
import {
|
|
76
78
|
assertType,
|
|
77
79
|
createIdFromString,
|
|
@@ -370,16 +372,6 @@ describe("Evolu", () => {
|
|
|
370
372
|
assertSame(setup.deps.evoluError.get(), null);
|
|
371
373
|
});
|
|
372
374
|
|
|
373
|
-
it("says nothing to a worker that reports it keeps databases in memory", async () => {
|
|
374
|
-
using setup = await setupCreateEvoluDeps();
|
|
375
|
-
|
|
376
|
-
setup.sharedWorkerPort.postMessage({ type: "StorageUnavailable" });
|
|
377
|
-
await testWaitForWorkerMessage();
|
|
378
|
-
|
|
379
|
-
assertEqual(setup.messages, []);
|
|
380
|
-
assertSame(setup.deps.evoluError.get(), null);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
375
|
it("announces itself as tab leader with its console level once its worker connects", async () => {
|
|
384
376
|
const testConsole = testCreateConsole();
|
|
385
377
|
using setup = await setupCreateEvoluDeps(testConsole);
|
|
@@ -428,6 +420,7 @@ describe("Evolu", () => {
|
|
|
428
420
|
tables: { todo: new Set(["title"]) },
|
|
429
421
|
indexes: [],
|
|
430
422
|
};
|
|
423
|
+
const sharedWorkerId = testCreateId()<"SharedWorker">();
|
|
431
424
|
const sharedWorkerPort: {
|
|
432
425
|
value: MessagePort<SharedWorkerOutput, SharedWorkerInput> | null;
|
|
433
426
|
} = { value: null };
|
|
@@ -468,6 +461,7 @@ describe("Evolu", () => {
|
|
|
468
461
|
sqliteSchema,
|
|
469
462
|
encryptionKey: testAppOwner.encryptionKey,
|
|
470
463
|
memoryOnly: true,
|
|
464
|
+
sharedWorkerId,
|
|
471
465
|
port: dbWorkerChannel.port1.native,
|
|
472
466
|
},
|
|
473
467
|
[dbWorkerChannel.port1.native],
|
|
@@ -483,6 +477,7 @@ describe("Evolu", () => {
|
|
|
483
477
|
sqliteSchema,
|
|
484
478
|
encryptionKey: testAppOwner.encryptionKey,
|
|
485
479
|
memoryOnly: true,
|
|
480
|
+
sharedWorkerId,
|
|
486
481
|
port: dbWorkerChannel.port1.native,
|
|
487
482
|
});
|
|
488
483
|
|
|
@@ -592,7 +587,7 @@ describe("Evolu", () => {
|
|
|
592
587
|
);
|
|
593
588
|
const createState = (label: string): SyncState => ({
|
|
594
589
|
transports: [],
|
|
595
|
-
tenants: [{ name: Name.orThrow(label),
|
|
590
|
+
tenants: [{ type: "Active", name: Name.orThrow(label), owners: [] }],
|
|
596
591
|
});
|
|
597
592
|
|
|
598
593
|
// Nothing reaches the tab before its worker names the channel.
|
|
@@ -616,6 +611,214 @@ describe("Evolu", () => {
|
|
|
616
611
|
assertEqual(deps.syncState.get(), createState("changed"));
|
|
617
612
|
});
|
|
618
613
|
|
|
614
|
+
it("keeps the unchanged parts of each sync state snapshot", async () => {
|
|
615
|
+
using setup = await setupCreateEvoluDeps();
|
|
616
|
+
const { deps, connect } = setup;
|
|
617
|
+
const workerId = testCreateId()<"SharedWorker">();
|
|
618
|
+
using channel = testCreateBroadcastChannel<SyncState>(
|
|
619
|
+
`evolu:sync-state:${workerId}`,
|
|
620
|
+
);
|
|
621
|
+
await connect(workerId);
|
|
622
|
+
let notificationCount = 0;
|
|
623
|
+
deps.syncState.subscribe(() => {
|
|
624
|
+
notificationCount++;
|
|
625
|
+
});
|
|
626
|
+
const transportId = testCreateId()<"SyncTransport">();
|
|
627
|
+
// Every snapshot is a new structured clone. The worker wraps a caught
|
|
628
|
+
// Error inside an error in an UnknownError, whose cause can hold the
|
|
629
|
+
// bytes of a received change.
|
|
630
|
+
const createState = (
|
|
631
|
+
lastSentAt: number,
|
|
632
|
+
message = "wrong key",
|
|
633
|
+
): SyncState => ({
|
|
634
|
+
transports: [
|
|
635
|
+
{
|
|
636
|
+
type: "WebSocket",
|
|
637
|
+
id: transportId,
|
|
638
|
+
label: "wss://relay.example",
|
|
639
|
+
connection: {
|
|
640
|
+
type: "Open",
|
|
641
|
+
openedAt: Millis.orThrow(1000),
|
|
642
|
+
error: null,
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
tenants: [
|
|
647
|
+
{
|
|
648
|
+
type: "Active",
|
|
649
|
+
name: testName,
|
|
650
|
+
owners: [
|
|
651
|
+
{
|
|
652
|
+
type: "Writable",
|
|
653
|
+
ownerId: testAppOwner.id,
|
|
654
|
+
routes: [
|
|
655
|
+
{
|
|
656
|
+
type: "Settled",
|
|
657
|
+
transportId,
|
|
658
|
+
skippedError: {
|
|
659
|
+
type: "DecryptWithXChaCha20Poly1305Error",
|
|
660
|
+
error: {
|
|
661
|
+
type: "UnknownError",
|
|
662
|
+
error: {
|
|
663
|
+
message,
|
|
664
|
+
cause: { values: { photo: Uint8Array.of(1, 2, 3) } },
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
at: Millis.orThrow(1500),
|
|
668
|
+
},
|
|
669
|
+
completeAt: null,
|
|
670
|
+
lastSentAt: Millis.orThrow(lastSentAt),
|
|
671
|
+
lastReceivedAt: Millis.orThrow(1500),
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
},
|
|
675
|
+
],
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
});
|
|
679
|
+
const routeOf = (state: SyncState | null) => {
|
|
680
|
+
const tenant = state?.tenants[0];
|
|
681
|
+
assert(tenant?.type === "Active", "The tenant should be active.");
|
|
682
|
+
const owner = tenant.owners[0];
|
|
683
|
+
assert(owner?.type === "Writable", "The owner should be writable.");
|
|
684
|
+
const route = owner.routes[0];
|
|
685
|
+
assert(route?.type === "Settled", "The route should be settled.");
|
|
686
|
+
return route;
|
|
687
|
+
};
|
|
688
|
+
const statusOf = (state: SyncState | null) =>
|
|
689
|
+
syncStateToOwnerSyncStatus(state, testName, testAppOwner.id);
|
|
690
|
+
|
|
691
|
+
channel.postMessage(createState(1000));
|
|
692
|
+
await testWaitForWorkerMessage();
|
|
693
|
+
const first = deps.syncState.get();
|
|
694
|
+
assertNotNull(first);
|
|
695
|
+
assertSame(notificationCount, 1);
|
|
696
|
+
|
|
697
|
+
// An equal snapshot leaves the store unchanged.
|
|
698
|
+
channel.postMessage(createState(1000));
|
|
699
|
+
await testWaitForWorkerMessage();
|
|
700
|
+
assertSame(deps.syncState.get(), first);
|
|
701
|
+
assertSame(notificationCount, 1);
|
|
702
|
+
|
|
703
|
+
// A changed route gets a new object, and every unchanged part keeps the
|
|
704
|
+
// previous one, so the owner's status is the same object.
|
|
705
|
+
channel.postMessage(createState(2000));
|
|
706
|
+
await testWaitForWorkerMessage();
|
|
707
|
+
const second = deps.syncState.get();
|
|
708
|
+
assertNotNull(second);
|
|
709
|
+
assertNotSame(second, first);
|
|
710
|
+
assertSame(notificationCount, 2);
|
|
711
|
+
assertSame(second.transports, first.transports);
|
|
712
|
+
assertNotSame(routeOf(second), routeOf(first));
|
|
713
|
+
assertSame(routeOf(second).skippedError, routeOf(first).skippedError);
|
|
714
|
+
assertSame(statusOf(second), statusOf(first));
|
|
715
|
+
|
|
716
|
+
// An error with another message is a new error and a new status.
|
|
717
|
+
channel.postMessage(createState(2000, "tampered"));
|
|
718
|
+
await testWaitForWorkerMessage();
|
|
719
|
+
const third = deps.syncState.get();
|
|
720
|
+
assertNotSame(routeOf(third).skippedError, routeOf(second).skippedError);
|
|
721
|
+
assertNotSame(statusOf(third), statusOf(second));
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it("keeps each part of a sync state snapshot when one before it goes away", async () => {
|
|
725
|
+
using setup = await setupCreateEvoluDeps();
|
|
726
|
+
const { deps, connect } = setup;
|
|
727
|
+
const createId = testCreateId();
|
|
728
|
+
const workerId = createId<"SharedWorker">();
|
|
729
|
+
using channel = testCreateBroadcastChannel<SyncState>(
|
|
730
|
+
`evolu:sync-state:${workerId}`,
|
|
731
|
+
);
|
|
732
|
+
await connect(workerId);
|
|
733
|
+
const removedTransportId = createId<"SyncTransport">();
|
|
734
|
+
const transportId = createId<"SyncTransport">();
|
|
735
|
+
const at = Millis.orThrow(1000);
|
|
736
|
+
// The full snapshot lists a transport, tenant, owner, and route before
|
|
737
|
+
// each part the other snapshot keeps. Only the kept route has an error.
|
|
738
|
+
const createState = (isFull: boolean): SyncState => ({
|
|
739
|
+
transports: [removedTransportId, transportId]
|
|
740
|
+
.slice(isFull ? 0 : 1)
|
|
741
|
+
.map((id) => ({
|
|
742
|
+
type: "WebSocket",
|
|
743
|
+
id,
|
|
744
|
+
label: `wss://${id}.example`,
|
|
745
|
+
connection: { type: "Open", openedAt: at, error: null },
|
|
746
|
+
})),
|
|
747
|
+
tenants: [
|
|
748
|
+
...(isFull
|
|
749
|
+
? [
|
|
750
|
+
{
|
|
751
|
+
type: "Active" as const,
|
|
752
|
+
name: Name.orThrow("removed"),
|
|
753
|
+
owners: [],
|
|
754
|
+
},
|
|
755
|
+
]
|
|
756
|
+
: []),
|
|
757
|
+
{
|
|
758
|
+
type: "Active",
|
|
759
|
+
name: testName,
|
|
760
|
+
owners: [
|
|
761
|
+
...(isFull
|
|
762
|
+
? [
|
|
763
|
+
{
|
|
764
|
+
type: "Writable" as const,
|
|
765
|
+
ownerId: createId<"OwnerId">(),
|
|
766
|
+
routes: [],
|
|
767
|
+
},
|
|
768
|
+
]
|
|
769
|
+
: []),
|
|
770
|
+
{
|
|
771
|
+
type: "Writable",
|
|
772
|
+
ownerId: testAppOwner.id,
|
|
773
|
+
routes: [
|
|
774
|
+
...(isFull
|
|
775
|
+
? [
|
|
776
|
+
{
|
|
777
|
+
type: "Complete" as const,
|
|
778
|
+
transportId: removedTransportId,
|
|
779
|
+
completeAt: at,
|
|
780
|
+
lastSentAt: at,
|
|
781
|
+
lastReceivedAt: at,
|
|
782
|
+
},
|
|
783
|
+
]
|
|
784
|
+
: []),
|
|
785
|
+
{
|
|
786
|
+
type: "Pending",
|
|
787
|
+
transportId,
|
|
788
|
+
failure: {
|
|
789
|
+
type: "ProtocolQuotaError",
|
|
790
|
+
ownerId: testAppOwner.id,
|
|
791
|
+
at,
|
|
792
|
+
},
|
|
793
|
+
skippedError: null,
|
|
794
|
+
completeAt: null,
|
|
795
|
+
lastSentAt: at,
|
|
796
|
+
lastReceivedAt: at,
|
|
797
|
+
},
|
|
798
|
+
],
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
},
|
|
802
|
+
],
|
|
803
|
+
});
|
|
804
|
+
const statusOf = (state: SyncState | null) =>
|
|
805
|
+
syncStateToOwnerSyncStatus(state, testName, testAppOwner.id);
|
|
806
|
+
|
|
807
|
+
channel.postMessage(createState(true));
|
|
808
|
+
await testWaitForWorkerMessage();
|
|
809
|
+
const full = deps.syncState.get();
|
|
810
|
+
assertNotNull(full);
|
|
811
|
+
assertSame(statusOf(full).type, "Error");
|
|
812
|
+
|
|
813
|
+
channel.postMessage(createState(false));
|
|
814
|
+
await testWaitForWorkerMessage();
|
|
815
|
+
const kept = deps.syncState.get();
|
|
816
|
+
assertNotNull(kept);
|
|
817
|
+
assertEqual(kept, createState(false));
|
|
818
|
+
assertSame(kept.transports[0], full.transports[1]);
|
|
819
|
+
assertSame(statusOf(kept), statusOf(full));
|
|
820
|
+
});
|
|
821
|
+
|
|
619
822
|
it("listens on its worker's channel before asking for a snapshot", async () => {
|
|
620
823
|
const name = "evolu:sync-state:worker";
|
|
621
824
|
using worker = testCreateSharedWorker<
|
|
@@ -727,6 +930,29 @@ describe("Evolu", () => {
|
|
|
727
930
|
assertEqual(deps.evoluError.get(), error);
|
|
728
931
|
});
|
|
729
932
|
|
|
933
|
+
it("only logs a sync error that an older build broadcasts", async () => {
|
|
934
|
+
const testConsole = testCreateConsole();
|
|
935
|
+
using setup = await setupCreateEvoluDeps(testConsole);
|
|
936
|
+
const { deps, consoleEntryOrErrorBroadcastChannel } = setup;
|
|
937
|
+
const error = {
|
|
938
|
+
type: "ProtocolQuotaError",
|
|
939
|
+
ownerId: testAppOwner.id,
|
|
940
|
+
} as const;
|
|
941
|
+
|
|
942
|
+
consoleEntryOrErrorBroadcastChannel.postMessage({
|
|
943
|
+
type: "Error",
|
|
944
|
+
// @ts-expect-error A ConsoleEntryOrError Error carries only an UnknownError, but a still-running worker of an older build posts a ProtocolQuotaError.
|
|
945
|
+
error,
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
await testWaitForWorkerMessage();
|
|
949
|
+
|
|
950
|
+
assertSame(deps.evoluError.get(), null);
|
|
951
|
+
assertEqual(testConsole.getEntriesSnapshot(), [
|
|
952
|
+
{ method: "error", path: [], args: [error] },
|
|
953
|
+
]);
|
|
954
|
+
});
|
|
955
|
+
|
|
730
956
|
for (const source of ["SharedWorker", "Error", "ConsoleEntry"] as const) {
|
|
731
957
|
it(`preserves the first startup refusal while logging a later ${source} message`, async () => {
|
|
732
958
|
const testConsole = testCreateConsole();
|
|
@@ -1266,6 +1492,106 @@ describe("Evolu", () => {
|
|
|
1266
1492
|
});
|
|
1267
1493
|
});
|
|
1268
1494
|
|
|
1495
|
+
describe("device persistence", () => {
|
|
1496
|
+
const setupPersistenceRequests = async () => {
|
|
1497
|
+
let requestCount = 0;
|
|
1498
|
+
const setup = await setupRunWithEvoluDeps({
|
|
1499
|
+
requestPersistentStorage: () => {
|
|
1500
|
+
requestCount += 1;
|
|
1501
|
+
},
|
|
1502
|
+
});
|
|
1503
|
+
return Object.assign(setup, { getRequestCount: () => requestCount });
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
it("resolves devicePersistence to what its tenant sends", async () => {
|
|
1507
|
+
await using setup = await setupRunWithEvoluDeps();
|
|
1508
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1509
|
+
|
|
1510
|
+
setup.postEvoluOutput({
|
|
1511
|
+
type: "OnDevicePersistence",
|
|
1512
|
+
devicePersistence: "NotPersisted",
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
assertSame(await evolu.devicePersistence, "NotPersisted");
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it("resolves devicePersistence to Unknown when disposed before its tenant sends it", async () => {
|
|
1519
|
+
await using setup = await setupRunWithEvoluDeps();
|
|
1520
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1521
|
+
|
|
1522
|
+
await evolu[Symbol.asyncDispose]();
|
|
1523
|
+
|
|
1524
|
+
assertSame(await evolu.devicePersistence, "Unknown");
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
it("requests persistent storage once, after the first mutation, when devicePersistence is Unknown", async () => {
|
|
1528
|
+
await using setup = await setupPersistenceRequests();
|
|
1529
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1530
|
+
setup.postEvoluOutput({
|
|
1531
|
+
type: "OnDevicePersistence",
|
|
1532
|
+
devicePersistence: "Unknown",
|
|
1533
|
+
});
|
|
1534
|
+
await evolu.devicePersistence;
|
|
1535
|
+
await testWaitForWorkerMessage();
|
|
1536
|
+
assertSame(setup.getRequestCount(), 0);
|
|
1537
|
+
|
|
1538
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("A") });
|
|
1539
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("B") });
|
|
1540
|
+
await testWaitForWorkerMessage();
|
|
1541
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("C") });
|
|
1542
|
+
await testWaitForWorkerMessage();
|
|
1543
|
+
|
|
1544
|
+
assertSame(setup.getRequestCount(), 1);
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
it("requests persistent storage once devicePersistence resolves after the first mutation", async () => {
|
|
1548
|
+
await using setup = await setupPersistenceRequests();
|
|
1549
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1550
|
+
|
|
1551
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("A") });
|
|
1552
|
+
await testWaitForWorkerMessage();
|
|
1553
|
+
assertSame(setup.getRequestCount(), 0);
|
|
1554
|
+
|
|
1555
|
+
setup.postEvoluOutput({
|
|
1556
|
+
type: "OnDevicePersistence",
|
|
1557
|
+
devicePersistence: "Unknown",
|
|
1558
|
+
});
|
|
1559
|
+
await evolu.devicePersistence;
|
|
1560
|
+
await testWaitForWorkerMessage();
|
|
1561
|
+
|
|
1562
|
+
assertSame(setup.getRequestCount(), 1);
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
for (const devicePersistence of ["Persisted", "NotPersisted"] as const) {
|
|
1566
|
+
it(`does not request persistent storage when devicePersistence is ${devicePersistence}`, async () => {
|
|
1567
|
+
await using setup = await setupPersistenceRequests();
|
|
1568
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1569
|
+
setup.postEvoluOutput({
|
|
1570
|
+
type: "OnDevicePersistence",
|
|
1571
|
+
devicePersistence,
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("A") });
|
|
1575
|
+
await evolu.devicePersistence;
|
|
1576
|
+
await testWaitForWorkerMessage();
|
|
1577
|
+
|
|
1578
|
+
assertSame(setup.getRequestCount(), 0);
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
it("does not request persistent storage after disposal", async () => {
|
|
1583
|
+
await using setup = await setupPersistenceRequests();
|
|
1584
|
+
const evolu = await setup.run.ok(testCreateEvolu);
|
|
1585
|
+
|
|
1586
|
+
evolu.insert("todo", { title: NonEmptyTrimmedString100.orThrow("A") });
|
|
1587
|
+
await evolu[Symbol.asyncDispose]();
|
|
1588
|
+
await evolu.devicePersistence;
|
|
1589
|
+
await testWaitForWorkerMessage();
|
|
1590
|
+
|
|
1591
|
+
assertSame(setup.getRequestCount(), 0);
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1269
1595
|
describe("dispose evolu", () => {
|
|
1270
1596
|
it("logs disposeEvolu once", async () => {
|
|
1271
1597
|
const testConsole = testCreateConsole();
|
|
@@ -1634,6 +1960,50 @@ describe("Evolu", () => {
|
|
|
1634
1960
|
assertEqual(called, 0);
|
|
1635
1961
|
});
|
|
1636
1962
|
|
|
1963
|
+
it("releases the onComplete callbacks of a failed mutation without running them", async () => {
|
|
1964
|
+
await using setup = await setupRunWithEvoluDeps();
|
|
1965
|
+
const { run, evoluInputs, postEvoluOutput } = setup;
|
|
1966
|
+
const evolu = await run.ok(testCreateEvolu);
|
|
1967
|
+
|
|
1968
|
+
let called = 0;
|
|
1969
|
+
evolu.insert(
|
|
1970
|
+
"todo",
|
|
1971
|
+
{ title: NonEmptyTrimmedString100.orThrow("First failed") },
|
|
1972
|
+
{
|
|
1973
|
+
onComplete: () => {
|
|
1974
|
+
called += 1;
|
|
1975
|
+
},
|
|
1976
|
+
},
|
|
1977
|
+
);
|
|
1978
|
+
evolu.insert(
|
|
1979
|
+
"todo",
|
|
1980
|
+
{ title: NonEmptyTrimmedString100.orThrow("Second failed") },
|
|
1981
|
+
{
|
|
1982
|
+
onComplete: () => {
|
|
1983
|
+
called += 10;
|
|
1984
|
+
},
|
|
1985
|
+
},
|
|
1986
|
+
);
|
|
1987
|
+
|
|
1988
|
+
await testWaitForWorkerMessage();
|
|
1989
|
+
|
|
1990
|
+
const mutate = evoluInputs[0] as ExtractTyped<EvoluInput, "Mutate">;
|
|
1991
|
+
const { onCompleteIds } = mutate;
|
|
1992
|
+
assertLength(onCompleteIds, 2);
|
|
1993
|
+
|
|
1994
|
+
postEvoluOutput({ type: "OnMutateFailed", onCompleteIds });
|
|
1995
|
+
// Patches naming released callbacks find nothing to run.
|
|
1996
|
+
postEvoluOutput({
|
|
1997
|
+
type: "OnPatchesByQuery",
|
|
1998
|
+
patchesByQuery: new Map(),
|
|
1999
|
+
onCompleteIds,
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
await testWaitForWorkerMessage();
|
|
2003
|
+
|
|
2004
|
+
assertEqual(called, 0);
|
|
2005
|
+
});
|
|
2006
|
+
|
|
1637
2007
|
it("executes mutate onComplete callback when query patches are received", async () => {
|
|
1638
2008
|
await using setup = await setupRunWithEvoluDeps();
|
|
1639
2009
|
const { run, evoluInputs, postEvoluOutput } = setup;
|