@verdant-web/store 4.0.0 → 4.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -650
  2. package/dist/bundle/index.js +11 -11
  3. package/dist/bundle/index.js.map +4 -4
  4. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -2
  5. package/dist/esm/__tests__/fixtures/testStorage.js +3 -5
  6. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  7. package/dist/esm/client/Client.d.ts +6 -2
  8. package/dist/esm/client/Client.js +18 -6
  9. package/dist/esm/client/Client.js.map +1 -1
  10. package/dist/esm/client/ClientDescriptor.d.ts +7 -5
  11. package/dist/esm/client/ClientDescriptor.js +18 -4
  12. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  13. package/dist/esm/context/ShutdownHandler.d.ts +8 -0
  14. package/dist/esm/context/ShutdownHandler.js +24 -0
  15. package/dist/esm/context/ShutdownHandler.js.map +1 -0
  16. package/dist/esm/context/context.d.ts +15 -4
  17. package/dist/esm/entities/EntityStore.js +6 -3
  18. package/dist/esm/entities/EntityStore.js.map +1 -1
  19. package/dist/esm/files/EntityFile.d.ts +1 -0
  20. package/dist/esm/files/EntityFile.js +16 -11
  21. package/dist/esm/files/EntityFile.js.map +1 -1
  22. package/dist/esm/files/FileManager.d.ts +1 -3
  23. package/dist/esm/files/FileManager.js +12 -10
  24. package/dist/esm/files/FileManager.js.map +1 -1
  25. package/dist/esm/index.d.ts +4 -5
  26. package/dist/esm/index.js +2 -3
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/internal.d.ts +6 -0
  29. package/dist/esm/internal.js +5 -0
  30. package/dist/esm/internal.js.map +1 -0
  31. package/dist/esm/persistence/MessageCreator.d.ts +3 -1
  32. package/dist/esm/persistence/MessageCreator.js +58 -55
  33. package/dist/esm/persistence/MessageCreator.js.map +1 -1
  34. package/dist/esm/persistence/PersistenceFiles.d.ts +8 -21
  35. package/dist/esm/persistence/PersistenceFiles.js +44 -30
  36. package/dist/esm/persistence/PersistenceFiles.js.map +1 -1
  37. package/dist/esm/persistence/PersistenceMetadata.d.ts +12 -11
  38. package/dist/esm/persistence/PersistenceMetadata.js +201 -137
  39. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
  40. package/dist/esm/persistence/PersistenceQueries.d.ts +10 -11
  41. package/dist/esm/persistence/PersistenceQueries.js +33 -5
  42. package/dist/esm/persistence/PersistenceQueries.js.map +1 -1
  43. package/dist/esm/persistence/PersistenceRebaser.d.ts +5 -9
  44. package/dist/esm/persistence/PersistenceRebaser.js +63 -47
  45. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
  46. package/dist/esm/persistence/idb/IdbService.d.ts +0 -1
  47. package/dist/esm/persistence/idb/IdbService.js +28 -16
  48. package/dist/esm/persistence/idb/IdbService.js.map +1 -1
  49. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +11 -31
  50. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js +31 -36
  51. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -1
  52. package/dist/esm/persistence/idb/idbPersistence.d.ts +17 -9
  53. package/dist/esm/persistence/idb/idbPersistence.js +80 -39
  54. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
  55. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +7 -10
  56. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +45 -71
  57. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
  58. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.d.ts +1 -12
  59. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +3 -56
  60. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
  61. package/dist/esm/persistence/idb/queries/{IdbQueryDb.d.ts → IdbDocumentDb.d.ts} +7 -13
  62. package/dist/esm/persistence/idb/queries/{IdbQueryDb.js → IdbDocumentDb.js} +15 -32
  63. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -0
  64. package/dist/esm/persistence/idb/queries/migration/db.d.ts +3 -5
  65. package/dist/esm/persistence/idb/queries/migration/db.js +13 -28
  66. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
  67. package/dist/esm/persistence/idb/util.d.ts +8 -4
  68. package/dist/esm/persistence/idb/util.js +64 -21
  69. package/dist/esm/persistence/idb/util.js.map +1 -1
  70. package/dist/esm/persistence/interfaces.d.ts +68 -75
  71. package/dist/esm/persistence/{idb/queries/migration → migration}/engine.d.ts +4 -7
  72. package/dist/esm/persistence/{idb/queries/migration → migration}/engine.js +18 -10
  73. package/dist/esm/persistence/migration/engine.js.map +1 -0
  74. package/dist/esm/persistence/migration/finalize.d.ts +9 -0
  75. package/dist/esm/persistence/migration/finalize.js +75 -0
  76. package/dist/esm/persistence/migration/finalize.js.map +1 -0
  77. package/dist/esm/persistence/migration/migrate.d.ts +12 -0
  78. package/dist/esm/persistence/migration/migrate.js +89 -0
  79. package/dist/esm/persistence/migration/migrate.js.map +1 -0
  80. package/dist/esm/persistence/migration/paths.js.map +1 -0
  81. package/dist/esm/persistence/migration/paths.test.js.map +1 -0
  82. package/dist/esm/persistence/migration/types.d.ts +3 -0
  83. package/dist/esm/persistence/migration/types.js.map +1 -0
  84. package/dist/esm/persistence/persistence.js +25 -15
  85. package/dist/esm/persistence/persistence.js.map +1 -1
  86. package/dist/esm/queries/FindAllQuery.js +1 -1
  87. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  88. package/dist/esm/queries/FindInfiniteQuery.js +2 -2
  89. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  90. package/dist/esm/queries/FindOneQuery.js +1 -1
  91. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  92. package/dist/esm/queries/FindPageQuery.js +1 -1
  93. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  94. package/dist/esm/sync/FileSync.js +3 -3
  95. package/dist/esm/sync/FileSync.js.map +1 -1
  96. package/dist/esm/sync/PushPullSync.d.ts +2 -3
  97. package/dist/esm/sync/PushPullSync.js +4 -2
  98. package/dist/esm/sync/PushPullSync.js.map +1 -1
  99. package/dist/esm/sync/ServerSyncEndpointProvider.d.ts +3 -7
  100. package/dist/esm/sync/ServerSyncEndpointProvider.js +3 -2
  101. package/dist/esm/sync/ServerSyncEndpointProvider.js.map +1 -1
  102. package/dist/esm/sync/Sync.d.ts +6 -1
  103. package/dist/esm/sync/Sync.js +12 -4
  104. package/dist/esm/sync/Sync.js.map +1 -1
  105. package/dist/esm/sync/WebSocketSync.js +10 -4
  106. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  107. package/dist/esm/utils/wip.js +1 -1
  108. package/package.json +6 -2
  109. package/src/__tests__/fixtures/testStorage.ts +6 -6
  110. package/src/client/Client.ts +26 -8
  111. package/src/client/ClientDescriptor.ts +27 -9
  112. package/src/context/ShutdownHandler.ts +26 -0
  113. package/src/context/context.ts +16 -4
  114. package/src/entities/EntityStore.ts +9 -3
  115. package/src/files/EntityFile.ts +11 -6
  116. package/src/files/FileManager.ts +13 -10
  117. package/src/index.ts +8 -9
  118. package/src/internal.ts +27 -0
  119. package/src/persistence/MessageCreator.ts +79 -73
  120. package/src/persistence/PersistenceFiles.ts +57 -31
  121. package/src/persistence/PersistenceMetadata.ts +287 -195
  122. package/src/persistence/PersistenceQueries.ts +45 -9
  123. package/src/persistence/PersistenceRebaser.ts +105 -70
  124. package/src/persistence/idb/IdbService.ts +40 -22
  125. package/src/persistence/idb/files/IdbPersistenceFileDb.ts +30 -62
  126. package/src/persistence/idb/idbPersistence.ts +123 -47
  127. package/src/persistence/idb/metadata/IdbMetadataDb.ts +75 -97
  128. package/src/persistence/idb/metadata/openMetadataDatabase.ts +2 -96
  129. package/src/persistence/idb/queries/{IdbQueryDb.ts → IdbDocumentDb.ts} +17 -57
  130. package/src/persistence/idb/queries/migration/db.ts +20 -39
  131. package/src/persistence/idb/util.ts +84 -21
  132. package/src/persistence/interfaces.ts +89 -90
  133. package/src/persistence/{idb/queries/migration → migration}/engine.ts +30 -15
  134. package/src/persistence/migration/finalize.ts +126 -0
  135. package/src/persistence/migration/migrate.ts +169 -0
  136. package/src/persistence/migration/types.ts +4 -0
  137. package/src/persistence/persistence.ts +37 -14
  138. package/src/queries/FindAllQuery.ts +1 -1
  139. package/src/queries/FindInfiniteQuery.ts +2 -2
  140. package/src/queries/FindOneQuery.ts +1 -1
  141. package/src/queries/FindPageQuery.ts +1 -1
  142. package/src/sync/FileSync.ts +21 -15
  143. package/src/sync/PushPullSync.ts +3 -4
  144. package/src/sync/ServerSyncEndpointProvider.ts +6 -8
  145. package/src/sync/Sync.ts +20 -7
  146. package/src/sync/WebSocketSync.ts +10 -4
  147. package/src/utils/wip.ts +1 -1
  148. package/dist/esm/client/constants.d.ts +0 -1
  149. package/dist/esm/client/constants.js +0 -2
  150. package/dist/esm/client/constants.js.map +0 -1
  151. package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +0 -1
  152. package/dist/esm/persistence/idb/queries/migration/engine.js.map +0 -1
  153. package/dist/esm/persistence/idb/queries/migration/migrations.d.ts +0 -15
  154. package/dist/esm/persistence/idb/queries/migration/migrations.js +0 -243
  155. package/dist/esm/persistence/idb/queries/migration/migrations.js.map +0 -1
  156. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.d.ts +0 -8
  157. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js +0 -24
  158. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +0 -1
  159. package/dist/esm/persistence/idb/queries/migration/paths.js.map +0 -1
  160. package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +0 -1
  161. package/dist/esm/persistence/idb/queries/migration/types.d.ts +0 -6
  162. package/dist/esm/persistence/idb/queries/migration/types.js.map +0 -1
  163. package/src/client/constants.ts +0 -1
  164. package/src/persistence/idb/queries/migration/migrations.ts +0 -345
  165. package/src/persistence/idb/queries/migration/openQueryDatabase.ts +0 -54
  166. package/src/persistence/idb/queries/migration/types.ts +0 -8
  167. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.d.ts +0 -0
  168. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.js +0 -0
  169. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.d.ts +0 -0
  170. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.js +0 -0
  171. /package/dist/esm/persistence/{idb/queries/migration → migration}/types.js +0 -0
  172. /package/src/persistence/{idb/queries/migration → migration}/paths.test.ts +0 -0
  173. /package/src/persistence/{idb/queries/migration → migration}/paths.ts +0 -0
