@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.
Files changed (205) hide show
  1. package/dist/src/Crypto.d.ts +2 -1
  2. package/dist/src/Crypto.d.ts.map +1 -1
  3. package/dist/src/Redacted.d.ts +19 -39
  4. package/dist/src/Redacted.d.ts.map +1 -1
  5. package/dist/src/Redacted.js +18 -33
  6. package/dist/src/Type.d.ts +161 -0
  7. package/dist/src/Type.d.ts.map +1 -1
  8. package/dist/src/Type.js +152 -2
  9. package/dist/src/WebSocket.d.ts +16 -1
  10. package/dist/src/WebSocket.d.ts.map +1 -1
  11. package/dist/src/WebSocket.js +14 -2
  12. package/dist/src/intl/_en.d.ts +5 -1
  13. package/dist/src/intl/_en.d.ts.map +1 -1
  14. package/dist/src/intl/_en.js +4 -0
  15. package/dist/src/intl/ar.d.ts +5 -1
  16. package/dist/src/intl/ar.d.ts.map +1 -1
  17. package/dist/src/intl/ar.js +4 -0
  18. package/dist/src/intl/bn.d.ts +5 -1
  19. package/dist/src/intl/bn.d.ts.map +1 -1
  20. package/dist/src/intl/bn.js +4 -0
  21. package/dist/src/intl/ca.d.ts +5 -1
  22. package/dist/src/intl/ca.d.ts.map +1 -1
  23. package/dist/src/intl/ca.js +4 -0
  24. package/dist/src/intl/cs.d.ts +5 -1
  25. package/dist/src/intl/cs.d.ts.map +1 -1
  26. package/dist/src/intl/cs.js +4 -0
  27. package/dist/src/intl/da.d.ts +5 -1
  28. package/dist/src/intl/da.d.ts.map +1 -1
  29. package/dist/src/intl/da.js +4 -0
  30. package/dist/src/intl/de.d.ts +5 -1
  31. package/dist/src/intl/de.d.ts.map +1 -1
  32. package/dist/src/intl/de.js +4 -0
  33. package/dist/src/intl/el.d.ts +5 -1
  34. package/dist/src/intl/el.d.ts.map +1 -1
  35. package/dist/src/intl/el.js +4 -0
  36. package/dist/src/intl/es.d.ts +5 -1
  37. package/dist/src/intl/es.d.ts.map +1 -1
  38. package/dist/src/intl/es.js +4 -0
  39. package/dist/src/intl/fa.d.ts +5 -1
  40. package/dist/src/intl/fa.d.ts.map +1 -1
  41. package/dist/src/intl/fa.js +4 -0
  42. package/dist/src/intl/fi.d.ts +5 -1
  43. package/dist/src/intl/fi.d.ts.map +1 -1
  44. package/dist/src/intl/fi.js +4 -0
  45. package/dist/src/intl/fil.d.ts +5 -1
  46. package/dist/src/intl/fil.d.ts.map +1 -1
  47. package/dist/src/intl/fil.js +4 -0
  48. package/dist/src/intl/fr.d.ts +5 -1
  49. package/dist/src/intl/fr.d.ts.map +1 -1
  50. package/dist/src/intl/fr.js +4 -0
  51. package/dist/src/intl/he.d.ts +5 -1
  52. package/dist/src/intl/he.d.ts.map +1 -1
  53. package/dist/src/intl/he.js +4 -0
  54. package/dist/src/intl/hi.d.ts +5 -1
  55. package/dist/src/intl/hi.d.ts.map +1 -1
  56. package/dist/src/intl/hi.js +4 -0
  57. package/dist/src/intl/hr.d.ts +5 -1
  58. package/dist/src/intl/hr.d.ts.map +1 -1
  59. package/dist/src/intl/hr.js +4 -0
  60. package/dist/src/intl/hu.d.ts +3 -1
  61. package/dist/src/intl/hu.d.ts.map +1 -1
  62. package/dist/src/intl/hu.js +2 -0
  63. package/dist/src/intl/id.d.ts +5 -1
  64. package/dist/src/intl/id.d.ts.map +1 -1
  65. package/dist/src/intl/id.js +4 -0
  66. package/dist/src/intl/it.d.ts +5 -1
  67. package/dist/src/intl/it.d.ts.map +1 -1
  68. package/dist/src/intl/it.js +4 -0
  69. package/dist/src/intl/ja.d.ts +5 -1
  70. package/dist/src/intl/ja.d.ts.map +1 -1
  71. package/dist/src/intl/ja.js +4 -0
  72. package/dist/src/intl/ko.d.ts +5 -1
  73. package/dist/src/intl/ko.d.ts.map +1 -1
  74. package/dist/src/intl/ko.js +4 -0
  75. package/dist/src/intl/ml.d.ts +5 -1
  76. package/dist/src/intl/ml.d.ts.map +1 -1
  77. package/dist/src/intl/ml.js +4 -0
  78. package/dist/src/intl/mr.d.ts +5 -1
  79. package/dist/src/intl/mr.d.ts.map +1 -1
  80. package/dist/src/intl/mr.js +4 -0
  81. package/dist/src/intl/ms.d.ts +5 -1
  82. package/dist/src/intl/ms.d.ts.map +1 -1
  83. package/dist/src/intl/ms.js +4 -0
  84. package/dist/src/intl/nb.d.ts +3 -1
  85. package/dist/src/intl/nb.d.ts.map +1 -1
  86. package/dist/src/intl/nb.js +2 -0
  87. package/dist/src/intl/nl.d.ts +5 -1
  88. package/dist/src/intl/nl.d.ts.map +1 -1
  89. package/dist/src/intl/nl.js +4 -0
  90. package/dist/src/intl/pa.d.ts +5 -1
  91. package/dist/src/intl/pa.d.ts.map +1 -1
  92. package/dist/src/intl/pa.js +4 -0
  93. package/dist/src/intl/pl.d.ts +4 -0
  94. package/dist/src/intl/pl.d.ts.map +1 -1
  95. package/dist/src/intl/pl.js +4 -0
  96. package/dist/src/intl/pt-BR.d.ts +5 -1
  97. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  98. package/dist/src/intl/pt-BR.js +4 -0
  99. package/dist/src/intl/pt.d.ts +5 -1
  100. package/dist/src/intl/pt.d.ts.map +1 -1
  101. package/dist/src/intl/pt.js +4 -0
  102. package/dist/src/intl/ro.d.ts +5 -1
  103. package/dist/src/intl/ro.d.ts.map +1 -1
  104. package/dist/src/intl/ro.js +4 -0
  105. package/dist/src/intl/sk.d.ts +5 -1
  106. package/dist/src/intl/sk.d.ts.map +1 -1
  107. package/dist/src/intl/sk.js +4 -0
  108. package/dist/src/intl/sl.d.ts +5 -1
  109. package/dist/src/intl/sl.d.ts.map +1 -1
  110. package/dist/src/intl/sl.js +4 -0
  111. package/dist/src/intl/sv.d.ts +5 -1
  112. package/dist/src/intl/sv.d.ts.map +1 -1
  113. package/dist/src/intl/sv.js +4 -0
  114. package/dist/src/intl/sw.d.ts +2 -0
  115. package/dist/src/intl/sw.d.ts.map +1 -1
  116. package/dist/src/intl/sw.js +2 -0
  117. package/dist/src/intl/ta.d.ts +5 -1
  118. package/dist/src/intl/ta.d.ts.map +1 -1
  119. package/dist/src/intl/ta.js +4 -0
  120. package/dist/src/intl/te.d.ts +5 -1
  121. package/dist/src/intl/te.d.ts.map +1 -1
  122. package/dist/src/intl/te.js +4 -0
  123. package/dist/src/intl/th.d.ts +5 -1
  124. package/dist/src/intl/th.d.ts.map +1 -1
  125. package/dist/src/intl/th.js +4 -0
  126. package/dist/src/intl/tr.d.ts +5 -1
  127. package/dist/src/intl/tr.d.ts.map +1 -1
  128. package/dist/src/intl/tr.js +4 -0
  129. package/dist/src/intl/uk.d.ts +5 -1
  130. package/dist/src/intl/uk.d.ts.map +1 -1
  131. package/dist/src/intl/uk.js +4 -0
  132. package/dist/src/intl/ur.d.ts +5 -1
  133. package/dist/src/intl/ur.d.ts.map +1 -1
  134. package/dist/src/intl/ur.js +4 -0
  135. package/dist/src/intl/vi.d.ts +5 -1
  136. package/dist/src/intl/vi.d.ts.map +1 -1
  137. package/dist/src/intl/vi.js +4 -0
  138. package/dist/src/intl/zh-CN.d.ts +5 -1
  139. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  140. package/dist/src/intl/zh-CN.js +4 -0
  141. package/dist/src/intl/zh-TW.d.ts +5 -1
  142. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  143. package/dist/src/intl/zh-TW.js +4 -0
  144. package/dist/src/local-first/Evolu.d.ts +66 -2
  145. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  146. package/dist/src/local-first/Evolu.js +28 -4
  147. package/dist/src/local-first/Shared.d.ts +46 -39
  148. package/dist/src/local-first/Shared.d.ts.map +1 -1
  149. package/dist/src/local-first/Shared.js +30 -19
  150. package/package.json +1 -1
  151. package/src/Crypto.ts +2 -1
  152. package/src/Redacted.test.ts +231 -0
  153. package/src/Redacted.ts +36 -47
  154. package/src/Type.test.ts +166 -0
  155. package/src/Type.ts +198 -2
  156. package/src/WebSocket.ts +33 -4
  157. package/src/intl/_en.ts +10 -0
  158. package/src/intl/ar.ts +8 -0
  159. package/src/intl/bn.ts +10 -0
  160. package/src/intl/ca.ts +10 -0
  161. package/src/intl/cs.ts +10 -0
  162. package/src/intl/da.ts +10 -0
  163. package/src/intl/de.ts +10 -0
  164. package/src/intl/el.ts +10 -0
  165. package/src/intl/es.ts +10 -0
  166. package/src/intl/fa.ts +10 -0
  167. package/src/intl/fi.ts +10 -0
  168. package/src/intl/fil.ts +10 -0
  169. package/src/intl/fr.ts +10 -0
  170. package/src/intl/he.ts +10 -0
  171. package/src/intl/hi.ts +10 -0
  172. package/src/intl/hr.ts +10 -0
  173. package/src/intl/hu.ts +6 -0
  174. package/src/intl/id.ts +10 -0
  175. package/src/intl/intl.test.ts +88 -0
  176. package/src/intl/it.ts +10 -0
  177. package/src/intl/ja.ts +10 -0
  178. package/src/intl/ko.ts +10 -0
  179. package/src/intl/ml.ts +10 -0
  180. package/src/intl/mr.ts +10 -0
  181. package/src/intl/ms.ts +8 -0
  182. package/src/intl/nb.ts +6 -0
  183. package/src/intl/nl.ts +10 -0
  184. package/src/intl/pa.ts +10 -0
  185. package/src/intl/pl.ts +12 -0
  186. package/src/intl/pt-BR.ts +10 -0
  187. package/src/intl/pt.ts +8 -0
  188. package/src/intl/ro.ts +10 -0
  189. package/src/intl/sk.ts +8 -0
  190. package/src/intl/sl.ts +10 -0
  191. package/src/intl/sv.ts +10 -0
  192. package/src/intl/sw.ts +4 -0
  193. package/src/intl/ta.ts +10 -0
  194. package/src/intl/te.ts +10 -0
  195. package/src/intl/th.ts +10 -0
  196. package/src/intl/tr.ts +10 -0
  197. package/src/intl/uk.ts +10 -0
  198. package/src/intl/ur.ts +8 -0
  199. package/src/intl/vi.ts +8 -0
  200. package/src/intl/zh-CN.ts +10 -0
  201. package/src/intl/zh-TW.ts +10 -0
  202. package/src/local-first/Evolu.test.ts +162 -18
  203. package/src/local-first/Evolu.ts +102 -6
  204. package/src/local-first/Shared.test.ts +113 -30
  205. package/src/local-first/Shared.ts +73 -57
