@verdant-web/store 3.12.1 → 4.0.0-next.1

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 (279) 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/errors.d.ts +8 -0
  43. package/dist/esm/errors.js +12 -0
  44. package/dist/esm/errors.js.map +1 -0
  45. package/dist/esm/files/EntityFile.d.ts +6 -3
  46. package/dist/esm/files/EntityFile.js +22 -19
  47. package/dist/esm/files/EntityFile.js.map +1 -1
  48. package/dist/esm/files/FileManager.d.ts +8 -39
  49. package/dist/esm/files/FileManager.js +26 -170
  50. package/dist/esm/files/FileManager.js.map +1 -1
  51. package/dist/esm/files/utils.d.ts +0 -1
  52. package/dist/esm/files/utils.js +0 -14
  53. package/dist/esm/files/utils.js.map +1 -1
  54. package/dist/esm/index.d.ts +1 -2
  55. package/dist/esm/index.js +0 -1
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/{metadata → persistence}/MessageCreator.d.ts +5 -6
  58. package/dist/esm/{metadata → persistence}/MessageCreator.js +31 -38
  59. package/dist/esm/persistence/MessageCreator.js.map +1 -0
  60. package/dist/esm/persistence/PersistenceFiles.d.ts +48 -0
  61. package/dist/esm/persistence/PersistenceFiles.js +160 -0
  62. package/dist/esm/persistence/PersistenceFiles.js.map +1 -0
  63. package/dist/esm/persistence/PersistenceMetadata.d.ts +69 -0
  64. package/dist/esm/persistence/PersistenceMetadata.js +302 -0
  65. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -0
  66. package/dist/esm/persistence/PersistenceQueries.d.ts +34 -0
  67. package/dist/esm/persistence/PersistenceQueries.js +15 -0
  68. package/dist/esm/persistence/PersistenceQueries.js.map +1 -0
  69. package/dist/esm/persistence/PersistenceRebaser.d.ts +32 -0
  70. package/dist/esm/persistence/PersistenceRebaser.js +120 -0
  71. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -0
  72. package/dist/esm/{IDBService.d.ts → persistence/idb/IdbService.d.ts} +9 -7
  73. package/dist/esm/{IDBService.js → persistence/idb/IdbService.js} +29 -8
  74. package/dist/esm/persistence/idb/IdbService.js.map +1 -0
  75. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +58 -0
  76. package/dist/esm/{files/FileStorage.js → persistence/idb/files/IdbPersistenceFileDb.js} +85 -50
  77. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -0
  78. package/dist/esm/persistence/idb/idbPersistence.d.ts +19 -0
  79. package/dist/esm/persistence/idb/idbPersistence.js +80 -0
  80. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -0
  81. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +72 -0
  82. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +235 -0
  83. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -0
  84. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.d.ts +3 -1
  85. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.js +12 -3
  86. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -0
  87. package/dist/esm/persistence/idb/queries/IdbQueryDb.d.ts +41 -0
  88. package/dist/esm/persistence/idb/queries/IdbQueryDb.js +174 -0
  89. package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +1 -0
  90. package/dist/esm/{migration → persistence/idb/queries/migration}/db.d.ts +1 -1
  91. package/dist/esm/{migration → persistence/idb/queries/migration}/db.js +10 -48
  92. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -0
  93. package/dist/esm/persistence/idb/queries/migration/engine.d.ts +12 -0
  94. package/dist/esm/{migration → persistence/idb/queries/migration}/engine.js +29 -46
  95. package/dist/esm/persistence/idb/queries/migration/engine.js.map +1 -0
  96. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.d.ts +1 -3
  97. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.js +11 -10
  98. package/dist/esm/persistence/idb/queries/migration/migrations.js.map +1 -0
  99. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.d.ts +1 -3
  100. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.js +4 -7
  101. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +1 -0
  102. package/dist/esm/{migration → persistence/idb/queries/migration}/paths.js +2 -2
  103. package/dist/esm/persistence/idb/queries/migration/paths.js.map +1 -0
  104. package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +1 -0
  105. package/dist/esm/persistence/idb/queries/migration/types.d.ts +6 -0
  106. package/dist/esm/persistence/idb/queries/migration/types.js.map +1 -0
  107. package/dist/esm/persistence/idb/queries/ranges.d.ts +2 -0
  108. package/dist/esm/persistence/idb/queries/ranges.js +66 -0
  109. package/dist/esm/persistence/idb/queries/ranges.js.map +1 -0
  110. package/dist/esm/{idb.d.ts → persistence/idb/util.d.ts} +11 -0
  111. package/dist/esm/{idb.js → persistence/idb/util.js} +58 -1
  112. package/dist/esm/persistence/idb/util.js.map +1 -0
  113. package/dist/esm/persistence/interfaces.d.ts +181 -0
  114. package/dist/esm/persistence/interfaces.js +2 -0
  115. package/dist/esm/persistence/interfaces.js.map +1 -0
  116. package/dist/esm/persistence/persistence.d.ts +4 -0
  117. package/dist/esm/persistence/persistence.js +126 -0
  118. package/dist/esm/persistence/persistence.js.map +1 -0
  119. package/dist/esm/queries/BaseQuery.d.ts +2 -1
  120. package/dist/esm/queries/BaseQuery.js +3 -0
  121. package/dist/esm/queries/BaseQuery.js.map +1 -1
  122. package/dist/esm/queries/CollectionQueries.d.ts +1 -1
  123. package/dist/esm/queries/FindAllQuery.js +1 -3
  124. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  125. package/dist/esm/queries/FindInfiniteQuery.js +2 -5
  126. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  127. package/dist/esm/queries/FindOneQuery.js +1 -3
  128. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  129. package/dist/esm/queries/FindPageQuery.js +1 -3
  130. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  131. package/dist/esm/queries/QueryCache.d.ts +1 -1
  132. package/dist/esm/queries/QueryCache.js +4 -0
  133. package/dist/esm/queries/QueryCache.js.map +1 -1
  134. package/dist/esm/sync/FileSync.d.ts +23 -8
  135. package/dist/esm/sync/FileSync.js +76 -28
  136. package/dist/esm/sync/FileSync.js.map +1 -1
  137. package/dist/esm/sync/PresenceManager.d.ts +4 -3
  138. package/dist/esm/sync/PresenceManager.js +2 -2
  139. package/dist/esm/sync/PresenceManager.js.map +1 -1
  140. package/dist/esm/sync/PushPullSync.d.ts +4 -6
  141. package/dist/esm/sync/PushPullSync.js +13 -12
  142. package/dist/esm/sync/PushPullSync.js.map +1 -1
  143. package/dist/esm/sync/Sync.d.ts +10 -11
  144. package/dist/esm/sync/Sync.js +55 -30
  145. package/dist/esm/sync/Sync.js.map +1 -1
  146. package/dist/esm/sync/WebSocketSync.d.ts +4 -6
  147. package/dist/esm/sync/WebSocketSync.js +20 -22
  148. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  149. package/dist/esm/utils/Disposable.d.ts +5 -2
  150. package/dist/esm/utils/Disposable.js +3 -2
  151. package/dist/esm/utils/Disposable.js.map +1 -1
  152. package/dist/esm/utils/wip.d.ts +2 -0
  153. package/dist/esm/utils/wip.js +5 -0
  154. package/dist/esm/utils/wip.js.map +1 -0
  155. package/package.json +2 -2
  156. package/src/__tests__/batching.test.ts +6 -6
  157. package/src/__tests__/entities.test.ts +1 -1
  158. package/src/__tests__/fixtures/testStorage.ts +2 -10
  159. package/src/__tests__/queries.test.ts +1 -1
  160. package/src/backup.ts +3 -4
  161. package/src/client/Client.ts +69 -226
  162. package/src/client/ClientDescriptor.ts +53 -184
  163. package/src/context/Time.ts +35 -0
  164. package/src/context/context.ts +200 -0
  165. package/src/entities/DocumentManager.ts +0 -3
  166. package/src/entities/Entity.test.ts +9 -9
  167. package/src/entities/Entity.ts +6 -12
  168. package/src/entities/EntityCache.ts +0 -9
  169. package/src/entities/EntityMetadata.ts +4 -4
  170. package/src/entities/EntityStore.ts +26 -29
  171. package/src/entities/OperationBatcher.ts +9 -11
  172. package/src/errors.ts +13 -0
  173. package/src/files/EntityFile.ts +16 -5
  174. package/src/files/FileManager.ts +35 -245
  175. package/src/files/utils.ts +0 -15
  176. package/src/index.ts +2 -1
  177. package/src/{metadata → persistence}/MessageCreator.ts +46 -36
  178. package/src/persistence/PersistenceFiles.ts +227 -0
  179. package/src/persistence/PersistenceMetadata.ts +425 -0
  180. package/src/persistence/PersistenceQueries.ts +22 -0
  181. package/src/persistence/PersistenceRebaser.ts +171 -0
  182. package/src/{IDBService.ts → persistence/idb/IdbService.ts} +45 -12
  183. package/src/{files/FileStorage.ts → persistence/idb/files/IdbPersistenceFileDb.ts} +128 -86
  184. package/src/persistence/idb/idbPersistence.ts +116 -0
  185. package/src/persistence/idb/metadata/IdbMetadataDb.ts +460 -0
  186. package/src/{metadata → persistence/idb/metadata}/openMetadataDatabase.ts +21 -4
  187. package/src/persistence/idb/queries/IdbQueryDb.ts +251 -0
  188. package/src/{migration → persistence/idb/queries/migration}/db.ts +18 -72
  189. package/src/{migration → persistence/idb/queries/migration}/engine.ts +39 -62
  190. package/src/{migration → persistence/idb/queries/migration}/migrations.ts +13 -18
  191. package/src/{migration → persistence/idb/queries/migration}/openQueryDatabase.ts +5 -14
  192. package/src/{migration → persistence/idb/queries/migration}/paths.ts +4 -3
  193. package/src/persistence/idb/queries/migration/types.ts +8 -0
  194. package/src/persistence/idb/queries/ranges.ts +107 -0
  195. package/src/{idb.ts → persistence/idb/util.ts} +75 -0
  196. package/src/persistence/interfaces.ts +240 -0
  197. package/src/persistence/persistence.ts +223 -0
  198. package/src/queries/BaseQuery.ts +5 -1
  199. package/src/queries/CollectionQueries.ts +2 -2
  200. package/src/queries/FindAllQuery.ts +1 -3
  201. package/src/queries/FindInfiniteQuery.ts +2 -5
  202. package/src/queries/FindOneQuery.ts +1 -3
  203. package/src/queries/FindPageQuery.ts +1 -3
  204. package/src/queries/QueryCache.ts +20 -1
  205. package/src/sync/FileSync.ts +93 -30
  206. package/src/sync/PresenceManager.ts +5 -7
  207. package/src/sync/PushPullSync.ts +23 -19
  208. package/src/sync/Sync.ts +71 -37
  209. package/src/sync/WebSocketSync.ts +41 -27
  210. package/src/utils/Disposable.ts +7 -4
  211. package/src/utils/wip.ts +5 -0
  212. package/dist/esm/IDBService.js.map +0 -1
  213. package/dist/esm/__tests__/legacyOids.test.d.ts +0 -1
  214. package/dist/esm/__tests__/legacyOids.test.js +0 -352
  215. package/dist/esm/__tests__/legacyOids.test.js.map +0 -1
  216. package/dist/esm/context.d.ts +0 -45
  217. package/dist/esm/files/FileStorage.d.ts +0 -47
  218. package/dist/esm/files/FileStorage.js.map +0 -1
  219. package/dist/esm/idb.js.map +0 -1
  220. package/dist/esm/metadata/AckInfoStore.d.ts +0 -10
  221. package/dist/esm/metadata/AckInfoStore.js +0 -22
  222. package/dist/esm/metadata/AckInfoStore.js.map +0 -1
  223. package/dist/esm/metadata/BaselinesStore.d.ts +0 -40
  224. package/dist/esm/metadata/BaselinesStore.js +0 -102
  225. package/dist/esm/metadata/BaselinesStore.js.map +0 -1
  226. package/dist/esm/metadata/LocalReplicaStore.d.ts +0 -19
  227. package/dist/esm/metadata/LocalReplicaStore.js +0 -56
  228. package/dist/esm/metadata/LocalReplicaStore.js.map +0 -1
  229. package/dist/esm/metadata/MessageCreator.js.map +0 -1
  230. package/dist/esm/metadata/Metadata.d.ts +0 -146
  231. package/dist/esm/metadata/Metadata.js +0 -452
  232. package/dist/esm/metadata/Metadata.js.map +0 -1
  233. package/dist/esm/metadata/OperationsStore.d.ts +0 -62
  234. package/dist/esm/metadata/OperationsStore.js +0 -175
  235. package/dist/esm/metadata/OperationsStore.js.map +0 -1
  236. package/dist/esm/metadata/SchemaStore.d.ts +0 -9
  237. package/dist/esm/metadata/SchemaStore.js +0 -35
  238. package/dist/esm/metadata/SchemaStore.js.map +0 -1
  239. package/dist/esm/metadata/openMetadataDatabase.js.map +0 -1
  240. package/dist/esm/migration/db.js.map +0 -1
  241. package/dist/esm/migration/engine.d.ts +0 -15
  242. package/dist/esm/migration/engine.js.map +0 -1
  243. package/dist/esm/migration/errors.d.ts +0 -5
  244. package/dist/esm/migration/errors.js +0 -8
  245. package/dist/esm/migration/errors.js.map +0 -1
  246. package/dist/esm/migration/migrations.js.map +0 -1
  247. package/dist/esm/migration/openQueryDatabase.js.map +0 -1
  248. package/dist/esm/migration/openWIPDatabase.d.ts +0 -11
  249. package/dist/esm/migration/openWIPDatabase.js +0 -65
  250. package/dist/esm/migration/openWIPDatabase.js.map +0 -1
  251. package/dist/esm/migration/paths.js.map +0 -1
  252. package/dist/esm/migration/paths.test.js.map +0 -1
  253. package/dist/esm/migration/types.d.ts +0 -3
  254. package/dist/esm/migration/types.js.map +0 -1
  255. package/dist/esm/queries/QueryableStorage.d.ts +0 -20
  256. package/dist/esm/queries/QueryableStorage.js +0 -90
  257. package/dist/esm/queries/QueryableStorage.js.map +0 -1
  258. package/dist/esm/queries/dbQueries.d.ts +0 -22
  259. package/dist/esm/queries/dbQueries.js +0 -130
  260. package/dist/esm/queries/dbQueries.js.map +0 -1
  261. package/src/__tests__/legacyOids.test.ts +0 -375
  262. package/src/context.ts +0 -55
  263. package/src/metadata/AckInfoStore.ts +0 -30
  264. package/src/metadata/BaselinesStore.ts +0 -188
  265. package/src/metadata/LocalReplicaStore.ts +0 -79
  266. package/src/metadata/Metadata.ts +0 -685
  267. package/src/metadata/OperationsStore.ts +0 -332
  268. package/src/metadata/SchemaStore.ts +0 -47
  269. package/src/migration/errors.ts +0 -7
  270. package/src/migration/openWIPDatabase.ts +0 -97
  271. package/src/migration/types.ts +0 -4
  272. package/src/queries/QueryableStorage.ts +0 -122
  273. package/src/queries/dbQueries.ts +0 -161
  274. /package/dist/esm/{context.js → context/context.js} +0 -0
  275. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.d.ts +0 -0
  276. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.d.ts +0 -0
  277. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.js +0 -0
  278. /package/dist/esm/{migration → persistence/idb/queries/migration}/types.js +0 -0
  279. /package/src/{migration → persistence/idb/queries/migration}/paths.test.ts +0 -0