@@ -1,22 +1,27 @@
1
1
  import { Context, InitialContext } from '../../context/context.js';
2
- import { PersistenceImplementation, PersistenceFileDb } from '../interfaces.js';
2
+ import {
3
+ PersistenceImplementation,
4
+ PersistenceFileDb,
5
+ PersistenceNamespace,
6
+ } from '../interfaces.js';
3
7
  import { IdbPersistenceFileDb } from './files/IdbPersistenceFileDb.js';
4
8
  import { IdbMetadataDb } from './metadata/IdbMetadataDb.js';
5
9
  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';
10
+ import { IdbDocumentDb } from './queries/IdbDocumentDb.js';
9
11
  import {
10
12
  closeDatabase,
11
13
  deleteDatabase,
14
+ getDocumentDbName,
12
15
  getMetadataDbName,
13
16
  getNamespaceFromDatabaseInfo,
14
- getSizesOfAllObjectStores,
17
+ overwriteDatabase,
15
18
  } from './util.js';
16
- import { getDatabaseVersion } from './queries/migration/db.js';
19
+ import { openDatabase, upgradeDatabase } from './queries/migration/db.js';
20
+ import { Migration } from '@verdant-web/common';
21
+ import { OpenDocumentDbContext } from '../migration/types.js';
17
22
 
