@verdant-web/store 4.0.0 → 4.1.0-alpha.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.
- package/LICENSE +21 -650
- package/dist/bundle/index.js +11 -11
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -2
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -5
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/client/Client.d.ts +6 -2
- package/dist/esm/client/Client.js +18 -6
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +7 -5
- package/dist/esm/client/ClientDescriptor.js +18 -4
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context/ShutdownHandler.d.ts +8 -0
- package/dist/esm/context/ShutdownHandler.js +24 -0
- package/dist/esm/context/ShutdownHandler.js.map +1 -0
- package/dist/esm/context/context.d.ts +15 -4
- package/dist/esm/entities/EntityStore.js +6 -3
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/EntityFile.d.ts +1 -0
- package/dist/esm/files/EntityFile.js +16 -11
- package/dist/esm/files/EntityFile.js.map +1 -1
- package/dist/esm/files/FileManager.d.ts +1 -3
- package/dist/esm/files/FileManager.js +12 -10
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +4 -5
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +6 -0
- package/dist/esm/internal.js +5 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/persistence/MessageCreator.d.ts +3 -1
- package/dist/esm/persistence/MessageCreator.js +58 -55
- package/dist/esm/persistence/MessageCreator.js.map +1 -1
- package/dist/esm/persistence/PersistenceFiles.d.ts +8 -21
- package/dist/esm/persistence/PersistenceFiles.js +44 -30
- package/dist/esm/persistence/PersistenceFiles.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +12 -11
- package/dist/esm/persistence/PersistenceMetadata.js +201 -137
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/PersistenceQueries.d.ts +10 -11
- package/dist/esm/persistence/PersistenceQueries.js +33 -5
- package/dist/esm/persistence/PersistenceQueries.js.map +1 -1
- package/dist/esm/persistence/PersistenceRebaser.d.ts +5 -9
- package/dist/esm/persistence/PersistenceRebaser.js +63 -47
- package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +0 -1
- package/dist/esm/persistence/idb/IdbService.js +28 -16
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +11 -31
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js +31 -36
- package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +17 -9
- package/dist/esm/persistence/idb/idbPersistence.js +80 -39
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +7 -10
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +45 -71
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.d.ts +1 -12
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +3 -56
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.d.ts → IdbDocumentDb.d.ts} +7 -13
- package/dist/esm/persistence/idb/queries/{IdbQueryDb.js → IdbDocumentDb.js} +15 -32
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -0
- package/dist/esm/persistence/idb/queries/migration/db.d.ts +3 -5
- package/dist/esm/persistence/idb/queries/migration/db.js +13 -28
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.d.ts +8 -4
- package/dist/esm/persistence/idb/util.js +64 -21
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +68 -75
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.d.ts +4 -7
- package/dist/esm/persistence/{idb/queries/migration → migration}/engine.js +18 -10
- package/dist/esm/persistence/migration/engine.js.map +1 -0
- package/dist/esm/persistence/migration/finalize.d.ts +9 -0
- package/dist/esm/persistence/migration/finalize.js +75 -0
- package/dist/esm/persistence/migration/finalize.js.map +1 -0
- package/dist/esm/persistence/migration/migrate.d.ts +12 -0
- package/dist/esm/persistence/migration/migrate.js +89 -0
- package/dist/esm/persistence/migration/migrate.js.map +1 -0
- package/dist/esm/persistence/migration/paths.js.map +1 -0
- package/dist/esm/persistence/migration/paths.test.js.map +1 -0
- package/dist/esm/persistence/migration/types.d.ts +3 -0
- package/dist/esm/persistence/migration/types.js.map +1 -0
- package/dist/esm/persistence/persistence.js +25 -15
- package/dist/esm/persistence/persistence.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -1
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +2 -2
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +1 -1
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +1 -1
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/sync/FileSync.js +3 -3
- package/dist/esm/sync/FileSync.js.map +1 -1
- package/dist/esm/sync/PushPullSync.d.ts +2 -3
- package/dist/esm/sync/PushPullSync.js +4 -2
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/ServerSyncEndpointProvider.d.ts +3 -7
- package/dist/esm/sync/ServerSyncEndpointProvider.js +3 -2
- package/dist/esm/sync/ServerSyncEndpointProvider.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -1
- package/dist/esm/sync/Sync.js +12 -4
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +10 -4
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/package.json +6 -2
- package/src/__tests__/fixtures/testStorage.ts +6 -6
- package/src/client/Client.ts +26 -8
- package/src/client/ClientDescriptor.ts +27 -9
- package/src/context/ShutdownHandler.ts +26 -0
- package/src/context/context.ts +16 -4
- package/src/entities/EntityStore.ts +9 -3
- package/src/files/EntityFile.ts +11 -6
- package/src/files/FileManager.ts +13 -10
- package/src/index.ts +8 -9
- package/src/internal.ts +27 -0
- package/src/persistence/MessageCreator.ts +79 -73
- package/src/persistence/PersistenceFiles.ts +57 -31
- package/src/persistence/PersistenceMetadata.ts +287 -195
- package/src/persistence/PersistenceQueries.ts +45 -9
- package/src/persistence/PersistenceRebaser.ts +105 -70
- package/src/persistence/idb/IdbService.ts +40 -22
- package/src/persistence/idb/files/IdbPersistenceFileDb.ts +30 -62
- package/src/persistence/idb/idbPersistence.ts +123 -47
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +75 -97
- package/src/persistence/idb/metadata/openMetadataDatabase.ts +2 -96
- package/src/persistence/idb/queries/{IdbQueryDb.ts → IdbDocumentDb.ts} +17 -57
- package/src/persistence/idb/queries/migration/db.ts +20 -39
- package/src/persistence/idb/util.ts +84 -21
- package/src/persistence/interfaces.ts +89 -90
- package/src/persistence/{idb/queries/migration → migration}/engine.ts +30 -15
- package/src/persistence/migration/finalize.ts +126 -0
- package/src/persistence/migration/migrate.ts +169 -0
- package/src/persistence/migration/types.ts +4 -0
- package/src/persistence/persistence.ts +37 -14
- package/src/queries/FindAllQuery.ts +1 -1
- package/src/queries/FindInfiniteQuery.ts +2 -2
- package/src/queries/FindOneQuery.ts +1 -1
- package/src/queries/FindPageQuery.ts +1 -1
- package/src/sync/FileSync.ts +21 -15
- package/src/sync/PushPullSync.ts +3 -4
- package/src/sync/ServerSyncEndpointProvider.ts +6 -8
- package/src/sync/Sync.ts +20 -7
- package/src/sync/WebSocketSync.ts +10 -4
- package/dist/esm/client/constants.d.ts +0 -1
- package/dist/esm/client/constants.js +0 -2
- package/dist/esm/client/constants.js.map +0 -1
- package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/engine.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/migrations.d.ts +0 -15
- package/dist/esm/persistence/idb/queries/migration/migrations.js +0 -243
- package/dist/esm/persistence/idb/queries/migration/migrations.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.d.ts +0 -8
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js +0 -24
- package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +0 -1
- package/dist/esm/persistence/idb/queries/migration/types.d.ts +0 -6
- package/dist/esm/persistence/idb/queries/migration/types.js.map +0 -1
- package/src/client/constants.ts +0 -1
- package/src/persistence/idb/queries/migration/migrations.ts +0 -345
- package/src/persistence/idb/queries/migration/openQueryDatabase.ts +0 -54
- package/src/persistence/idb/queries/migration/types.ts +0 -8
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.d.ts +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.js +0 -0
- /package/dist/esm/persistence/{idb/queries/migration → migration}/types.js +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.test.ts +0 -0
- /package/src/persistence/{idb/queries/migration → migration}/paths.ts +0 -0
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
AbstractTransaction,
|
|
15
15
|
ClientOperation,
|
|
16
16
|
CommonQueryOptions,
|
|
17
|
+
LocalReplicaInfo,
|
|
17
18
|
MetadataExport,
|
|
18
19
|
PersistenceMetadataDb,
|
|
19
20
|
} from './interfaces.js';
|
|
20
21
|
import { InitialContext } from '../context/context.js';
|
|
21
22
|
import { PersistenceRebaser } from './PersistenceRebaser.js';
|
|
22
23
|
import { MessageCreator } from './MessageCreator.js';
|
|
23
|
-
import
|
|
24
|
+
import cuid from 'cuid';
|
|
24
25
|
|
|
25
|
-
export class PersistenceMetadata
|
|
26
|
+
export class PersistenceMetadata {
|
|
26
27
|
private rebaser: PersistenceRebaser;
|
|
27
28
|
/** Available to others, like sync... */
|
|
28
29
|
readonly messageCreator: MessageCreator;
|
|
@@ -30,14 +31,9 @@ export class PersistenceMetadata extends Disposable {
|
|
|
30
31
|
syncMessage: (message: ClientMessage) => void;
|
|
31
32
|
}>();
|
|
32
33
|
|
|
33
|
-
constructor(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
) {
|
|
37
|
-
super();
|
|
38
|
-
this.rebaser = new PersistenceRebaser(db, ctx);
|
|
39
|
-
this.messageCreator = new MessageCreator(db, ctx);
|
|
40
|
-
this.compose(this.db);
|
|
34
|
+
constructor(private db: PersistenceMetadataDb, private ctx: InitialContext) {
|
|
35
|
+
this.rebaser = new PersistenceRebaser(db, this, ctx);
|
|
36
|
+
this.messageCreator = new MessageCreator(db, this, ctx);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
private insertOperations = async (
|
|
@@ -60,7 +56,10 @@ export class PersistenceMetadata extends Disposable {
|
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
// we can now enqueue and check for rebase opportunities
|
|
63
|
-
if (
|
|
59
|
+
if (
|
|
60
|
+
!this.ctx.config.persistence?.disableRebasing &&
|
|
61
|
+
!this.ctx.pauseRebasing
|
|
62
|
+
) {
|
|
64
63
|
this.rebaser.tryAutonomousRebase();
|
|
65
64
|
}
|
|
66
65
|
|
|
@@ -78,7 +77,10 @@ export class PersistenceMetadata extends Disposable {
|
|
|
78
77
|
(operation as ClientOperation).isLocal = true;
|
|
79
78
|
}
|
|
80
79
|
await this.insertOperations(operations as ClientOperation[], options);
|
|
81
|
-
|
|
80
|
+
this.ctx.log(
|
|
81
|
+
'debug',
|
|
82
|
+
`Inserted ${operations.length} local operations; sending sync message`,
|
|
83
|
+
);
|
|
82
84
|
const message = await this.messageCreator.createOperation({ operations });
|
|
83
85
|
this.events.emit('syncMessage', message);
|
|
84
86
|
};
|
|
@@ -124,72 +126,80 @@ export class PersistenceMetadata extends Disposable {
|
|
|
124
126
|
assert(documentOid === rootOid, 'Must be root document OID');
|
|
125
127
|
oids.add(documentOid);
|
|
126
128
|
// readwrite mode to block on other write transactions
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
oid
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
129
|
+
return this.db.transaction(
|
|
130
|
+
{
|
|
131
|
+
storeNames: ['baselines', 'operations'],
|
|
132
|
+
},
|
|
133
|
+
async (transaction) => {
|
|
134
|
+
await Promise.all([
|
|
135
|
+
this.db.iterateDocumentBaselines(
|
|
136
|
+
documentOid,
|
|
137
|
+
(baseline) => {
|
|
138
|
+
oids.add(baseline.oid);
|
|
139
|
+
},
|
|
140
|
+
{ transaction },
|
|
141
|
+
),
|
|
142
|
+
this.db.iterateDocumentOperations(
|
|
143
|
+
documentOid,
|
|
144
|
+
(patch) => {
|
|
145
|
+
oids.add(patch.oid);
|
|
146
|
+
},
|
|
147
|
+
{ transaction },
|
|
148
|
+
),
|
|
149
|
+
]);
|
|
150
|
+
const authz = await this.getDocumentAuthz(documentOid);
|
|
151
|
+
const ops = new Array<Operation>();
|
|
152
|
+
for (const oid of oids) {
|
|
153
|
+
ops.push({
|
|
154
|
+
oid,
|
|
155
|
+
timestamp: this.ctx.time.now,
|
|
156
|
+
data: { op: 'delete' },
|
|
157
|
+
authz,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return this.insertLocalOperations(ops, { transaction });
|
|
161
|
+
},
|
|
162
|
+
);
|
|
157
163
|
};
|
|
158
164
|
|
|
159
165
|
deleteCollection = async (collection: string) => {
|
|
160
166
|
const oids = new Set<ObjectIdentifier>();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
oid
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
167
|
+
return this.db.transaction(
|
|
168
|
+
{
|
|
169
|
+
storeNames: ['baselines', 'operations'],
|
|
170
|
+
mode: 'readwrite',
|
|
171
|
+
},
|
|
172
|
+
async (transaction) => {
|
|
173
|
+
await Promise.all([
|
|
174
|
+
this.db.iterateCollectionBaselines(
|
|
175
|
+
collection,
|
|
176
|
+
(baseline) => {
|
|
177
|
+
oids.add(baseline.oid);
|
|
178
|
+
},
|
|
179
|
+
{ transaction },
|
|
180
|
+
),
|
|
181
|
+
this.db.iterateCollectionOperations(
|
|
182
|
+
collection,
|
|
183
|
+
(patch) => {
|
|
184
|
+
oids.add(patch.oid);
|
|
185
|
+
},
|
|
186
|
+
{ transaction },
|
|
187
|
+
),
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
const ops = new Array<Operation>();
|
|
191
|
+
for (const oid of oids) {
|
|
192
|
+
ops.push({
|
|
193
|
+
oid,
|
|
194
|
+
timestamp: this.ctx.time.now,
|
|
195
|
+
data: { op: 'delete' },
|
|
196
|
+
authz: undefined,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
return this.insertLocalOperations(ops, { transaction });
|
|
201
|
+
},
|
|
202
|
+
);
|
|
193
203
|
};
|
|
194
204
|
|
|
195
205
|
getDocumentSnapshot = async (
|
|
@@ -198,83 +208,91 @@ export class PersistenceMetadata extends Disposable {
|
|
|
198
208
|
) => {
|
|
199
209
|
const documentOid = getOidRoot(oid);
|
|
200
210
|
assert(documentOid === oid, 'Must be root document OID');
|
|
201
|
-
|
|
202
|
-
storeNames: ['baselines', 'operations'],
|
|
203
|
-
mode: 'readwrite',
|
|
204
|
-
});
|
|
205
|
-
const baselines: DocumentBaseline[] = [];
|
|
206
|
-
await this.db.iterateDocumentBaselines(
|
|
207
|
-
documentOid,
|
|
208
|
-
(b) => {
|
|
209
|
-
baselines.push(b);
|
|
210
|
-
},
|
|
211
|
+
return this.db.transaction(
|
|
211
212
|
{
|
|
212
|
-
|
|
213
|
+
storeNames: ['baselines', 'operations'],
|
|
214
|
+
mode: 'readwrite',
|
|
213
215
|
},
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
216
|
+
async (transaction) => {
|
|
217
|
+
const baselines: DocumentBaseline[] = [];
|
|
218
|
+
await this.db.iterateDocumentBaselines(
|
|
219
|
+
documentOid,
|
|
220
|
+
(b) => {
|
|
221
|
+
baselines.push(b);
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
transaction,
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
const objectMap = new Map<ObjectIdentifier, any>();
|
|
228
|
+
for (const baseline of baselines) {
|
|
229
|
+
if (baseline.snapshot) {
|
|
230
|
+
assignOid(baseline.snapshot, baseline.oid);
|
|
231
|
+
}
|
|
232
|
+
objectMap.set(baseline.oid, baseline.snapshot);
|
|
229
233
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
await this.db.iterateDocumentOperations(
|
|
235
|
+
documentOid,
|
|
236
|
+
(op) => {
|
|
237
|
+
const obj = objectMap.get(op.oid) || undefined;
|
|
238
|
+
const newObj = applyPatch(obj, op.data);
|
|
239
|
+
if (newObj) {
|
|
240
|
+
assignOid(newObj, op.oid);
|
|
241
|
+
}
|
|
242
|
+
objectMap.set(op.oid, newObj);
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
transaction,
|
|
246
|
+
// only apply operations up to the current time
|
|
247
|
+
to: options.to || this.ctx.time.now,
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
const root = objectMap.get(documentOid);
|
|
251
|
+
if (root) {
|
|
252
|
+
substituteRefsWithObjects(root, objectMap);
|
|
253
|
+
}
|
|
254
|
+
return root;
|
|
236
255
|
},
|
|
237
256
|
);
|
|
238
|
-
const root = objectMap.get(documentOid);
|
|
239
|
-
if (root) {
|
|
240
|
-
substituteRefsWithObjects(root, objectMap);
|
|
241
|
-
}
|
|
242
|
-
return root;
|
|
243
257
|
};
|
|
244
258
|
|
|
245
259
|
getDocumentData = async (
|
|
246
260
|
oid: ObjectIdentifier,
|
|
247
261
|
options?: { abort?: AbortSignal },
|
|
248
262
|
) => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
263
|
+
return this.db.transaction(
|
|
264
|
+
{
|
|
265
|
+
storeNames: ['baselines', 'operations'],
|
|
266
|
+
abort: options?.abort,
|
|
267
|
+
},
|
|
268
|
+
async (transaction) => {
|
|
269
|
+
const baselines: DocumentBaseline[] = [];
|
|
270
|
+
const operations: Record<ObjectIdentifier, Operation[]> = {};
|
|
271
|
+
await Promise.all([
|
|
272
|
+
this.db.iterateDocumentBaselines(
|
|
273
|
+
oid,
|
|
274
|
+
(baseline) => {
|
|
275
|
+
baselines.push(baseline);
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
transaction,
|
|
279
|
+
},
|
|
280
|
+
),
|
|
281
|
+
this.db.iterateDocumentOperations(
|
|
282
|
+
oid,
|
|
283
|
+
(op) => {
|
|
284
|
+
operations[op.oid] ??= [];
|
|
285
|
+
operations[op.oid].push(op);
|
|
286
|
+
},
|
|
287
|
+
{ transaction },
|
|
288
|
+
),
|
|
289
|
+
]);
|
|
290
|
+
return {
|
|
291
|
+
baselines,
|
|
292
|
+
operations,
|
|
293
|
+
};
|
|
294
|
+
},
|
|
295
|
+
);
|
|
278
296
|
};
|
|
279
297
|
|
|
280
298
|
getDocumentAuthz = async (oid: ObjectIdentifier) => {
|
|
@@ -296,28 +314,37 @@ export class PersistenceMetadata extends Disposable {
|
|
|
296
314
|
},
|
|
297
315
|
options?: { abort?: AbortSignal },
|
|
298
316
|
) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
317
|
+
return this.db.transaction(
|
|
318
|
+
{
|
|
319
|
+
storeNames: ['baselines', 'operations'],
|
|
320
|
+
abort: options?.abort,
|
|
321
|
+
mode: 'readwrite',
|
|
322
|
+
},
|
|
323
|
+
async (transaction) => {
|
|
324
|
+
this.ctx.log('debug', 'Begin insert data transaction');
|
|
325
|
+
if (data.baselines) {
|
|
326
|
+
await this.insertRemoteBaselines(data.baselines, { transaction });
|
|
327
|
+
}
|
|
328
|
+
this.ctx.log('debug', 'Inserted baselines (if any)');
|
|
329
|
+
if (options?.abort?.aborted) throw new Error('Aborted');
|
|
330
|
+
if (data.operations) {
|
|
331
|
+
if (data.isLocal) {
|
|
332
|
+
this.ctx.log('debug', 'Inserting local operations');
|
|
333
|
+
await this.insertLocalOperations(data.operations, { transaction });
|
|
334
|
+
} else {
|
|
335
|
+
this.ctx.log('debug', 'Inserting remote operations');
|
|
336
|
+
await this.insertRemoteOperations(data.operations, { transaction });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
this.ctx.log('debug', 'End insert data transaction');
|
|
340
|
+
},
|
|
341
|
+
);
|
|
315
342
|
};
|
|
316
343
|
|
|
317
344
|
updateLastSynced = async (timestamp: string) => {
|
|
318
345
|
if (this.ctx.closing) return;
|
|
319
346
|
|
|
320
|
-
return this.
|
|
347
|
+
return this.updateLocalReplica({
|
|
321
348
|
lastSyncedLogicalTime: timestamp,
|
|
322
349
|
});
|
|
323
350
|
};
|
|
@@ -329,8 +356,50 @@ export class PersistenceMetadata extends Disposable {
|
|
|
329
356
|
}
|
|
330
357
|
};
|
|
331
358
|
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
// caching local replica as it's accessed often and only changed
|
|
360
|
+
// via this class.
|
|
361
|
+
private _cachedLocalReplica: LocalReplicaInfo | null = null;
|
|
362
|
+
private _creatingLocalReplica: Promise<LocalReplicaInfo> | undefined =
|
|
363
|
+
undefined;
|
|
364
|
+
getLocalReplica = async (
|
|
365
|
+
options?: CommonQueryOptions,
|
|
366
|
+
): Promise<LocalReplicaInfo> => {
|
|
367
|
+
if (this._cachedLocalReplica) return this._cachedLocalReplica;
|
|
368
|
+
|
|
369
|
+
const lookup = await this.db.getLocalReplica(options);
|
|
370
|
+
if (lookup) {
|
|
371
|
+
this.ctx.log('debug', 'Read local replica', lookup);
|
|
372
|
+
this._cachedLocalReplica = lookup;
|
|
373
|
+
return lookup;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (this._creatingLocalReplica) {
|
|
377
|
+
return this._creatingLocalReplica;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this._creatingLocalReplica = (async () => {
|
|
381
|
+
const replicaId = cuid();
|
|
382
|
+
const replicaInfo: LocalReplicaInfo = {
|
|
383
|
+
id: replicaId,
|
|
384
|
+
userId: null,
|
|
385
|
+
ackedLogicalTime: null,
|
|
386
|
+
lastSyncedLogicalTime: null,
|
|
387
|
+
};
|
|
388
|
+
await this.db.updateLocalReplica(replicaInfo);
|
|
389
|
+
this._cachedLocalReplica = replicaInfo;
|
|
390
|
+
return replicaInfo;
|
|
391
|
+
})();
|
|
392
|
+
return this._creatingLocalReplica;
|
|
393
|
+
};
|
|
394
|
+
updateLocalReplica = async (
|
|
395
|
+
data: Partial<LocalReplicaInfo>,
|
|
396
|
+
opts?: { transaction?: AbstractTransaction },
|
|
397
|
+
) => {
|
|
398
|
+
const original = await this.getLocalReplica(opts);
|
|
399
|
+
assert(!!original, 'Local replica must exist');
|
|
400
|
+
Object.assign(original, data);
|
|
401
|
+
this._cachedLocalReplica = original;
|
|
402
|
+
await this.db.updateLocalReplica(original, opts);
|
|
334
403
|
};
|
|
335
404
|
|
|
336
405
|
// used to construct sync messages
|
|
@@ -338,54 +407,77 @@ export class PersistenceMetadata extends Disposable {
|
|
|
338
407
|
iterateAllOperations = this.db.iterateAllOperations;
|
|
339
408
|
iterateAllBaselines = this.db.iterateAllBaselines;
|
|
340
409
|
|
|
341
|
-
reset =
|
|
410
|
+
reset = async () => {
|
|
411
|
+
if (this.ctx.closing) return;
|
|
412
|
+
await this.db.reset();
|
|
413
|
+
};
|
|
342
414
|
stats = this.db.stats;
|
|
343
415
|
|
|
344
416
|
export = async (): Promise<MetadataExport> => {
|
|
345
|
-
const db = this.db;
|
|
346
417
|
const baselines = new Array<DocumentBaseline>();
|
|
347
418
|
const operations = new Array<ClientOperation>();
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
await this.iterateAllOperations(
|
|
353
|
-
(op) => {
|
|
354
|
-
operations.push(op);
|
|
419
|
+
return this.db.transaction(
|
|
420
|
+
{
|
|
421
|
+
storeNames: ['baselines', 'operations'],
|
|
422
|
+
mode: 'readwrite',
|
|
355
423
|
},
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
424
|
+
async (transaction) => {
|
|
425
|
+
await this.iterateAllOperations(
|
|
426
|
+
(op) => {
|
|
427
|
+
operations.push(op);
|
|
428
|
+
},
|
|
429
|
+
{ transaction },
|
|
430
|
+
);
|
|
431
|
+
await this.iterateAllBaselines(
|
|
432
|
+
(baseline) => {
|
|
433
|
+
baselines.push(baseline);
|
|
434
|
+
},
|
|
435
|
+
{ transaction },
|
|
436
|
+
);
|
|
437
|
+
const localReplica = await this.getLocalReplica();
|
|
438
|
+
return {
|
|
439
|
+
operations,
|
|
440
|
+
baselines,
|
|
441
|
+
localReplica,
|
|
442
|
+
schemaVersion: this.ctx.schema.version,
|
|
443
|
+
};
|
|
361
444
|
},
|
|
362
|
-
{ transaction },
|
|
363
445
|
);
|
|
364
|
-
const localReplica = await this.db.getLocalReplica();
|
|
365
|
-
return {
|
|
366
|
-
operations,
|
|
367
|
-
baselines,
|
|
368
|
-
localReplica,
|
|
369
|
-
schemaVersion: this.ctx.schema.version,
|
|
370
|
-
};
|
|
371
446
|
};
|
|
372
447
|
|
|
373
448
|
resetFrom = async (data: MetadataExport) => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
await this.db.reset({ clearReplica: true, transaction });
|
|
449
|
+
this._cachedLocalReplica = null;
|
|
450
|
+
|
|
451
|
+
// await this.db.transaction(
|
|
452
|
+
// { mode: 'readwrite', storeNames: ['baselines', 'operations', 'info'] },
|
|
453
|
+
// async (tx) => {
|
|
454
|
+
// await this.db.reset({ clearReplica: true, transaction: tx });
|
|
455
|
+
|
|
456
|
+
// if (data.localReplica) {
|
|
457
|
+
// await this.updateLocalReplica(
|
|
458
|
+
// {
|
|
459
|
+
// ackedLogicalTime: data.localReplica.ackedLogicalTime,
|
|
460
|
+
// lastSyncedLogicalTime: data.localReplica.lastSyncedLogicalTime,
|
|
461
|
+
// },
|
|
462
|
+
// {
|
|
463
|
+
// transaction: tx,
|
|
464
|
+
// },
|
|
465
|
+
// );
|
|
466
|
+
// }
|
|
467
|
+
// },
|
|
468
|
+
// );
|
|
469
|
+
|
|
470
|
+
// transaction wasn't working for IDB (invalid state -- it was closing early?)
|
|
471
|
+
await this.db.reset({ clearReplica: true });
|
|
472
|
+
|
|
380
473
|
if (data.localReplica) {
|
|
381
|
-
await this.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
},
|
|
386
|
-
{ transaction },
|
|
387
|
-
);
|
|
474
|
+
await this.updateLocalReplica({
|
|
475
|
+
ackedLogicalTime: data.localReplica.ackedLogicalTime,
|
|
476
|
+
lastSyncedLogicalTime: data.localReplica.lastSyncedLogicalTime,
|
|
477
|
+
});
|
|
388
478
|
}
|
|
479
|
+
// after transaction completes, insert new data.
|
|
480
|
+
// TODO: does this have to be split up like this?
|
|
389
481
|
this.ctx.log('debug', 'Resetting metadata from export', data);
|
|
390
482
|
await this.insertData({
|
|
391
483
|
operations: data.operations,
|
|
@@ -404,7 +496,7 @@ export class PersistenceMetadata extends Disposable {
|
|
|
404
496
|
};
|
|
405
497
|
|
|
406
498
|
private ack = async (timestamp: string) => {
|
|
407
|
-
const localReplicaInfo = await this.
|
|
499
|
+
const localReplicaInfo = await this.getLocalReplica();
|
|
408
500
|
// can't ack timestamps from the future.
|
|
409
501
|
if (timestamp > this.ctx.time.now) return;
|
|
410
502
|
|
|
@@ -419,7 +511,7 @@ export class PersistenceMetadata extends Disposable {
|
|
|
419
511
|
(!localReplicaInfo.ackedLogicalTime ||
|
|
420
512
|
timestamp > localReplicaInfo.ackedLogicalTime)
|
|
421
513
|
) {
|
|
422
|
-
this.
|
|
514
|
+
this.updateLocalReplica({ ackedLogicalTime: timestamp });
|
|
423
515
|
}
|
|
424
516
|
};
|
|
425
517
|
}
|
|
@@ -1,19 +1,55 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PersistenceDocumentDb } from './interfaces.js';
|
|
2
2
|
import { Context } from '../context/context.js';
|
|
3
|
-
import {
|
|
3
|
+
import { decomposeOid, ObjectIdentifier } from '@verdant-web/common';
|
|
4
4
|
|
|
5
|
-
export class
|
|
5
|
+
export class PersistenceDocuments {
|
|
6
6
|
constructor(
|
|
7
|
-
private db:
|
|
7
|
+
private db: PersistenceDocumentDb,
|
|
8
8
|
private ctx: Omit<Context, 'queries'>,
|
|
9
|
-
) {
|
|
10
|
-
super();
|
|
11
|
-
this.compose(this.db);
|
|
12
|
-
}
|
|
9
|
+
) {}
|
|
13
10
|
|
|
14
11
|
reset = this.db.reset.bind(this.db);
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
close = this.db.close.bind(this.db);
|
|
14
|
+
|
|
15
|
+
saveEntities = async (
|
|
16
|
+
entities: { oid: ObjectIdentifier; getSnapshot: () => any }[],
|
|
17
|
+
options?: { abort?: AbortSignal },
|
|
18
|
+
) => {
|
|
19
|
+
if (entities.length === 0) return;
|
|
20
|
+
|
|
21
|
+
// filter entities to remove collections which don't
|
|
22
|
+
// exist in the schema anymore
|
|
23
|
+
const currentCollectionSet = new Set(
|
|
24
|
+
Object.keys(this.ctx.schema.collections),
|
|
25
|
+
);
|
|
26
|
+
const collections: string[] = [];
|
|
27
|
+
const filteredEntities = entities.filter((entity) => {
|
|
28
|
+
const { collection } = decomposeOid(entity.oid);
|
|
29
|
+
if (!currentCollectionSet.has(collection)) {
|
|
30
|
+
this.ctx.log(
|
|
31
|
+
'warn',
|
|
32
|
+
`Entity ${entity.oid} is in a collection that no longer exists in the schema. It will not be saved.`,
|
|
33
|
+
);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (!collections.includes(collection)) collections.push(collection);
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (collections.length === 0) return;
|
|
41
|
+
|
|
42
|
+
this.ctx.log('debug', 'Saving', filteredEntities.length, 'entities');
|
|
43
|
+
await this.db.saveEntities(filteredEntities, {
|
|
44
|
+
abort: options?.abort,
|
|
45
|
+
collections,
|
|
46
|
+
});
|
|
47
|
+
this.ctx.log('debug', 'Saved', filteredEntities.length, 'entities');
|
|
48
|
+
this.ctx.entityEvents.emit('collectionsChanged', collections);
|
|
49
|
+
for (const entity of entities) {
|
|
50
|
+
this.ctx.entityEvents.emit('documentChanged', entity.oid);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
17
53
|
|
|
18
54
|
findOneOid = this.db.findOneOid.bind(this.db);
|
|
19
55
|
findAllOids = this.db.findAllOids.bind(this.db);
|