@@ -0,0 +1,223 @@
1
+ import { EventSubscriber, getOidRoot, VerdantError } from '@verdant-web/common';
2
+ import { Context, InitialContext } from '../context/context.js';
3
+ import { getWipNamespace } from '../utils/wip.js';
4
+ import { ExportedData } from './interfaces.js';
5
+ import { PersistenceFiles } from './PersistenceFiles.js';
6
+ import { PersistenceMetadata } from './PersistenceMetadata.js';
7
+ import { PersistenceQueries } from './PersistenceQueries.js';
8
+
9
+ export async function initializePersistence(
10
+ ctx: InitialContext,
11
+ ): Promise<Context> {
12
+ let context = ctx as any as Context;
13
+ if (ctx.schema.wip) {
14
+ // this is a WIP database, so we need to create a new namespace for the WIP data.
15
+ context.namespace = getWipNamespace(ctx.originalNamespace, ctx.schema);
16
+ context.log('info', 'Switched to WIP namespace', context.namespace);
17
+ // check if this WIP database is already in use
18
+ const namespaces = await context.persistence.getNamespaces();
19
+
20
+ if (!namespaces.includes(context.namespace)) {
21
+ // copy all data to WIP namespace -- from the current version of local
22
+ // data, not the WIP schema version. this may not be n-1, we might
23
+ // be loading a WIP schema over older data.
24
+ const currentVersion = await context.persistence.getNamespaceVersion(
25
+ context.originalNamespace,
26
+ );
27
+
28
+ if (currentVersion === 0) {
29
+ // there is no existing data. nothing to copy.
30
+ } else {
31
+ const currentSchema = ctx.oldSchemas?.find(
32
+ (s) => s.version === currentVersion,
33
+ );
34
+ if (!currentSchema) {
35
+ throw new VerdantError(
36
+ VerdantError.Code.MigrationPathNotFound,
37
+ undefined,
38
+ `Trying to open WIP database for version ${ctx.schema.version}, but the current local data is version ${currentVersion} and a historical schema for that version is not available.`,
39
+ );
40
+ }
41
+ context.log(
42
+ 'info',
43
+ `Copying data from ${context.originalNamespace} to ${context.namespace}`,
44
+ );
45
+ await context.persistence.copyNamespace(
46
+ context.originalNamespace,
47
+ context.namespace,
48
+ {
49
+ ...context,
50
+ schema: currentSchema,
51
+ },
52
+ );
53
+ }
54
+ }
55
+ }
56
+
57
+ context.log('info', 'Opening persistence metadata');
58
+ context.meta = new PersistenceMetadata(
59
+ await ctx.persistence.openMetadata(ctx),
60
+ ctx,
61
+ );
62
+
63
+ context.log('info', 'Opening persistence files');
64
+ context.files = new PersistenceFiles(
65
+ await ctx.persistence.openFiles(context),
66
+ context,
67
+ );
68
+
69
+ context.log('info', 'Opening persistence queries');
70
+ context.queries = new PersistenceQueries(
71
+ await ctx.persistence.openQueries(context),
72
+ context,
73
+ );
74
+
75
+ if (!ctx.schema.wip) {
76
+ const namespaces = await ctx.persistence.getNamespaces();
77
+ // cleanup old WIP databases
78
+ for (const namespace of namespaces) {
79
+ if (namespace.startsWith(`@@wip_`)) {
80
+ ctx.log('debug', 'Cleaning up old WIP namespace', namespace);
81
+ await ctx.persistence.deleteNamespace(namespace, ctx);
82
+ }
83
+ }
84
+ }
85
+
86
+ return context;
87
+ }
88
+
89
+ export async function importPersistence(
90
+ ctx: Context,
91
+ exportedData: ExportedData,
92
+ ): Promise<void> {
93
+ ctx.log('info', 'Importing data from export');
94
+ // open persistence at the version of the import
95
+ const exportedSchema = ctx.oldSchemas?.find(
96
+ (s) => s.version === exportedData.data.schemaVersion,
97
+ );
98
+ if (!exportedSchema) {
99
+ // this means the user hasn't upgraded their CLI/client to include old schemas, or
100
+ // this exported data comes from a version which has since been shortcut.
101
+ throw new Error(
102
+ `Could not find schema for version ${exportedData.data.schemaVersion}`,
103
+ );
104
+ }
105
+
106
+ // using a new namespace to put all of this into a temporary zone
107
+ const importedNamespace = `@@import_${Date.now()}`;
108
+
109
+ const importedContext = await initializePersistence({
110
+ ...ctx,
111
+ schema: exportedSchema,
112
+ namespace: importedNamespace,
113
+ originalNamespace: importedNamespace,
114
+ // no-op entity events -- don't need to inform queries of changes.
115
+ entityEvents: new EventSubscriber(),
116
+ config: {
117
+ ...ctx.config,
118
+ persistence: {
119
+ ...ctx.config.persistence,
120
+ disableRebasing: true,
121
+ },
122
+ },
123
+ });
124
+ // load imported data into persistence
125
+ await importedContext.meta.resetFrom(exportedData.data);
126
+ // need to write indexes here!
127
+ const affectedOids = new Set<string>();
128
+ for (const baseline of exportedData.data.baselines) {
129
+ affectedOids.add(getOidRoot(baseline.oid));
130
+ }
131
+ for (const operation of exportedData.data.operations) {
132
+ affectedOids.add(getOidRoot(operation.oid));
133
+ }
134
+ const toSave = await Promise.all(
135
+ Array.from(affectedOids).map(async (oid) => {
136
+ const snapshot = await importedContext.meta.getDocumentSnapshot(oid);
137
+ return {
138
+ oid,
139
+ getSnapshot: () => snapshot,
140
+ };
141
+ }),
142
+ );
143
+ await importedContext.queries.saveEntities(toSave);
144
+ await importedContext.files.import(exportedData);
145
+
146
+ ctx.log('debug', 'Imported data into temporary namespace', importedNamespace);
147
+
148
+ // shut down the imported databases
149
+ await importedContext.queries.dispose();
150
+ await importedContext.meta.dispose();
151
+ await importedContext.files.dispose();
152
+
153
+ if (exportedSchema.version !== ctx.schema.version) {
154
+ // an upgrade of the imported data is needed ; it's an older version
155
+ // of the schema.
156
+ ctx.log('debug', 'Shut down imported databases');
157
+
158
+ // upgrade the imported data to the latest schema
159
+ const currentSchema = ctx.schema;
160
+ const upgradedContext = await initializePersistence({
161
+ ...importedContext,
162
+ schema: currentSchema,
163
+ });
164
+
165
+ ctx.log('debug', 'Upgraded imported data to current schema');
166
+
167
+ await upgradedContext.queries.dispose();
168
+ await upgradedContext.meta.dispose();
169
+ await upgradedContext.files.dispose();
170
+
171
+ ctx.log('debug', 'Shut down upgraded databases');
172
+ }
173
+
174
+ // copy the imported data into the current namespace
175
+ await ctx.persistence.copyNamespace(importedNamespace, ctx.namespace, ctx);
176
+
177
+ // verify integrity -- this can only be done if imported data was same
178
+ // version as current schema, because migrations could add or remove
179
+ // operations. still, it's a good sanity check.
180
+ if (exportedData.data.schemaVersion === ctx.schema.version) {
181
+ const stats = await ctx.meta.stats();
182
+ if (stats.operationsSize.count !== exportedData.data.operations.length) {
183
+ ctx.log(
184
+ 'critical',
185
+ 'Imported operations count mismatch',
186
+ 'expected',
187
+ exportedData.data.operations.length,
188
+ 'actual',
189
+ stats.operationsSize.count,
190
+ );
191
+ throw new VerdantError(
192
+ VerdantError.Code.ImportFailed,
193
+ undefined,
194
+ 'Imported operations count mismatch',
195
+ );
196
+ }
197
+ if (stats.baselinesSize.count !== exportedData.data.baselines.length) {
198
+ ctx.log(
199
+ 'critical',
200
+ 'Imported documents count mismatch',
201
+ 'expected',
202
+ exportedData.data.baselines.length,
203
+ 'actual',
204
+ stats.baselinesSize.count,
205
+ );
206
+ throw new VerdantError(
207
+ VerdantError.Code.ImportFailed,
208
+ undefined,
209
+ 'Imported documents count mismatch',
210
+ );
211
+ }
212
+ }
213
+
214
+ ctx.log('debug', 'Data copied to primary namespace');
215
+
216
+ // cleanup the imported namespace
217
+ await ctx.persistence.deleteNamespace(importedNamespace, ctx);
218
+
219
+ ctx.log('debug', 'Deleted temporary namespace');
220
+
221
+ ctx.internalEvents.emit('persistenceReset');
222
+ ctx.log('info', 'Data imported successfully');
223
+ }
@@ -1,5 +1,5 @@
1
1
  import { EventSubscriber } from '@verdant-web/common';