18
23
  export class IdbPersistence implements PersistenceImplementation {
19
- private metadataDb: IDBDatabase | undefined;
24
+ name = 'IdbPersistence';
20
25
  constructor(private indexedDB: IDBFactory = window.indexedDB) {}
21
26
 
22
27
  getNamespaces = async (): Promise<string[]> => {
@@ -30,7 +35,14 @@ export class IdbPersistence implements PersistenceImplementation {
30
35
  };
31
36
 
32
37
  getNamespaceVersion = async (namespace: string): Promise<number> => {
33
- return getDatabaseVersion(this.indexedDB, namespace);
38
+ const databaseName = getDocumentDbName(namespace);
39
+ const dbInfo = await this.indexedDB.databases();
40
+ const existingDb = dbInfo.find((info) => info.name === databaseName);
41
+ if (existingDb) {
42
+ return existingDb.version ?? 0;
43
+ }
44
+
45
+ return 0;
34
46
  };
35
47
 
36
48
  deleteNamespace = async (
@@ -43,8 +55,64 @@ export class IdbPersistence implements PersistenceImplementation {
43
55
  ]);
44
56
  };
45
57
 
58
+ openNamespace = async (
59
+ namespace: string,
60
+ ): Promise<IdbPersistenceNamespace> => {
61
+ return new IdbPersistenceNamespace(this.indexedDB, namespace);
62
+ };
63
+
64
+ copyNamespace = async (
65
+ from: string,
66
+ to: string,
67
+ ctx: InitialContext,
68
+ ): Promise<void> => {
69
+ const fromCtx = { ...ctx, namespace: from };
70
+ const toCtx = { ...ctx, namespace: to };
71
+ const { db: fromMetaDb } = await openMetadataDatabase({
72
+ indexedDB: this.indexedDB,
73
+ log: fromCtx.log,
74
+ namespace: fromCtx.namespace,
75
+ });
76
+
77
+ // no need to involve files, as they store all data
78
+ // in the metadata database.
79
+
80
+ const fromDocumentsDb = await openDatabase({
81
+ indexedDB: this.indexedDB,
82
+ namespace: fromCtx.namespace,
83
+ version: fromCtx.schema.version,
84
+ log: fromCtx.log,
85
+ });
86
+
87
+ fromCtx.log(
88
+ 'info',
89
+ `Copying data from ${fromCtx.namespace} to ${toCtx.namespace}`,
90
+ );
91
+
92
+ await overwriteDatabase(
93
+ fromMetaDb,
94
+ getMetadataDbName(toCtx.namespace),
95
+ toCtx,
96
+ this.indexedDB,
97
+ );
98
+ await overwriteDatabase(
99
+ fromDocumentsDb,
100
+ getDocumentDbName(toCtx.namespace),
101
+ toCtx,
102
+ this.indexedDB,
103
+ );
104
+
105
+ await closeDatabase(fromMetaDb);
106
+ await closeDatabase(fromDocumentsDb);
107
+ };
108
+ }
109
+
110
+ class IdbPersistenceNamespace implements PersistenceNamespace {
111
+ constructor(private indexedDB: IDBFactory, private namespace: string) {}
112
+ private metadataDb: IDBDatabase | undefined;
113
+
46
114
  openFiles(
47
- ctx: Omit<Context, 'files' | 'queries'>,
115
+ ctx: Omit<Context, 'files' | 'documents'>,
48
116
  ): Promise<PersistenceFileDb> {
49
117
  if (!this.metadataDb) {
50
118
  throw new Error(
@@ -58,59 +126,67 @@ export class IdbPersistence implements PersistenceImplementation {
58
126
  const { db } = await openMetadataDatabase({
59
127
  indexedDB: this.indexedDB,
60
128
  log: ctx.log,
61
- namespace: ctx.namespace,
129
+ namespace: this.namespace,
62
130
  });
63
131
  this.metadataDb = db;
132
+ ctx.persistenceShutdownHandler.register(() => closeDatabase(db));
64
133
  return new IdbMetadataDb(db, ctx);
65
134
  };
66
135
 
67
- openQueries = async (ctx: InitialContext & { meta: PersistenceMetadata }) => {
68
- const db = await openQueryDatabase({
136
+ openDocuments = async (ctx: OpenDocumentDbContext) => {
137
+ const db = await openDatabase({
69
138
  version: ctx.schema.version,
70
139
  indexedDB: this.indexedDB,
71
- migrations: ctx.migrations,
72
- context: ctx,
140
+ log: ctx.log,
141
+ namespace: this.namespace,
73
142
  });
74
- return new IdbQueryDb(db, ctx);
143
+ ctx.persistenceShutdownHandler.register(() => closeDatabase(db));
144
+ return new IdbDocumentDb(db, ctx);
75
145
  };
76
146
 
77
- copyNamespace = async (
78
- from: string,
79
- to: string,
147
+ applyMigration = async (
80
148
  ctx: InitialContext,
149
+ migration: Migration<any>,
81
150
  ): 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
151
  ctx.log(
106
152
  'debug',
107
- 'New document store stats:',
108
- await getSizesOfAllObjectStores(toQueryDb),
153
+ 'Applying migration',
154
+ migration.newSchema.version,
155
+ migration,
109
156
  );
157
+ await upgradeDatabase(
158
+ this.indexedDB,
159
+ this.namespace,
160
+ migration.newSchema.version,
161
+ (transaction, db) => {
162
+ for (const newCollection of migration.addedCollections) {
163
+ db.createObjectStore(newCollection, {
164
+ keyPath: migration.newSchema.collections[newCollection].primaryKey,
165
+ autoIncrement: false,
166
+ });
167
+ }
110
168
 
111
- await fromMetaDb.dispose();
112
- await closeDatabase(toMetaDb);
113
- await fromQueries.dispose();
114
- await closeDatabase(toQueryDb);
169
+ for (const collection of migration.allCollections) {
170
+ const store = transaction.objectStore(collection);
171
+ // apply new indexes
172
+ for (const newIndex of migration.addedIndexes[collection] || []) {
173
+ store.createIndex(newIndex.name, newIndex.name, {
174
+ multiEntry: newIndex.multiEntry,
175
+ });
176
+ }
177
+ // remove old indexes
178
+ for (const oldIndex of migration.removedIndexes[collection] || []) {
179
+ store.deleteIndex(oldIndex.name);
180
+ }
181
+ }
182
+ for (const removedCollection of migration.removedCollections) {
183
+ // !! can't delete the store, because old operations that relate to
184
+ // this store may still exist in history. instead, we can clear it out
185
+ // and leave it in place
186
+ transaction.objectStore(removedCollection).clear();
187
+ }
188
+ },
189
+ ctx.log,
190
+ );
115
191
  };
116
192
  }
@@ -18,7 +18,6 @@ import {
18
18
  PersistenceMetadataDb,
19
19
  } from '../../interfaces.js';
20
20
  import { IdbService } from '../IdbService.js';
21
- import cuid from 'cuid';
22
21
  import { closeDatabase, getSizeOfObjectStore } from '../util.js';
23
22
  import { Context } from '../../../context/context.js';
24
23
 
@@ -34,24 +33,35 @@ export type StoredSchema = {
34
33
  schema: string;
35
34
  };
36
35
 
37
- export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
36
+ export class IdbMetadataDb
37
+ extends IdbService
38
+ implements PersistenceMetadataDb<IDBTransaction>
39
+ {
38
40
  constructor(
39
41
  db: IDBDatabase,
40
42
  private ctx: Pick<Context, 'log' | 'namespace'>,
41
43
  ) {
42
44
  super(db, ctx);
43
- this.addDispose(() => closeDatabase(db));
45
+ this.addDispose(() => {
46
+ this.ctx.log('info', `Closing metadata DB for`, this.ctx.namespace);
47
+ return closeDatabase(db);
48
+ });
44
49
  }
45
50
 
46
- transaction = (opts: {
47
- mode?: 'readwrite' | 'readonly';
48
- storeNames: string[];
49
- abort?: AbortSignal;
50
- }) => {
51
- return this.createTransaction(opts.storeNames, {
51
+ transaction = async <T>(
52
+ opts: {
53
+ mode?: 'readwrite' | 'readonly';
54
+ storeNames: string[];
55
+ abort?: AbortSignal;
56
+ },
57
+ procedure: (tx: IDBTransaction) => Promise<T>,
58
+ ) => {
59
+ const tx = this.createTransaction(opts.storeNames, {
52
60
  mode: opts.mode,
53
61
  abort: opts.abort,
54
62
  });
63
+ const result = await procedure(tx);
64
+ return result;
55
65
  };
56
66
 
57
67
  getAckInfo = async (): Promise<AckInfo> => {
@@ -61,7 +71,6 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
61
71
  } else {
62
72
  return {
63
73
  globalAckTimestamp: null,
64
- type: 'ack',
65
74
  };
66
75
  }
67
76
  };
@@ -74,62 +83,37 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
74
83
  );
75
84
  };
76
85
 
77
- private _creatingLocalReplica: Promise<void> | undefined;
78
- private cachedLocalReplica: LocalReplicaInfo | undefined;
79
-
80
86
  getLocalReplica = async (
81
87
  opts?: CommonQueryOptions,
82
- ): Promise<LocalReplicaInfo> => {
83
- if (this.cachedLocalReplica) {
84
- return this.cachedLocalReplica;
85
- }
86
-
87
- const lookup = await this.run<LocalReplicaInfo>(
88
+ ): Promise<LocalReplicaInfo | undefined> => {
89
+ return this.run<LocalReplicaInfo | undefined>(
88
90
  'info',
89
91
  (store) => store.get('localReplicaInfo'),
90
- this.convertOpts(opts),
92
+ opts,
91
93
  );
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
94
  };
122
95
 
123
96
  updateLocalReplica = async (
124
- data: Partial<LocalReplicaInfo>,
125
- opts: CommonQueryOptions = writeOpts,
97
+ data: LocalReplicaInfo,
98
+ opts?: CommonQueryOptions,
126
99
  ): 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;
100
+ try {
101
+ await this.run(
102
+ 'info',
103
+ (store) =>
104
+ store.put({
105
+ ...data,
106
+ type: 'localReplicaInfo',
107
+ }),
108
+ {
109
+ mode: 'readwrite',
110
+ transaction: opts?.transaction,
111
+ },
112
+ );
113
+ } catch (e) {
114
+ this.ctx.log('critical', 'Error updating local replica', data, e);
115
+ throw e;
116
+ }
133
117
  };
134
118
 
135
119
  iterateDocumentBaselines = async (
@@ -151,7 +135,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
151
135
  ];
152
136
  },
153
137
  iterator,
154
- this.convertOpts(opts),
138
+ opts,
155
139
  );
156
140
  };
157
141
 
@@ -170,7 +154,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
170
154
  ];
171
155
  },
172
156
  iterator,
173
- this.convertOpts(opts),
157
+ opts,
174
158
  );
175
159
  };
