@verdant-web/store 3.12.0 → 4.0.0-next.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 (281) hide show
  1. package/dist/bundle/index.js +11 -13
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/esm/__tests__/batching.test.js +5 -5
  4. package/dist/esm/__tests__/batching.test.js.map +1 -1
  5. package/dist/esm/__tests__/entities.test.js +1 -1
  6. package/dist/esm/__tests__/entities.test.js.map +1 -1
  7. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -3
  8. package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
  9. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  10. package/dist/esm/__tests__/queries.test.js.map +1 -1
  11. package/dist/esm/backup.d.ts +3 -4
  12. package/dist/esm/backup.js.map +1 -1
  13. package/dist/esm/client/Client.d.ts +28 -33
  14. package/dist/esm/client/Client.js +50 -161
  15. package/dist/esm/client/Client.js.map +1 -1
  16. package/dist/esm/client/ClientDescriptor.d.ts +8 -11
  17. package/dist/esm/client/ClientDescriptor.js +39 -141
  18. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  19. package/dist/esm/context/Time.d.ts +13 -0
  20. package/dist/esm/context/Time.js +27 -0
  21. package/dist/esm/context/Time.js.map +1 -0
  22. package/dist/esm/context/context.d.ts +170 -0
  23. package/dist/esm/{context.js.map → context/context.js.map} +1 -1
  24. package/dist/esm/entities/DocumentManager.js.map +1 -1
  25. package/dist/esm/entities/Entity.d.ts +4 -5
  26. package/dist/esm/entities/Entity.js +5 -3
  27. package/dist/esm/entities/Entity.js.map +1 -1
  28. package/dist/esm/entities/Entity.test.js +4 -3
  29. package/dist/esm/entities/Entity.test.js.map +1 -1
  30. package/dist/esm/entities/EntityCache.d.ts +0 -3
  31. package/dist/esm/entities/EntityCache.js +0 -9
  32. package/dist/esm/entities/EntityCache.js.map +1 -1
  33. package/dist/esm/entities/EntityMetadata.d.ts +1 -1
  34. package/dist/esm/entities/EntityMetadata.js +6 -5
  35. package/dist/esm/entities/EntityMetadata.js.map +1 -1
  36. package/dist/esm/entities/EntityStore.d.ts +2 -6
  37. package/dist/esm/entities/EntityStore.js +22 -16
  38. package/dist/esm/entities/EntityStore.js.map +1 -1
  39. package/dist/esm/entities/OperationBatcher.d.ts +2 -5
  40. package/dist/esm/entities/OperationBatcher.js +9 -7
  41. package/dist/esm/entities/OperationBatcher.js.map +1 -1
  42. package/dist/esm/entities/types.d.ts +1 -1
  43. package/dist/esm/errors.d.ts +8 -0
  44. package/dist/esm/errors.js +12 -0
  45. package/dist/esm/errors.js.map +1 -0
  46. package/dist/esm/files/EntityFile.d.ts +6 -3
  47. package/dist/esm/files/EntityFile.js +22 -19
  48. package/dist/esm/files/EntityFile.js.map +1 -1
  49. package/dist/esm/files/FileManager.d.ts +8 -39
  50. package/dist/esm/files/FileManager.js +15 -170
  51. package/dist/esm/files/FileManager.js.map +1 -1
  52. package/dist/esm/files/utils.d.ts +0 -1
  53. package/dist/esm/files/utils.js +0 -14
  54. package/dist/esm/files/utils.js.map +1 -1
  55. package/dist/esm/index.d.ts +1 -2
  56. package/dist/esm/index.js +0 -1
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/esm/{metadata → persistence}/MessageCreator.d.ts +5 -6
  59. package/dist/esm/{metadata → persistence}/MessageCreator.js +31 -38
  60. package/dist/esm/persistence/MessageCreator.js.map +1 -0
  61. package/dist/esm/persistence/PersistenceFiles.d.ts +48 -0
  62. package/dist/esm/persistence/PersistenceFiles.js +160 -0
  63. package/dist/esm/persistence/PersistenceFiles.js.map +1 -0
  64. package/dist/esm/persistence/PersistenceMetadata.d.ts +69 -0
  65. package/dist/esm/persistence/PersistenceMetadata.js +302 -0
  66. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -0
  67. package/dist/esm/persistence/PersistenceQueries.d.ts +34 -0
  68. package/dist/esm/persistence/PersistenceQueries.js +15 -0
  69. package/dist/esm/persistence/PersistenceQueries.js.map +1 -0
  70. package/dist/esm/persistence/PersistenceRebaser.d.ts +32 -0
  71. package/dist/esm/persistence/PersistenceRebaser.js +120 -0
  72. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -0
  73. package/dist/esm/{IDBService.d.ts → persistence/idb/IdbService.d.ts} +9 -7
  74. package/dist/esm/{IDBService.js → persistence/idb/IdbService.js} +29 -8
  75. package/dist/esm/persistence/idb/IdbService.js.map +1 -0
  76. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +58 -0
  77. package/dist/esm/{files/FileStorage.js → persistence/idb/files/IdbPersistenceFileDb.js} +85 -50
  78. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -0
  79. package/dist/esm/persistence/idb/idbPersistence.d.ts +19 -0
  80. package/dist/esm/persistence/idb/idbPersistence.js +80 -0
  81. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -0
  82. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +72 -0
  83. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +235 -0
  84. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -0
  85. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.d.ts +3 -1
  86. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.js +12 -3
  87. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -0
  88. package/dist/esm/persistence/idb/queries/IdbQueryDb.d.ts +41 -0
  89. package/dist/esm/persistence/idb/queries/IdbQueryDb.js +174 -0
  90. package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +1 -0
  91. package/dist/esm/{migration → persistence/idb/queries/migration}/db.d.ts +1 -1
  92. package/dist/esm/{migration → persistence/idb/queries/migration}/db.js +10 -48
  93. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -0
  94. package/dist/esm/persistence/idb/queries/migration/engine.d.ts +12 -0
  95. package/dist/esm/{migration → persistence/idb/queries/migration}/engine.js +29 -46
  96. package/dist/esm/persistence/idb/queries/migration/engine.js.map +1 -0
  97. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.d.ts +1 -3
  98. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.js +11 -10
  99. package/dist/esm/persistence/idb/queries/migration/migrations.js.map +1 -0
  100. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.d.ts +1 -3
  101. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.js +4 -7
  102. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +1 -0
  103. package/dist/esm/{migration → persistence/idb/queries/migration}/paths.js +2 -2
  104. package/dist/esm/persistence/idb/queries/migration/paths.js.map +1 -0
  105. package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +1 -0
  106. package/dist/esm/persistence/idb/queries/migration/types.d.ts +6 -0
  107. package/dist/esm/persistence/idb/queries/migration/types.js.map +1 -0
  108. package/dist/esm/persistence/idb/queries/ranges.d.ts +2 -0
  109. package/dist/esm/persistence/idb/queries/ranges.js +66 -0
  110. package/dist/esm/persistence/idb/queries/ranges.js.map +1 -0
  111. package/dist/esm/{idb.d.ts → persistence/idb/util.d.ts} +11 -0
  112. package/dist/esm/{idb.js → persistence/idb/util.js} +58 -1
  113. package/dist/esm/persistence/idb/util.js.map +1 -0
  114. package/dist/esm/persistence/interfaces.d.ts +181 -0
  115. package/dist/esm/persistence/interfaces.js +2 -0
  116. package/dist/esm/persistence/interfaces.js.map +1 -0
  117. package/dist/esm/persistence/persistence.d.ts +4 -0
  118. package/dist/esm/persistence/persistence.js +126 -0
  119. package/dist/esm/persistence/persistence.js.map +1 -0
  120. package/dist/esm/queries/BaseQuery.d.ts +2 -1
  121. package/dist/esm/queries/BaseQuery.js +3 -0
  122. package/dist/esm/queries/BaseQuery.js.map +1 -1
  123. package/dist/esm/queries/CollectionQueries.d.ts +1 -1
  124. package/dist/esm/queries/FindAllQuery.js +1 -3
  125. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  126. package/dist/esm/queries/FindInfiniteQuery.js +2 -5
  127. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  128. package/dist/esm/queries/FindOneQuery.js +1 -3
  129. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  130. package/dist/esm/queries/FindPageQuery.js +1 -3
  131. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  132. package/dist/esm/queries/QueryCache.d.ts +1 -1
  133. package/dist/esm/queries/QueryCache.js +4 -0
  134. package/dist/esm/queries/QueryCache.js.map +1 -1
  135. package/dist/esm/sync/FileSync.d.ts +23 -8
  136. package/dist/esm/sync/FileSync.js +76 -28
  137. package/dist/esm/sync/FileSync.js.map +1 -1
  138. package/dist/esm/sync/PresenceManager.d.ts +4 -3
  139. package/dist/esm/sync/PresenceManager.js +2 -2
  140. package/dist/esm/sync/PresenceManager.js.map +1 -1
  141. package/dist/esm/sync/PushPullSync.d.ts +4 -6
  142. package/dist/esm/sync/PushPullSync.js +13 -12
  143. package/dist/esm/sync/PushPullSync.js.map +1 -1
  144. package/dist/esm/sync/Sync.d.ts +9 -11
  145. package/dist/esm/sync/Sync.js +34 -29
  146. package/dist/esm/sync/Sync.js.map +1 -1
  147. package/dist/esm/sync/WebSocketSync.d.ts +4 -6
  148. package/dist/esm/sync/WebSocketSync.js +20 -22
  149. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  150. package/dist/esm/utils/Disposable.d.ts +5 -2
  151. package/dist/esm/utils/Disposable.js +3 -2
  152. package/dist/esm/utils/Disposable.js.map +1 -1
  153. package/dist/esm/utils/wip.d.ts +2 -0
  154. package/dist/esm/utils/wip.js +5 -0
  155. package/dist/esm/utils/wip.js.map +1 -0
  156. package/package.json +2 -2
  157. package/src/__tests__/batching.test.ts +6 -6
  158. package/src/__tests__/entities.test.ts +1 -1
  159. package/src/__tests__/fixtures/testStorage.ts +2 -10
  160. package/src/__tests__/queries.test.ts +1 -1
  161. package/src/backup.ts +3 -4
  162. package/src/client/Client.ts +69 -226
  163. package/src/client/ClientDescriptor.ts +53 -184
  164. package/src/context/Time.ts +35 -0
  165. package/src/context/context.ts +200 -0
  166. package/src/entities/DocumentManager.ts +0 -3
  167. package/src/entities/Entity.test.ts +9 -9
  168. package/src/entities/Entity.ts +6 -12
  169. package/src/entities/EntityCache.ts +0 -9
  170. package/src/entities/EntityMetadata.ts +4 -4
  171. package/src/entities/EntityStore.ts +26 -29
  172. package/src/entities/OperationBatcher.ts +9 -11
  173. package/src/entities/types.ts +1 -1
  174. package/src/errors.ts +13 -0
  175. package/src/files/EntityFile.ts +16 -5
  176. package/src/files/FileManager.ts +18 -245
  177. package/src/files/utils.ts +0 -15
  178. package/src/index.ts +2 -1
  179. package/src/{metadata → persistence}/MessageCreator.ts +46 -36
  180. package/src/persistence/PersistenceFiles.ts +227 -0
  181. package/src/persistence/PersistenceMetadata.ts +425 -0
  182. package/src/persistence/PersistenceQueries.ts +22 -0
  183. package/src/persistence/PersistenceRebaser.ts +171 -0
  184. package/src/{IDBService.ts → persistence/idb/IdbService.ts} +45 -12
  185. package/src/{files/FileStorage.ts → persistence/idb/files/IdbPersistenceFileDb.ts} +128 -86
  186. package/src/persistence/idb/idbPersistence.ts +116 -0
  187. package/src/persistence/idb/metadata/IdbMetadataDb.ts +460 -0
  188. package/src/{metadata → persistence/idb/metadata}/openMetadataDatabase.ts +21 -4
  189. package/src/persistence/idb/queries/IdbQueryDb.ts +251 -0
  190. package/src/{migration → persistence/idb/queries/migration}/db.ts +18 -72
  191. package/src/{migration → persistence/idb/queries/migration}/engine.ts +39 -62
  192. package/src/{migration → persistence/idb/queries/migration}/migrations.ts +13 -18
  193. package/src/{migration → persistence/idb/queries/migration}/openQueryDatabase.ts +5 -14
  194. package/src/{migration → persistence/idb/queries/migration}/paths.ts +4 -3
  195. package/src/persistence/idb/queries/migration/types.ts +8 -0
  196. package/src/persistence/idb/queries/ranges.ts +107 -0
  197. package/src/{idb.ts → persistence/idb/util.ts} +75 -0
  198. package/src/persistence/interfaces.ts +240 -0
  199. package/src/persistence/persistence.ts +223 -0
  200. package/src/queries/BaseQuery.ts +5 -1
  201. package/src/queries/CollectionQueries.ts +2 -2
  202. package/src/queries/FindAllQuery.ts +1 -3
  203. package/src/queries/FindInfiniteQuery.ts +2 -5
  204. package/src/queries/FindOneQuery.ts +1 -3
  205. package/src/queries/FindPageQuery.ts +1 -3
  206. package/src/queries/QueryCache.ts +20 -1
  207. package/src/sync/FileSync.ts +93 -30
  208. package/src/sync/PresenceManager.ts +5 -7
  209. package/src/sync/PushPullSync.ts +23 -19
  210. package/src/sync/Sync.ts +45 -36
  211. package/src/sync/WebSocketSync.ts +41 -27
  212. package/src/utils/Disposable.ts +7 -4
  213. package/src/utils/wip.ts +5 -0
  214. package/dist/esm/IDBService.js.map +0 -1
  215. package/dist/esm/__tests__/legacyOids.test.d.ts +0 -1
  216. package/dist/esm/__tests__/legacyOids.test.js +0 -352
  217. package/dist/esm/__tests__/legacyOids.test.js.map +0 -1
  218. package/dist/esm/context.d.ts +0 -45
  219. package/dist/esm/files/FileStorage.d.ts +0 -47
  220. package/dist/esm/files/FileStorage.js.map +0 -1
  221. package/dist/esm/idb.js.map +0 -1
  222. package/dist/esm/metadata/AckInfoStore.d.ts +0 -10
  223. package/dist/esm/metadata/AckInfoStore.js +0 -22
  224. package/dist/esm/metadata/AckInfoStore.js.map +0 -1
  225. package/dist/esm/metadata/BaselinesStore.d.ts +0 -40
  226. package/dist/esm/metadata/BaselinesStore.js +0 -102
  227. package/dist/esm/metadata/BaselinesStore.js.map +0 -1
  228. package/dist/esm/metadata/LocalReplicaStore.d.ts +0 -19
  229. package/dist/esm/metadata/LocalReplicaStore.js +0 -56
  230. package/dist/esm/metadata/LocalReplicaStore.js.map +0 -1
  231. package/dist/esm/metadata/MessageCreator.js.map +0 -1
  232. package/dist/esm/metadata/Metadata.d.ts +0 -146
  233. package/dist/esm/metadata/Metadata.js +0 -452
  234. package/dist/esm/metadata/Metadata.js.map +0 -1
  235. package/dist/esm/metadata/OperationsStore.d.ts +0 -62
  236. package/dist/esm/metadata/OperationsStore.js +0 -175
  237. package/dist/esm/metadata/OperationsStore.js.map +0 -1
  238. package/dist/esm/metadata/SchemaStore.d.ts +0 -9
  239. package/dist/esm/metadata/SchemaStore.js +0 -35
  240. package/dist/esm/metadata/SchemaStore.js.map +0 -1
  241. package/dist/esm/metadata/openMetadataDatabase.js.map +0 -1
  242. package/dist/esm/migration/db.js.map +0 -1
  243. package/dist/esm/migration/engine.d.ts +0 -15
  244. package/dist/esm/migration/engine.js.map +0 -1
  245. package/dist/esm/migration/errors.d.ts +0 -5
  246. package/dist/esm/migration/errors.js +0 -8
  247. package/dist/esm/migration/errors.js.map +0 -1
  248. package/dist/esm/migration/migrations.js.map +0 -1
  249. package/dist/esm/migration/openQueryDatabase.js.map +0 -1
  250. package/dist/esm/migration/openWIPDatabase.d.ts +0 -11
  251. package/dist/esm/migration/openWIPDatabase.js +0 -65
  252. package/dist/esm/migration/openWIPDatabase.js.map +0 -1
  253. package/dist/esm/migration/paths.js.map +0 -1
  254. package/dist/esm/migration/paths.test.js.map +0 -1
  255. package/dist/esm/migration/types.d.ts +0 -3
  256. package/dist/esm/migration/types.js.map +0 -1
  257. package/dist/esm/queries/QueryableStorage.d.ts +0 -20
  258. package/dist/esm/queries/QueryableStorage.js +0 -90
  259. package/dist/esm/queries/QueryableStorage.js.map +0 -1
  260. package/dist/esm/queries/dbQueries.d.ts +0 -22
  261. package/dist/esm/queries/dbQueries.js +0 -130
  262. package/dist/esm/queries/dbQueries.js.map +0 -1
  263. package/src/__tests__/legacyOids.test.ts +0 -375
  264. package/src/context.ts +0 -55
  265. package/src/metadata/AckInfoStore.ts +0 -30
  266. package/src/metadata/BaselinesStore.ts +0 -188
  267. package/src/metadata/LocalReplicaStore.ts +0 -79
  268. package/src/metadata/Metadata.ts +0 -685
  269. package/src/metadata/OperationsStore.ts +0 -332
  270. package/src/metadata/SchemaStore.ts +0 -47
  271. package/src/migration/errors.ts +0 -7
  272. package/src/migration/openWIPDatabase.ts +0 -97
  273. package/src/migration/types.ts +0 -4
  274. package/src/queries/QueryableStorage.ts +0 -122
  275. package/src/queries/dbQueries.ts +0 -161
  276. /package/dist/esm/{context.js → context/context.js} +0 -0
  277. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.d.ts +0 -0
  278. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.d.ts +0 -0
  279. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.js +0 -0
  280. /package/dist/esm/{migration → persistence/idb/queries/migration}/types.js +0 -0
  281. /package/src/{migration → persistence/idb/queries/migration}/paths.test.ts +0 -0
