@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
@@ -0,0 +1,116 @@
1
+ import { Context, InitialContext } from '../../context/context.js';
2
+ import { PersistenceImplementation, PersistenceFileDb } from '../interfaces.js';
3
+ import { IdbPersistenceFileDb } from './files/IdbPersistenceFileDb.js';
4
+ import { IdbMetadataDb } from './metadata/IdbMetadataDb.js';
5
+ import { openMetadataDatabase } from './metadata/openMetadataDatabase.js';
6
+ import { IdbQueryDb } from './queries/IdbQueryDb.js';
7
+ import { openQueryDatabase } from './queries/migration/openQueryDatabase.js';
8
+ import { PersistenceMetadata } from '../PersistenceMetadata.js';
9
+ import {
10
+ closeDatabase,
11
+ deleteDatabase,
12
+ getMetadataDbName,
13
+ getNamespaceFromDatabaseInfo,
14
+ getSizesOfAllObjectStores,
15
+ } from './util.js';
16
+ import { getDatabaseVersion } from './queries/migration/db.js';
17
+
18
+ export class IdbPersistence implements PersistenceImplementation {
19
+ private metadataDb: IDBDatabase | undefined;
20
+ constructor(private indexedDB: IDBFactory = window.indexedDB) {}
21
+
22
+ getNamespaces = async (): Promise<string[]> => {
23
+ // list all idb database names
24
+ const dbs = await this.indexedDB.databases();
25
+ return Array.from(
26
+ new Set<string>(
27
+ dbs.map(getNamespaceFromDatabaseInfo).filter((n): n is string => !!n),
28
+ ),
29
+ );
30
+ };
31
+
32
+ getNamespaceVersion = async (namespace: string): Promise<number> => {
33
+ return getDatabaseVersion(this.indexedDB, namespace);
34
+ };
35
+
36
+ deleteNamespace = async (
37
+ namespace: string,
38
+ ctx: InitialContext,
39
+ ): Promise<void> => {
40
+ await Promise.all([
41
+ deleteDatabase(getMetadataDbName(namespace), this.indexedDB),
42
+ deleteDatabase([namespace, 'collections'].join('_'), this.indexedDB),
43
+ ]);
44
+ };
45
+
46
+ openFiles(
47
+ ctx: Omit<Context, 'files' | 'queries'>,
48
+ ): Promise<PersistenceFileDb> {
49
+ if (!this.metadataDb) {
50
+ throw new Error(
51
+ 'Metadata database must be opened first. This is a bug in Verdant.',
52
+ );
53
+ }
54
+ return Promise.resolve(new IdbPersistenceFileDb(this.metadataDb, ctx));
55
+ }
56
+
57
+ openMetadata = async (ctx: InitialContext) => {
58
+ const { db } = await openMetadataDatabase({
59
+ indexedDB: this.indexedDB,
60
+ log: ctx.log,
61
+ namespace: ctx.namespace,
62
+ });
63
+ this.metadataDb = db;
64
+ return new IdbMetadataDb(db, ctx);
65
+ };
66
+
67
+ openQueries = async (ctx: InitialContext & { meta: PersistenceMetadata }) => {
68
+ const db = await openQueryDatabase({
69
+ version: ctx.schema.version,
70
+ indexedDB: this.indexedDB,
71
+ migrations: ctx.migrations,
72
+ context: ctx,
73
+ });
74
+ return new IdbQueryDb(db, ctx);
75
+ };
76
+
77
+ copyNamespace = async (
78
+ from: string,
79
+ to: string,
80
+ ctx: InitialContext,
81
+ ): Promise<void> => {
82
+ const fromCtx = { ...ctx, namespace: from, originalNamespace: from };
83
+ const fromMetaDb = await this.openMetadata(fromCtx);
84
+ const fromMeta = new PersistenceMetadata(fromMetaDb, fromCtx);
85
+ const fromQueries = await this.openQueries({ ...fromCtx, meta: fromMeta });
86
+ ctx.log('info', `Copying data from ${from} to ${to}`);
87
+
88
+ const { db: toMetaDb } = await openMetadataDatabase({
89
+ indexedDB: this.indexedDB,
90
+ log: ctx.log,
91
+ namespace: to,
92
+ });
93
+ ctx.log('debug', 'Metadata database opened');
94
+ await fromMetaDb.cloneTo(toMetaDb);
95
+ ctx.log('debug', 'Metadata copied');
96
+
97
+ const toQueryDb = await openQueryDatabase({
98
+ version: ctx.schema.version,
99
+ indexedDB: this.indexedDB,
100
+ migrations: ctx.migrations,
101
+ context: { ...ctx, namespace: to, originalNamespace: to, meta: fromMeta },
102
+ });
103
+ await fromQueries.cloneTo(toQueryDb);
104
+ ctx.log('debug', 'Indexes copied');
105
+ ctx.log(
106
+ 'debug',
107
+ 'New document store stats:',
108
+ await getSizesOfAllObjectStores(toQueryDb),
109
+ );
110
+
111
+ await fromMetaDb.dispose();
112
+ await closeDatabase(toMetaDb);
113
+ await fromQueries.dispose();
114
+ await closeDatabase(toQueryDb);
115
+ };
116
+ }
@@ -0,0 +1,460 @@
1
+ import {
2
+ createCompoundIndexValue,
3
+ createLowerBoundIndexValue,
4
+ createUpperBoundIndexValue,
5
+ DocumentBaseline,
6
+ getLegacyDotOidSubIdRange,
7
+ getOidRoot,
8
+ getOidSubIdRange,
9
+ ObjectIdentifier,
10
+ } from '@verdant-web/common';
11
+ import {
12
+ AbstractTransaction,
13
+ AckInfo,
14
+ ClientOperation,
15
+ CommonQueryOptions,
16
+ Iterator,
17
+ LocalReplicaInfo,
18
+ PersistenceMetadataDb,
19
+ } from '../../interfaces.js';
20
+ import { IdbService } from '../IdbService.js';
21
+ import cuid from 'cuid';
22
+ import { closeDatabase, getSizeOfObjectStore } from '../util.js';
23
+ import { Context } from '../../../context/context.js';
24
+
25
+ export type StoredClientOperation = ClientOperation & {
26
+ /** This acts as the primary key */
27
+ oid_timestamp: string;
28
+ l_t: string;
29
+ d_t: string;
30
+ };
31
+
32
+ export type StoredSchema = {
33
+ type: 'schema';
34
+ schema: string;
35
+ };
36
+
37
+ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
38
+ constructor(
39
+ db: IDBDatabase,
40
+ private ctx: Pick<Context, 'log' | 'namespace'>,
41
+ ) {
42
+ super(db, ctx);
43
+ this.addDispose(() => closeDatabase(db));
44
+ }
45
+
46
+ transaction = (opts: {
47
+ mode?: 'readwrite' | 'readonly';
48
+ storeNames: string[];
49
+ abort?: AbortSignal;
50
+ }) => {
51
+ return this.createTransaction(opts.storeNames, {
52
+ mode: opts.mode,
53
+ abort: opts.abort,
54
+ });
55
+ };
56
+
57
+ getAckInfo = async (): Promise<AckInfo> => {
58
+ const result = await this.run<AckInfo>('info', (store) => store.get('ack'));
59
+ if (result) {
60
+ return result;
61
+ } else {
62
+ return {
63
+ globalAckTimestamp: null,
64
+ type: 'ack',
65
+ };
66
+ }
67
+ };
68
+
69
+ setGlobalAck = async (ack: string): Promise<void> => {
70
+ await this.run(
71
+ 'info',
72
+ (store) => store.put({ type: 'ack', globalAckTimestamp: ack }),
73
+ { mode: 'readwrite' },
74
+ );
75
+ };
76
+
77
+ private _creatingLocalReplica: Promise<void> | undefined;
78
+ private cachedLocalReplica: LocalReplicaInfo | undefined;
79
+
80
+ getLocalReplica = async (
81
+ opts?: CommonQueryOptions,
82
+ ): Promise<LocalReplicaInfo> => {
83
+ if (this.cachedLocalReplica) {
84
+ return this.cachedLocalReplica;
85
+ }
86
+
87
+ const lookup = await this.run<LocalReplicaInfo>(
88
+ 'info',
89
+ (store) => store.get('localReplicaInfo'),
90
+ this.convertOpts(opts),
91
+ );
92
+
93
+ // not cached, not in db, create it
94
+ if (!lookup) {
95
+ // prevent a race condition if get() is called again while we are
96
+ // creating the replica info
97
+ if (!this._creatingLocalReplica) {
98
+ this._creatingLocalReplica = (async () => {
99
+ // create our own replica info now
100
+ const replicaId = cuid();
101
+ const replicaInfo: LocalReplicaInfo = {
102
+ type: 'localReplicaInfo',
103
+ id: replicaId,
104
+ userId: undefined,
105
+ ackedLogicalTime: null,
106
+ lastSyncedLogicalTime: null,
107
+ };
108
+ await this.run('info', (store) => store.put(replicaInfo), {
109
+ mode: 'readwrite',
110
+ });
111
+ this.cachedLocalReplica = replicaInfo;
112
+ })();
113
+ }
114
+ await this._creatingLocalReplica;
115
+
116
+ return this.getLocalReplica(opts);
117
+ }
118
+
119
+ this.cachedLocalReplica = lookup;
120
+ return lookup;
121
+ };
122
+
123
+ updateLocalReplica = async (
124
+ data: Partial<LocalReplicaInfo>,
125
+ opts: CommonQueryOptions = writeOpts,
126
+ ): Promise<void> => {
127
+ const localReplicaInfo = await this.getLocalReplica(opts);
128
+ Object.assign(localReplicaInfo, data);
129
+ await this.run('info', (store) => store.put(localReplicaInfo), {
130
+ mode: 'readwrite',
131
+ });
132
+ this.cachedLocalReplica = localReplicaInfo;
133
+ };
134
+
135
+ iterateDocumentBaselines = async (
136
+ rootOid: string,
137
+ iterator: (baseline: DocumentBaseline) => void,
138
+ opts?: CommonQueryOptions,
139
+ ): Promise<void> => {
140
+ await this.iterate(
141
+ 'baselines',
142
+ (store) => {
143
+ const root = getOidRoot(rootOid);
144
+ const [start, end] = getOidSubIdRange(rootOid);
145
+ // FIXME: get rid of legacy dot OIDs...
146
+ const [dotStart, dotEnd] = getLegacyDotOidSubIdRange(rootOid);
147
+ return [
148
+ store.openCursor(IDBKeyRange.only(root)),
149
+ store.openCursor(IDBKeyRange.bound(start, end, false, false)),
150
+ store.openCursor(IDBKeyRange.bound(dotStart, dotEnd, false, false)),
151
+ ];
152
+ },
153
+ iterator,
154
+ this.convertOpts(opts),
155
+ );
156
+ };
157
+
158
+ iterateCollectionBaselines = async (
159
+ collection: string,
160
+ iterator: (baseline: DocumentBaseline) => void,
161
+ opts?: CommonQueryOptions,
162
+ ): Promise<void> => {
163
+ await this.iterate(
164
+ 'baselines',
165
+ (store) => {
166
+ return [
167
+ store.openCursor(
168
+ IDBKeyRange.bound(collection, collection + '\uffff', false, false),
169
+ ),
170
+ ];
171
+ },
172
+ iterator,
173
+ this.convertOpts(opts),
174
+ );
175
+ };
176
+
177
+ iterateAllBaselines = async (
178
+ iterator: Iterator<DocumentBaseline>,
179
+ opts: CommonQueryOptions,
180
+ ): Promise<void> => {
181
+ await this.iterate(
182
+ 'baselines',
183
+ (store) => store.index('timestamp').openCursor(),
184
+ iterator,
185
+ this.convertOpts(opts),
186
+ );
187
+ };
188
+
189
+ getBaseline = (
190
+ oid: string,
191
+ opts?: CommonQueryOptions,
192
+ ): Promise<DocumentBaseline> => {
193
+ return this.run<DocumentBaseline>(
194
+ 'baselines',
195
+ (store) => store.get(oid),
196
+ this.convertOpts(opts),
197
+ );
198
+ };
199
+
200
+ setBaselines = async (
201
+ baselines: DocumentBaseline[],
202
+ opts: CommonQueryOptions = writeOpts,
203
+ ): Promise<void> => {
204
+ await this.runAll<any>(
205
+ 'baselines',
206
+ (store) => baselines.map((b) => store.put(b)),
207
+ this.convertOpts(opts),
208
+ );
209
+ };
210
+
211
+ deleteBaseline = async (
212
+ oid: string,
213
+ opts: CommonQueryOptions = writeOpts,
214
+ ): Promise<void> => {
215
+ await this.run(
216
+ 'baselines',
217
+ (store) => store.delete(oid),
218
+ this.convertOpts(opts),
219
+ );
220
+ };
221
+
222
+ iterateDocumentOperations = (
223
+ rootOid: string,
224
+ iterator: (op: StoredClientOperation) => void,
225
+ opts?: CommonQueryOptions & { to?: string | null },
226
+ ): Promise<void> => {
227
+ return this.iterate<StoredClientOperation>(
228
+ 'operations',
229
+ (store) => {
230
+ const index = store.index('d_t');
231
+ const start = createLowerBoundIndexValue(rootOid);
232
+ const end = opts?.to
233
+ ? createCompoundIndexValue(rootOid, opts.to)
234
+ : createUpperBoundIndexValue(rootOid);
235
+
236
+ const range = IDBKeyRange.bound(start, end, false, false);
237
+ return index.openCursor(range);
238
+ },
239
+ iterator,
240
+ this.convertOpts(opts),
241
+ );
242
+ };
243
+
244
+ iterateEntityOperations = (
245
+ oid: string,
246
+ iterator: (op: StoredClientOperation) => void,
247
+ opts?: CommonQueryOptions & { to?: string | null },
248
+ ): Promise<void> => {
249
+ // NOTE: this is simplified from original impl.
250
+ // perhaps I missed some nuance as to why it was
251
+ // so complex before?
252
+
253
+ return this.iterate<StoredClientOperation>(
254
+ 'operations',
255
+ (store) => {
256
+ const start = createLowerBoundIndexValue(oid);
257
+ const end = opts?.to
258
+ ? createCompoundIndexValue(oid, opts.to)
259
+ : createUpperBoundIndexValue(oid);
260
+
261
+ const range = IDBKeyRange.bound(start, end, false, false);
262
+ return store.openCursor(range);
263
+ },
264
+ iterator,
265
+ this.convertOpts(opts),
266
+ );
267
+ };
268
+
269
+ consumeEntityOperations = (
270
+ oid: string,
271
+ iterator: Iterator<ClientOperation>,
272
+ opts: CommonQueryOptions & { to?: string | null } = writeOpts,
273
+ ): Promise<void> => {
274
+ return this.iterate<StoredClientOperation>(
275
+ 'operations',
276
+ (store) => {
277
+ const start = createLowerBoundIndexValue(oid);
278
+ const end = opts?.to
279
+ ? createCompoundIndexValue(oid, opts.to)
280
+ : createUpperBoundIndexValue(oid);
281
+
282
+ const range = IDBKeyRange.bound(start, end, false, false);
283
+ return store.openCursor(range);
284
+ },
285
+ (op, store) => {
286
+ iterator(op);
287
+ store.delete(op.oid_timestamp);
288
+ },
289
+ this.convertOpts(opts),
290
+ );
291
+ };
292
+
293
+ iterateCollectionOperations = (
294
+ collection: string,
295
+ iterator: (op: StoredClientOperation) => void,
296
+ opts?: CommonQueryOptions,
297
+ ): Promise<void> => {
298
+ return this.iterate(
299
+ 'operations',
300
+ (store) => {
301
+ return store.openCursor(
302
+ IDBKeyRange.bound(collection, collection + '\uffff', false, false),
303
+ 'next',
304
+ );
305
+ },
306
+ iterator,
307
+ this.convertOpts(opts),
308
+ );
309
+ };
310
+
311
+ iterateLocalOperations = (
312
+ iterator: (op: StoredClientOperation) => void,
313
+ opts?: CommonQueryOptions & {
314
+ after?: string | null;
315
+ },
316
+ ): Promise<void> => {
317
+ return this.iterate(
318
+ 'operations',
319
+ (store) => {
320
+ const start = opts?.after
321
+ ? createCompoundIndexValue(true, opts.after)
322
+ : createLowerBoundIndexValue(true);
323
+ const end = createUpperBoundIndexValue(true);
324
+ const index = store.index('l_t');
325
+ return index.openCursor(
326
+ // NOTE: differs from original impl -- last arg is 'false' instead of 'true'
327
+ IDBKeyRange.bound(start, end, !!opts?.after, false),
328
+ 'next',
329
+ );
330
+ },
331
+ iterator,
332
+ );
333
+ };
334
+
335
+ iterateAllOperations = (
336
+ iterator: (op: StoredClientOperation) => void,
337
+ opts?: CommonQueryOptions & {
338
+ before?: string | null;
339
+ from?: string | null;
340
+ },
341
+ ): Promise<void> => {
342
+ return this.iterate(
343
+ 'operations',
344
+ (store) => {
345
+ const start = opts?.from
346
+ ? createLowerBoundIndexValue(opts.from)
347
+ : undefined;
348
+ const end = opts?.before
349
+ ? createUpperBoundIndexValue(opts.before)
350
+ : createLowerBoundIndexValue(true);
351
+ const range =
352
+ start && end
353
+ ? IDBKeyRange.bound(start, end, false, true)
354
+ : start
355
+ ? IDBKeyRange.lowerBound(start, false)
356
+ : end
357
+ ? IDBKeyRange.upperBound(end, true)
358
+ : undefined;
359
+ return store.index('timestamp').openCursor(range, 'next');
360
+ },
361
+ iterator,
362
+ this.convertOpts(opts),
363
+ );
364
+ };
365
+
366
+ addOperations = async (
367
+ ops: StoredClientOperation[],
368
+ opts: CommonQueryOptions = writeOpts,
369
+ ): Promise<ObjectIdentifier[]> => {
370
+ let affected = new Set<ObjectIdentifier>();
371
+ await this.runAll(
372
+ 'operations',
373
+ (store) =>
374
+ ops.map((op) => {
375
+ affected.add(getOidRoot(op.oid));
376
+ return store.put(this.addOperationIndexes(op));
377
+ }),
378
+ this.convertOpts(opts),
379
+ );
380
+ return Array.from(affected);
381
+ };
382
+
383
+ reset = async ({
384
+ clearReplica,
385
+ transaction,
386
+ }: {
387
+ clearReplica?: boolean;
388
+ transaction?: AbstractTransaction;
389
+ } = {}): Promise<void> => {
390
+ const tx =
391
+ (transaction as IDBTransaction) ||
392
+ this.createTransaction(['info', 'operations', 'baselines'], {
393
+ mode: 'readwrite',
394
+ });
395
+ await Promise.all([
396
+ this.resetLocalReplica(tx, clearReplica),
397
+ this.resetBaselines(tx),
398
+ this.resetOperations(tx),
399
+ ]);
400
+ };
401
+
402
+ stats = async (): Promise<{
403
+ operationsSize: { count: number; size: number };
404
+ baselinesSize: { count: number; size: number };
405
+ }> => {
406
+ const ops = await getSizeOfObjectStore(this.db, 'operations');
407
+ const baselines = await getSizeOfObjectStore(this.db, 'baselines');
408
+ return { operationsSize: ops, baselinesSize: baselines };
409
+ };
410
+
411
+ private resetLocalReplica = async (tx: IDBTransaction, clear = false) => {
412
+ if (clear) {
413
+ return this.run('info', (store) => store.delete('localReplicaInfo'), {
414
+ mode: 'readwrite',
415
+ transaction: tx,
416
+ });
417
+ }
418
+
419
+ const localInfo = await this.getLocalReplica();
420
+ localInfo.ackedLogicalTime = null;
421
+ localInfo.lastSyncedLogicalTime = null;
422
+ await this.run('info', (store) => store.put(localInfo), {
423
+ mode: 'readwrite',
424
+ transaction: tx,
425
+ });
426
+ };
427
+
428
+ private resetBaselines = async (tx: IDBTransaction) => {
429
+ return this.clear('baselines', tx);
430
+ };
431
+
432
+ private resetOperations = async (tx: IDBTransaction) => {
433
+ return this.clear('operations', tx);
434
+ };
435
+
436
+ private addOperationIndexes = (
437
+ op: ClientOperation,
438
+ ): StoredClientOperation => {
439
+ return {
440
+ ...op,
441
+ oid_timestamp: createCompoundIndexValue(op.oid, op.timestamp) as string,
442
+ l_t: createCompoundIndexValue(op.isLocal, op.timestamp) as string,
443
+ d_t: createCompoundIndexValue(getOidRoot(op.oid), op.timestamp) as string,
444
+ };
445
+ };
446
+
447
+ private convertOpts = (opts?: CommonQueryOptions) => {
448
+ if (opts?.transaction && !(opts.transaction instanceof IDBTransaction)) {
449
+ throw new Error(
450
+ `Invalid IndexedDB transaction. You cannot mix persistence providers. ${typeof opts.transaction}`,
451
+ );
452
+ }
453
+ return {
454
+ mode: opts?.mode,
455
+ transaction: opts?.transaction as IDBTransaction | undefined,
456
+ };
457
+ };
458
+ }
459
+
460
+ const writeOpts = { mode: 'readwrite' } as const;
@@ -1,22 +1,31 @@
1
1
  import { replaceLegacyOidsInObject } from '@verdant-web/common';