@@ -69,13 +69,20 @@
69
69
  *
70
70
  * ## Storage
71
71
  *
72
- * A platform that can lack persistent storage, as a browser does in Safari's
73
- * Private Browsing, provides {@link PersistentStorageDep}. The worker checks it
74
- * once, after it takes the build lock and before any DbWorker starts. Without
75
- * persistent storage, every DbWorker it starts keeps its database in memory,
76
- * replacements included, and each tab that connects is told with
77
- * `StorageUnavailable`. The decision holds for the worker's lifetime, so all
78
- * its tabs see one mode, and the next worker checks again.
72
+ * The platform tells the worker with {@link DevicePersistenceDep} what it can
73
+ * promise about stored databases. The worker asks once, after it takes the
74
+ * build lock and before any DbWorker starts. A browser checks whether it can
75
+ * open the origin private file system, which Safari's Private Browsing and
76
+ * Firefox's private windows refuse. Without persistent storage, every DbWorker
77
+ * the worker starts keeps its database in memory, replacements included. The
78
+ * decision holds for the worker's lifetime, so all its tabs see one mode, and
79
+ * the next worker checks again.
80
+ *
81
+ * A tenant tells each instance it adds its {@link Evolu.devicePersistence}:
82
+ * `NotPersisted` when its database is kept in memory, because of the platform
83
+ * or {@link EvoluConfig.memoryOnly}, otherwise what the platform promised.
84
+ * Instances of one name share the tenant, so a later instance gets the mode the
85
+ * first one chose.
79
86
  *
