@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,251 @@
1
+ import {
2
+ CollectionFilter,
3
+ createOid,
4
+ decomposeOid,
5
+ getIndexValues,
6
+ ObjectIdentifier,
7
+ } from '@verdant-web/common';
8
+ import { Context } from '../../../context/context.js';
9
+ import {
10
+ AbstractTransaction,
11
+ CommonQueryOptions,
12
+ PersistenceQueryDb,
13
+ QueryMode,
14
+ } from '../../interfaces.js';
15
+ import { IdbService } from '../IdbService.js';
16
+ import { getRange } from './ranges.js';
17
+ import { closeDatabase, getSizeOfObjectStore, isAbortError } from '../util.js';
18
+
19
+ export class IdbQueryDb extends IdbService implements PersistenceQueryDb {
20
+ private ctx;
21
+ constructor(db: IDBDatabase, context: Omit<Context, 'queries' | 'files'>) {
22
+ super(db, { log: context.log });
23
+ this.ctx = context;
24
+ }
25
+
26
+ stats = async (): Promise<
27
+ Record<string, { count: number; size: number }>
28
+ > => {
29
+ const collectionNames = Object.keys(this.ctx.schema.collections);
30
+ const collections: Record<string, { count: number; size: number }> = {};
31
+ await Promise.all(
32
+ collectionNames.map(async (name) => {
33
+ const size = await getSizeOfObjectStore(this.db, name);
34
+ collections[name] = size;
35
+ }),
36
+ );
37
+ return collections;
38
+ };
39
+
40
+ transaction = (opts: {
41
+ mode?: QueryMode;
42
+ storeNames: string[];
43
+ abort?: AbortSignal;
44
+ }): AbstractTransaction => {
45
+ return this.createTransaction(opts.storeNames, {
46
+ mode: opts.mode,
47
+ abort: opts.abort,
48
+ });
49
+ };
50
+
51
+ findOneOid = async (opts: {
52
+ collection: string;
53
+ index?: CollectionFilter;
54
+ }): Promise<ObjectIdentifier | null> => {
55
+ const result = await this.run<IDBCursorWithValue | null>(
56
+ opts.collection,
57
+ (store) => {
58
+ const source = opts.index?.where
59
+ ? store.index(opts.index.where)
60
+ : store;
61
+ const direction = opts.index?.order === 'desc' ? 'prev' : 'next';
62
+ const range = getRange(this.ctx.schema, opts.collection, opts.index);
63
+ return source.openCursor(range, direction);
64
+ },
65
+ { mode: 'readonly' },
66
+ );
67
+ if (result) {
68
+ return createOid(opts.collection, result.primaryKey.toString());
69
+ }
70
+ return null;
71
+ };
72
+ findAllOids = async ({
73
+ collection,
74
+ index,
75
+ offset,
76
+ limit,
77
+ }: {
78
+ collection: string;
79
+ index?: CollectionFilter;
80
+ limit?: number;
81
+ offset?: number;
82
+ }): Promise<{ result: ObjectIdentifier[]; hasNextPage: boolean }> => {
83
+ const tx = this.createTransaction([collection], { mode: 'readonly' });
84
+ const store = tx.objectStore(collection);
85
+ const source = index?.where ? store.index(index.where) : store;
86
+ const direction = index?.order === 'desc' ? 'prev' : 'next';
87
+ const range = getRange(this.ctx.schema, collection, index);
88
+ const request = source.openCursor(range, direction);
89
+
90
+ let hasNextPage = false;
91
+ const result = await new Promise<string[]>((resolve, reject) => {
92
+ let hasDoneOffset = !offset;
93
+ let visited = 0;
94
+ const results = new Set<ObjectIdentifier>();
95
+
96
+ request.onsuccess = () => {
97
+ visited++;
98
+ const cursor = request.result as IDBCursorWithValue | null;
99
+ if (!cursor) {
100
+ resolve(Array.from(results));
101
+ return;
102
+ }
103
+
104
+ // first offset, if we have one. cursor opens at beginning.
105
+ if (offset && !hasDoneOffset) {
106
+ cursor.advance(offset);
107
+ hasDoneOffset = true;
108
+ // next iteration we begin adding results.
109
+ } else {
110
+ // add result to set, unless we have reached limit.
111
+ if (!limit || results.size < limit) {
112
+ results.add(createOid(collection, cursor.primaryKey.toString()));
113
+ }
114
+ // even if we reached limit, we keep going one more to check if there's
115
+ // a next page.
116
+ if (limit && visited > limit) {
117
+ hasNextPage = true;
118
+ // stop iteration here; we reached limit and we have next page
119
+ // info we need.
120
+ resolve(Array.from(results));
121
+ } else {
122
+ cursor.continue();
123
+ }
124
+ }
125
+ };
126
+
127
+ request.onerror = () => {
128
+ if (request.error?.name === 'InvalidStateError') {
129
+ this.ctx.log(
130
+ 'error',
131
+ `find query failed with InvalidStateError`,
132
+ request.error,
133
+ );
134
+ resolve([]);
135
+ } else if (request.error && isAbortError(request.error)) {
136
+ resolve([]);
137
+ } else {
138
+ reject(request.error);
139
+ }
140
+ };
141
+ });
142
+
143
+ return {
144
+ result,
145
+ hasNextPage,
146
+ };
147
+ };
148
+
149
+ saveEntities = async (
150
+ entities: { oid: ObjectIdentifier; getSnapshot: () => any }[],
151
+ opts?: CommonQueryOptions & { abort?: AbortSignal },
152
+ ): Promise<void> => {
153
+ if (entities.length === 0) return;
154
+
155
+ let collections = Array.from(
156
+ new Set(entities.map((e) => decomposeOid(e.oid).collection)),
157
+ );
158
+
159
+ const toRemove = collections.filter((c) => !this.ctx.schema.collections[c]);
160
+ if (toRemove.length > 0) {
161
+ this.ctx.log(
162
+ 'warn',
163
+ `Ignoring entities from collections that no longer exist: ${toRemove.join(
164
+ ', ',
165
+ )}`,
166
+ );
167
+ }
168
+ const withRemoved = new Set(collections);
169
+ toRemove.forEach((c) => withRemoved.delete(c));
170
+ collections = Array.from(withRemoved);
171
+
172
+ const options = {
173
+ transaction: this.createTransaction(collections, {
174
+ mode: 'readwrite',
175
+ abort: opts?.abort,
176
+ }),
177
+ };
178
+
179
+ // FIXME: not test is making it to this line
180
+
181
+ await Promise.all(
182
+ entities.map(async (e) => {
183
+ const snapshot = e.getSnapshot();
184
+ try {
185
+ await this.saveDocument(e.oid, snapshot, options);
186
+ } catch (err) {
187
+ this.ctx.log(
188
+ 'error',
189
+ `Error saving document ${e.oid} (${JSON.stringify(snapshot)})`,
190
+ err,
191
+ );
192
+ if (err instanceof Error) {
193
+ throw err;
194
+ } else {
195
+ throw new Error('Unknown error saving document');
196
+ }
197
+ }
198
+ }),
199
+ );
200
+ options.transaction.commit();
201
+ this.ctx.entityEvents.emit('collectionsChanged', collections);
202
+ for (const entity of entities) {
203
+ this.ctx.entityEvents.emit('documentChanged', entity.oid);
204
+ }
205
+ };
206
+
207
+ reset = async (opts?: {
208
+ transaction?: AbstractTransaction;
209
+ }): Promise<void> => {
210
+ const names = Object.keys(this.ctx.schema.collections);
211
+ const tx =
212
+ (opts?.transaction as IDBTransaction) ||
213
+ this.createTransaction(names, { mode: 'readwrite' });
214
+ await Promise.all(
215
+ names.map((name) =>
216
+ this.run(name, (store) => store.clear(), { transaction: tx }),
217
+ ),
218
+ );
219
+ this.ctx.entityEvents.emit('collectionsChanged', names);
220
+ this.ctx.log('info', '💨 Reset queryable storage');
221
+ };
222
+
223
+ dispose = () => {
224
+ return closeDatabase(this.db);
225
+ };
226
+
227
+ private saveDocument = async (
228
+ oid: ObjectIdentifier,
229
+ doc: any,
230
+ { transaction }: { transaction?: IDBTransaction },
231
+ ) => {
232
+ this.ctx.log('debug', `Saving document indexes for querying ${oid}`);
233
+ const { collection, id } = decomposeOid(oid);
234
+ if (!doc) {
235
+ await this.run(collection, (store) => store.delete(id), {
236
+ mode: 'readwrite',
237
+ transaction,
238
+ });
239
+ this.ctx.log('debug', `Deleted document indexes for querying ${oid}`);
240
+ } else {
241
+ const schema = this.ctx.schema.collections[collection];
242
+ // no need to validate before storing; the entity's snapshot is already validated.
243
+ const indexes = getIndexValues(schema, doc);
244
+ await this.run(collection, (store) => store.put(indexes), {
245
+ mode: 'readwrite',
246
+ transaction,
247
+ });
248
+ this.ctx.log('debug', `Save complete for ${oid}`, indexes);
249
+ }
250
+ };
251
+ }
@@ -1,48 +1,23 @@
1
- import { closeDatabase, globalIDB, storeRequestPromise } from '../idb.js';
1
+ import {
2
+ globalIDB,
3
+ storeRequestPromise,
4
+ openDatabase as baseOpenDatabase,
5
+ getDocumentDbName,
6
+ } from '../../util.js';
2
7
  import { OpenDocumentDbContext } from './types.js';