176
160
 
@@ -182,7 +166,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
182
166
  'baselines',
183
167
  (store) => store.index('timestamp').openCursor(),
184
168
  iterator,
185
- this.convertOpts(opts),
169
+ opts,
186
170
  );
187
171
  };
188
172
 
@@ -193,7 +177,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
193
177
  return this.run<DocumentBaseline>(
194
178
  'baselines',
195
179
  (store) => store.get(oid),
196
- this.convertOpts(opts),
180
+ opts,
197
181
  );
198
182
  };
199
183
 
@@ -204,7 +188,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
204
188
  await this.runAll<any>(
205
189
  'baselines',
206
190
  (store) => baselines.map((b) => store.put(b)),
207
- this.convertOpts(opts),
191
+ opts,
208
192
  );
209
193
  };
210
194
 
@@ -212,11 +196,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
212
196
  oid: string,
213
197
  opts: CommonQueryOptions = writeOpts,
214
198
  ): Promise<void> => {
215
- await this.run(
216
- 'baselines',
217
- (store) => store.delete(oid),
218
- this.convertOpts(opts),
219
- );
199
+ await this.run('baselines', (store) => store.delete(oid), opts);
220
200
  };
221
201
 
222
202
  iterateDocumentOperations = (
@@ -237,7 +217,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
237
217
  return index.openCursor(range);
238
218
  },