@@ -1,184 +1,48 @@
1
- import { FileData, FileRef } from '@verdant-web/common';
2
- import { Context } from '../context.js';
3
- import { Metadata } from '../metadata/Metadata.js';
1
+ import { FileData } from '@verdant-web/common';
2
+ import { Context } from '../context/context.js';
4
3
  import { Sync } from '../sync/Sync.js';
5
- import {
6
- EntityFile,
7
- MARK_FAILED,
8
- MARK_UPLOADED,
9
- UPDATE,
10
- } from './EntityFile.js';
11
- import {
12
- FileStorage,
13
- ReturnedFileData,
14
- StoredFileData,
15
- } from './FileStorage.js';
16
-
17
- /**
18
- * Default: if file was deleted > 3 days ago
19
- */
20
- function defaultCanCleanup(fileData: ReturnedFileData) {
21
- return (
22
- fileData.deletedAt !== null &&
23
- fileData.deletedAt < Date.now() - 1000 * 60 * 24 * 3
24
- );
25
- }
26
-
27
- export interface FileManagerConfig {
28
- /**
29
- * Override the heuristic for deciding when a deleted file can be cleaned up.
30
- * By default this waits 3 days since deletion, then deletes the file data.
31
- * If the file has been synchronized to a server, it could still be restored
32
- * if the server has not yet deleted it.
33
- */
34
- canCleanupDeletedFile?: (file: ReturnedFileData) => boolean;
35
- }
4
+ import { EntityFile, MARK_FAILED, UPDATE } from './EntityFile.js';
36
5
 
