@verdant-web/store 2.8.4 → 3.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.
- package/dist/bundle/index.js +9 -10
- package/dist/bundle/index.js.map +4 -4
- package/dist/cjs/DocumentManager.d.ts +6 -5
- package/dist/cjs/DocumentManager.js +2 -2
- package/dist/cjs/DocumentManager.js.map +1 -1
- package/dist/cjs/IDBService.d.ts +28 -7
- package/dist/cjs/IDBService.js +50 -13
- package/dist/cjs/IDBService.js.map +1 -1
- package/dist/cjs/UndoHistory.d.ts +1 -1
- package/dist/cjs/UndoHistory.js +6 -2
- package/dist/cjs/UndoHistory.js.map +1 -1
- package/dist/cjs/__tests__/batching.test.js +3 -1
- package/dist/cjs/__tests__/batching.test.js.map +1 -1
- package/dist/cjs/__tests__/documents.test.js +54 -6
- package/dist/cjs/__tests__/documents.test.js.map +1 -1
- package/dist/cjs/__tests__/fixtures/testStorage.d.ts +8 -2
- package/dist/cjs/__tests__/fixtures/testStorage.js +8 -1
- package/dist/cjs/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/cjs/__tests__/legacyOids.test.js +50 -17
- package/dist/cjs/__tests__/legacyOids.test.js.map +1 -1
- package/dist/cjs/__tests__/mutations.test.js +9 -3
- package/dist/cjs/__tests__/mutations.test.js.map +1 -1
- package/dist/cjs/__tests__/queries.test.js +6 -2
- package/dist/cjs/__tests__/queries.test.js.map +1 -1
- package/dist/cjs/__tests__/setup/indexedDB.d.ts +1 -1
- package/dist/cjs/__tests__/setup/indexedDB.js +13 -1
- package/dist/cjs/__tests__/setup/indexedDB.js.map +1 -1
- package/dist/cjs/__tests__/undo.test.js +16 -9
- package/dist/cjs/__tests__/undo.test.js.map +1 -1
- package/dist/cjs/client/Client.d.ts +2 -3
- package/dist/cjs/client/Client.js +8 -4
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/client/ClientDescriptor.js +21 -6
- package/dist/cjs/client/ClientDescriptor.js.map +1 -1
- package/dist/cjs/context.d.ts +10 -1
- package/dist/cjs/entities/2/Entity.d.ts +148 -0
- package/dist/cjs/entities/2/Entity.js +711 -0
- package/dist/cjs/entities/2/Entity.js.map +1 -0
- package/dist/cjs/entities/2/Entity.test.d.ts +1 -0
- package/dist/cjs/entities/2/Entity.test.js +194 -0
- package/dist/cjs/entities/2/Entity.test.js.map +1 -0
- package/dist/cjs/entities/2/EntityCache.d.ts +15 -0
- package/dist/cjs/entities/2/EntityCache.js +39 -0
- package/dist/cjs/entities/2/EntityCache.js.map +1 -0
- package/dist/cjs/entities/2/EntityMetadata.d.ts +68 -0
- package/dist/cjs/entities/2/EntityMetadata.js +261 -0
- package/dist/cjs/entities/2/EntityMetadata.js.map +1 -0
- package/dist/cjs/entities/2/EntityStore.d.ts +78 -0
- package/dist/cjs/entities/2/EntityStore.js +352 -0
- package/dist/cjs/entities/2/EntityStore.js.map +1 -0
- package/dist/cjs/entities/2/OperationBatcher.d.ts +52 -0
- package/dist/cjs/entities/2/OperationBatcher.js +165 -0
- package/dist/cjs/entities/2/OperationBatcher.js.map +1 -0
- package/dist/cjs/entities/2/types.d.ts +84 -0
- package/dist/cjs/entities/2/types.js +3 -0
- package/dist/cjs/entities/2/types.js.map +1 -0
- package/dist/cjs/entities/Entity.d.ts +0 -7
- package/dist/cjs/entities/Entity.js +7 -0
- package/dist/cjs/entities/Entity.js.map +1 -1
- package/dist/cjs/entities/EntityStore.js +4 -20
- package/dist/cjs/entities/EntityStore.js.map +1 -1
- package/dist/cjs/entities/FakeWeakRef.d.ts +11 -0
- package/dist/cjs/entities/FakeWeakRef.js +19 -0
- package/dist/cjs/entities/FakeWeakRef.js.map +1 -0
- package/dist/cjs/files/EntityFile.d.ts +5 -2
- package/dist/cjs/files/EntityFile.js +8 -4
- package/dist/cjs/files/EntityFile.js.map +1 -1
- package/dist/cjs/files/FileManager.d.ts +3 -1
- package/dist/cjs/files/FileManager.js +5 -3
- package/dist/cjs/files/FileManager.js.map +1 -1
- package/dist/cjs/files/FileStorage.js +7 -7
- package/dist/cjs/files/FileStorage.js.map +1 -1
- package/dist/cjs/files/utils.d.ts +2 -0
- package/dist/cjs/files/utils.js +8 -2
- package/dist/cjs/files/utils.js.map +1 -1
- package/dist/cjs/idb.d.ts +2 -0
- package/dist/cjs/idb.js +50 -4
- package/dist/cjs/idb.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/indexes.d.ts +3 -0
- package/dist/cjs/indexes.js +20 -0
- package/dist/cjs/indexes.js.map +1 -0
- package/dist/cjs/metadata/AckInfoStore.js +1 -1
- package/dist/cjs/metadata/AckInfoStore.js.map +1 -1
- package/dist/cjs/metadata/BaselinesStore.d.ts +4 -1
- package/dist/cjs/metadata/BaselinesStore.js +19 -10
- package/dist/cjs/metadata/BaselinesStore.js.map +1 -1
- package/dist/cjs/metadata/LocalReplicaStore.d.ts +1 -1
- package/dist/cjs/metadata/LocalReplicaStore.js +11 -5
- package/dist/cjs/metadata/LocalReplicaStore.js.map +1 -1
- package/dist/cjs/metadata/Metadata.d.ts +26 -5
- package/dist/cjs/metadata/Metadata.js +55 -18
- package/dist/cjs/metadata/Metadata.js.map +1 -1
- package/dist/cjs/metadata/OperationsStore.d.ts +3 -0
- package/dist/cjs/metadata/OperationsStore.js +35 -15
- package/dist/cjs/metadata/OperationsStore.js.map +1 -1
- package/dist/cjs/migration/openDatabase.js +31 -10
- package/dist/cjs/migration/openDatabase.js.map +1 -1
- package/dist/cjs/queries/BaseQuery.js +14 -2
- package/dist/cjs/queries/BaseQuery.js.map +1 -1
- package/dist/cjs/queries/CollectionQueries.d.ts +2 -4
- package/dist/cjs/queries/CollectionQueries.js +1 -1
- package/dist/cjs/queries/CollectionQueries.js.map +1 -1
- package/dist/cjs/queries/FindAllQuery.js +1 -0
- package/dist/cjs/queries/FindAllQuery.js.map +1 -1
- package/dist/cjs/queries/QueryCache.d.ts +1 -0
- package/dist/cjs/queries/QueryCache.js +4 -0
- package/dist/cjs/queries/QueryCache.js.map +1 -1
- package/dist/cjs/queries/QueryableStorage.d.ts +20 -0
- package/dist/cjs/queries/QueryableStorage.js +84 -0
- package/dist/cjs/queries/QueryableStorage.js.map +1 -0
- package/dist/cjs/queries/dbQueries.js +13 -3
- package/dist/cjs/queries/dbQueries.js.map +1 -1
- package/dist/cjs/queries/utils.js +1 -1
- package/dist/cjs/queries/utils.js.map +1 -1
- package/dist/cjs/sync/FileSync.d.ts +1 -0
- package/dist/cjs/sync/FileSync.js +1 -0
- package/dist/cjs/sync/FileSync.js.map +1 -1
- package/dist/cjs/sync/PushPullSync.d.ts +2 -1
- package/dist/cjs/sync/PushPullSync.js +7 -1
- package/dist/cjs/sync/PushPullSync.js.map +1 -1
- package/dist/cjs/sync/Sync.d.ts +6 -3
- package/dist/cjs/sync/Sync.js +9 -4
- package/dist/cjs/sync/Sync.js.map +1 -1
- package/dist/cjs/sync/WebSocketSync.d.ts +4 -1
- package/dist/cjs/sync/WebSocketSync.js +41 -11
- package/dist/cjs/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/DocumentManager.d.ts +6 -5
- package/dist/esm/DocumentManager.js +2 -2
- package/dist/esm/DocumentManager.js.map +1 -1
- package/dist/esm/IDBService.d.ts +28 -7
- package/dist/esm/IDBService.js +51 -14
- package/dist/esm/IDBService.js.map +1 -1
- package/dist/esm/UndoHistory.d.ts +1 -1
- package/dist/esm/UndoHistory.js +6 -2
- package/dist/esm/UndoHistory.js.map +1 -1
- package/dist/esm/__tests__/batching.test.js +3 -1
- package/dist/esm/__tests__/batching.test.js.map +1 -1
- package/dist/esm/__tests__/documents.test.js +54 -6
- package/dist/esm/__tests__/documents.test.js.map +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +8 -2
- package/dist/esm/__tests__/fixtures/testStorage.js +8 -1
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/legacyOids.test.js +50 -17
- package/dist/esm/__tests__/legacyOids.test.js.map +1 -1
- package/dist/esm/__tests__/mutations.test.js +9 -3
- package/dist/esm/__tests__/mutations.test.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js +6 -2
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/__tests__/setup/indexedDB.d.ts +1 -1
- package/dist/esm/__tests__/setup/indexedDB.js +13 -1
- package/dist/esm/__tests__/setup/indexedDB.js.map +1 -1
- package/dist/esm/__tests__/undo.test.js +16 -9
- package/dist/esm/__tests__/undo.test.js.map +1 -1
- package/dist/esm/client/Client.d.ts +2 -3
- package/dist/esm/client/Client.js +8 -4
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.js +21 -6
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context.d.ts +10 -1
- package/dist/esm/entities/2/Entity.d.ts +148 -0
- package/dist/esm/entities/2/Entity.js +707 -0
- package/dist/esm/entities/2/Entity.js.map +1 -0
- package/dist/esm/entities/2/Entity.test.d.ts +1 -0
- package/dist/esm/entities/2/Entity.test.js +192 -0
- package/dist/esm/entities/2/Entity.test.js.map +1 -0
- package/dist/esm/entities/2/EntityCache.d.ts +15 -0
- package/dist/esm/entities/2/EntityCache.js +35 -0
- package/dist/esm/entities/2/EntityCache.js.map +1 -0
- package/dist/esm/entities/2/EntityMetadata.d.ts +68 -0
- package/dist/esm/entities/2/EntityMetadata.js +256 -0
- package/dist/esm/entities/2/EntityMetadata.js.map +1 -0
- package/dist/esm/entities/2/EntityStore.d.ts +78 -0
- package/dist/esm/entities/2/EntityStore.js +348 -0
- package/dist/esm/entities/2/EntityStore.js.map +1 -0
- package/dist/esm/entities/2/OperationBatcher.d.ts +52 -0
- package/dist/esm/entities/2/OperationBatcher.js +161 -0
- package/dist/esm/entities/2/OperationBatcher.js.map +1 -0
- package/dist/esm/entities/2/types.d.ts +84 -0
- package/dist/esm/entities/2/types.js +2 -0
- package/dist/esm/entities/2/types.js.map +1 -0
- package/dist/esm/entities/Entity.d.ts +0 -7
- package/dist/esm/entities/Entity.js +7 -0
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/EntityStore.js +4 -20
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/entities/FakeWeakRef.d.ts +11 -0
- package/dist/esm/entities/FakeWeakRef.js +15 -0
- package/dist/esm/entities/FakeWeakRef.js.map +1 -0
- package/dist/esm/files/EntityFile.d.ts +5 -2
- package/dist/esm/files/EntityFile.js +8 -4
- package/dist/esm/files/EntityFile.js.map +1 -1
- package/dist/esm/files/FileManager.d.ts +3 -1
- package/dist/esm/files/FileManager.js +5 -3
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/files/FileStorage.js +7 -7
- package/dist/esm/files/FileStorage.js.map +1 -1
- package/dist/esm/files/utils.d.ts +2 -0
- package/dist/esm/files/utils.js +6 -1
- package/dist/esm/files/utils.js.map +1 -1
- package/dist/esm/idb.d.ts +2 -0
- package/dist/esm/idb.js +47 -3
- package/dist/esm/idb.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.d.ts +3 -0
- package/dist/esm/indexes.js +15 -0
- package/dist/esm/indexes.js.map +1 -0
- package/dist/esm/metadata/AckInfoStore.js +1 -1
- package/dist/esm/metadata/AckInfoStore.js.map +1 -1
- package/dist/esm/metadata/BaselinesStore.d.ts +4 -1
- package/dist/esm/metadata/BaselinesStore.js +19 -10
- package/dist/esm/metadata/BaselinesStore.js.map +1 -1
- package/dist/esm/metadata/LocalReplicaStore.d.ts +1 -1
- package/dist/esm/metadata/LocalReplicaStore.js +11 -5
- package/dist/esm/metadata/LocalReplicaStore.js.map +1 -1
- package/dist/esm/metadata/Metadata.d.ts +26 -5
- package/dist/esm/metadata/Metadata.js +56 -19
- package/dist/esm/metadata/Metadata.js.map +1 -1
- package/dist/esm/metadata/OperationsStore.d.ts +3 -0
- package/dist/esm/metadata/OperationsStore.js +35 -15
- package/dist/esm/metadata/OperationsStore.js.map +1 -1
- package/dist/esm/migration/openDatabase.js +32 -11
- package/dist/esm/migration/openDatabase.js.map +1 -1
- package/dist/esm/queries/BaseQuery.js +14 -2
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/queries/CollectionQueries.d.ts +2 -4
- package/dist/esm/queries/CollectionQueries.js +1 -1
- package/dist/esm/queries/CollectionQueries.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -0
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/QueryCache.d.ts +1 -0
- package/dist/esm/queries/QueryCache.js +4 -0
- package/dist/esm/queries/QueryCache.js.map +1 -1
- package/dist/esm/queries/QueryableStorage.d.ts +20 -0
- package/dist/esm/queries/QueryableStorage.js +80 -0
- package/dist/esm/queries/QueryableStorage.js.map +1 -0
- package/dist/esm/queries/dbQueries.js +13 -3
- package/dist/esm/queries/dbQueries.js.map +1 -1
- package/dist/esm/queries/utils.js +1 -1
- package/dist/esm/queries/utils.js.map +1 -1
- package/dist/esm/sync/FileSync.d.ts +1 -0
- package/dist/esm/sync/FileSync.js +1 -0
- package/dist/esm/sync/FileSync.js.map +1 -1
- package/dist/esm/sync/PushPullSync.d.ts +2 -1
- package/dist/esm/sync/PushPullSync.js +7 -1
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -3
- package/dist/esm/sync/Sync.js +9 -4
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.d.ts +4 -1
- package/dist/esm/sync/WebSocketSync.js +41 -11
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/DocumentManager.ts +3 -7
- package/src/IDBService.ts +78 -17
- package/src/UndoHistory.ts +5 -3
- package/src/__tests__/batching.test.ts +5 -2
- package/src/__tests__/documents.test.ts +66 -6
- package/src/__tests__/fixtures/testStorage.ts +9 -0
- package/src/__tests__/legacyOids.test.ts +53 -17
- package/src/__tests__/mutations.test.ts +9 -3
- package/src/__tests__/queries.test.ts +6 -2
- package/src/__tests__/setup/indexedDB.ts +14 -1
- package/src/__tests__/undo.test.ts +17 -9
- package/src/client/Client.ts +8 -4
- package/src/client/ClientDescriptor.ts +24 -8
- package/src/context.ts +16 -1
- package/src/entities/2/Entity.test.ts +218 -0
- package/src/entities/2/Entity.ts +954 -0
- package/src/entities/2/EntityCache.ts +41 -0
- package/src/entities/2/EntityMetadata.ts +364 -0
- package/src/entities/2/EntityStore.ts +490 -0
- package/src/entities/2/NOTES.md +22 -0
- package/src/entities/2/OperationBatcher.ts +251 -0
- package/src/entities/2/types.ts +154 -0
- package/src/files/EntityFile.ts +9 -4
- package/src/files/FileManager.ts +5 -3
- package/src/files/FileStorage.ts +7 -13
- package/src/files/utils.ts +9 -1
- package/src/idb.ts +51 -3
- package/src/index.ts +2 -2
- package/src/metadata/AckInfoStore.ts +1 -1
- package/src/metadata/BaselinesStore.ts +16 -24
- package/src/metadata/LocalReplicaStore.ts +13 -6
- package/src/metadata/Metadata.ts +109 -24
- package/src/metadata/OperationsStore.ts +37 -16
- package/src/migration/openDatabase.ts +32 -10
- package/src/queries/BaseQuery.ts +15 -2
- package/src/queries/CollectionQueries.ts +3 -3
- package/src/queries/FindAllQuery.ts +4 -0
- package/src/queries/QueryCache.ts +5 -0
- package/src/queries/QueryableStorage.ts +107 -0
- package/src/queries/dbQueries.ts +10 -3
- package/src/queries/utils.ts +1 -1
- package/src/sync/FileSync.ts +2 -0
- package/src/sync/PushPullSync.ts +8 -1
- package/src/sync/Sync.ts +14 -6
- package/src/sync/WebSocketSync.ts +47 -10
- package/src/entities/DocumentFamiliyCache.ts +0 -426
- package/src/entities/Entity.ts +0 -874
- package/src/entities/EntityStore.ts +0 -731
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Entity, EntityInit } from './Entity.js';
|
|
2
|
+
import { EntityFile } from '../../files/EntityFile.js';
|
|
3
|
+
import { ObjectIdentifier } from '@verdant-web/common';
|
|
4
|
+
|
|
5
|
+
export class EntityCache {
|
|
6
|
+
private cache = new Map<string, Entity | EntityFile>();
|
|
7
|
+
|
|
8
|
+
constructor({ initial }: { initial?: Entity[] } = {}) {
|
|
9
|
+
if (initial) {
|
|
10
|
+
for (const entity of initial) {
|
|
11
|
+
this.cache.set(entity.oid, entity);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get = (init: EntityInit): Entity => {
|
|
17
|
+
if (this.cache.has(init.oid)) {
|
|
18
|
+
return this.cache.get(init.oid)! as Entity;
|
|
19
|
+
}
|
|
20
|
+
const entity = new Entity(init);
|
|
21
|
+
this.cache.set(init.oid, entity);
|
|
22
|
+
return entity;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
has = (oid: ObjectIdentifier) => {
|
|
26
|
+
return this.cache.has(oid);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
getFile = (id: string, options: { downloadRemote: boolean }): EntityFile => {
|
|
30
|
+
if (this.cache.has(id)) {
|
|
31
|
+
return this.cache.get(id)! as EntityFile;
|
|
32
|
+
}
|
|
33
|
+
const file = new EntityFile(id, options);
|
|
34
|
+
this.cache.set(id, file);
|
|
35
|
+
return file;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
getCached = (oid: string) => {
|
|
39
|
+
return this.cache.get(oid);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentBaseline,
|
|
3
|
+
ObjectIdentifier,
|
|
4
|
+
Operation,
|
|
5
|
+
applyPatch,
|
|
6
|
+
areOidsRelated,
|
|
7
|
+
assert,
|
|
8
|
+
assignOid,
|
|
9
|
+
cloneDeep,
|
|
10
|
+
compareTimestampSchemaVersions,
|
|
11
|
+
getWallClockTime,
|
|
12
|
+
} from '@verdant-web/common';
|
|
13
|
+
import { Context } from '../../context.js';
|
|
14
|
+
import { EntityChange } from './types.js';
|
|
15
|
+
|
|
16
|
+
export type EntityMetadataView = {
|
|
17
|
+
view: any;
|
|
18
|
+
fromOlderVersion: boolean;
|
|
19
|
+
deleted: boolean;
|
|
20
|
+
empty: boolean;
|
|
21
|
+
updatedAt: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class EntityMetadata {
|
|
25
|
+
private ctx;
|
|
26
|
+
private baseline: DocumentBaseline | null = null;
|
|
27
|
+
// these must be kept in timestamp order.
|
|
28
|
+
private confirmedOperations: Operation[] = [];
|
|
29
|
+
private pendingOperations: Operation[] = [];
|
|
30
|
+
readonly oid;
|
|
31
|
+
|
|
32
|
+
constructor({
|
|
33
|
+
oid,
|
|
34
|
+
ctx,
|
|
35
|
+
confirmedOperations,
|
|
36
|
+
pendingOperations,
|
|
37
|
+
baseline,
|
|
38
|
+
}: {
|
|
39
|
+
oid: ObjectIdentifier;
|
|
40
|
+
ctx: Context;
|
|
41
|
+
confirmedOperations?: Operation[];
|
|
42
|
+
pendingOperations?: Operation[];
|
|
43
|
+
baseline?: DocumentBaseline;
|
|
44
|
+
}) {
|
|
45
|
+
assert(oid, 'oid is required');
|
|
46
|
+
this.ctx = ctx;
|
|
47
|
+
this.oid = oid;
|
|
48
|
+
if (confirmedOperations) {
|
|
49
|
+
this.confirmedOperations = confirmedOperations;
|
|
50
|
+
}
|
|
51
|
+
if (pendingOperations) {
|
|
52
|
+
this.pendingOperations = pendingOperations;
|
|
53
|
+
}
|
|
54
|
+
if (baseline) {
|
|
55
|
+
this.baseline = baseline;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compute the current view of the entity.
|
|
61
|
+
*/
|
|
62
|
+
computeView = (omitPending = false): EntityMetadataView => {
|
|
63
|
+
const base = cloneDeep(this.baseline?.snapshot ?? undefined);
|
|
64
|
+
const baselineTimestamp = this.baseline?.timestamp ?? null;
|
|
65
|
+
const confirmedResult = this.applyOperations(
|
|
66
|
+
// apply ops to baseline
|
|
67
|
+
base,
|
|
68
|
+
// deleted if there's no baseline
|
|
69
|
+
!base,
|
|
70
|
+
// we're applying confirmed ops first
|
|
71
|
+
this.confirmedOperations,
|
|
72
|
+
// latest timestamp is the baseline timestamp, if any
|
|
73
|
+
baselineTimestamp,
|
|
74
|
+
// only apply ops after the baseline timestamp
|
|
75
|
+
baselineTimestamp,
|
|
76
|
+
);
|
|
77
|
+
// now's the time to declare we saw the future if we did.
|
|
78
|
+
if (confirmedResult.futureSeen) {
|
|
79
|
+
this.ctx.globalEvents.emit('futureSeen', confirmedResult.futureSeen);
|
|
80
|
+
}
|
|
81
|
+
const pendingResult = omitPending
|
|
82
|
+
? confirmedResult
|
|
83
|
+
: this.applyOperations(
|
|
84
|
+
confirmedResult.view,
|
|
85
|
+
confirmedResult.deleted,
|
|
86
|
+
// now we're applying pending operations
|
|
87
|
+
this.pendingOperations,
|
|
88
|
+
// keep our latest timestamp up to date
|
|
89
|
+
confirmedResult.latestTimestamp,
|
|
90
|
+
// we don't use after for pending ops, they're all
|
|
91
|
+
// logically in the future
|
|
92
|
+
null,
|
|
93
|
+
);
|
|
94
|
+
// before letting this data out into the wild, we need
|
|
95
|
+
// to associate its oid
|
|
96
|
+
if (pendingResult.view) {
|
|
97
|
+
assignOid(pendingResult.view, this.oid);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// note whether confirmed data has an operation/baseline from the current
|
|
101
|
+
// schema or not.
|
|
102
|
+
const fromOlderVersion =
|
|
103
|
+
!!confirmedResult.latestTimestamp &&
|
|
104
|
+
compareTimestampSchemaVersions(
|
|
105
|
+
confirmedResult.latestTimestamp,
|
|
106
|
+
this.ctx.getNow(),
|
|
107
|
+
) < 0;
|
|
108
|
+
|
|
109
|
+
const empty =
|
|
110
|
+
!this.baseline &&
|
|
111
|
+
!this.pendingOperations.length &&
|
|
112
|
+
!this.confirmedOperations.length;
|
|
113
|
+
if (empty) {
|
|
114
|
+
this.ctx.log('warn', `Tried to load Entity ${this.oid} with no data`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const updatedAtTimestamp =
|
|
118
|
+
pendingResult.latestTimestamp ??
|
|
119
|
+
confirmedResult.latestTimestamp ??
|
|
120
|
+
baselineTimestamp;
|
|
121
|
+
const updatedAt = updatedAtTimestamp
|
|
122
|
+
? getWallClockTime(updatedAtTimestamp)
|
|
123
|
+
: 0;
|
|
124
|
+
|
|
125
|
+
if (!pendingResult.view && !pendingResult.deleted && !empty) {
|
|
126
|
+
this.ctx.log(
|
|
127
|
+
'warn',
|
|
128
|
+
`Entity ${this.oid} has no view, no deleted flag, and not empty`,
|
|
129
|
+
);
|
|
130
|
+
debugger;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
view: pendingResult.view ?? undefined,
|
|
135
|
+
deleted: pendingResult.deleted,
|
|
136
|
+
empty,
|
|
137
|
+
fromOlderVersion,
|
|
138
|
+
updatedAt,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
addBaseline = (baseline: DocumentBaseline): void => {
|
|
143
|
+
// opt out if our baseline is newer
|
|
144
|
+
if (this.baseline && this.baseline.timestamp >= baseline.timestamp) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.baseline = baseline;
|
|
148
|
+
// we can now drop any confirmed ops older than the baseline
|
|
149
|
+
while (this.confirmedOperations[0]?.timestamp < baseline.timestamp) {
|
|
150
|
+
this.confirmedOperations.shift();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @returns total number of new operations added
|
|
156
|
+
*/
|
|
157
|
+
addConfirmedOperations = (operations: Operation[]): number => {
|
|
158
|
+
let totalAdded = 0;
|
|
159
|
+
// the operations must be inserted in timestamp order
|
|
160
|
+
for (const op of operations) {
|
|
161
|
+
const index = this.confirmedOperations.findIndex(
|
|
162
|
+
(o) => o.timestamp >= op.timestamp,
|
|
163
|
+
);
|
|
164
|
+
if (index !== -1) {
|
|
165
|
+
// ensure we don't have a duplicate
|
|
166
|
+
if (this.confirmedOperations[index].timestamp !== op.timestamp) {
|
|
167
|
+
// otherwise, insert at the right place
|
|
168
|
+
this.confirmedOperations.splice(index, 0, op);
|
|
169
|
+
totalAdded++;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// otherwise, append
|
|
173
|
+
this.confirmedOperations.push(op);
|
|
174
|
+
totalAdded++;
|
|
175
|
+
}
|
|
176
|
+
// FIXME: seems inefficient
|
|
177
|
+
// remove this incoming op from pending if it's in there
|
|
178
|
+
const pendingPrior = this.pendingOperations.length;
|
|
179
|
+
this.pendingOperations = this.pendingOperations.filter(
|
|
180
|
+
(pendingOp) => op.timestamp !== pendingOp.timestamp,
|
|
181
|
+
);
|
|
182
|
+
totalAdded -= pendingPrior - this.pendingOperations.length;
|
|
183
|
+
}
|
|
184
|
+
return totalAdded;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
addPendingOperation = (operation: Operation) => {
|
|
188
|
+
// we can assume pending ops are always newer
|
|
189
|
+
this.pendingOperations.push(operation);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
private applyOperations = (
|
|
193
|
+
base: any,
|
|
194
|
+
deleted: boolean,
|
|
195
|
+
operations: Operation[],
|
|
196
|
+
latestTimestamp: string | null,
|
|
197
|
+
after: string | null,
|
|
198
|
+
): {
|
|
199
|
+
view: any;
|
|
200
|
+
latestTimestamp: string | null;
|
|
201
|
+
deleted: boolean;
|
|
202
|
+
futureSeen: string | undefined;
|
|
203
|
+
} => {
|
|
204
|
+
let futureSeen: string | undefined = undefined;
|
|
205
|
+
const now = this.ctx.getNow();
|
|
206
|
+
for (const op of operations) {
|
|
207
|
+
// ignore ops before our after cutoff
|
|
208
|
+
if (after && op.timestamp <= after) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
// don't apply future ops
|
|
212
|
+
if (compareTimestampSchemaVersions(op.timestamp, now) > 0) {
|
|
213
|
+
futureSeen = op.timestamp;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
// we don't actually delete the view when a delete op
|
|
217
|
+
// comes in. the view remains useful for calculating
|
|
218
|
+
// undo operations.
|
|
219
|
+
if (op.data.op === 'delete') {
|
|
220
|
+
deleted = true;
|
|
221
|
+
} else {
|
|
222
|
+
base = applyPatch(base, op.data);
|
|
223
|
+
if (op.data.op === 'initialize') {
|
|
224
|
+
deleted = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// track the latest timestamp
|
|
229
|
+
if (!latestTimestamp || op.timestamp > latestTimestamp) {
|
|
230
|
+
latestTimestamp = op.timestamp;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
view: base,
|
|
235
|
+
latestTimestamp: latestTimestamp ?? null,
|
|
236
|
+
deleted,
|
|
237
|
+
futureSeen,
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Represents the metadata for a group of entities underneath a Document.
|
|
244
|
+
* Metadata is separated out this way so that these classes can be
|
|
245
|
+
* garbage collected when the root Document goes out of scope.
|
|
246
|
+
*/
|
|
247
|
+
export class EntityFamilyMetadata {
|
|
248
|
+
private ctx;
|
|
249
|
+
private entities: Map<ObjectIdentifier, EntityMetadata> = new Map();
|
|
250
|
+
private onPendingOperations;
|
|
251
|
+
private rootOid: ObjectIdentifier;
|
|
252
|
+
|
|
253
|
+
constructor({
|
|
254
|
+
ctx,
|
|
255
|
+
onPendingOperations,
|
|
256
|
+
rootOid,
|
|
257
|
+
}: {
|
|
258
|
+
ctx: Context;
|
|
259
|
+
onPendingOperations: (ops: Operation[]) => void;
|
|
260
|
+
rootOid: ObjectIdentifier;
|
|
261
|
+
}) {
|
|
262
|
+
this.ctx = ctx;
|
|
263
|
+
this.rootOid = rootOid;
|
|
264
|
+
this.onPendingOperations = onPendingOperations;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
get = (oid: ObjectIdentifier) => {
|
|
268
|
+
assert(oid, 'oid is required');
|
|
269
|
+
if (!this.entities.has(oid)) {
|
|
270
|
+
this.entities.set(oid, new EntityMetadata({ oid, ctx: this.ctx }));
|
|
271
|
+
}
|
|
272
|
+
return this.entities.get(oid)!;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
getAllOids = () => {
|
|
276
|
+
return Array.from(this.entities.keys());
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
addConfirmedData = ({
|
|
280
|
+
baselines = [],
|
|
281
|
+
operations = {},
|
|
282
|
+
isLocal = false,
|
|
283
|
+
}: {
|
|
284
|
+
baselines?: DocumentBaseline[];
|
|
285
|
+
operations?: Record<ObjectIdentifier, Operation[]>;
|
|
286
|
+
isLocal?: boolean;
|
|
287
|
+
}) => {
|
|
288
|
+
const changes: Record<ObjectIdentifier, EntityChange> = {};
|
|
289
|
+
for (const baseline of baselines) {
|
|
290
|
+
if (!areOidsRelated(this.rootOid, baseline.oid)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Invalid baseline for entity ${this.rootOid}: ` +
|
|
293
|
+
JSON.stringify(baseline),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
this.get(baseline.oid).addBaseline(baseline);
|
|
297
|
+
}
|
|
298
|
+
for (const [oid, ops] of Object.entries(operations)) {
|
|
299
|
+
if (!areOidsRelated(this.rootOid, oid)) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Invalid operations for entity ${this.rootOid}: ` +
|
|
302
|
+
JSON.stringify(ops),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
const added = this.get(oid).addConfirmedOperations(ops);
|
|
306
|
+
if (added !== 0) {
|
|
307
|
+
changes[oid] ??= { oid, isLocal };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return Object.values(changes);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Adds local, unconfirmed operations to the system.
|
|
315
|
+
* The API is different here to streamline for the way
|
|
316
|
+
* local changes are usually handled, as a list.
|
|
317
|
+
*/
|
|
318
|
+
addPendingData = (operations: Operation[]) => {
|
|
319
|
+
const changes: Record<ObjectIdentifier, EntityChange> = {};
|
|
320
|
+
for (const op of operations) {
|
|
321
|
+
this.get(op.oid).addPendingOperation(op);
|
|
322
|
+
changes[op.oid] ??= { oid: op.oid, isLocal: true };
|
|
323
|
+
}
|
|
324
|
+
this.onPendingOperations(operations);
|
|
325
|
+
return Object.values(changes);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
replaceAllData = ({
|
|
329
|
+
operations = {},
|
|
330
|
+
baselines = [],
|
|
331
|
+
}: {
|
|
332
|
+
operations?: Record<ObjectIdentifier, Operation[]>;
|
|
333
|
+
baselines?: DocumentBaseline[];
|
|
334
|
+
}) => {
|
|
335
|
+
const oids = Array.from(this.entities.keys());
|
|
336
|
+
this.entities.clear();
|
|
337
|
+
const changes: Record<ObjectIdentifier, EntityChange> = {};
|
|
338
|
+
// changes apply to all the entities we removed things from, too
|
|
339
|
+
for (const oid of oids) {
|
|
340
|
+
changes[oid] = { oid, isLocal: false };
|
|
341
|
+
}
|
|
342
|
+
for (const baseline of baselines) {
|
|
343
|
+
if (!areOidsRelated(this.rootOid, baseline.oid)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Invalid baseline for entity ${this.rootOid}: ` +
|
|
346
|
+
JSON.stringify(baseline),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
this.get(baseline.oid).addBaseline(baseline);
|
|
350
|
+
changes[baseline.oid] ??= { oid: baseline.oid, isLocal: false };
|
|
351
|
+
}
|
|
352
|
+
for (const [oid, ops] of Object.entries(operations)) {
|
|
353
|
+
if (!areOidsRelated(this.rootOid, oid)) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Invalid operations for entity ${this.rootOid}: ` +
|
|
356
|
+
JSON.stringify(ops),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
this.get(oid).addConfirmedOperations(ops);
|
|
360
|
+
changes[oid] ??= { oid, isLocal: false };
|
|
361
|
+
}
|
|
362
|
+
return Object.values(changes);
|
|
363
|
+
};
|
|
364
|
+
}
|