239
219
  iterator,
240
- this.convertOpts(opts),
220
+ opts,
241
221
  );
242
222
  };
243
223
 
@@ -262,14 +242,13 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
262
242
  return store.openCursor(range);
263
243
  },
264
244
  iterator,
265
- this.convertOpts(opts),
245
+ opts,
266
246
  );
267
247
  };
268
248
 
269
- consumeEntityOperations = (
249
+ deleteEntityOperations = (
270
250
  oid: string,
271
- iterator: Iterator<ClientOperation>,
272
- opts: CommonQueryOptions & { to?: string | null } = writeOpts,
251
+ opts: CommonQueryOptions & { to: string | null },
273
252
  ): Promise<void> => {
274
253
  return this.iterate<StoredClientOperation>(
275
254
  'operations',
@@ -283,10 +262,9 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
283
262
  return store.openCursor(range);
284
263
  },
285
264
  (op, store) => {
286
- iterator(op);
287
265
  store.delete(op.oid_timestamp);
288
266
  },
289
- this.convertOpts(opts),
267
+ opts,
290
268
  );
291
269
  };
292
270
 
@@ -304,7 +282,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
304
282
  );
305
283
  },
306
284
  iterator,
307
- this.convertOpts(opts),
285
+ opts,
308
286
  );
