@evolu/common 8.13.0 → 8.15.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/Crypto.d.ts +2 -1
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Redacted.d.ts +19 -39
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +18 -33
- package/dist/src/Type.d.ts +161 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +152 -2
- package/dist/src/WebSocket.d.ts +16 -1
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +14 -2
- package/dist/src/intl/_en.d.ts +5 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +4 -0
- package/dist/src/intl/ar.d.ts +5 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +4 -0
- package/dist/src/intl/bn.d.ts +5 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +4 -0
- package/dist/src/intl/ca.d.ts +5 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +4 -0
- package/dist/src/intl/cs.d.ts +5 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +4 -0
- package/dist/src/intl/da.d.ts +5 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +4 -0
- package/dist/src/intl/de.d.ts +5 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +4 -0
- package/dist/src/intl/el.d.ts +5 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +4 -0
- package/dist/src/intl/es.d.ts +5 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +4 -0
- package/dist/src/intl/fa.d.ts +5 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +4 -0
- package/dist/src/intl/fi.d.ts +5 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +4 -0
- package/dist/src/intl/fil.d.ts +5 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +4 -0
- package/dist/src/intl/fr.d.ts +5 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +4 -0
- package/dist/src/intl/he.d.ts +5 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +4 -0
- package/dist/src/intl/hi.d.ts +5 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +4 -0
- package/dist/src/intl/hr.d.ts +5 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +4 -0
- package/dist/src/intl/hu.d.ts +3 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +2 -0
- package/dist/src/intl/id.d.ts +5 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +4 -0
- package/dist/src/intl/it.d.ts +5 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +4 -0
- package/dist/src/intl/ja.d.ts +5 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +4 -0
- package/dist/src/intl/ko.d.ts +5 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +4 -0
- package/dist/src/intl/ml.d.ts +5 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +4 -0
- package/dist/src/intl/mr.d.ts +5 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +4 -0
- package/dist/src/intl/ms.d.ts +5 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +4 -0
- package/dist/src/intl/nb.d.ts +3 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +2 -0
- package/dist/src/intl/nl.d.ts +5 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +4 -0
- package/dist/src/intl/pa.d.ts +5 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +4 -0
- package/dist/src/intl/pl.d.ts +4 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +4 -0
- package/dist/src/intl/pt-BR.d.ts +5 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +4 -0
- package/dist/src/intl/pt.d.ts +5 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +4 -0
- package/dist/src/intl/ro.d.ts +5 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +4 -0
- package/dist/src/intl/sk.d.ts +5 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +4 -0
- package/dist/src/intl/sl.d.ts +5 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +4 -0
- package/dist/src/intl/sv.d.ts +5 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +4 -0
- package/dist/src/intl/sw.d.ts +2 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +2 -0
- package/dist/src/intl/ta.d.ts +5 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +4 -0
- package/dist/src/intl/te.d.ts +5 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +4 -0
- package/dist/src/intl/th.d.ts +5 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +4 -0
- package/dist/src/intl/tr.d.ts +5 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +4 -0
- package/dist/src/intl/uk.d.ts +5 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +4 -0
- package/dist/src/intl/ur.d.ts +5 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +4 -0
- package/dist/src/intl/vi.d.ts +5 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +4 -0
- package/dist/src/intl/zh-CN.d.ts +5 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +4 -0
- package/dist/src/intl/zh-TW.d.ts +5 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +4 -0
- package/dist/src/local-first/Evolu.d.ts +66 -2
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +28 -4
- package/dist/src/local-first/Shared.d.ts +46 -39
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +30 -19
- package/package.json +1 -1
- package/src/Crypto.ts +2 -1
- package/src/Redacted.test.ts +231 -0
- package/src/Redacted.ts +36 -47
- package/src/Type.test.ts +166 -0
- package/src/Type.ts +198 -2
- package/src/WebSocket.ts +33 -4
- package/src/intl/_en.ts +10 -0
- package/src/intl/ar.ts +8 -0
- package/src/intl/bn.ts +10 -0
- package/src/intl/ca.ts +10 -0
- package/src/intl/cs.ts +10 -0
- package/src/intl/da.ts +10 -0
- package/src/intl/de.ts +10 -0
- package/src/intl/el.ts +10 -0
- package/src/intl/es.ts +10 -0
- package/src/intl/fa.ts +10 -0
- package/src/intl/fi.ts +10 -0
- package/src/intl/fil.ts +10 -0
- package/src/intl/fr.ts +10 -0
- package/src/intl/he.ts +10 -0
- package/src/intl/hi.ts +10 -0
- package/src/intl/hr.ts +10 -0
- package/src/intl/hu.ts +6 -0
- package/src/intl/id.ts +10 -0
- package/src/intl/intl.test.ts +88 -0
- package/src/intl/it.ts +10 -0
- package/src/intl/ja.ts +10 -0
- package/src/intl/ko.ts +10 -0
- package/src/intl/ml.ts +10 -0
- package/src/intl/mr.ts +10 -0
- package/src/intl/ms.ts +8 -0
- package/src/intl/nb.ts +6 -0
- package/src/intl/nl.ts +10 -0
- package/src/intl/pa.ts +10 -0
- package/src/intl/pl.ts +12 -0
- package/src/intl/pt-BR.ts +10 -0
- package/src/intl/pt.ts +8 -0
- package/src/intl/ro.ts +10 -0
- package/src/intl/sk.ts +8 -0
- package/src/intl/sl.ts +10 -0
- package/src/intl/sv.ts +10 -0
- package/src/intl/sw.ts +4 -0
- package/src/intl/ta.ts +10 -0
- package/src/intl/te.ts +10 -0
- package/src/intl/th.ts +10 -0
- package/src/intl/tr.ts +10 -0
- package/src/intl/uk.ts +10 -0
- package/src/intl/ur.ts +8 -0
- package/src/intl/vi.ts +8 -0
- package/src/intl/zh-CN.ts +10 -0
- package/src/intl/zh-TW.ts +10 -0
- package/src/local-first/Evolu.test.ts +162 -18
- package/src/local-first/Evolu.ts +102 -6
- package/src/local-first/Shared.test.ts +113 -30
- package/src/local-first/Shared.ts +73 -57
package/src/local-first/Evolu.ts
CHANGED
|
@@ -385,6 +385,37 @@ export interface Evolu<
|
|
|
385
385
|
/** {@link AppOwner}. */
|
|
386
386
|
readonly appOwner: AppOwner;
|
|
387
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Whether this device keeps the database, resolved once the shared worker
|
|
390
|
+
* serves it, or `Unknown` if this instance is disposed first.
|
|
391
|
+
*
|
|
392
|
+
* Apps tell the user when it is `NotPersisted`, because then data that has
|
|
393
|
+
* not synced is lost when the database closes. Apps must not tell the user
|
|
394
|
+
* that data is saved on the device when it is `Unknown`.
|
|
395
|
+
*
|
|
396
|
+
* ### Example
|
|
397
|
+
*
|
|
398
|
+
* ```ts
|
|
399
|
+
* import { assertEqual } from "@evolu/common";
|
|
400
|
+
* import type { DevicePersistence } from "@evolu/common/local-first";
|
|
401
|
+
*
|
|
402
|
+
* // The notice for `await evolu.devicePersistence`, or null for none.
|
|
403
|
+
* const persistenceNotice = (
|
|
404
|
+
* devicePersistence: DevicePersistence,
|
|
405
|
+
* ): string | null =>
|
|
406
|
+
* devicePersistence === "NotPersisted"
|
|
407
|
+
* ? "Your data isn't kept on this device. Changes that haven't synced are lost when you close this tab."
|
|
408
|
+
* : null;
|
|
409
|
+
*
|
|
410
|
+
* assertEqual(persistenceNotice("Unknown"), null);
|
|
411
|
+
* assertEqual(
|
|
412
|
+
* persistenceNotice("NotPersisted"),
|
|
413
|
+
* "Your data isn't kept on this device. Changes that haven't synced are lost when you close this tab.",
|
|
414
|
+
* );
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
readonly devicePersistence: Promise<DevicePersistence>;
|
|
418
|
+
|
|
388
419
|
/**
|
|
389
420
|
* Inserts a row and returns the generated {@link Id}.
|
|
390
421
|
*
|
|
@@ -873,6 +904,28 @@ export interface Evolu<
|
|
|
873
904
|
*/
|
|
874
905
|
export type UnuseOwner = () => void;
|
|
875
906
|
|
|
907
|
+
/**
|
|
908
|
+
* Whether this device keeps an {@link Evolu} database, stating only what Evolu
|
|
909
|
+
* knows for sure.
|
|
910
|
+
*
|
|
911
|
+
* - `Persisted`: the platform stores it in the app's own file, as React Native
|
|
912
|
+
* does.
|
|
913
|
+
* - `NotPersisted`: it is kept in memory, because {@link EvoluConfig.memoryOnly}
|
|
914
|
+
* asks for that or the platform offers no persistent storage, as a browser
|
|
915
|
+
* does in Safari's Private Browsing or a Firefox private window. Data that
|
|
916
|
+
* has not synced is lost when the database closes.
|
|
917
|
+
* - `Unknown`: it is stored, but the platform may delete it. A browser can keep
|
|
918
|
+
* storage only for a private session, as Chrome's incognito does without
|
|
919
|
+
* telling the app, delete it after a while or when disk space runs low, and
|
|
920
|
+
* let the user clear it.
|
|
921
|
+
*
|
|
922
|
+
* See [Will my data stay on the
|
|
923
|
+
* device?](https://www.evolu.dev/docs/faq#will-my-data-stay-on-the-device).
|
|
924
|
+
*
|
|
925
|
+
* @group Core
|
|
926
|
+
*/
|
|
927
|
+
export type DevicePersistence = "Persisted" | "NotPersisted" | "Unknown";
|
|
928
|
+
|
|
876
929
|
/**
|
|
877
930
|
* Represents app-level errors that can occur in {@link Evolu}.
|
|
878
931
|
*
|
|
@@ -1125,6 +1178,20 @@ export interface SyncStateDep {
|
|
|
1125
1178
|
readonly syncState: ReadonlyStore<SyncState | null>;
|
|
1126
1179
|
}
|
|
1127
1180
|
|
|
1181
|
+
/**
|
|
1182
|
+
* Asks the platform to keep stored databases until the user deletes them.
|
|
1183
|
+
*
|
|
1184
|
+
* Only unsynced local changes exist nowhere else, so each {@link Evolu} instance
|
|
1185
|
+
* calls it after its first local mutation, when its
|
|
1186
|
+
* {@link Evolu.devicePersistence} is `Unknown`. A platform provides it when its
|
|
1187
|
+
* storage can be deleted without the user, as a browser's can.
|
|
1188
|
+
*
|
|
1189
|
+
* @group Construction
|
|
1190
|
+
*/
|
|
1191
|
+
export interface RequestPersistentStorageDep {
|
|
1192
|
+
readonly requestPersistentStorage: () => void;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1128
1195
|
/**
|
|
1129
1196
|
* Shared platform dependencies for creating {@link Evolu} instances.
|
|
1130
1197
|
*
|
|
@@ -1143,7 +1210,7 @@ export type EvoluDeps = EvoluPlatformDeps &
|
|
|
1143
1210
|
* Platform-specific dependencies required to create {@link EvoluDeps}.
|
|
1144
1211
|
*
|
|
1145
1212
|
* Provides worker and channel adapters plus optional platform integrations for
|
|
1146
|
-
* logging
|
|
1213
|
+
* logging, synchronous UI flush, and persistent storage requests.
|
|
1147
1214
|
*
|
|
1148
1215
|
* @group Construction
|
|
1149
1216
|
*/
|
|
@@ -1154,7 +1221,8 @@ export type EvoluPlatformDeps = CreateDbWorkerDep &
|
|
|
1154
1221
|
ReloadAppDep &
|
|
1155
1222
|
SharedWorkerDep &
|
|
1156
1223
|
Partial<ConsoleDep> &
|
|
1157
|
-
Partial<FlushSyncDep
|
|
1224
|
+
Partial<FlushSyncDep> &
|
|
1225
|
+
Partial<RequestPersistentStorageDep>;
|
|
1158
1226
|
|
|
1159
1227
|
/**
|
|
1160
1228
|
* Creates shared dependencies used by all {@link createEvolu} instances on a
|
|
@@ -1234,9 +1302,7 @@ export const createEvoluDeps = (deps: EvoluPlatformDeps): EvoluDeps => {
|
|
|
1234
1302
|
break;
|
|
1235
1303
|
|
|
1236
1304
|
case "Waiting":
|
|
1237
|
-
|
|
1238
|
-
// Platform adapters act on these; see Builds and Storage in the Shared
|
|
1239
|
-
// module.
|
|
1305
|
+
// Platform adapters act on it; see Builds in the Shared module.
|
|
1240
1306
|
break;
|
|
1241
1307
|
|
|
1242
1308
|
case "Connected": {
|
|
@@ -1423,6 +1489,13 @@ export const createEvolu =
|
|
|
1423
1489
|
exportDatabasePending = null;
|
|
1424
1490
|
});
|
|
1425
1491
|
|
|
1492
|
+
const devicePersistence = Promise.withResolvers<DevicePersistence>();
|
|
1493
|
+
// A disposed instance never hears from its tenant, so awaiters settle.
|
|
1494
|
+
disposer.defer(() => {
|
|
1495
|
+
devicePersistence.resolve("Unknown");
|
|
1496
|
+
});
|
|
1497
|
+
let isPersistentStorageRequested = false;
|
|
1498
|
+
|
|
1426
1499
|
let postMessage: (input: EvoluInput) => void;
|
|
1427
1500
|
|
|
1428
1501
|
// Scope worker/channel wiring and keep only postMessage outside.
|
|
@@ -1512,6 +1585,10 @@ export const createEvolu =
|
|
|
1512
1585
|
break;
|
|
1513
1586
|
}
|
|
1514
1587
|
|
|
1588
|
+
case "OnDevicePersistence":
|
|
1589
|
+
devicePersistence.resolve(message.devicePersistence);
|
|
1590
|
+
break;
|
|
1591
|
+
|
|
1515
1592
|
default:
|
|
1516
1593
|
exhaustiveCheck(message);
|
|
1517
1594
|
}
|
|
@@ -1639,6 +1716,18 @@ export const createEvolu =
|
|
|
1639
1716
|
);
|
|
1640
1717
|
}
|
|
1641
1718
|
|
|
1719
|
+
// Only unsynced local changes exist nowhere else, so the first one is
|
|
1720
|
+
// when asking the platform to keep them starts to matter. Asking on
|
|
1721
|
+
// load would show Firefox's permission prompt to every visitor.
|
|
1722
|
+
if (!isPersistentStorageRequested) {
|
|
1723
|
+
isPersistentStorageRequested = true;
|
|
1724
|
+
void devicePersistence.promise.then((persistence) => {
|
|
1725
|
+
if (persistence === "Unknown" && !disposed) {
|
|
1726
|
+
run.deps.requestPersistentStorage?.();
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1642
1731
|
mutateBatch.push({
|
|
1643
1732
|
change: { ...dbChange, ownerId: options?.ownerId ?? appOwner.id },
|
|
1644
1733
|
onComplete: options?.onComplete,
|
|
@@ -1683,8 +1772,14 @@ export const createEvolu =
|
|
|
1683
1772
|
"useOwner requires explicit non-empty transports when config.transports is empty.",
|
|
1684
1773
|
);
|
|
1685
1774
|
|
|
1775
|
+
// Post only what workers use. Forwarding the caller's object would copy
|
|
1776
|
+
// an AppOwner's mnemonic into every worker.
|
|
1777
|
+
const { id, encryptionKey } = owner;
|
|
1686
1778
|
const syncOwner: SyncOwner = {
|
|
1687
|
-
owner
|
|
1779
|
+
owner:
|
|
1780
|
+
"writeKey" in owner
|
|
1781
|
+
? { id, encryptionKey, writeKey: owner.writeKey }
|
|
1782
|
+
: { id, encryptionKey },
|
|
1688
1783
|
transports: effectiveTransports,
|
|
1689
1784
|
};
|
|
1690
1785
|
|
|
@@ -1722,6 +1817,7 @@ export const createEvolu =
|
|
|
1722
1817
|
{
|
|
1723
1818
|
name,
|
|
1724
1819
|
appOwner,
|
|
1820
|
+
devicePersistence: devicePersistence.promise,
|
|
1725
1821
|
|
|
1726
1822
|
insert: createMutation("insert"),
|
|
1727
1823
|
update: createMutation("update"),
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type ProtocolTimestampMismatchError,
|
|
43
43
|
SubscriptionFlags,
|
|
44
44
|
} from "./Protocol.ts";
|
|
45
|
+
import type { DevicePersistence } from "./Evolu.ts";
|
|
45
46
|
import {
|
|
46
47
|
createQueryBuilder,
|
|
47
48
|
type EvoluSchema,
|
|
@@ -170,11 +171,11 @@ const setupSharedWorker = async ({
|
|
|
170
171
|
createWebSocket = testCreateWebSocket({ throwOnCreate: true }),
|
|
171
172
|
time,
|
|
172
173
|
seed,
|
|
173
|
-
|
|
174
|
+
getDevicePersistence = () => Promise.resolve("Unknown"),
|
|
174
175
|
}: {
|
|
175
176
|
createWebSocket?: CreateWebSocket;
|
|
176
177
|
time?: TestTime;
|
|
177
|
-
|
|
178
|
+
getDevicePersistence?: () => Promise<DevicePersistence>;
|
|
178
179
|
/**
|
|
179
180
|
* Seeds the worker's random bytes, which are otherwise the same for every
|
|
180
181
|
* worker.
|
|
@@ -214,7 +215,7 @@ const setupSharedWorker = async ({
|
|
|
214
215
|
createWebSocket,
|
|
215
216
|
...(time && { time }),
|
|
216
217
|
...(seed && { randomBytes: testCreateDeps({ seed }).randomBytes }),
|
|
217
|
-
|
|
218
|
+
getDevicePersistence,
|
|
218
219
|
}),
|
|
219
220
|
);
|
|
220
221
|
|
|
@@ -239,8 +240,10 @@ const setupSharedWorker = async ({
|
|
|
239
240
|
releaseDbWorkerLeaderOnDispose = true,
|
|
240
241
|
autoDispose = true,
|
|
241
242
|
initialClock = createTimestamp(),
|
|
243
|
+
memoryOnly = false,
|
|
242
244
|
}: {
|
|
243
245
|
tenantName?: Name;
|
|
246
|
+
memoryOnly?: boolean;
|
|
244
247
|
evoluChannel?: TestMessageChannel<EvoluOutput, EvoluInput>;
|
|
245
248
|
releaseDbWorkerLeaderOnDispose?: boolean;
|
|
246
249
|
autoDispose?: boolean;
|
|
@@ -289,7 +292,7 @@ const setupSharedWorker = async ({
|
|
|
289
292
|
consoleLevel: "debug",
|
|
290
293
|
sqliteSchema: testSqliteSchema,
|
|
291
294
|
encryptionKey: testAppOwner.encryptionKey,
|
|
292
|
-
memoryOnly
|
|
295
|
+
memoryOnly,
|
|
293
296
|
evoluPort: evoluChannel.port1.native,
|
|
294
297
|
};
|
|
295
298
|
|
|
@@ -339,7 +342,25 @@ const setupSharedWorker = async ({
|
|
|
339
342
|
) => {
|
|
340
343
|
const instance = await createEvoluBeforeDbWorkerLeader(options);
|
|
341
344
|
await instance.acquireDbWorkerLeader();
|
|
342
|
-
|
|
345
|
+
|
|
346
|
+
// Once the DbWorker starts, the tenant adds the instance and tells it its
|
|
347
|
+
// device persistence first, so tests see only the outputs that follow.
|
|
348
|
+
const firstOutputs: Array<EvoluOutput> = [];
|
|
349
|
+
instance.evoluChannel.port2.onMessage = (output) => {
|
|
350
|
+
firstOutputs.push(output);
|
|
351
|
+
};
|
|
352
|
+
await testWaitForWorkerMessage();
|
|
353
|
+
instance.evoluChannel.port2.onMessage = null;
|
|
354
|
+
assertLength(firstOutputs, 1);
|
|
355
|
+
const [devicePersistenceOutput] = firstOutputs;
|
|
356
|
+
assert(
|
|
357
|
+
devicePersistenceOutput.type === "OnDevicePersistence",
|
|
358
|
+
"Expected the device persistence first.",
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
return Object.assign(instance, {
|
|
362
|
+
devicePersistence: devicePersistenceOutput.devicePersistence,
|
|
363
|
+
});
|
|
343
364
|
};
|
|
344
365
|
|
|
345
366
|
return {
|
|
@@ -607,17 +628,30 @@ describe("storage", () => {
|
|
|
607
628
|
const isMemoryOnly = (output: SharedWorkerOutput | undefined): boolean =>
|
|
608
629
|
getDbWorkerInit(output).memoryOnly;
|
|
609
630
|
|
|
631
|
+
/** Every device persistence the tenant sent to the instance so far. */
|
|
632
|
+
const receiveDevicePersistences = async (
|
|
633
|
+
evoluChannel: TestMessageChannel<EvoluOutput, EvoluInput>,
|
|
634
|
+
): Promise<ReadonlyArray<DevicePersistence>> => {
|
|
635
|
+
const devicePersistences: Array<DevicePersistence> = [];
|
|
636
|
+
evoluChannel.port2.onMessage = (output) => {
|
|
637
|
+
if (output.type === "OnDevicePersistence") {
|
|
638
|
+
devicePersistences.push(output.devicePersistence);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
await testWaitForWorkerMessage();
|
|
642
|
+
return devicePersistences;
|
|
643
|
+
};
|
|
644
|
+
|
|
610
645
|
it("keeps every database in memory, replacements included, without persistent storage", async () => {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
});
|
|
646
|
+
const getDevicePersistence = mock.fn(() =>
|
|
647
|
+
Promise.resolve<DevicePersistence>("NotPersisted"),
|
|
648
|
+
);
|
|
649
|
+
await using setup = await setupSharedWorker({ getDevicePersistence });
|
|
615
650
|
using disposer = new DisposableStack();
|
|
616
651
|
|
|
617
|
-
// The first tab heard it right after Connected.
|
|
618
|
-
assertEqual(setup.sharedWorkerOutputs, [{ type: "StorageUnavailable" }]);
|
|
619
652
|
const first = await setup.createEvolu();
|
|
620
|
-
assertTrue(isMemoryOnly(setup.sharedWorkerOutputs.at(
|
|
653
|
+
assertTrue(isMemoryOnly(setup.sharedWorkerOutputs.at(0)));
|
|
654
|
+
assertSame(first.devicePersistence, "NotPersisted");
|
|
621
655
|
|
|
622
656
|
// A replacement DbWorker, as after its tab closed, keeps it in memory too.
|
|
623
657
|
await first.releaseDbWorkerLeader();
|
|
@@ -625,8 +659,7 @@ describe("storage", () => {
|
|
|
625
659
|
tab.port.postMessage({ type: "AnnounceTabLeader", consoleLevel: "debug" });
|
|
626
660
|
await testWaitForWorkerMessage();
|
|
627
661
|
await testWaitForWorkerMessage();
|
|
628
|
-
|
|
629
|
-
const replacement = getDbWorkerInit(tab.outputs.at(1));
|
|
662
|
+
const replacement = getDbWorkerInit(tab.outputs.at(0));
|
|
630
663
|
assertTrue(replacement.memoryOnly);
|
|
631
664
|
using leaderPort = testCreateMessagePort<DbWorkerOutput, DbWorkerInput>(
|
|
632
665
|
replacement.port,
|
|
@@ -638,29 +671,62 @@ describe("storage", () => {
|
|
|
638
671
|
});
|
|
639
672
|
await testWaitForWorkerMessage();
|
|
640
673
|
|
|
641
|
-
assertSame(
|
|
674
|
+
assertSame(getDevicePersistence.mock.callCount(), 1);
|
|
642
675
|
});
|
|
643
676
|
|
|
644
|
-
|
|
677
|
+
for (const devicePersistence of ["Persisted", "Unknown"] as const) {
|
|
678
|
+
it(`stores databases and tells instances ${devicePersistence} where the platform promises it`, async () => {
|
|
679
|
+
await using setup = await setupSharedWorker({
|
|
680
|
+
getDevicePersistence: () => Promise.resolve(devicePersistence),
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const instance = await setup.createEvolu();
|
|
684
|
+
|
|
685
|
+
assertEqual(
|
|
686
|
+
setup.sharedWorkerOutputs.map(({ type }) => type),
|
|
687
|
+
["DbWorkerInit"],
|
|
688
|
+
);
|
|
689
|
+
assertFalse(isMemoryOnly(setup.sharedWorkerOutputs.at(0)));
|
|
690
|
+
assertSame(instance.devicePersistence, devicePersistence);
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
it("tells an instance configured memoryOnly that its database is not persisted", async () => {
|
|
645
695
|
await using setup = await setupSharedWorker({
|
|
646
|
-
|
|
696
|
+
getDevicePersistence: () => Promise.resolve("Persisted"),
|
|
647
697
|
});
|
|
648
698
|
|
|
649
|
-
await setup.createEvolu();
|
|
699
|
+
const instance = await setup.createEvolu({ memoryOnly: true });
|
|
650
700
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
["DbWorkerInit"],
|
|
654
|
-
);
|
|
655
|
-
assertFalse(isMemoryOnly(setup.sharedWorkerOutputs.at(0)));
|
|
701
|
+
assertTrue(isMemoryOnly(setup.sharedWorkerOutputs.at(0)));
|
|
702
|
+
assertSame(instance.devicePersistence, "NotPersisted");
|
|
656
703
|
});
|
|
657
704
|
|
|
658
|
-
it("
|
|
659
|
-
await using setup = await setupSharedWorker(
|
|
705
|
+
it("tells a later instance of the same name the mode the first one chose", async () => {
|
|
706
|
+
await using setup = await setupSharedWorker({
|
|
707
|
+
getDevicePersistence: () => Promise.resolve("Persisted"),
|
|
708
|
+
});
|
|
709
|
+
const { run, worker } = setup;
|
|
710
|
+
await setup.createEvolu({ memoryOnly: true });
|
|
660
711
|
|
|
661
|
-
|
|
712
|
+
const secondId: EvoluInstanceId = createId(run.deps);
|
|
713
|
+
await using _secondInstanceLock = await run.ok(acquireLeaderLock(secondId));
|
|
714
|
+
using secondChannel = testCreateMessageChannel<EvoluOutput, EvoluInput>();
|
|
715
|
+
worker.port.postMessage({
|
|
716
|
+
type: "CreateEvolu",
|
|
717
|
+
name: testName,
|
|
718
|
+
id: secondId,
|
|
719
|
+
consoleLevel: "debug",
|
|
720
|
+
sqliteSchema: testSqliteSchema,
|
|
721
|
+
encryptionKey: testAppOwner.encryptionKey,
|
|
722
|
+
memoryOnly: false,
|
|
723
|
+
evoluPort: secondChannel.port1.native,
|
|
724
|
+
});
|
|
725
|
+
await testWaitForWorkerMessage();
|
|
662
726
|
|
|
663
|
-
|
|
727
|
+
assertEqual(await receiveDevicePersistences(secondChannel), [
|
|
728
|
+
"NotPersisted",
|
|
729
|
+
]);
|
|
664
730
|
});
|
|
665
731
|
});
|
|
666
732
|
|
|
@@ -681,6 +747,7 @@ describe("builds", () => {
|
|
|
681
747
|
lockManager,
|
|
682
748
|
createMessagePort: testCreateMessagePort,
|
|
683
749
|
createWebSocket: testCreateWebSocket({ throwOnCreate: true }),
|
|
750
|
+
getDevicePersistence: () => Promise.resolve("Unknown" as const),
|
|
684
751
|
}),
|
|
685
752
|
);
|
|
686
753
|
const outputs: Array<SharedWorkerOutput> = [];
|
|
@@ -7265,7 +7332,14 @@ describe("with one evolu instance", () => {
|
|
|
7265
7332
|
memoryOnly: false,
|
|
7266
7333
|
evoluPort: siblingChannel.port1.native,
|
|
7267
7334
|
});
|
|
7335
|
+
const siblingOutputs: Array<EvoluOutput> = [];
|
|
7336
|
+
siblingChannel.port2.onMessage = (output) => {
|
|
7337
|
+
siblingOutputs.push(output);
|
|
7338
|
+
};
|
|
7268
7339
|
await testWaitForWorkerMessage();
|
|
7340
|
+
assertEqual(siblingOutputs, [
|
|
7341
|
+
{ type: "OnDevicePersistence", devicePersistence: "Unknown" },
|
|
7342
|
+
]);
|
|
7269
7343
|
siblingChannel.port2.postMessage({
|
|
7270
7344
|
type: "Mutate",
|
|
7271
7345
|
changes: [
|
|
@@ -9644,6 +9718,10 @@ describe("with one evolu instance", () => {
|
|
|
9644
9718
|
[siblingChannel.port1.native],
|
|
9645
9719
|
);
|
|
9646
9720
|
await testWaitForWorkerMessage();
|
|
9721
|
+
// The tenant keeps the first instance's persistent database.
|
|
9722
|
+
assertEqual(siblingOutputs.splice(0), [
|
|
9723
|
+
{ type: "OnDevicePersistence", devicePersistence: "Unknown" },
|
|
9724
|
+
]);
|
|
9647
9725
|
|
|
9648
9726
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
9649
9727
|
const mutation: ExtractTyped<EvoluInput, "Mutate"> = {
|
|
@@ -10811,10 +10889,13 @@ describe("startup refusal", () => {
|
|
|
10811
10889
|
});
|
|
10812
10890
|
await testWaitForWorkerMessage();
|
|
10813
10891
|
// The tab is told through its own connection, not the broadcast channel,
|
|
10814
|
-
// and its instance
|
|
10892
|
+
// and its instance learns only its device persistence: pending work stays
|
|
10893
|
+
// pending.
|
|
10815
10894
|
assertEqual(firstTabOutputs(), [{ type: "Error", error: refusal }]);
|
|
10816
10895
|
assertEqual(broadcasts, []);
|
|
10817
|
-
assertEqual(evoluOutputs, [
|
|
10896
|
+
assertEqual(evoluOutputs.splice(0), [
|
|
10897
|
+
{ type: "OnDevicePersistence", devicePersistence: "Unknown" },
|
|
10898
|
+
]);
|
|
10818
10899
|
|
|
10819
10900
|
// Later requests are dropped without a response.
|
|
10820
10901
|
instance.evoluChannel.port2.postMessage({
|
|
@@ -10852,7 +10933,9 @@ describe("startup refusal", () => {
|
|
|
10852
10933
|
);
|
|
10853
10934
|
await testWaitForWorkerMessage();
|
|
10854
10935
|
assertEqual(firstTabOutputs(), [{ type: "Error", error: refusal }]);
|
|
10855
|
-
assertEqual(secondOutputs, [
|
|
10936
|
+
assertEqual(secondOutputs, [
|
|
10937
|
+
{ type: "OnDevicePersistence", devicePersistence: "Unknown" },
|
|
10938
|
+
]);
|
|
10856
10939
|
|
|
10857
10940
|
// A tab that connects later is told once, through its own connection.
|
|
10858
10941
|
const laterTab = setupTab(setup, disposer);
|