@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,490 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentBaseline,
|
|
3
|
+
ObjectIdentifier,
|
|
4
|
+
Operation,
|
|
5
|
+
StorageFieldsSchema,
|
|
6
|
+
StorageObjectFieldSchema,
|
|
7
|
+
assert,
|
|
8
|
+
assignOid,
|
|
9
|
+
decomposeOid,
|
|
10
|
+
getOidRoot,
|
|
11
|
+
groupBaselinesByRootOid,
|
|
12
|
+
groupPatchesByOid,
|
|
13
|
+
groupPatchesByRootOid,
|
|
14
|
+
isRootOid,
|
|
15
|
+
removeOidsFromAllSubObjects,
|
|
16
|
+
} from '@verdant-web/common';
|
|
17
|
+
import { Context } from '../../context.js';
|
|
18
|
+
import { Metadata } from '../../metadata/Metadata.js';
|
|
19
|
+
import { Entity } from './Entity.js';
|
|
20
|
+
import { Disposable } from '../../utils/Disposable.js';
|
|
21
|
+
import { EntityFamilyMetadata } from './EntityMetadata.js';
|
|
22
|
+
import { FileManager } from '../../files/FileManager.js';
|
|
23
|
+
import { OperationBatcher } from './OperationBatcher.js';
|
|
24
|
+
import { QueryableStorage } from '../../queries/QueryableStorage.js';
|
|
25
|
+
import { WeakEvent } from 'weak-event';
|
|
26
|
+
import { processValueFiles } from '../../files/utils.js';
|
|
27
|
+
import { abort } from 'process';
|
|
28
|
+
|
|
29
|
+
enum AbortReason {
|
|
30
|
+
Reset,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type EntityStoreEventData = {
|
|
34
|
+
oid: ObjectIdentifier;
|
|
35
|
+
operations?: Record<string, Operation[]>;
|
|
36
|
+
baselines?: DocumentBaseline[];
|
|
37
|
+
isLocal: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type EntityStoreEvents = {
|
|
41
|
+
add: WeakEvent<EntityStore, EntityStoreEventData>;
|
|
42
|
+
replace: WeakEvent<EntityStore, EntityStoreEventData>;
|
|
43
|
+
resetAll: WeakEvent<EntityStore, void>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type IncomingData = {
|
|
47
|
+
operations?: Operation[];
|
|
48
|
+
baselines?: DocumentBaseline[];
|
|
49
|
+
reset?: boolean;
|
|
50
|
+
isLocal?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export class EntityStore extends Disposable {
|
|
54
|
+
private ctx;
|
|
55
|
+
private meta;
|
|
56
|
+
private files;
|
|
57
|
+
private batcher;
|
|
58
|
+
private queryableStorage;
|
|
59
|
+
private events: EntityStoreEvents = {
|
|
60
|
+
add: new WeakEvent(),
|
|
61
|
+
replace: new WeakEvent(),
|
|
62
|
+
resetAll: new WeakEvent(),
|
|
63
|
+
};
|
|
64
|
+
private cache = new Map<ObjectIdentifier, WeakRef<Entity>>();
|
|
65
|
+
private pendingEntityPromises = new Map<
|
|
66
|
+
ObjectIdentifier,
|
|
67
|
+
Promise<Entity | null>
|
|
68
|
+
>();
|
|
69
|
+
// halts the current data queue processing
|
|
70
|
+
private abortDataQueueController = new AbortController();
|
|
71
|
+
private ongoingResetPromise: Promise<void> | null = null;
|
|
72
|
+
private entityFinalizationRegistry = new FinalizationRegistry(
|
|
73
|
+
(oid: ObjectIdentifier) => {
|
|
74
|
+
this.ctx.log('debug', 'Entity GC', oid);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
constructor({
|
|
79
|
+
ctx,
|
|
80
|
+
meta,
|
|
81
|
+
files,
|
|
82
|
+
}: {
|
|
83
|
+
ctx: Context;
|
|
84
|
+
meta: Metadata;
|
|
85
|
+
files: FileManager;
|
|
86
|
+
}) {
|
|
87
|
+
super();
|
|
88
|
+
|
|
89
|
+
this.ctx = ctx;
|
|
90
|
+
this.meta = meta;
|
|
91
|
+
this.files = files;
|
|
92
|
+
this.queryableStorage = new QueryableStorage({ ctx });
|
|
93
|
+
this.batcher = new OperationBatcher({
|
|
94
|
+
ctx,
|
|
95
|
+
meta,
|
|
96
|
+
entities: this,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// expose batch APIs
|
|
101
|
+
get batch() {
|
|
102
|
+
return this.batcher.batch;
|
|
103
|
+
}
|
|
104
|
+
get flushAllBatches() {
|
|
105
|
+
return this.batcher.flushAll;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// internal-ish API to load remote / stored data
|
|
109
|
+
addData = async (data: IncomingData) => {
|
|
110
|
+
if (this.disposed) {
|
|
111
|
+
this.ctx.log('warn', 'EntityStore is disposed, not adding incoming data');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// for resets - abort any other changes, reset everything,
|
|
115
|
+
// then proceed
|
|
116
|
+
if (data.reset) {
|
|
117
|
+
this.ctx.log(
|
|
118
|
+
'info',
|
|
119
|
+
'Resetting local store to replicate remote synced data - dropping any current transactions',
|
|
120
|
+
);
|
|
121
|
+
// cancel any other ongoing data - it will all
|
|
122
|
+
// be replaced by the reset
|
|
123
|
+
this.abortDataQueueController.abort(AbortReason.Reset);
|
|
124
|
+
this.abortDataQueueController = new AbortController();
|
|
125
|
+
this.ongoingResetPromise = this.resetData().finally(() => {
|
|
126
|
+
this.ongoingResetPromise = null;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// await either the reset we just started, or any that was
|
|
131
|
+
// in progress when this data came in.
|
|
132
|
+
if (this.ongoingResetPromise) {
|
|
133
|
+
this.ctx.log('debug', 'Waiting for ongoing reset to complete');
|
|
134
|
+
await this.ongoingResetPromise;
|
|
135
|
+
this.ctx.log('debug', 'Ongoing reset complete');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await this.processData(data);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
private resetData = async () => {
|
|
142
|
+
if (this.disposed) {
|
|
143
|
+
this.ctx.log('warn', 'EntityStore is disposed, not resetting local data');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await this.meta.reset();
|
|
147
|
+
await this.queryableStorage.reset();
|
|
148
|
+
this.events.resetAll.invoke(this);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
private processData = async (data: IncomingData) => {
|
|
152
|
+
if (this.disposed) {
|
|
153
|
+
this.ctx.log(
|
|
154
|
+
'warn',
|
|
155
|
+
'EntityStore is disposed, not processing incoming data',
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const baselines = data?.baselines ?? [];
|
|
161
|
+
const operations = data?.operations ?? [];
|
|
162
|
+
|
|
163
|
+
this.ctx.log('debug', 'Processing incoming data', {
|
|
164
|
+
operations: operations.length,
|
|
165
|
+
baselines: baselines.length,
|
|
166
|
+
reset: !!data.reset,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const allDocumentOids: ObjectIdentifier[] = Array.from(
|
|
170
|
+
new Set(
|
|
171
|
+
baselines
|
|
172
|
+
.map((b) => getOidRoot(b.oid))
|
|
173
|
+
.concat(operations.map((o) => getOidRoot(o.oid))),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
const baselinesGroupedByOid = groupBaselinesByRootOid(baselines);
|
|
177
|
+
const operationsGroupedByOid = groupPatchesByRootOid(operations);
|
|
178
|
+
|
|
179
|
+
this.ctx.log('debug', 'Applying data to live entities');
|
|
180
|
+
// synchronously add/replace data in any open entities via eventing
|
|
181
|
+
for (const oid of allDocumentOids) {
|
|
182
|
+
const baselines = baselinesGroupedByOid[oid];
|
|
183
|
+
const operations = operationsGroupedByOid[oid] ?? [];
|
|
184
|
+
const groupedOperations = groupPatchesByOid(operations);
|
|
185
|
+
// what happens if an entity is being hydrated
|
|
186
|
+
// while this is happening? - we wait for the hydration promise
|
|
187
|
+
// to complete, then invoke the event
|
|
188
|
+
const event = data.reset ? this.events.replace : this.events.add;
|
|
189
|
+
const hydrationPromise = this.pendingEntityPromises.get(oid);
|
|
190
|
+
if (hydrationPromise) {
|
|
191
|
+
hydrationPromise.then(() => {
|
|
192
|
+
event.invoke(this, {
|
|
193
|
+
oid,
|
|
194
|
+
baselines,
|
|
195
|
+
operations: groupedOperations,
|
|
196
|
+
isLocal: false,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
if (this.cache.has(oid)) {
|
|
201
|
+
this.ctx.log('debug', 'Cache has', oid, ', an event should follow.');
|
|
202
|
+
}
|
|
203
|
+
event.invoke(this, {
|
|
204
|
+
oid,
|
|
205
|
+
baselines,
|
|
206
|
+
operations: groupedOperations,
|
|
207
|
+
isLocal: false,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const abortOptions = {
|
|
213
|
+
abort: this.abortDataQueueController.signal,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// then, asynchronously add to the database
|
|
217
|
+
await this.meta.insertData(data, abortOptions);
|
|
218
|
+
|
|
219
|
+
// FIXME: entities hydrated here are not seeing
|
|
220
|
+
// the operations just inserted above!!
|
|
221
|
+
// IDEA: can we coordinate here with hydrate promises
|
|
222
|
+
// based on affected OIDs?
|
|
223
|
+
|
|
224
|
+
// recompute all affected documents for querying
|
|
225
|
+
const entities = await Promise.all(
|
|
226
|
+
allDocumentOids.map(async (oid) => {
|
|
227
|
+
const entity = await this.hydrate(oid, abortOptions);
|
|
228
|
+
// if the entity is not found, we return a stub that
|
|
229
|
+
// indicates it's deleted and should be cleared
|
|
230
|
+
return (
|
|
231
|
+
entity ?? {
|
|
232
|
+
oid,
|
|
233
|
+
getSnapshot(): any {
|
|
234
|
+
return null;
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
try {
|
|
241
|
+
await this.queryableStorage.saveEntities(entities, abortOptions);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
if (this.disposed) {
|
|
244
|
+
this.ctx.log(
|
|
245
|
+
'warn',
|
|
246
|
+
'Error saving entities to queryable storage - EntityStore is disposed',
|
|
247
|
+
err,
|
|
248
|
+
);
|
|
249
|
+
} else {
|
|
250
|
+
this.ctx.log(
|
|
251
|
+
'error',
|
|
252
|
+
'Error saving entities to queryable storage',
|
|
253
|
+
err,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// internal-ish API for creating Entities from OIDs
|
|
260
|
+
// when query results come in
|
|
261
|
+
hydrate = async (
|
|
262
|
+
oid: string,
|
|
263
|
+
opts?: { abort: AbortSignal },
|
|
264
|
+
): Promise<Entity | null> => {
|
|
265
|
+
if (!isRootOid(oid)) {
|
|
266
|
+
throw new Error('Cannot hydrate non-root entity');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (this.cache.has(oid)) {
|
|
270
|
+
this.ctx.log('debug', 'Hydrating entity from cache', oid);
|
|
271
|
+
const cached = this.cache.get(oid);
|
|
272
|
+
if (cached) {
|
|
273
|
+
const entity = cached.deref();
|
|
274
|
+
if (entity) {
|
|
275
|
+
if (entity.deleted) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return entity;
|
|
279
|
+
} else {
|
|
280
|
+
this.ctx.log('debug', "Removing GC'd entity from cache", oid);
|
|
281
|
+
this.cache.delete(oid);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// we don't want to hydrate two entities in parallel, so
|
|
287
|
+
// we use a promise to ensure that only one is ever
|
|
288
|
+
// constructed at a time
|
|
289
|
+
const pendingPromise = this.pendingEntityPromises.get(oid);
|
|
290
|
+
if (!pendingPromise) {
|
|
291
|
+
this.ctx.log('debug', 'Hydrating entity from storage', oid);
|
|
292
|
+
const entity = this.constructEntity(oid);
|
|
293
|
+
if (!entity) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
const pendingPromise = this.loadEntity(entity, opts);
|
|
297
|
+
pendingPromise.finally(() => {
|
|
298
|
+
this.pendingEntityPromises.delete(oid);
|
|
299
|
+
});
|
|
300
|
+
this.pendingEntityPromises.set(oid, pendingPromise);
|
|
301
|
+
return pendingPromise;
|
|
302
|
+
} else {
|
|
303
|
+
this.ctx.log('debug', 'Waiting for entity hydration', oid);
|
|
304
|
+
return pendingPromise;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
destroy = async () => {
|
|
309
|
+
this.dispose();
|
|
310
|
+
await this.batcher.flushAll();
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// public APIs for manipulating entities
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Creates a new Entity with the given initial data.
|
|
317
|
+
*/
|
|
318
|
+
create = async (initial: any, oid: ObjectIdentifier) => {
|
|
319
|
+
this.ctx.log('debug', 'Creating new entity', oid);
|
|
320
|
+
const { collection } = decomposeOid(oid);
|
|
321
|
+
// remove any OID associations from the initial data
|
|
322
|
+
removeOidsFromAllSubObjects(initial);
|
|
323
|
+
// grab files and replace them with refs
|
|
324
|
+
const processed = processValueFiles(initial, this.files.add);
|
|
325
|
+
|
|
326
|
+
assignOid(processed, oid);
|
|
327
|
+
|
|
328
|
+
// creating a new Entity with no data, then preloading the operations
|
|
329
|
+
const entity = this.constructEntity(oid);
|
|
330
|
+
if (!entity) {
|
|
331
|
+
throw new Error(
|
|
332
|
+
`Could not put new document: no schema exists for collection ${collection}`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const operations = this.meta.patchCreator.createInitialize(processed, oid);
|
|
337
|
+
await this.batcher.commitOperations(operations, {
|
|
338
|
+
undoable: true,
|
|
339
|
+
source: entity,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// TODO: what happens if you create an entity with an OID that already
|
|
343
|
+
// exists?
|
|
344
|
+
|
|
345
|
+
// we still need to synchronously add the initial operations to the Entity
|
|
346
|
+
// even though they are flowing through the system
|
|
347
|
+
// TODO: this could be better aligned to avoid grouping here
|
|
348
|
+
const operationsGroupedByOid = groupPatchesByOid(operations);
|
|
349
|
+
this.events.add.invoke(this, {
|
|
350
|
+
operations: operationsGroupedByOid,
|
|
351
|
+
isLocal: true,
|
|
352
|
+
oid,
|
|
353
|
+
});
|
|
354
|
+
this.cache.set(oid, this.ctx.weakRef(entity));
|
|
355
|
+
|
|
356
|
+
return entity;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
deleteAll = async (
|
|
360
|
+
oids: ObjectIdentifier[],
|
|
361
|
+
options?: { undoable?: boolean },
|
|
362
|
+
) => {
|
|
363
|
+
this.ctx.log('info', 'Deleting documents', oids);
|
|
364
|
+
assert(
|
|
365
|
+
oids.every((oid) => oid === getOidRoot(oid)),
|
|
366
|
+
'Only root documents may be deleted via client methods',
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const allOids = await Promise.all(
|
|
370
|
+
oids.flatMap(async (oid) => {
|
|
371
|
+
const entity = await this.hydrate(oid);
|
|
372
|
+
return entity?.__getFamilyOids__() ?? [];
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// remove the entities from cache
|
|
377
|
+
oids.forEach((oid) => {
|
|
378
|
+
this.cache.delete(oid);
|
|
379
|
+
this.ctx.log('debug', 'Deleted document from cache', oid);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// create the delete patches and wait for them to be applied
|
|
383
|
+
const operations = this.meta.patchCreator.createDeleteAll(allOids.flat());
|
|
384
|
+
await this.batcher.commitOperations(operations, {
|
|
385
|
+
undoable: options?.undoable === undefined ? true : options.undoable,
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
delete = async (oid: ObjectIdentifier, options?: { undoable?: boolean }) => {
|
|
390
|
+
return this.deleteAll([oid], options);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
private getCollectionSchema = (
|
|
394
|
+
collectionName: string,
|
|
395
|
+
): {
|
|
396
|
+
schema: StorageObjectFieldSchema | null;
|
|
397
|
+
readonlyKeys: string[];
|
|
398
|
+
} => {
|
|
399
|
+
const schema = this.ctx.schema.collections[collectionName];
|
|
400
|
+
if (!schema) {
|
|
401
|
+
this.ctx.log('warn', `Missing schema for collection: ${collectionName}`);
|
|
402
|
+
return {
|
|
403
|
+
schema: null,
|
|
404
|
+
readonlyKeys: [],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
// convert to object schema for compatibility
|
|
409
|
+
schema: {
|
|
410
|
+
type: 'object',
|
|
411
|
+
nullable: false,
|
|
412
|
+
properties: schema.fields as any,
|
|
413
|
+
},
|
|
414
|
+
readonlyKeys: [schema.primaryKey],
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Constructs an entity from an OID, but does not load it.
|
|
420
|
+
*/
|
|
421
|
+
private constructEntity = (oid: string): Entity | null => {
|
|
422
|
+
const { collection } = decomposeOid(oid);
|
|
423
|
+
const { schema, readonlyKeys } = this.getCollectionSchema(collection);
|
|
424
|
+
|
|
425
|
+
if (!schema) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this.disposed) {
|
|
430
|
+
throw new Error('Cannot hydrate entity after store has been disposed');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const metadataFamily = new EntityFamilyMetadata({
|
|
434
|
+
ctx: this.ctx,
|
|
435
|
+
onPendingOperations: this.onPendingOperations,
|
|
436
|
+
rootOid: oid,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// this is created synchronously so it's immediately available
|
|
440
|
+
// to begin capturing incoming data.
|
|
441
|
+
return new Entity({
|
|
442
|
+
ctx: this.ctx,
|
|
443
|
+
oid,
|
|
444
|
+
schema,
|
|
445
|
+
readonlyKeys,
|
|
446
|
+
files: this.files,
|
|
447
|
+
metadataFamily: metadataFamily,
|
|
448
|
+
patchCreator: this.meta.patchCreator,
|
|
449
|
+
events: this.events,
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
private onPendingOperations = (operations: Operation[]) => {
|
|
454
|
+
this.batcher.addOperations(operations);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Loads initial Entity data from storage
|
|
459
|
+
*/
|
|
460
|
+
private loadEntity = async (
|
|
461
|
+
entity: Entity,
|
|
462
|
+
opts?: { abort: AbortSignal },
|
|
463
|
+
): Promise<Entity | null> => {
|
|
464
|
+
const { operations, baselines } = await this.meta.getDocumentData(
|
|
465
|
+
entity.oid,
|
|
466
|
+
opts,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
if (!baselines.length && !Object.keys(operations).length) {
|
|
470
|
+
this.ctx.log('debug', 'No data found for entity', entity.oid);
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.ctx.log('debug', 'Loaded entity from storage', entity.oid);
|
|
475
|
+
|
|
476
|
+
this.events.replace.invoke(this, {
|
|
477
|
+
oid: entity.oid,
|
|
478
|
+
baselines,
|
|
479
|
+
operations,
|
|
480
|
+
isLocal: false,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// only set the cache after loading.
|
|
484
|
+
// TODO: is this cache/promise stuff redundant?
|
|
485
|
+
this.cache.set(entity.oid, this.ctx.weakRef(entity));
|
|
486
|
+
this.entityFinalizationRegistry.register(entity, entity.oid);
|
|
487
|
+
|
|
488
|
+
return entity;
|
|
489
|
+
};
|
|
490
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Starting with a snapshot, a root Entity is created
|
|
2
|
+
|
|
3
|
+
The root snapshot is maintained and updated as things change
|
|
4
|
+
|
|
5
|
+
All child entities keep a reference to their portion of the root snapshot
|
|
6
|
+
|
|
7
|
+
Child entities are cached in their parent
|
|
8
|
+
A child is only ever a child of one parent - no reparenting
|
|
9
|
+
|
|
10
|
+
On change, an event is forwarded to the appropriate entity in the tree
|
|
11
|
+
how?
|
|
12
|
+
|
|
13
|
+
Migration stuff...
|
|
14
|
+
|
|
15
|
+
During migration, create a 'touched' empty op for every single root OID.
|
|
16
|
+
|
|
17
|
+
When loading a document, if there isn't an op for this version, we know the doc is not migrated. The range for migration is from the version of its current op/baseline.
|
|
18
|
+
|
|
19
|
+
# Lifecycle of an Entity
|
|
20
|
+
|
|
21
|
+
At first, we've come from the queryable storage, so we have a plain object
|
|
22
|
+
representing the entity's snapshot. But without associated OIDs, we can't do much besides read the data. For now, this initial snapshot isn't used.
|