309
287
  };
310
288
 
@@ -359,7 +337,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
359
337
  return store.index('timestamp').openCursor(range, 'next');
360
338
  },
361
339
  iterator,
362
- this.convertOpts(opts),
340
+ opts,
363
341
  );
364
342
  };
365
343
 
@@ -375,7 +353,7 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
375
353
  affected.add(getOidRoot(op.oid));
376
354
  return store.put(this.addOperationIndexes(op));
377
355
  }),
378
- this.convertOpts(opts),
356
+ opts,
379
357
  );
380
358
  return Array.from(affected);
381
359
  };
@@ -414,15 +392,27 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
414
392
  mode: 'readwrite',
415
393
  transaction: tx,
416
394
  });
395
+ } else {
396
+ const localInfo = await this.getLocalReplica({
397
+ transaction: tx,
398
+ });
399
+ if (localInfo) {
400
+ localInfo.ackedLogicalTime = null;
401
+ localInfo.lastSyncedLogicalTime = null;
402
+ await this.run(
403
+ 'info',
404
+ (store) =>
405
+ store.put({
406
+ ...localInfo,
407
+ type: 'localReplicaInfo',
408
+ }),
409
+ {
410
+ mode: 'readwrite',
411
+ transaction: tx,
412
+ },
413
+ );
414
+ }
417
415
  }
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
416
  };