80
87
  * The check cannot tell a private session from a storage failure, but memory
81
88
  * loses nothing that refusing to start would have kept, and the persistent
@@ -333,7 +340,12 @@ import type {
333
340
  WorkerDeps,
334
341
  } from "../Worker.ts";
335
342
  import type { DbWorkerInit, UnsupportedDbVersionError } from "./Db.ts";
336
- import type { Evolu, SyncStateDep } from "./Evolu.ts";
343
+ import type {
344
+ DevicePersistence,
345
+ Evolu,
346
+ EvoluConfig,
347
+ SyncStateDep,
348
+ } from "./Evolu.ts";
337
349
  import type { Owner, OwnerId, OwnerTransport, SyncOwner } from "./Owner.ts";
338
350
  import {
339
351
  createProtocolBroadcastMessagesFromCrdtMessages,
@@ -420,14 +432,6 @@ export type SharedWorkerOutput =
420
432
  readonly type: "Connected";
421
433
  readonly workerId: SharedWorkerId;
422
434
  readonly syncStateChannelName: string;
423
- }
424
- | {
425
- /**
426
- * Sent to a connecting tab after `Connected` when the platform offers no
427
- * persistent storage, so the worker keeps every database in memory; see
428
- * Storage in this module's documentation.
429
- */
430
- readonly type: "StorageUnavailable";
431
435
  };