2
- import { closeDatabase, storeRequestPromise } from '../idb.js';
2
+ import {
3
+ closeDatabase,
4
+ getMetadataDbName,
5
+ storeRequestPromise,
6
+ } from '../util.js';
7
+ import { Context } from '../../../context/context.js';
3
8
 
4
9
  const migrations = [version1, version2, version3, version4, version5, version6];
10
+ export const CURRENT_METADATA_VERSION = migrations.length;
5
11
 
6
12
  export function openMetadataDatabase({
7
13
  indexedDB = window.indexedDB,
8
14
  namespace,
9
15
  log,
10
- metadataVersion = 5,
16
+ metadataVersion = CURRENT_METADATA_VERSION,
11
17
  }: {
12
18
  indexedDB?: IDBFactory;
13
19
  namespace: string;
14
- log?: (...args: any[]) => void;
20
+ log?: Context['log'];
15
21
  metadataVersion?: number;
16
22
  }): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
17
23
  return new Promise<{ wasInitialized: boolean; db: IDBDatabase }>(
18
24
  (resolve, reject) => {
19
- const request = indexedDB.open(`${namespace}_meta`, metadataVersion);
25
+ const request = indexedDB.open(
26
+ getMetadataDbName(namespace),
27
+ metadataVersion,
28
+ );
20
29
  let wasInitialized = false;
21
30
  request.onupgradeneeded = async (event) => {
22
31
  const db = request.result;
@@ -200,6 +209,10 @@ async function version3(db: IDBDatabase, tx: IDBTransaction) {
200
209
  operations.createIndex('timestamp', 'timestamp');
201
210
  }
202
211
 
212
+ /**
213
+ * 3 -> 4 changes:
214
+ * Add files store
215
+ */
203
216
  async function version4(db: IDBDatabase, tx: IDBTransaction) {
204
217
  const files = db.createObjectStore('files', {
205
218
  keyPath: 'id',
@@ -208,6 +221,10 @@ async function version4(db: IDBDatabase, tx: IDBTransaction) {
208
221
  files.createIndex('deletedAt', 'deletedAt');
209
222
  }
210
223
 
224
+ /**
225
+ * 4 -> 5 changes:
226
+ * replace legacy OIDs
227
+ */
211
228
  async function version5(db: IDBDatabase, tx: IDBTransaction) {
212
229
  // rewrites all baselines and operations to replace legacy OIDs
213
230
  // with new ones.