427
417
 
428
418
  private resetBaselines = async (tx: IDBTransaction) => {
@@ -443,18 +433,6 @@ export class IdbMetadataDb extends IdbService implements PersistenceMetadataDb {
443
433
  d_t: createCompoundIndexValue(getOidRoot(op.oid), op.timestamp) as string,
444
434
  };
445
435
  };
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
436
  }
459
437
 
460
438
  const writeOpts = { mode: 'readwrite' } as const;
@@ -1,9 +1,5 @@
1
1
  import { replaceLegacyOidsInObject } from '@verdant-web/common';
2
- import {
3
- closeDatabase,
4
- getMetadataDbName,
5
- storeRequestPromise,
6
- } from '../util.js';
2
+ import { getMetadataDbName } from '../util.js';
7
3
  import { Context } from '../../../context/context.js';
8
4
 
9
5
  const migrations = [version1, version2, version3, version4, version5, version6];
@@ -13,18 +9,16 @@ export function openMetadataDatabase({
13
9
  indexedDB = window.indexedDB,
14
10
  namespace,
15
11
  log,
16
- metadataVersion = CURRENT_METADATA_VERSION,
17
12
  }: {
18
13
  indexedDB?: IDBFactory;
19
14
  namespace: string;
20
15
  log?: Context['log'];
21
- metadataVersion?: number;
22
16
  }): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