432
436
 
433
437
  export type ConsoleEntryOrError =
@@ -741,22 +745,23 @@ export interface RelaySyncState {
741
745
  * stops syncing through its relay, while a skipped change leaves out only
742
746
  * that change.
743
747
  *
744
- * Evolu saves changes on the device before they sync, so sync needs no UI while
745
- * it works: show nothing for `NoRelays`, `Syncing`, and `Synced`. An indicator
746
- * that changes with every edit distracts, and screen readers announce each
747
- * change.
748
+ * Evolu stores changes in the local database before they sync, so sync needs no
749
+ * UI while it works: show nothing for `NoRelays`, `Syncing`, and `Synced`. An
750
+ * indicator that changes with every edit distracts, and screen readers announce
751
+ * each change.
748
752
  *
749
753
  * For `Offline` and `Error`, show one quiet line that lasts as long as the
750
754
  * status, not a dialog, which interrupts, or a toast, which disappears while
751
- * the problem lasts. Say that changes are saved on this device. Evolu reports
752
- * `Offline` at once; an app may wait a few seconds before showing it, because
753
- * brief disconnections, such as waking from sleep, reconnect quickly. For
754
- * `Error`, write actionable text for the error types the app can act on, such
755
- * as a {@link ProtocolQuotaError}: the relay stores no more data for the owner,
756
- * so offer more quota, such as a plan upgrade, then call
757
- * {@link Evolu.requestSync} with the owner's ID. For any other error, show
758
- * generic text that names the error type, which helps when the user reports
759
- * it.
755
+ * the problem lasts. Do not say that changes are saved on this device unless
756
+ * {@link Evolu.devicePersistence} is `Persisted`: a browser may keep them only
757
+ * for a private session or delete them later. Evolu reports `Offline` at once;
758
+ * an app may wait a few seconds before showing it, because brief
759
+ * disconnections, such as waking from sleep, reconnect quickly. For `Error`,
760
+ * write actionable text for the error types the app can act on, such as a
761
+ * {@link ProtocolQuotaError}: the relay stores no more data for the owner, so
762
+ * offer more quota, such as a plan upgrade, then call {@link Evolu.requestSync}
763
+ * with the owner's ID. For any other error, show generic text that names the
764
+ * error type, which helps when the user reports it.
760
765
  *
761
766
  * Render the line inside one element with `role="status"` that stays mounted:
762
767
  * screen readers announce changes only in a live region that already exists,
@@ -785,18 +790,18 @@ export interface RelaySyncState {
785
790
  * case "Synced":
786
791
  * return null;
787
792
  * case "Offline":
788
- * return "Offline. Your changes are saved on this device.";
793
+ * return "Offline. Changes will sync when you're back online.";
789
794
  * case "Error":
790
795
  * return status.error.type === "ProtocolQuotaError"
791
- * ? "Sync is paused because the sync server is full. Your changes are saved on this device."
792
- * : `Sync error: ${status.error.type}. Your changes are saved on this device.`;
796
+ * ? "Sync is paused because the sync server is full."
797
+ * : `Sync error: ${status.error.type}.`;
793
798
  * }
794
799
  * };