2
- import { Context } from '../context.js';
2
+ import { Context } from '../context/context.js';
3
3
  import { Entity } from '../entities/Entity.js';
4
4
  import { Disposable } from '../utils/Disposable.js';
5
5
  import { filterResultSet } from './utils.js';
@@ -253,4 +253,8 @@ export abstract class BaseQuery<T> extends Disposable {
253
253
  [ON_ALL_UNSUBSCRIBED] = (handler: (query: BaseQuery<T>) => void) => {
254
254
  this._allUnsubscribedHandler = handler;
255
255
  };
256
+
257
+ get __rawValue() {
258
+ return this._rawValue;
259
+ }
256
260
  }
@@ -3,7 +3,7 @@ import {
3
3
  CollectionFilter,
4
4
  hashObject,
5
5
  } from '@verdant-web/common';
6
- import { Context } from '../context.js';
6
+ import { Context } from '../context/context.js';
7
7
  import { EntityStore } from '../entities/EntityStore.js';
8
8
  import { GetQuery } from './GetQuery.js';
9
9
  import { QueryCache } from './QueryCache.js';
@@ -12,7 +12,7 @@ import { FindPageQuery } from './FindPageQuery.js';
12
12
  import { FindInfiniteQuery } from './FindInfiniteQuery.js';
