@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,5 +1,4 @@
1
1
  import { Migration } from '@verdant-web/common';
2
- import { Metadata } from '../metadata/Metadata.js';
3
2
  import { getDatabaseVersion, openDatabase } from './db.js';
4
3
  import { runMigrations } from './migrations.js';
5
4
  import { getMigrationPath } from './paths.js';
@@ -12,32 +11,24 @@ export async function openQueryDatabase({
12
11
  version,
13
12
  indexedDB = globalIDB,
14
13
  migrations,
15
- meta,
16
14
  context,
17
15
  }: {
18
16
  version: number;
19
17
  migrations: Migration<any>[];
20
18
  indexedDB?: IDBFactory;
21
- meta: Metadata;
22
19
  context: OpenDocumentDbContext;
23
20
  }) {
24
- if (context.schema.wip) {
25
- throw new Error('Cannot open a production client with a WIP schema!');
26
- }
27
-
28
- const currentVersion = await getDatabaseVersion(
29
- indexedDB,
30
- context.namespace,
31
- version,
32
- context.log,
33
- );
21
+ const currentVersion = await getDatabaseVersion(indexedDB, context.namespace);
34
22
 
35
23
  context.log(
36
24
  'debug',
25
+ 'Opening index database',
26
+ context.namespace,
37
27
  'Current database version:',
38
28
  currentVersion,
39
29
  'target version:',
40
30
  version,
31
+ context.schema.wip ? '(wip)' : '',
41
32
  );
42
33
 
43
34
  const toRun = getMigrationPath({
@@ -52,7 +43,7 @@ export async function openQueryDatabase({
52
43
  'Migrations to run:',
53
44
  toRun.map((m) => m.version),
54
45
  );
55
- await runMigrations({ context, toRun, meta, indexedDB });
46
+ await runMigrations({ context, toRun, indexedDB });
56
47
  }
57
48
  return openDatabase({
58
49
  indexedDB,
@@ -1,5 +1,4 @@
1
- import { Migration } from '@verdant-web/common';
2
- import { MigrationPathError } from './errors.js';
1
+ import { Migration, VerdantError } from '@verdant-web/common';
3
2
 
4
3
  export function getMigrationPath({
5
4
  currentVersion,
@@ -16,7 +15,9 @@ export function getMigrationPath({
16
15
  migrations,
17
16
  });
18
17
  if (!path) {
19
- throw new MigrationPathError(
18
+ throw new VerdantError(
19
+ VerdantError.Code.MigrationPathNotFound,
20
+ undefined,
20
21
  `No migration path found from ${currentVersion} to ${targetVersion}! This is a bug. If you're seeing this, contact the developer and provide them with the full contents of this message.`,
21
22
  );
22
23
  }
@@ -0,0 +1,8 @@
1
+ import { Context } from '../../../../context/context.js';
2
+
3
+ /** During migration, only a partial context is available */
4
+ export type OpenDocumentDbContext = Omit<Context, 'queries' | 'files'>;
5
+
6
+ export type MigrationDbRef = {
7
+ current: IDBDatabase;
8
+ };
@@ -0,0 +1,107 @@
1
+ import {
2
+ assert,
3
+ CollectionCompoundIndexFilter,
4
+ CollectionFilter,
5
+ createCompoundIndexValue,
6
+ createLowerBoundIndexValue,
7
+ createUpperBoundIndexValue,
8
+ isMatchIndexFilter,
9
+ isRangeIndexFilter,
10
+ isSortIndexFilter,
11
+ isStartsWithIndexFilter,
12
+ MatchCollectionIndexFilter,
13
+ RangeCollectionIndexFilter,
14
+ sanitizeIndexValue,
15
+ SortIndexFilter,
16
+ StartsWithIndexFilter,
17
+ StorageSchema,
18
+ } from '@verdant-web/common';
19
+
20
+ const matchIndexToIdbKeyRange = (filter: MatchCollectionIndexFilter) => {
21
+ return IDBKeyRange.only(sanitizeIndexValue(filter.equals));
22
+ };
23
+
24
+ const sortIndexToIdbKeyRange = (filter: SortIndexFilter) => {
25
+ return undefined;
26
+ };
27
+
28
+ const rangeIndexToIdbKeyRange = (filter: RangeCollectionIndexFilter) => {
29
+ const lower = filter.gte || filter.gt;
30
+ const upper = filter.lte || filter.lt;
31
+ if (lower === upper) {
32
+ return IDBKeyRange.only(sanitizeIndexValue(lower));
33
+ }
34
+ if (!lower) {
35
+ return IDBKeyRange.upperBound(sanitizeIndexValue(upper), !!filter.lt);
36
+ } else if (!upper) {
37
+ return IDBKeyRange.lowerBound(sanitizeIndexValue(lower), !!filter.gt);
38
+ } else {
39
+ return IDBKeyRange.bound(
40
+ sanitizeIndexValue(lower),
41
+ sanitizeIndexValue(upper),
42
+ !!filter.gt,
43
+ !!filter.lt,
44
+ );
45
+ }
46
+ };
47
+
48
+ const compoundIndexToIdbKeyRange = (
49
+ // FIXME:
50
+ schema: any,
51
+ collection: string,
52
+ filter: CollectionCompoundIndexFilter,
53
+ ) => {
54
+ // validate the usage of the compound index:
55
+ // - all match fields must be contiguous at the start of the compound order
56
+ const indexDefinition =
57
+ schema.collections[collection].compounds[filter.where];
58
+ assert(
59
+ indexDefinition,
60
+ `Index ${filter.where} does not exist on collection ${collection}`,
61
+ );
62
+ const matchedKeys = Object.keys(filter.match).sort(
63
+ (a, b) => indexDefinition.of.indexOf(a) - indexDefinition.of.indexOf(b),
64
+ );
65
+ for (const key of matchedKeys) {
66
+ if (indexDefinition.of.indexOf(key) !== matchedKeys.indexOf(key)) {
67
+ throw new Error(
68
+ `Compound index ${filter.where} does not have ${key} at the start of its order`,
69
+ );
70
+ }
71
+ }
72
+
73
+ const matchedValues = matchedKeys.map(
74
+ (key) => filter.match[key as keyof typeof filter.match] as string | number,
75
+ );
76
+
77
+ // special case: all match fields are specified - we don't need a range
78
+ // query, just a single key query
79
+ if (matchedKeys.length === indexDefinition.of.length) {
80
+ return IDBKeyRange.only(createCompoundIndexValue(...matchedValues));
81
+ }
82
+
83
+ // create our bounds for the matched values
84
+ const lower = createLowerBoundIndexValue(...matchedValues);
85
+ const upper = createUpperBoundIndexValue(...matchedValues);
86
+ return IDBKeyRange.bound(lower, upper);
87
+ };
88
+
89
+ function startsWithIndexToIdbKeyRange(filter: StartsWithIndexFilter) {
90
+ const lower = filter.startsWith;
91
+ const upper = filter.startsWith + '\uffff';
92
+ return IDBKeyRange.bound(lower, upper);
93
+ }
94
+
95
+ export function getRange(
96
+ schema: StorageSchema,
97
+ collection: string,
98
+ index?: CollectionFilter,
99
+ ) {
100
+ if (!index) return undefined;
101
+ if (isRangeIndexFilter(index)) return rangeIndexToIdbKeyRange(index);
102
+ if (isMatchIndexFilter(index)) return matchIndexToIdbKeyRange(index);
103
+ if (isSortIndexFilter(index)) return sortIndexToIdbKeyRange(index);
104
+ if (isStartsWithIndexFilter(index))
105
+ return startsWithIndexToIdbKeyRange(index);
106
+ return compoundIndexToIdbKeyRange(schema, collection, index);
107
+ }
@@ -93,6 +93,20 @@ export function getSizeOfObjectStore(
93
93
  });
94
94
  }
95
95
 
96
+ export async function getSizesOfAllObjectStores(
97
+ database: IDBDatabase,
98
+ ): Promise<{ [storeName: string]: { count: number; size: number } }> {
99
+ const storeNames = Array.from(database.objectStoreNames);
100
+ const promises = storeNames.map(async (storeName) => {
101
+ const result = await getSizeOfObjectStore(database, storeName);
102
+ return { [storeName]: result };
103
+ });
104
+ const results = await Promise.all(promises);
105
+ return results.reduce((acc, result_1) => {
106
+ return { ...acc, ...result_1 };
107
+ }, {});
108
+ }
109
+
96
110
  export function getAllFromObjectStores(db: IDBDatabase, stores: string[]) {
97
111
  const transaction = db.transaction(stores, 'readonly');
98
112
  const promises = stores.map((store) => {
@@ -182,3 +196,64 @@ export function emptyDatabase(db: IDBDatabase) {
182
196
  tx.onerror = () => reject(tx.error);
183
197
  });
184
198
  }
199
+
200
+ export async function copyDatabase(from: IDBDatabase, to: IDBDatabase) {
201
+ await emptyDatabase(to);
202
+ const records = await getAllFromObjectStores(
203
+ from,
204
+ Array.from(from.objectStoreNames),
205
+ );
206
+ const writeTx = to.transaction(Array.from(to.objectStoreNames), 'readwrite');
207
+ for (let i = 0; i < records.length; i++) {
208
+ const store = writeTx.objectStore(from.objectStoreNames[i]);
209
+ for (const record of records[i]) {
210
+ store.add(record);
211
+ }
212
+ }
213
+ return new Promise<void>((resolve, reject) => {
214
+ writeTx.oncomplete = () => resolve();
215
+ writeTx.onerror = () => reject(writeTx.error);
216
+ });
217
+ }
218
+
219
+ export function openDatabase(
220
+ name: string,
221
+ expectedVersion: number,
222
+ indexedDB: IDBFactory = window.indexedDB,
223
+ ) {
224
+ return new Promise<IDBDatabase>((resolve, reject) => {
225
+ const req = indexedDB.open(name, expectedVersion);
226
+ req.onsuccess = () => {
227
+ resolve(req.result);
228
+ };
229
+ req.onerror = () => {
230
+ reject(req.error);
231
+ };
232
+ req.onblocked = () => {
233
+ reject(new Error('Database blocked'));
234
+ };
235
+ req.onupgradeneeded = (event) => {
236
+ const db = req.result;
237
+ if (db.version !== expectedVersion) {
238
+ db.close();
239
+ reject(
240
+ new Error(
241
+ `Migration error: database version changed unexpectedly when reading current data. Expected ${expectedVersion}, got ${db.version}`,
242
+ ),
243
+ );
244
+ }
245
+ };
246
+ });
247
+ }
248
+
249
+ export function getMetadataDbName(namespace: string) {
250
+ return [namespace, 'meta'].join('_');
251
+ }
252
+
253
+ export function getDocumentDbName(namespace: string) {
254
+ return [namespace, 'collections'].join('_');
255
+ }
256
+
257
+ export function getNamespaceFromDatabaseInfo(info: IDBDatabaseInfo) {
258
+ return info.name?.split('_')[0];
259
+ }
@@ -0,0 +1,240 @@
1
+ import {
2
+ CollectionFilter,
3
+ DocumentBaseline,
4
+ FileData,
5
+ ObjectIdentifier,
6
+ Operation,
7
+ } from '@verdant-web/common';
8
+ import { Context, InitialContext } from '../context/context.js';
9
+
10
+ export interface AckInfo {
11
+ type: 'ack';
12
+ globalAckTimestamp: string | null;
13
+ }
14
+
15
+ export interface LocalReplicaInfo {
16
+ type: 'localReplicaInfo';
17
+ id: string;
18
+ userId: string | undefined;
19
+ ackedLogicalTime: string | null;
20
+ lastSyncedLogicalTime: string | null;
21
+ }
22
+
23
+ export type ClientOperation = Operation & {
24
+ isLocal: boolean;
25
+ };
26
+
27
+ export interface MetadataExport {
28
+ operations: Operation[];
29
+ baselines: DocumentBaseline[];
30
+ localReplica?: LocalReplicaInfo;
31
+ schemaVersion: number;
32
+ }
33
+
34
+ export interface ExportedData {
35
+ data: MetadataExport;
36
+ fileData: Array<Omit<PersistedFileData, 'file'>>;
37
+ files: File[];
38
+ }
39
+
40
+ export type AbstractTransaction = unknown;
41
+ export type QueryMode = 'readwrite' | 'readonly';
42
+ export interface CommonQueryOptions {
43
+ transaction?: AbstractTransaction;
44
+ mode?: QueryMode;
45
+ }
46
+ export type Iterator<T> = (item: T) => void | boolean;
47
+
48
+ export interface PersistenceMetadataDb {
49
+ transaction(opts: {
50
+ mode?: QueryMode;
51
+ storeNames: string[];
52
+ abort?: AbortSignal;
53
+ }): AbstractTransaction;
54
+ dispose(): void | Promise<void>;
55
+
56
+ // infos
57
+ getAckInfo(): Promise<AckInfo>;
58
+ setGlobalAck(ack: string): Promise<void>;
59
+ getLocalReplica(opts?: CommonQueryOptions): Promise<LocalReplicaInfo>;
60
+ updateLocalReplica(
61
+ data: Partial<LocalReplicaInfo>,
62
+ opts?: CommonQueryOptions,
63
+ ): Promise<void>;
64
+
65
+ // baselines
66
+ iterateDocumentBaselines(
67
+ rootOid: string,
68
+ iterator: Iterator<DocumentBaseline>,
69
+ opts?: CommonQueryOptions,
70
+ ): Promise<void>;
71
+ iterateCollectionBaselines(
72
+ collection: string,
73
+ iterator: Iterator<DocumentBaseline>,
74
+ opts?: CommonQueryOptions,
75
+ ): Promise<void>;
76
+ iterateAllBaselines(
77
+ iterator: Iterator<DocumentBaseline>,
78
+ opts?: CommonQueryOptions,
79
+ ): Promise<void>;
80
+ getBaseline(
81
+ oid: string,
82
+ opts?: CommonQueryOptions,
83
+ ): Promise<DocumentBaseline>;
84
+ setBaselines(
85
+ baselines: DocumentBaseline[],
86
+ opts?: CommonQueryOptions,
87
+ ): Promise<void>;
88
+ deleteBaseline(oid: string, opts?: CommonQueryOptions): Promise<void>;
89
+
90
+ // operations
91
+ iterateDocumentOperations(
92
+ rootOid: string,
93
+ iterator: Iterator<ClientOperation>,
94
+ opts?: CommonQueryOptions & {
95
+ to?: string | null;
96
+ },
97
+ ): Promise<void>;
98
+ iterateEntityOperations(
99
+ oid: string,
100
+ iterator: Iterator<ClientOperation>,
101
+ opts?: CommonQueryOptions & { to?: string | null },
102
+ ): Promise<void>;
103
+ iterateCollectionOperations(
104
+ collection: string,
105
+ iterator: Iterator<ClientOperation>,
106
+ opts?: CommonQueryOptions,
107
+ ): Promise<void>;
108
+ iterateLocalOperations(
109
+ iterator: Iterator<ClientOperation>,
110
+ opts?: CommonQueryOptions & {
111
+ before?: string | null;
112
+ after?: string | null;
113
+ },
114
+ ): Promise<void>;
115
+ /** Iterates over operations for an entity for processing and deletes them as it goes. */
116
+ consumeEntityOperations(
117
+ oid: string,
118
+ iterator: Iterator<ClientOperation>,
119
+ opts?: CommonQueryOptions & { to?: string | null },
120
+ ): Promise<void>;
121
+ iterateAllOperations(
122
+ iterator: Iterator<ClientOperation>,
123
+ opts?: CommonQueryOptions & {
124
+ before?: string | null;
125
+ from?: string | null;
126
+ },
127
+ ): Promise<void>;
128
+ /**
129
+ * @returns a list of all document (root) OIDs affected by the adds.
130
+ */
131
+ addOperations(
132
+ ops: ClientOperation[],
133
+ opts?: CommonQueryOptions,
134
+ ): Promise<ObjectIdentifier[]>;
135
+
136
+ /* WARNING: deletes all data */
137
+ reset(opts?: {
138
+ clearReplica?: boolean;
139
+ transaction?: AbstractTransaction;
140
+ }): Promise<void>;
141
+
142
+ stats(): Promise<{
143
+ operationsSize: { count: number; size: number };
144
+ baselinesSize: { count: number; size: number };
145
+ }>;
146
+ }
147
+
148
+ export interface PersistenceQueryDb {
149
+ transaction(opts: {
150
+ mode?: QueryMode;
151
+ storeNames: string[];
152
+ abort?: AbortSignal;
153
+ }): AbstractTransaction;
154
+ dispose(): void | Promise<void>;
155
+
156
+ findOneOid(opts: {
157
+ collection: string;
158
+ index?: CollectionFilter;
159
+ }): Promise<ObjectIdentifier | null>;
160
+ findAllOids(opts: {
161
+ collection: string;
162
+ index?: CollectionFilter;
163
+ limit?: number;
164
+ offset?: number;
165
+ }): Promise<{ result: ObjectIdentifier[]; hasNextPage: boolean }>;
166
+
167
+ saveEntities(
168
+ entities: { oid: ObjectIdentifier; getSnapshot: () => any }[],
169
+ opts?: CommonQueryOptions & { abort?: AbortSignal },
170
+ ): Promise<void>;
171
+
172
+ reset(opts?: { transaction?: AbstractTransaction }): Promise<void>;
173
+
174
+ stats(): Promise<Record<string, { count: number; size: number }>>;
175
+ }
176
+
177
+ export interface PersistedFileData extends FileData {
178
+ deletedAt: number | null;
179
+ }
180
+
181
+ export interface PersistenceFileDb {
182
+ transaction(opts: {
183
+ mode?: QueryMode;
184
+ storeNames: string[];
185
+ abort?: AbortSignal;
186
+ }): AbstractTransaction;
187
+ dispose(): void | Promise<void>;
188
+
189
+ add(
190
+ file: FileData,
191
+ options?: { transaction?: AbstractTransaction; downloadRemote?: boolean },
192
+ ): Promise<void>;
193
+ markUploaded(
194
+ fileId: string,
195
+ options?: { transaction?: AbstractTransaction },
196
+ ): Promise<void>;
197
+ get(
198
+ fileId: string,
199
+ options?: { transaction?: AbstractTransaction },
200
+ ): Promise<PersistedFileData | null>;
201
+ delete(
202
+ fileId: string,
203
+ options?: { transaction?: AbstractTransaction },
204
+ ): Promise<void>;
205
+ markPendingDelete(
206
+ fileId: string,
207
+ options?: { transaction?: AbstractTransaction },
208
+ ): Promise<void>;
209
+ listUnsynced(options?: {
210
+ transaction?: AbstractTransaction;
211
+ }): Promise<PersistedFileData[]>;
212
+ resetSyncedStatusSince(
213
+ since: string | null,
214
+ options?: { transaction?: AbstractTransaction },
215
+ ): Promise<void>;
216
+ iterateOverPendingDelete(
217
+ iterator: (file: PersistedFileData, store: IDBObjectStore) => void,
218
+ options?: { transaction?: IDBTransaction },
219
+ ): Promise<void>;
220
+ getAll(options?: {
221
+ transaction?: AbstractTransaction;
222
+ }): Promise<PersistedFileData[]>;
223
+ stats(): Promise<{ size: { count: number; size: number } }>;
224
+ }
225
+
226
+ export interface PersistenceImplementation {
227
+ openMetadata(ctx: InitialContext): Promise<PersistenceMetadataDb>;
228
+ openQueries(ctx: Omit<Context, 'queries'>): Promise<PersistenceQueryDb>;
229
+ openFiles(
230
+ ctx: Omit<Context, 'files' | 'queries'>,
231
+ ): Promise<PersistenceFileDb>;
232
+ /** Copies all data (metadata/document queries/files) from one namespace to another. */
233
+ copyNamespace(from: string, to: string, ctx: InitialContext): Promise<void>;
234
+ /** Returns a list of all persisted namespaces visible to this app. */
235
+ getNamespaces(): Promise<string[]>;
236
+ /** Deletes all data from a particular namespace. */
237
+ deleteNamespace(namespace: string, ctx: InitialContext): Promise<void>;
238
+ /** Gets the schema version of the given namespace */
239
+ getNamespaceVersion(namespace: string): Promise<number>;
240
+ }