795
800
  *
796
801
  * assertEqual(syncStatusToMessage({ type: "Synced" }), null);
797
802
  * assertEqual(
798
803
  * syncStatusToMessage({ type: "Offline" }),
799
- * "Offline. Your changes are saved on this device.",
804
+ * "Offline. Changes will sync when you're back online.",
800
805
  * );
801
806
  * assertEqual(
802
807
  * syncStatusToMessage({
@@ -807,14 +812,14 @@ export interface RelaySyncState {
807
812
  * at: Millis.orThrow(1000),
808
813
  * },
809
814
  * }),
810
- * "Sync is paused because the sync server is full. Your changes are saved on this device.",
815
+ * "Sync is paused because the sync server is full.",
811
816
  * );
812
817
  * assertEqual(
813
818
  * syncStatusToMessage({
814
819
  * type: "Error",
815
820
  * error: { type: "SyncFailed", at: Millis.orThrow(1000) },
816
821
  * }),
817
- * "Sync error: SyncFailed. Your changes are saved on this device.",
822
+ * "Sync error: SyncFailed.",
818
823
  * );
819
824
  * ```
820
825
  */
@@ -1210,6 +1215,11 @@ export type EvoluOutput =
1210
1215
  /** The mutation with these onComplete callbacks could not be stored. */
1211
1216
  readonly type: "OnMutateFailed";
1212
1217
  readonly onCompleteIds: ReadonlyArray<Id>;
1218
+ }
1219
+ | {
1220
+ /** Sent once, when the tenant adds the instance; see Storage. */
1221
+ readonly type: "OnDevicePersistence";
1222
+ readonly devicePersistence: DevicePersistence;
1213
1223
  };
1214
1224
 
1215
1225
  export type DbWorkerInput =
@@ -1334,21 +1344,22 @@ export type DbWorkerQueuedResponse =
1334
1344
  };
1335
1345
 
1336
1346
  /**
1337
- * Tells whether the platform can store databases persistently.
1347
+ * Tells what the platform can promise about the databases it stores.
1338
1348
  *
1339
- * Only a platform that can lack persistent storage provides it, as a browser
1340
- * does in Safari's Private Browsing; see Storage in the Shared module.
1349
+ * `NotPersisted` means the platform offers no persistent storage now, as a
1350
+ * browser does in Safari's Private Browsing or a Firefox private window, so the
1351
+ * worker keeps every database in memory; see Storage in the Shared module.
1341
1352
  */
1342
- export interface PersistentStorageDep {
1343
- readonly isPersistentStorageAvailable: () => Promise<boolean>;
1353
+ export interface DevicePersistenceDep {
1354
+ readonly getDevicePersistence: () => Promise<DevicePersistence>;
1344
1355
  }
1345
1356
 
1346
1357
  export type SharedWorkerDeps = WorkerDeps &
1347
1358
  CreateBroadcastChannelDep &
1348
1359
  CreateMessageChannelDep &
1349
1360
  CreateWebSocketDep &
1350
- LockManagerDep &
1351
- Partial<PersistentStorageDep>;
1361
+ DevicePersistenceDep &
1362
+ LockManagerDep;
1352
1363
 
1353
1364
  /**
1354
1365
  * Coordinates all instances of one named local database within a SharedWorker.
@@ -1748,9 +1759,6 @@ export const initSharedWorker =
1748
1759
  workerId,
1749
1760
  syncStateChannelName,
1750
1761
  });
1751
- if (isPersistentStorageUnavailable) {
1752
- port.postMessage({ type: "StorageUnavailable" });
1753
- }
1754
1762
  });
1755
1763
  };
1756
1764
 
@@ -1766,12 +1774,10 @@ export const initSharedWorker =
1766
1774
  disposer.use(await run.ok(acquireLeaderLock("tab")));
1767
1775
  starting.dispose();
1768
1776
 
1769
- // Checked once, before any DbWorker starts, so every DbWorker of this
1770
- // worker, replacements included, keeps its database in memory; see
1771
- // Storage.
1772
- const isPersistentStorageUnavailable =
1773
- deps.isPersistentStorageAvailable !== undefined &&
1774
- !(await deps.isPersistentStorageAvailable());
1777
+ // Checked once, before any DbWorker starts, so without persistent
1778
+ // storage every DbWorker of this worker, replacements included, keeps its
1779
+ // database in memory; see Storage.
1780
+ const platformDevicePersistence = await deps.getDevicePersistence();
1775
1781
 
1776
1782
  disposer.defer(
1777
1783
  deps.consoleStoreOutputEntry.subscribe(() => {
@@ -2233,14 +2239,17 @@ export const initSharedWorker =
2233
2239
  const tenantsByName = disposer.use(
2234
2240
  await sharedWorkerRun.ok(
2235
2241
  createSharedResourceByKey(
2236
- (message: ExtractTyped<SharedWorkerInput, "CreateEvolu">) =>
2237
- createEvoluTenant(
2238
- isPersistentStorageUnavailable
2239
- ? { ...message, memoryOnly: true }
2240
- : message,
2242
+ (message: ExtractTyped<SharedWorkerInput, "CreateEvolu">) => {
2243
+ const memoryOnly =
2244
+ message.memoryOnly ||
2245
+ platformDevicePersistence === "NotPersisted";
2246
+ return createEvoluTenant(
2247
+ { ...message, memoryOnly },
2248
+ memoryOnly ? "NotPersisted" : platformDevicePersistence,
2241
2249
  currentTenantsByName,
2242
2250
  workerId,
2243
- ),
2251
+ );
2252
+ },
2244
2253
  {
2245
2254
  idleDisposeAfter: "3s",
2246
2255
  lookup: (message) => message.name,
@@ -2269,6 +2278,7 @@ const createEvoluTenant =
2269
2278
  encryptionKey,
2270
2279
  memoryOnly,
2271
2280
  }: ExtractTyped<SharedWorkerInput, "CreateEvolu">,
2281
+ devicePersistence: DevicePersistence,
2272
2282
  currentTenantsByName: Map<Name, BorrowedResource<EvoluTenant>>,
2273
2283
  workerId: SharedWorkerId,
2274
2284
  ): Task<EvoluTenant, never, EvoluTenantDeps> =>
@@ -3404,6 +3414,12 @@ const createEvoluTenant =
3404
3414
  disposer.defer(() => {
3405
3415
  instance.port.onMessage = null;
3406
3416
  });
3417
+ // Instances of one name share the tenant, so each learns the mode
3418
+ // the first one chose.
3419
+ instance.port.postMessage({
3420
+ type: "OnDevicePersistence",
3421
+ devicePersistence,
3422
+ });
3407
3423
 
3408
3424
  // The main-thread Evolu instance holds this per-instance leader lock
3409
3425
  // while it is alive. Acquiring the same lock here means the main