13
13
  import { FindAllQuery } from './FindAllQuery.js';
14
14
  import { DocumentManager } from '../entities/DocumentManager.js';
15
- import { Entity, ObjectEntity } from '../index.js';
15
+ import { ObjectEntity } from '../index.js';
16
16
  import { UPDATE } from './BaseQuery.js';
17
17
 
18
18
  export class CollectionQueries<
@@ -1,6 +1,5 @@
1
1
  import { CollectionFilter } from '@verdant-web/common';
2
2
  import { BaseQuery, BaseQueryOptions, UPDATE } from './BaseQuery.js';
3
- import { findAllOids } from './dbQueries.js';
4
3
  import { areIndexesEqual } from './utils.js';
5
4
 
6
5
  export class FindAllQuery<T> extends BaseQuery<T[]> {
@@ -24,10 +23,9 @@ export class FindAllQuery<T> extends BaseQuery<T[]> {
24
23
  }
25
24
 
26
25
  protected run = async () => {
27
- const oids = await findAllOids({
26
+ const { result: oids } = await this.context.queries.findAllOids({
28
27
  collection: this.collection,
29
28
  index: this.index,
30
- context: this.context,
31
29
  });
32
30
  this.context.log(
33
31
  'debug',
@@ -1,6 +1,5 @@
1
1
  import { CollectionFilter } from '@verdant-web/common';
2
2
  import { BaseQuery, BaseQueryOptions, UPDATE } from './BaseQuery.js';
3
- import { findPageOfOids } from './dbQueries.js';
4
3
  import { areIndexesEqual } from './utils.js';
5
4
 
6
5
  export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
@@ -38,9 +37,8 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
38
37
  }
39
38
 
40
39
  protected run = async () => {
41
- const { result, hasNextPage } = await findPageOfOids({
40
+ const { result, hasNextPage } = await this.context.queries.findAllOids({
42
41
  collection: this.collection,
43
- context: this.context,
44
42
  limit: this._pageSize * this._upToPage,
45
43
  offset: 0,
46
44
  index: this.index,
@@ -50,9 +48,8 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
50
48
  };
51
49
 
52
50
  public loadMore = async () => {
53
- const { result, hasNextPage } = await findPageOfOids({
51
+ const { result, hasNextPage } = await this.context.queries.findAllOids({
54
52
  collection: this.collection,
55
- context: this.context,
56
53
  limit: this._pageSize,
57
54
  offset: this._pageSize * this._upToPage,
58
55
  index: this.index,
@@ -1,6 +1,5 @@
1
1
  import { CollectionFilter } from '@verdant-web/common';
2
2
  import { BaseQuery, BaseQueryOptions, UPDATE } from './BaseQuery.js';
3
- import { findOneOid } from './dbQueries.js';
4
3
  import { areIndexesEqual } from './utils.js';
5
4
 
6
5
  export class FindOneQuery<T> extends BaseQuery<T | null> {
@@ -24,10 +23,9 @@ export class FindOneQuery<T> extends BaseQuery<T | null> {
24
23
  }
25
24
 
26
25
  protected run = async () => {
27
- const oid = await findOneOid({
26
+ const oid = await this.context.queries.findOneOid({
28
27
  collection: this.collection,
29
28
  index: this.index,
30
- context: this.context,
31
29
  });
32
30
  this.setValue(oid ? await this.hydrate(oid) : null);
33
31
  };
@@ -1,6 +1,5 @@
1
1
  import { CollectionFilter } from '@verdant-web/common';
2
2
  import { BaseQuery, BaseQueryOptions, UPDATE } from './BaseQuery.js';
3
- import { findPageOfOids } from './dbQueries.js';
4
3
  import { areIndexesEqual } from './utils.js';
5
4
 
6
5
  export class FindPageQuery<T> extends BaseQuery<T[]> {
@@ -49,10 +48,9 @@ export class FindPageQuery<T> extends BaseQuery<T[]> {
49
48
  }
50
49
 
51
50
  protected run = async () => {
52
- const { result, hasNextPage } = await findPageOfOids({
51
+ const { result, hasNextPage } = await this.context.queries.findAllOids({
53
52
  collection: this.collection,
54
53
  index: this.index,
55
- context: this.context,
56
54
  limit: this._pageSize,
57
55
  offset: this._page * this._pageSize,
58
56
  });
@@ -1,4 +1,4 @@
1
- import { Context } from '../context.js';
1
+ import { Context } from '../context/context.js';
2
2
  import { Disposable } from '../utils/Disposable.js';
3
3
  import { BaseQuery, ON_ALL_UNSUBSCRIBED } from './BaseQuery.js';
4
4
 
@@ -18,6 +18,12 @@ export class QueryCache extends Disposable {
18
18
 
19
19
  this._evictionTime = evictionTime;
20
20
  this.context = context;
21
+ this.addDispose(
22
+ this.context.internalEvents.subscribe(
23
+ 'persistenceReset',
24
+ this.forceRefreshAll,
25
+ ),
26
+ );
21
27
  }
22
28
 
23
29
  get<T extends BaseQuery<any>>(key: string): T | null {
@@ -40,6 +46,7 @@ export class QueryCache extends Disposable {
40
46
  update?.(existing);
41
47
  return existing;
42
48
  }
49
+ this.context.log('debug', 'QueryCache: creating new query', key);
43
50
  return this.set(create());
44
51
  }
45
52
 
@@ -55,11 +62,23 @@ export class QueryCache extends Disposable {
55
62
  };
56
63
 
57
64
  dropAll = () => {
65
+ this.context.log(
66
+ 'debug',
67
+ 'QueryCache: drop all',
68
+ this._cache.size,
69
+ 'queries',
70
+ );
58
71
  this._cache.forEach((query) => query.dispose());
59
72
  this._cache.clear();
60
73
  };
61
74
 
62
75
  forceRefreshAll = () => {
76
+ this.context.log(
77
+ 'debug',
78
+ 'QueryCache: force refresh all',
79
+ this._cache.size,
80
+ 'queries',
81
+ );
63
82
  this._cache.forEach((q) => q.execute());
64
83
  };
65
84
  }
@@ -1,9 +1,10 @@
1
1
  import { FileData } from '@verdant-web/common';
2
2
  import { ServerSyncEndpointProvider } from './ServerSyncEndpointProvider.js';
3
+ import { Context } from '../context/context.js';
4
+ import { Disposable } from '../utils/Disposable.js';
3
5
 
4
6
  export interface FileUploadResult {
5
7
  success: boolean;
6
- retry: boolean;
7
8
  error?: string;
8
9
  }
9
10
 
@@ -14,26 +15,47 @@ export type FilePullResult =
14
15
  }
15
16
  | {
16
17
  success: false;
17
- retry: boolean;
18
18
  error?: any;
19
19
  };
20
20
 
21
- export class FileSync {
21
+ export class FileSync extends Disposable {
22
22
  private endpointProvider: ServerSyncEndpointProvider;
23
- private log: (...args: any[]) => any;
23
+ private ctx;
24
24
 
25
25
  constructor({
26
26
  endpointProvider,
27
- log,
27
+ ctx,
28
28
  }: {
29
29
  endpointProvider: ServerSyncEndpointProvider;
30
- log: (...args: any[]) => any;
30
+ ctx: Context;
31
31
  }) {
32
+ super();
32
33
  this.endpointProvider = endpointProvider;
33
- this.log = log;
34
+ this.ctx = ctx;
35
+ this.addDispose(
36
+ ctx.internalEvents.subscribe('fileAdded', this.onFileAdded),
37
+ );
34
38
  }
35
39
 
36
- uploadFile = async (data: FileData): Promise<FileUploadResult> => {
40
+ private onFileAdded = async (data: FileData) => {
41
+ if (data.remote) return;
42
+ this.ctx.log('debug', 'Uploading file', data.id, data.name);
43
+ try {
44
+ await this.uploadFile(data);
45
+ this.ctx.internalEvents.emit(`fileUploaded:${data.id}`);
46
+ } catch (e) {
47
+ this.ctx.log('error', 'File upload failed', e);
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Attempts to upload a file to the sync server. Will be retried
53
+ * according to retry config.
54
+ */
55
+ uploadFile = async (
56
+ data: FileData,
57
+ retries: { max: number; current: number } = { current: 0, max: 3 },
58
+ ): Promise<FileUploadResult> => {
37
59
  const file = data.file;
38
60
 
39
61
  if (!file) {
@@ -58,31 +80,54 @@ export class FileSync {
58
80
  });
59
81
 
60
82
  if (response.ok) {
61
- this.log('info', 'File upload successful');
83
+ this.ctx.log('info', 'File upload successful');
62
84
  return {
63
85
  success: true,
64
- retry: false,
65
86
  };
66
87
  } else {
67
88
  const responseText = await response.text();
68
- this.log('error', 'File upload failed', response.status, responseText);
89
+ this.ctx.log(
90
+ 'error',
91
+ 'File upload failed',
92
+ response.status,
93
+ responseText,
94
+ );
95
+ if (response.status < 500 || retries.current >= retries.max) {
96
+ return {
97
+ success: false,
98
+ error: `Failed to upload file: ${response.status} ${responseText}`,
99
+ };
100
+ }
101
+ await new Promise((resolve) => setTimeout(resolve, 1000));
102
+ return this.uploadFile(data, {
103
+ max: retries.max,
104
+ current: retries.current + 1,
105
+ });
106
+ }
107
+ } catch (e) {
108
+ this.ctx.log('error', 'File upload failed', e);
109
+ if (retries.current >= retries.max) {
69
110
  return {
70
111
  success: false,
71
- retry: response.status >= 500,
72
- error: `Failed to upload file: ${response.status} ${responseText}`,
112
+ error: (e as Error).message,
73
113
  };
74
114
  }
75
- } catch (e) {
76
- this.log('error', 'File upload failed', e);
77
- return {
78
- success: false,
79
- retry: true,
80
- error: (e as Error).message,
81
- };
115
+ await new Promise((resolve) => setTimeout(resolve, 1000));
116
+ return this.uploadFile(data, {
117
+ max: retries.max,
118
+ current: retries.current + 1,
119
+ });
82
120
  }
83
121
  };
84
122
 
85
- getFile = async (id: string): Promise<FilePullResult> => {
123
+ /**
124
+ * Pulls a file from the server by its ID. Will be retried
125
+ * according to retry config.
126
+ */
127
+ getFile = async (
128
+ id: string,
129
+ retries: { current: number; max: number } = { current: 0, max: 3 },
130
+ ): Promise<FilePullResult> => {
86
131
  const { files: fileEndpoint, token } =
87
132
  await this.endpointProvider.getEndpoints();
88
133
 
@@ -103,24 +148,42 @@ export class FileSync {
103
148
  data,
104
149
  };
105
150
  } else {
106
- this.log(
151
+ this.ctx.log(
107
152
  'error',
108
153
  'File information fetch failed',
109
154
  response.status,
110
155
  await response.text(),
111
156
  );
157
+ if (
158
+ (response.status < 500 && response.status !== 404) ||
159
+ retries.current >= retries.max
160
+ ) {
161
+ return {
162
+ success: false,
163
+ error: `Failed to fetch file: ${response.status}`,
164
+ };
165
+ }
166
+
167
+ await new Promise((resolve) => setTimeout(resolve, 1000));
168
+ return this.getFile(id, {
169
+ current: retries.current + 1,
170
+ max: retries.max,
171
+ });
172
+ }
173
+ } catch (e) {
174
+ this.ctx.log('error', 'File information fetch failed', e);
175
+ if (retries.current >= retries.max) {
112
176
  return {
113
177
  success: false,
114
- retry: response.status >= 500 || response.status === 404,
178
+ error: (e as Error).message,
115
179
  };
116
180
  }
117
- } catch (e) {
118
- this.log('error', 'File information fetch failed', e);
119
- return {
120
- success: false,
121
- error: e,
122
- retry: true,
123
- };
181
+
182
+ await new Promise((resolve) => setTimeout(resolve, 1000));
183
+ return this.getFile(id, {
184
+ current: retries.current + 1,
185
+ max: retries.max,
186
+ });
124
187
  }
125
188
  };
126
189
  }
@@ -7,10 +7,8 @@ import {
7
7
  initialInternalPresence,
8
8
  } from '@verdant-web/common';
9
9
  import type { UserInfo } from '../index.js';
10
- import {
11
- LocalReplicaInfo,
12
- LocalReplicaStore,
13
- } from '../metadata/LocalReplicaStore.js';
10
+ import { Context } from '../context/context.js';
11
+ import { LocalReplicaInfo } from '../persistence/interfaces.js';
14
12
 
15
13
  export const HANDLE_MESSAGE = Symbol('handleMessage');
16
14
 
@@ -77,12 +75,12 @@ export class PresenceManager<
77
75
  initialPresence,
78
76
  updateBatchTimeout = 200,
79
77
  defaultProfile,
80
- replicaStore,
78
+ ctx,
81
79
  }: {
82
80
  initialPresence: Presence;
83
81
  defaultProfile: Profile;
84
82
  updateBatchTimeout?: number;
85
- replicaStore: LocalReplicaStore;
83
+ ctx: Context;
86
84
  }) {
87
85
  super();
88
86
  this.self.presence = initialPresence;
@@ -92,7 +90,7 @@ export class PresenceManager<
92
90
  this.self.replicaId = '';
93
91
 
94
92
  // set the local replica ID as soon as it's loaded
95
- replicaStore.get().then((info) => {
93
+ ctx.meta.getLocalReplica().then((info) => {
96
94
  this.self.replicaId = info.id;
97
95
  });
98
96