23
17
  return new Promise<{ wasInitialized: boolean; db: IDBDatabase }>(
24
18
  (resolve, reject) => {
25
19
  const request = indexedDB.open(
26
20
  getMetadataDbName(namespace),
27
- metadataVersion,
21
+ CURRENT_METADATA_VERSION,
28
22
  );
29
23
  let wasInitialized = false;
30
24
  request.onupgradeneeded = async (event) => {
@@ -56,94 +50,6 @@ export function openMetadataDatabase({
56
50
  );
57
51
  }
58
52
 
59
- export async function openWIPMetadataDatabase({
60
- wipNamespace,
61
- namespace,
62
- indexedDB,
63
- log,
64
- metadataVersion,
65
- }: {
66
- indexedDB?: IDBFactory;
67
- namespace: string;
68
- wipNamespace: string;
69
- log?: (...args: any[]) => void;
70
- metadataVersion?: number;
71
- }): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
72
- const result = await openMetadataDatabase({
73
- namespace: wipNamespace,
74
- indexedDB,
75
- log,
76
- metadataVersion,
77
- });
78
-
79
- // this WIP database was already set up.
80
- if (!result.wasInitialized) {
81
- return result;
82
- }
83
-
84
- log?.('debug', 'Beginning copy of production metadata database to WIP');
85
- // copy all data from production metadata database
86
- const { db: prodDb } = await openMetadataDatabase({
87
- namespace,
88
- indexedDB,
89
- log,
90
- metadataVersion,
91
- });
92
-
93
- const tx = prodDb.transaction(
94
- ['baselines', 'operations', 'info'],
95
- 'readonly',
96
- );
97
- const [baselines, operations, info] = await Promise.all([
98
- storeRequestPromise(tx.objectStore('baselines').getAll()),
99
- storeRequestPromise(tx.objectStore('operations').getAll()),
100
- storeRequestPromise(tx.objectStore('info').getAll()),
101
- ]);
102
-
103
- const wipTx = result.db.transaction(
104
- ['baselines', 'operations', 'info'],
105
- 'readwrite',
106
- );
107
- const wipBaselines = wipTx.objectStore('baselines');
108
- const wipOperations = wipTx.objectStore('operations');
109
- const wipInfo = wipTx.objectStore('info');
110
-
111
- for (const baseline of baselines) {
112
- wipBaselines.put(baseline);
113
- }
114
- for (const operation of operations) {
115
- wipOperations.put(operation);
116
- }
117
- for (const infoItem of info) {
118
- wipInfo.put(infoItem);
119
- }
120
-
121
- await new Promise<void>((resolve, reject) => {
122
- wipTx.oncomplete = () => {
123
- resolve();
124
- };
125
- wipTx.onerror = (event) => {
126
- reject(event);
127
- };
128
- wipTx.onabort = (event) => {
129
- reject(event);
130
- };
131
- });
132
-
133
- await closeDatabase(prodDb);
134
-
135
- log?.(
136
- 'debug',
137
- 'Finished copy of production metadata database to WIP. Copied:',
138
- baselines.length,
139
- 'baselines,',
140
- operations.length,
141
- 'operations',
142
- );
143
-
144
- return result;
145
- }
146
-
147
53
  async function version1(db: IDBDatabase, tx: IDBTransaction) {
148
54
  const baselinesStore = db.createObjectStore('baselines', {
149
55
  keyPath: 'oid',