37
6
  export class FileManager {
38
- private storage;
39
7
  private sync;
40
8
  private context;
41
9
 
42
- private files = new Map<string, EntityFile>();
43
- private config: Required<FileManagerConfig>;
44
- private meta: Metadata;
10
+ private cache = new Map<string, EntityFile>();
45
11
 
46
- private maxUploadRetries = 3;
47
- private maxDownloadRetries = 3;
48
-
49
- constructor({
50
- db,
51
- sync,
52
- context,
53
- meta,
54
- config = {},
55
- }: {
56
- db: IDBDatabase;
57
- sync: Sync;
58
- context: Context;
59
- config?: FileManagerConfig;
60
- meta: Metadata;
61
- }) {
62
- this.storage = new FileStorage(db);
12
+ constructor({ sync, context }: { sync: Sync; context: Context }) {
63
13
  this.sync = sync;
64
14
  this.context = context;
65
- this.meta = meta;
66
- this.config = {
67
- canCleanupDeletedFile: defaultCanCleanup,
68
- ...config,
69
- };
70
-
71
- this.sync.subscribe('onlineChange', this.onOnlineChange);
72
- this.meta.subscribe('filesDeleted', this.handleFileRefsDeleted);
73
- this.sync.subscribe('serverReset', this.storage.resetSyncedStatusSince);
74
- // check on startup to see if files can be cleaned up
75
- this.tryCleanupDeletedFiles();
76
15
  }
77
16
 
78
- add = async (file: FileData) => {
79
- // this method accepts a FileData which refers to a remote
80
- // file, as well as local files. in the case of a remote file,
81
- // we actually re-download and upload the file again. this powers
82
- // the cloning of documents with files; we clone their filedata
83
- // and re-upload to a new file ID. otherwise, when the cloned
84
- // filedata was marked deleted, the original file would be deleted
85
- // and the clone would refer to a missing file.
86
- if (file.url && !file.file) {
87
- const blob = await this.downloadRemoteFile(file.url);
88
- // convert blob to file with name and type
89
- file.file = new File([blob], file.name, { type: file.type });
90
- }
91
-
92
- file.remote = false;
17
+ add = async (file: FileData, options?: { downloadRemote: boolean }) => {
93
18
  // immediately cache the file
94
- if (!this.files.has(file.id)) {
95
- const entityFile = new EntityFile(file.id);
19
+ if (!this.cache.has(file.id)) {
20
+ const entityFile = new EntityFile(file.id, { ctx: this.context });
96
21
  entityFile[UPDATE](file);
97
- this.files.set(file.id, entityFile);
22
+ this.cache.set(file.id, entityFile);
98
23
  } else {
99
- this.files.get(file.id)![UPDATE](file);
100
- }
101
- // write to local storage and send to sync immediately
102
- await this.storage.addFile(file);
103
- // send to sync
104
- if (file.file && this.sync.status === 'active') {
105
- await this.uploadFile(file);
24
+ this.cache.get(file.id)![UPDATE](file);
106
25
  }
107
- };
108
26
 
109
- private uploadFile = async (file: FileData, retries = 0) => {
110
- const result = await this.sync.uploadFile(file);
111
- if (result.success) {
112
- await this.storage.markUploaded(file.id);
113
- const cached = this.files.get(file.id);
114
- if (cached) {
115
- cached[MARK_UPLOADED]();
116
- }
117
- this.context.log('info', 'File uploaded', file.id);
118
- } else {
119
- if (result.retry && retries < this.maxUploadRetries) {
120
- this.context.log(
121
- 'error',
122
- `Error uploading file ${file.id}, retrying...`,
123
- result.error,
124
- );
125
- // schedule a retry
126
- setTimeout(this.uploadFile, 1000, file, retries + 1);
127
- } else {
128
- this.context.log(
129
- 'error',
130
- `Failed to upload file ${file.id}. Not retrying until next sync.`,
131
- result.error,
132
- );
133
- }
134
- }
135
- };
136
-
137
- private downloadRemoteFile = async (
138
- url: string,
139
- retries = 0,
140
- ): Promise<Blob> => {
141
- const resp = await fetch(url, {
142
- method: 'GET',
143
- credentials: 'include',
144
- });
145
- if (!resp.ok) {
146
- if (retries < this.maxDownloadRetries) {
147
- return new Promise((resolve, reject) => {
148
- setTimeout(() => {
149
- this.downloadRemoteFile(url, retries + 1).then(resolve, reject);
150
- }, 1000);
151
- });
152
- } else {
153
- throw new Error(`Failed to download file: ${resp.status}`);
154
- }
155
- }
156
- const blob = await resp.blob();
157
- return blob;
27
+ await this.context.files.add(file, options);
158
28
  };
159
29
 
160
30
  /**
161
31
  * Immediately returns an EntityFile to use, then either loads
162
32
  * the file from cache, local database, or the server.
163
33
  */
164
- get = (id: string, options?: { downloadRemote?: boolean }) => {
165
- if (this.files.has(id)) {
166
- return this.files.get(id)!;
34
+ get = (id: string, options: { downloadRemote?: boolean; ctx: Context }) => {
35
+ if (this.cache.has(id)) {
36
+ return this.cache.get(id)!;
167
37
  }
168
38
  const file = new EntityFile(id, options);
169
- this.files.set(id, file);
39
+ this.cache.set(id, file);
170
40
  this.load(file);
171
41
  return file;
172
42
  };
173
43
 
174
- private load = async (file: EntityFile, retries = 0) => {
175
- if (retries > this.maxDownloadRetries) {
176
- this.context.log('error', 'Failed to load file after 5 retries');
177
- file[MARK_FAILED]();
178
- return;
179
- }
180
-
181
- const fileData = await this.storage.getFile(file.id);
44
+ private load = async (file: EntityFile) => {
45
+ const fileData = await this.context.files.get(file.id);
182
46
  if (fileData) {
183
47
  file[UPDATE](fileData);
184
48
  } else {
@@ -187,108 +51,17 @@ export class FileManager {
187
51
  const result = await this.sync.getFile(file.id);
188
52
  if (result.success) {
189
53
  file[UPDATE](result.data);
190
- await this.storage.addFile(result.data, {
54
+ await this.context.files.add(result.data, {
191
55
  downloadRemote: file.downloadRemote,
192
56
  });
193
57
  } else {
194
58
  this.context.log('error', 'Failed to load file', result);
195
59
  file[MARK_FAILED]();
196
- if (result.retry) {
197
- // schedule a retry
198
- setTimeout(this.load, 1000, file, retries + 1);
199
- }
200
60
  }
201
61
  } catch (err) {
202
62
  this.context.log('error', 'Failed to load file', err);
203
63
  file[MARK_FAILED]();
204
- // schedule a retry
205
- setTimeout(this.load, 1000, file, retries + 1);
206
64
  }
207
65
  }
208
66
  };
209
-
210
- listUnsynced = async () => {
211
- return this.storage.listUnsynced();
212
- };
213
-
214
- exportAll = async (downloadRemote = false) => {
215
- const storedFiles = await this.storage.getAll();
216
- if (downloadRemote) {
217
- for (const storedFile of storedFiles) {
218
- // if it doesn't have a buffer, we need to read
219
- // one from the server
220
- if (!storedFile.file && storedFile.url) {
221
- try {
222
- const blob = await fetch(storedFile.url, {
223
- method: 'GET',
224
- credentials: 'include',
225
- }).then((r) => r.blob());
226
- storedFile.file = blob;
227
- } catch (err) {
228
- this.context.log(
229
- 'error',
230
- "Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
231
- err,
232
- );
233
- }
234
- }
235
- }
236
- }
237
- return storedFiles;
238
- };
239
-
240
- importAll = async (files: ReturnedFileData[]) => {
241
- await Promise.all(files.map((file) => this.add(file)));
242
- };
243
-
244
- private onOnlineChange = async (online: boolean) => {
245
- // if online, try to upload any unsynced files
246
- if (online) {
247
- const unsynced = await this.listUnsynced();
248
- await Promise.all(unsynced.map(this.uploadFile));
249
- }
250
- };
251
-
252
- tryCleanupDeletedFiles = async () => {
253
- let count = 0;
254
- let skipCount = 0;
255
- await this.storage.iterateOverPendingDelete((fileData, store) => {
256
- if (this.config.canCleanupDeletedFile(fileData)) {
257
- count++;
258
- store.delete(fileData.id);
259
- } else {
260
- skipCount++;
261
- }
262
- });
263
-
264
- this.context.log(
265
- 'info',
266
- `Cleaned up ${count} files, skipped ${skipCount} files`,
267
- );
268
- };
269
-
270
- private handleFileRefsDeleted = async (fileRefs: FileRef[]) => {
271
- const tx = this.storage.createTransaction(['files'], { mode: 'readwrite' });
272
- await Promise.all(
273
- fileRefs.map(async (fileRef) => {
274
- try {
275
- await this.storage.markPendingDelete(fileRef.id, { transaction: tx });
276
- } catch (err) {
277
- this.context.log('error', 'Failed to mark file for deletion', err);
278
- }
279
- }),
280
- );
281
- this.context.log(
282
- 'info',
283
- `Marked ${fileRefs.length} files as pending delete`,
284
- );
285
- };
286
-
287
- close = () => {
288
- this.storage.dispose();
289
- };
290
-
291
- stats = () => {
292
- return this.storage.stats();
293
- };
294
67
  }
@@ -55,18 +55,3 @@ export function processValueFiles(
55
55
 
56
56
  return value;
57
57
  }
58
-
59
- export function fileToArrayBuffer(file: File | Blob) {
60
- // special case for testing...
61
- if ('__testReadBuffer' in file) {
62
- return file.__testReadBuffer;
63
- }
64
- return new Promise<ArrayBuffer>((resolve, reject) => {
65
- const reader = new FileReader();
66
- reader.onload = () => {
67
- resolve(reader.result as ArrayBuffer);
68
- };
69
- reader.onerror = reject;
70
- reader.readAsArrayBuffer(file);
71
- });
72
- }
package/src/index.ts CHANGED
@@ -48,12 +48,13 @@ export type {
48
48
  StorageFieldsSchema,
49
49
  IndexValueTag,
50
50
  Migration,
51
+ VerdantError,
52
+ VerdantErrorCode,
51
53
  } from '@verdant-web/common';
52
54
  export type { UserInfo } from '@verdant-web/common';
53
55
  export type { Query } from './queries/types.js';
54
56
  export type { QueryStatus } from './queries/BaseQuery.js';
55
57
  export type { CollectionQueries } from './queries/CollectionQueries.js';
56
- export { MigrationPathError } from './migration/errors.js';
57
58
  export * from './utils/id.js';
58
59
  export { UndoHistory } from './UndoHistory.js';
59
60
  export * from './authorization.js';
@@ -8,53 +8,38 @@ import {
8
8
  OperationMessage,
9
9
  pickValidOperationKeys,
10
10
  PresenceUpdateMessage,
11
- SyncAckMessage,
12
11
  SyncMessage,
13
12
  VerdantInternalPresence,
14
13
  } from '@verdant-web/common';
15
14
 
16
- import { Metadata } from './Metadata.js';
15
+ import { Context } from '../context/context.js';
16
+ import { PersistenceMetadataDb } from './interfaces.js';
17
17
 
18
18
  export class MessageCreator {
19
- constructor(private meta: Metadata) {}
19
+ constructor(
20
+ private db: PersistenceMetadataDb,
21
+ private ctx: Pick<Context, 'time' | 'schema' | 'log'>,
22
+ ) {}
20
23
 
21
24
  createOperation = async (
22
25
  init: Pick<OperationMessage, 'operations'> & {
23
26
  timestamp?: string;
24
27
  },
25
28
  ): Promise<OperationMessage> => {
26
- const localInfo = await this.meta.localReplica.get();
29
+ const localInfo = await this.db.getLocalReplica();
27
30
  return {
28
31
  type: 'op',
29
- timestamp: this.meta.now,
32
+ timestamp: this.ctx.time.now,
30
33
  replicaId: localInfo.id,
31
34
  operations: init.operations.map(pickValidOperationKeys),
32
35
  };
33
36
  };
34
37
 
35
- createMigrationOperation = async ({
36
- targetVersion,
37
- ...init
38
- }: Pick<OperationMessage, 'operations'> & {
39
- targetVersion: number;
40
- }): Promise<OperationMessage> => {
41
- const localInfo = await this.meta.localReplica.get();
42
- return {
43
- type: 'op',
44
- operations: init.operations.map((op) => ({
45
- ...op,
46
- timestamp: this.meta.time.zero(targetVersion),
47
- })),
48
- timestamp: this.meta.time.zero(targetVersion),
49
- replicaId: localInfo.id,
50
- };
51
- };
52
-
53
38
  /**
54
39
  * @param since - override local understanding of last sync time
55
40
  */
56
41
  createSyncStep1 = async (since?: string | null): Promise<SyncMessage> => {
57
- const localReplicaInfo = await this.meta.localReplica.get();
42
+ const localReplicaInfo = await this.db.getLocalReplica();
58
43
 
59
44
  const provideChangesSince =
60
45
  since === null ? null : localReplicaInfo.lastSyncedLogicalTime;
@@ -64,12 +49,22 @@ export class MessageCreator {
64
49
  const operations: Operation[] = [];
65
50
  const affectedDocs = new Set<ObjectIdentifier>();
66
51
 
52
+ const tx = await this.db.transaction({
53
+ mode: 'readwrite',
54
+ storeNames: ['operations', 'baselines'],
55
+ });
56
+
67
57
  // FIXME: this branch gives bad vibes. should we always
68
58
  // send all operations from other replicas too? is there
69
59
  // ever a case where we have a "since" timestamp and there
70
60
  // are foreign ops that match it?
71
61
  if (provideChangesSince) {
72
- await this.meta.operations.iterateOverAllLocalOperations(
62
+ this.ctx.log(
63
+ 'debug',
64
+ 'Syncing local operations since',
65
+ provideChangesSince,
66
+ );
67
+ await this.db.iterateLocalOperations(
73
68
  (patch) => {
74
69
  operations.push(pickValidOperationKeys(patch));
75
70
  affectedDocs.add(getOidRoot(patch.oid));
@@ -77,32 +72,47 @@ export class MessageCreator {
77
72
  {
78
73
  after: provideChangesSince,
79
74
  // block on writes to prevent race conditions
80
- mode: 'readwrite',
75
+ transaction: tx,
81
76
  },
82
77
  );
83
78
  } else {
79
+ this.ctx.log('debug', 'Syncing all operations');
84
80
  // if providing the whole history, don't limit to only local
85
81
  // operations
86
- await this.meta.operations.iterateOverAllOperations(
82
+ await this.db.iterateAllOperations(
87
83
  (patch) => {
88
84
  operations.push(pickValidOperationKeys(patch));
89
85
  affectedDocs.add(getOidRoot(patch.oid));
90
86
  },
91
87
  {
92
- mode: 'readwrite',
88
+ transaction: tx,
93
89
  },
94
90
  );
95
91
  }
96
92
  // we only need to send baselines if we've never synced before
97
93
  let baselines: DocumentBaseline[] = [];
98
94
  if (!provideChangesSince) {
99
- baselines = await this.meta.baselines.getAllSince('');
95
+ await this.db.iterateAllBaselines(
96
+ (b) => {
97
+ baselines.push(b);
98
+ },
99
+ {
100
+ transaction: tx,
101
+ },
102
+ );
103
+ }
104
+
105
+ if (operations.length > 0) {
106
+ this.ctx.log(
107
+ 'debug',
108
+ `Syncing ${operations.length} operations since ${provideChangesSince}`,
109
+ );
100
110
  }
101
111
 
102
112
  return {
103
113
  type: 'sync',
104
- schemaVersion: this.meta.schema.currentVersion,
105
- timestamp: this.meta.now,
114
+ schemaVersion: this.ctx.schema.version,
115
+ timestamp: this.ctx.time.now,
106
116
  replicaId: localReplicaInfo.id,
107
117
  resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
108
118
  operations,
@@ -115,7 +125,7 @@ export class MessageCreator {
115
125
  presence?: any;
116
126
  internal?: VerdantInternalPresence;
117
127
  }): Promise<PresenceUpdateMessage> => {
118
- const localReplicaInfo = await this.meta.localReplica.get();
128
+ const localReplicaInfo = await this.db.getLocalReplica();
119
129
  return {
120
130
  type: 'presence-update',
121
131
  presence: data.presence,
@@ -125,19 +135,19 @@ export class MessageCreator {
125
135
  };
126
136
 
127
137
  createHeartbeat = async (): Promise<HeartbeatMessage> => {
128
- const localReplicaInfo = await this.meta.localReplica.get();
138
+ const localReplicaInfo = await this.db.getLocalReplica();
129
139
  return {
130
140
  type: 'heartbeat',
131
- timestamp: this.meta.now,
141
+ timestamp: this.ctx.time.now,
132
142
  replicaId: localReplicaInfo.id,
133
143
  };
134
144
  };
135
145
 
136
146
  createAck = async (nonce: string): Promise<AckMessage> => {
137
- const localReplicaInfo = await this.meta.localReplica.get();
147
+ const localReplicaInfo = await this.db.getLocalReplica();
138
148
  return {
139
149
  type: 'ack',
140
- timestamp: this.meta.now,
150
+ timestamp: this.ctx.time.now,
141
151
  replicaId: localReplicaInfo.id,
142
152
  nonce,
143
153
  };