3
8
 
4
9
  export async function getDatabaseVersion(
5
10
  indexedDB: IDBFactory,
6
11
  namespace: string,
7
- version: number,
8
- log?: (...args: any[]) => void,
9
12
  ): Promise<number> {
10
- function openAndGetVersion(
11
- resolve: (res: [number, IDBDatabase]) => void,
12
- reject: (err: Error) => void,
13
- ) {
14
- let currentVersion: number;
15
- let database: IDBDatabase;
16
- const request = indexedDB.open(
17
- [namespace, 'collections'].join('_'),
18
- version,
19
- );
20
- request.onupgradeneeded = async (event) => {
21
- currentVersion = event.oldVersion;
22
- const transaction = request.transaction!;
23
- database = request.result;
24
- transaction.abort();
25
- };
26
- request.onsuccess = (event) => {
27
- resolve([request.result.version, request.result]);
28
- };
29
- request.onblocked = (event) => {
30
- // retry if blocked
31
- log?.('Database blocked, waiting...');
32
- // setTimeout(() => {
33
- // openAndGetVersion(resolve, reject);
34
- // }, 200);
35
- };
36
- request.onerror = (event) => {
37
- // FIXME: this fails if the code is older than the local database
38
- resolve([currentVersion!, database!]);
39
- };
13
+ const databaseName = getDocumentDbName(namespace);
14
+ const dbInfo = await indexedDB.databases();
15
+ const existingDb = dbInfo.find((info) => info.name === databaseName);
16
+ if (existingDb) {
17
+ return existingDb.version ?? 0;
40
18
  }
41
- const [currentVersion, db] = await new Promise<[number, IDBDatabase]>(
42
- openAndGetVersion,
43
- );
44
- await closeDatabase(db);
45
- return currentVersion;
19
+
20
+ return 0;
46
21
  }
47
22
 
48
23
  /**
@@ -121,40 +96,11 @@ export async function openDatabase({
121
96
  context: OpenDocumentDbContext;
122
97
  }): Promise<IDBDatabase> {
123
98
  context.log('debug', 'Opening database', namespace, 'at version', version);
124
- const db = await new Promise<IDBDatabase>((resolve, reject) => {
125
- const request = indexedDB.open(
126
- [namespace, 'collections'].join('_'),
127
- version,
128
- );
129
- request.onupgradeneeded = async (event) => {
130
- const transaction = request.transaction!;
131
- transaction.abort();
132
-
133
- context.log(
134
- 'error',
135
- 'Database upgrade needed, but not expected',
136
- 'Expected',
137
- version,
138
- 'Got',
139
- request.result.version,
140
- );
141
- reject(
142
- request.error ||
143
- new Error(
144
- `Migration error: database version changed unexpectedly when reading current data. Expected ${version}, got ${request.result.version}`,
145
- ),
146
- );
147
- };
148
- request.onsuccess = (event) => {
149
- resolve(request.result);
150
- };
151
- request.onblocked = (event) => {
152
- reject(new Error('Migration error: database blocked'));
153
- };
154
- request.onerror = (event) => {
155
- reject(new Error('Migration error: database error'));
156
- };
157
- });
99
+ const db = await baseOpenDatabase(
100
+ getDocumentDbName(namespace),
101
+ version,
102
+ indexedDB,
103
+ );
158
104
 
159
105
  db.addEventListener('versionchange', (event) => {
160
106
  db.close();
@@ -10,25 +10,20 @@ import {
10
10
  createOid,
11
11
  diffToPatches,
12
12
  getOid,
13
- initialToPatches,
14
13
  removeOidPropertiesFromAllSubObjects,
15
14
  AuthorizationKey,
16
15
  } from '@verdant-web/common';
17
- import { Context } from '../context.js';
18
- import { Metadata } from '../metadata/Metadata.js';
19
- import { findAllOids, findOneOid } from '../queries/dbQueries.js';
20
16
  import { OpenDocumentDbContext } from './types.js';
17
+ import { IdbQueryDb } from '../IdbQueryDb.js';
21
18
 
22
19
  function getMigrationMutations({
23
20
  migration,
24
- meta,
25
- getMigrationNow,
26
21
  newOids,
22
+ ctx,
27
23
  }: {
28
24
  migration: Migration<any>;
29
25
  newOids: string[];
30
- getMigrationNow: () => string;
31
- meta: Metadata;
26
+ ctx: OpenDocumentDbContext;
32
27
  }) {
33
28
  return migration.allCollections.reduce((acc, collectionName) => {
34
29
  acc[collectionName] = {
@@ -40,24 +35,22 @@ function getMigrationMutations({
40
35
  const oid = createOid(collectionName, primaryKey);
41
36
  newOids.push(oid);
42
37
 
43
- await meta.insertLocalOperations(
44
- initialToPatches(doc, oid, getMigrationNow, undefined, undefined, {
45
- authz: options?.access,
38
+ await ctx.time.withMigrationTime(migration.version, () =>
39
+ ctx.meta.insertData({
40
+ operations: ctx.patchCreator.createInitialize(
41
+ doc,
42
+ oid,
43
+ options?.access,
44
+ ),
45
+ isLocal: true,
46
46
  }),
47
47
  );
48
48
  return doc;
49
49
  },
50
50
  delete: async (id: string) => {
51
51
  const rootOid = createOid(collectionName, id);
52
- const authz = await meta.getDocumentAuthz(rootOid);
53
- const allOids = await meta.getAllDocumentRelatedOids(rootOid);
54
- return meta.insertLocalOperations(
55
- allOids.map((oid) => ({
56
- oid,
57
- timestamp: getMigrationNow(),
58
- data: { op: 'delete' },
59
- authz,
60
- })),
52
+ await ctx.time.withMigrationTime(migration.version, () =>
53
+ ctx.meta.deleteDocument(rootOid),
61
54
  );
62
55
  },
63
56
  };
@@ -68,46 +61,46 @@ function getMigrationMutations({
68
61
  function getMigrationQueries({
69
62
  migration,
70
63
  context,
71
- meta,
64
+ queryDb,
72
65
  }: {
73
66
  migration: Migration<any>;
74
- context: Context;
75
- meta: Metadata;
67
+ context: OpenDocumentDbContext;
68
+ queryDb: IDBDatabase;
76
69
  }) {
70
+ const queries = new IdbQueryDb(queryDb, context);
71
+
77
72
  return migration.oldCollections.reduce((acc, collectionName) => {
78
73
  acc[collectionName] = {
79
74
  get: async (id: string) => {
80
75
  const oid = createOid(collectionName, id);
81
- const doc = await meta.getDocumentSnapshot(oid, {
76
+ const doc = await context.meta.getDocumentSnapshot(oid, {
82
77
  // only get the snapshot up to the previous version (newer operations may have synced)
83
- to: meta.time.now(migration.oldSchema.version),
78
+ to: context.time.nowWithVersion(migration.oldSchema.version),
84
79
  });
85
80
  return doc;
86
81
  },
87
82
  findOne: async (filter: CollectionFilter) => {
88
- const oid = await findOneOid({
83
+ const oid = await queries.findOneOid({
89
84
  collection: collectionName,
90
85
  index: filter,
91
- context,
92
86
  });
93
87
  if (!oid) return null;
94
- const doc = await meta.getDocumentSnapshot(oid, {
88
+ const doc = await context.meta.getDocumentSnapshot(oid, {
95
89
  // only get the snapshot up to the previous version (newer operations may have synced)
96
- to: meta.time.now(migration.oldSchema.version),
90
+ to: context.time.nowWithVersion(migration.oldSchema.version),
97
91
  });
98
92
  return doc;
99
93
  },
100
94
  findAll: async (filter: CollectionFilter) => {
101
- const oids = await findAllOids({
95
+ const { result: oids } = await queries.findAllOids({
102
96
  collection: collectionName,
103
97
  index: filter,
104
- context,
105
98
  });
106
99
  const docs = await Promise.all(
107
100
  oids.map((oid) =>
108
- meta.getDocumentSnapshot(oid, {
101
+ context.meta.getDocumentSnapshot(oid, {
109
102
  // only get the snapshot up to the previous version (newer operations may have synced)
110
- to: meta.time.now(migration.oldSchema.version),
103
+ to: context.time.nowWithVersion(migration.oldSchema.version),
111
104
  }),
112
105
  ),
113
106
  );
@@ -119,41 +112,29 @@ function getMigrationQueries({
119
112
  }
120
113
 
121
114
  export function getMigrationEngine({
122
- meta,
123
115
  migration,
124
116
  context,
117
+ queryDb,
125
118
  }: {
126
119
  log?: (...args: any[]) => void;
127
120
  migration: Migration;
128
- meta: Metadata;
129
- context: Context;
121
+ context: OpenDocumentDbContext;
122
+ queryDb: IDBDatabase;
130
123
  }): MigrationEngine {
131
- function getMigrationNow() {
132
- return meta.time.zero(migration.version);
133
- }
134
-
135
124
  const newOids = new Array<ObjectIdentifier>();
136
125
 
137
126
  const queries = getMigrationQueries({
138
127
  migration,
139
128
  context,
140
- meta,
129
+ queryDb,
141
130
  });
142
131
  const mutations = getMigrationMutations({
143
132
  migration,
144
- getMigrationNow,
145
133
  newOids,
146
- meta,
134
+ ctx: context,
147
135
  });
148
136
  const deleteCollection = async (collection: string) => {
149
- const allOids = await meta.getAllCollectionRelatedOids(collection);
150
- return meta.insertLocalOperations(
151
- allOids.map((oid) => ({
152
- oid,
153
- timestamp: getMigrationNow(),
154
- data: { op: 'delete' },
155
- })),
156
- );
137
+ await context.meta.deleteCollection(collection);
157
138
  };
158
139
  const awaitables = new Array<Promise<any>>();
159
140
  const engine: MigrationEngine = {
@@ -174,7 +155,7 @@ export function getMigrationEngine({
174
155
  // when the snapshots themselves are derived from the same data...)
175
156
  // maybe don't use the findAll query, and instead go a level
176
157
  // lower to retain access to lower level data here?
177
- const authz = await meta.getDocumentAuthz(rootOid);
158
+ const authz = await context.meta.getDocumentAuthz(rootOid);
178
159
  const original = cloneDeep(doc);
179
160
  // @ts-ignore - excessive type resolution
180
161
  const newValue = await strategy(doc);
@@ -188,7 +169,7 @@ export function getMigrationEngine({
188
169
  const patches = diffToPatches(
189
170
  original,
190
171
  newValue,
191
- getMigrationNow,
172
+ () => context.time.zeroWithVersion(migration.version),
192
173
  undefined,
193
174
  [],
194
175
  {
@@ -197,7 +178,10 @@ export function getMigrationEngine({
197
178
  },
198
179
  );
199
180
  if (patches.length > 0) {
200
- await meta.insertLocalOperations(patches);
181
+ await context.meta.insertData({
182
+ operations: patches,
183
+ isLocal: true,
184
+ });
201
185
  }
202
186
  }
203
187
  }),
@@ -211,18 +195,12 @@ export function getMigrationEngine({
211
195
  }
212
196
 
213
197
  export function getInitialMigrationEngine({
214
- meta,
215
198
  migration,
216
199
  context,
217
200
  }: {
218
201
  context: OpenDocumentDbContext;
219
202
  migration: Migration;
220
- meta: Metadata;
221
203
  }): MigrationEngine {
222
- function getMigrationNow() {
223
- return meta.time.zero(migration.version);
224
- }
225
-
226
204
  const newOids = new Array<ObjectIdentifier>();
227
205
 
228
206
  const queries = new Proxy({} as any, {
@@ -235,9 +213,8 @@ export function getInitialMigrationEngine({
235
213
 
236
214
  const mutations = getMigrationMutations({
237
215
  migration,
238
- getMigrationNow,
239
216
  newOids,
240
- meta,
217
+ ctx: context,
241
218
  });
242
219
  const engine: MigrationEngine = {
243
220
  log: context.log,
@@ -6,12 +6,11 @@ import {
6
6
  getIndexValues,
7
7
  getOidRoot,
8
8
  } from '@verdant-web/common';
9
- import { Metadata } from '../metadata/Metadata.js';
10
- import { ClientOperation } from '../metadata/OperationsStore.js';
9
+ import { ClientOperation } from '../../../interfaces.js';
11
10
  import { acquireLock, openDatabase, upgradeDatabase } from './db.js';
12
11
  import { getInitialMigrationEngine, getMigrationEngine } from './engine.js';
13
12
  import { OpenDocumentDbContext } from './types.js';
14
- import { closeDatabase } from '../idb.js';
13
+ import { closeDatabase } from '../../util.js';
15
14
 
16
15
  const globalIDB =
17
16
  typeof window !== 'undefined' ? window.indexedDB : (undefined as any);
@@ -19,13 +18,11 @@ const globalIDB =
19
18
  export async function runMigrations({
20
19
  context,
21
20
  toRun,
22
- meta,
23
21
  indexedDB = globalIDB,
24
22
  namespace = context.namespace,
25
23
  }: {
26
24
  context: OpenDocumentDbContext;
27
25
  toRun: Migration<any>[];
28
- meta: Metadata;
29
26
  indexedDB?: IDBFactory;
30
27
  /** This namespace value controls where the database being migrated is. */
31
28
  namespace?: string;
@@ -39,7 +36,6 @@ export async function runMigrations({
39
36
  // migrations from 0 (i.e. initial migrations) don't attempt to open an existing db
40
37
  if (migration.oldSchema.version === 0) {
41
38
  engine = getInitialMigrationEngine({
42
- meta,
43
39
  migration,
44
40
  context,
45
41
  });
@@ -56,12 +52,9 @@ export async function runMigrations({
56
52
 
57
53
  // this will only write to our metadata store via operations!
58
54
  engine = getMigrationEngine({
59
- meta,
60
55
  migration,
61
- context: {
62
- ...context,
63
- documentDb: originalDatabase,
64
- },
56
+ context,
57
+ queryDb: originalDatabase,
65
58
  });
66
59
  try {
67
60
  await migration.migrate(engine);
@@ -115,9 +108,9 @@ export async function runMigrations({
115
108
  * would be missing from findAll and findOne queries.
116
109
  */
117
110
  const docsWithUnappliedMigrations = await getDocsWithUnappliedMigrations({
118
- meta,
119
111
  currentVersion: migration.oldSchema.version,
120
112
  newVersion: migration.newSchema.version,
113
+ ctx: context,
121
114
  });
122
115
 
123
116
  // once the schema is ready, we can write back the migrated documents
@@ -163,7 +156,7 @@ export async function runMigrations({
163
156
  const snapshots = await Promise.all(
164
157
  oids.map(async (oid) => {
165
158
  try {
166
- const snap = await meta.getDocumentSnapshot(oid);
159
+ const snap = await context.meta.getDocumentSnapshot(oid);
167
160
  return [oid, snap];
168
161
  } catch (e) {
169
162
  // this seems to happen with baselines/ops which are not fully
@@ -279,22 +272,24 @@ async function putView(store: IDBObjectStore, view: any) {
279
272
  * future. These documents need to be refreshed in storage.
280
273
  */
281
274
  async function getDocsWithUnappliedMigrations({
282
- meta,
283
275
  currentVersion,
284
276
  newVersion: _,
277
+ ctx,
285
278
  }: {
286
279
  currentVersion: number;
287
280
  newVersion: number;
288
- meta: Metadata;
281
+ ctx: OpenDocumentDbContext;
289
282
  }) {
290
283
  // scan for all operations in metadata after the current version.
291
284
  // this could be more efficient if also filtering below or equal newVersion but
292
285
  // that seems so unlikely in practice...
293
286
  const unappliedOperations: ClientOperation[] = [];
294
- await meta.operations.iterateOverAllOperations(
295
- (op) => unappliedOperations.push(op),
287
+ await ctx.meta.iterateAllOperations(
288
+ (op) => {
289
+ unappliedOperations.push(op);
290
+ },
296
291
  {
297
- from: meta.time.zero(currentVersion + 1),
292
+ from: ctx.time.zeroWithVersion(currentVersion + 1),
298
293
  },
299
294
  );
300
295
  return Array.from(