@verdant-web/store 2.8.5 → 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 +37 -6
- package/dist/cjs/__tests__/documents.test.js.map +1 -1
- package/dist/cjs/__tests__/fixtures/testStorage.d.ts +2 -2
- package/dist/cjs/__tests__/fixtures/testStorage.js +2 -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 +8 -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 +5 -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 +37 -6
- package/dist/esm/__tests__/documents.test.js.map +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +2 -2
- package/dist/esm/__tests__/fixtures/testStorage.js +2 -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 +8 -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 +4 -2
- 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 +44 -6
- package/src/__tests__/fixtures/testStorage.ts +3 -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 +8 -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 +6 -2
- 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,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Batcher,
|
|
3
|
+
Operation,
|
|
4
|
+
generateId,
|
|
5
|
+
getOidRoot,
|
|
6
|
+
getUndoOperations,
|
|
7
|
+
groupPatchesByOid,
|
|
8
|
+
} from '@verdant-web/common';
|
|
9
|
+
import { Metadata } from '../../metadata/Metadata.js';
|
|
10
|
+
import { Context } from '../../context.js';
|
|
11
|
+
import type { EntityStore } from './EntityStore.js';
|
|
12
|
+
import { Entity } from './Entity.js';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_BATCH_KEY = '@@default';
|
|
15
|
+
|
|
16
|
+
export interface OperationBatch {
|
|
17
|
+
run: (fn: () => void) => this;
|
|
18
|
+
/** @deprecated - use commit() */
|
|
19
|
+
flush: () => Promise<void>;
|
|
20
|
+
commit: () => Promise<void>;
|
|
21
|
+
discard: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class OperationBatcher {
|
|
25
|
+
private batcher;
|
|
26
|
+
private currentBatchKey = DEFAULT_BATCH_KEY;
|
|
27
|
+
private defaultBatchTimeout: number;
|
|
28
|
+
private meta;
|
|
29
|
+
private ctx;
|
|
30
|
+
private entities;
|
|
31
|
+
|
|
32
|
+
constructor({
|
|
33
|
+
batchTimeout = 200,
|
|
34
|
+
meta,
|
|
35
|
+
ctx,
|
|
36
|
+
entities,
|
|
37
|
+
}: {
|
|
38
|
+
batchTimeout?: number;
|
|
39
|
+
meta: Metadata;
|
|
40
|
+
ctx: Context;
|
|
41
|
+
entities: EntityStore;
|
|
42
|
+
}) {
|
|
43
|
+
this.meta = meta;
|
|
44
|
+
this.ctx = ctx;
|
|
45
|
+
this.entities = entities;
|
|
46
|
+
this.defaultBatchTimeout = batchTimeout;
|
|
47
|
+
this.batcher = new Batcher<Operation, { undoable?: boolean }>(
|
|
48
|
+
this.flushOperations,
|
|
49
|
+
);
|
|
50
|
+
this.batcher.add({
|
|
51
|
+
key: DEFAULT_BATCH_KEY,
|
|
52
|
+
items: [],
|
|
53
|
+
max: 100,
|
|
54
|
+
timeout: batchTimeout,
|
|
55
|
+
userData: { undoable: true },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isDefaultBatch() {
|
|
60
|
+
return this.currentBatchKey === DEFAULT_BATCH_KEY;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private flushOperations = async (
|
|
64
|
+
operations: Operation[],
|
|
65
|
+
batchKey: string,
|
|
66
|
+
meta: { undoable?: boolean },
|
|
67
|
+
) => {
|
|
68
|
+
this.ctx.log(
|
|
69
|
+
'debug',
|
|
70
|
+
'Flushing',
|
|
71
|
+
operations.length,
|
|
72
|
+
'operations from batch',
|
|
73
|
+
batchKey,
|
|
74
|
+
'to storage / sync',
|
|
75
|
+
);
|
|
76
|
+
if (!operations.length) return;
|
|
77
|
+
// rewrite timestamps of all operations to now - this preserves
|
|
78
|
+
// the linear history of operations which are sent to the server.
|
|
79
|
+
// even if multiple batches are spun up in parallel and flushed
|
|
80
|
+
// after delay, the final operations in each one should reflect
|
|
81
|
+
// when the batch flushed, not when the changes were made.
|
|
82
|
+
// This also corresponds to user-observed behavior, since unconfirmed
|
|
83
|
+
// operations are applied universally after confirmed operations locally,
|
|
84
|
+
// so even operations which were made before a remote operation but
|
|
85
|
+
// have not been confirmed yet will appear to come after the remote one
|
|
86
|
+
// despite the provisional timestamp being earlier
|
|
87
|
+
// NOTE: this MUST be mutating the original operation object! this timestamp
|
|
88
|
+
// also serves as a unique ID for deduplication later.
|
|
89
|
+
for (const op of operations) {
|
|
90
|
+
op.timestamp = this.meta.now;
|
|
91
|
+
}
|
|
92
|
+
await this.commitOperations(operations, meta);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Immediately flushes operations to storage / sync.
|
|
97
|
+
* Providing source to second arg skips hydrating related
|
|
98
|
+
* Entity from storage, which is useful when that Entity
|
|
99
|
+
* isn't in storage (i.e. still creating) or just to speed
|
|
100
|
+
* up the commit.
|
|
101
|
+
*/
|
|
102
|
+
commitOperations = async (
|
|
103
|
+
operations: Operation[],
|
|
104
|
+
meta: { undoable?: boolean; source?: Entity },
|
|
105
|
+
) => {
|
|
106
|
+
if (!operations.length) return;
|
|
107
|
+
// now is the time to decide on what the undo operations will
|
|
108
|
+
// look like, based on the confirmed view of the related entities.
|
|
109
|
+
if (meta.undoable) {
|
|
110
|
+
const undo = await this.createUndo({
|
|
111
|
+
ops: operations,
|
|
112
|
+
source: meta.source,
|
|
113
|
+
});
|
|
114
|
+
if (undo) this.ctx.undoHistory.addUndo(undo);
|
|
115
|
+
}
|
|
116
|
+
// ship it out to EntityStore to compute final snapshots
|
|
117
|
+
// write to storage and refresh entities and queries
|
|
118
|
+
await this.entities.addData({
|
|
119
|
+
operations,
|
|
120
|
+
baselines: [],
|
|
121
|
+
isLocal: true,
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Adds operations to the active batch.
|
|
127
|
+
*/
|
|
128
|
+
addOperations = (operations: Operation[]) => {
|
|
129
|
+
if (!operations.length) return;
|
|
130
|
+
this.batcher.add({
|
|
131
|
+
key: this.currentBatchKey,
|
|
132
|
+
items: operations,
|
|
133
|
+
});
|
|
134
|
+
this.ctx.log(
|
|
135
|
+
`debug`,
|
|
136
|
+
'added',
|
|
137
|
+
operations.length,
|
|
138
|
+
'ops to batch',
|
|
139
|
+
this.currentBatchKey,
|
|
140
|
+
', size = ',
|
|
141
|
+
this.batcher.getSize(this.currentBatchKey),
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
batch = ({
|
|
146
|
+
undoable = true,
|
|
147
|
+
batchName = generateId(),
|
|
148
|
+
max = null,
|
|
149
|
+
timeout = this.defaultBatchTimeout,
|
|
150
|
+
}: {
|
|
151
|
+
undoable?: boolean;
|
|
152
|
+
batchName?: string;
|
|
153
|
+
max?: number | null;
|
|
154
|
+
timeout?: number | null;
|
|
155
|
+
} = {}): OperationBatch => {
|
|
156
|
+
const internalBatch = this.batcher.add({
|
|
157
|
+
key: batchName,
|
|
158
|
+
max,
|
|
159
|
+
timeout,
|
|
160
|
+
items: [],
|
|
161
|
+
userData: { undoable },
|
|
162
|
+
});
|
|
163
|
+
const externalApi = {
|
|
164
|
+
run: (fn: () => void) => {
|
|
165
|
+
// while the provided function runs, operations are forwarded
|
|
166
|
+
// to the new batch instead of default. this relies on the function
|
|
167
|
+
// being synchronous.
|
|
168
|
+
this.currentBatchKey = batchName;
|
|
169
|
+
fn();
|
|
170
|
+
this.currentBatchKey = DEFAULT_BATCH_KEY;
|
|
171
|
+
return externalApi;
|
|
172
|
+
},
|
|
173
|
+
commit: async () => {
|
|
174
|
+
// before running a batch, the default operations must be flushed
|
|
175
|
+
// this better preserves undo history behavior...
|
|
176
|
+
// if we left the default batch open while flushing a named batch,
|
|
177
|
+
// then the default batch would be flushed after the named batch,
|
|
178
|
+
// and the default batch could contain operations both prior and
|
|
179
|
+
// after the named batch. this would result in a confusing undo
|
|
180
|
+
// history where the first undo might reverse changes before and
|
|
181
|
+
// after a set of other changes.
|
|
182
|
+
await this.batcher.flush(DEFAULT_BATCH_KEY);
|
|
183
|
+
return internalBatch.flush();
|
|
184
|
+
},
|
|
185
|
+
flush: () => externalApi.commit(),
|
|
186
|
+
discard: () => {
|
|
187
|
+
this.batcher.discard(batchName);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
return externalApi;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
flushAll = () => Promise.all(this.batcher.flushAll());
|
|
194
|
+
|
|
195
|
+
private createUndo = async (data: { ops: Operation[]; source?: Entity }) => {
|
|
196
|
+
// this can't be done on-demand because we rely on the current
|
|
197
|
+
// state of the entities to calculate the inverse operations.
|
|
198
|
+
const inverseOps = await this.getInverseOperations(data);
|
|
199
|
+
|
|
200
|
+
if (!inverseOps.length) return null;
|
|
201
|
+
|
|
202
|
+
return async () => {
|
|
203
|
+
const redo = await this.createUndo({
|
|
204
|
+
ops: inverseOps,
|
|
205
|
+
source: data.source,
|
|
206
|
+
});
|
|
207
|
+
// set time to now for all undo operations, they're happening now.
|
|
208
|
+
for (const op of inverseOps) {
|
|
209
|
+
op.timestamp = this.meta.now;
|
|
210
|
+
}
|
|
211
|
+
await this.commitOperations(
|
|
212
|
+
inverseOps,
|
|
213
|
+
// undos should not generate their own undo operations
|
|
214
|
+
// since they already calculate redo as the inverse.
|
|
215
|
+
{ undoable: false },
|
|
216
|
+
);
|
|
217
|
+
return redo;
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
private getInverseOperations = async ({
|
|
221
|
+
ops,
|
|
222
|
+
source,
|
|
223
|
+
}: {
|
|
224
|
+
ops: Operation[];
|
|
225
|
+
source?: Entity;
|
|
226
|
+
}) => {
|
|
227
|
+
const grouped = groupPatchesByOid(ops);
|
|
228
|
+
const inverseOps: Operation[] = [];
|
|
229
|
+
const getNow = () => this.meta.now;
|
|
230
|
+
await Promise.all(
|
|
231
|
+
Object.entries(grouped).map(async ([oid, patches]): Promise<void> => {
|
|
232
|
+
const entity = source ?? (await this.entities.hydrate(getOidRoot(oid)));
|
|
233
|
+
// TODO: this is getting the rebased baseline? how? are ops being submitted early?
|
|
234
|
+
const viewData = entity?.__getViewData__(oid, 'confirmed');
|
|
235
|
+
if (!viewData) {
|
|
236
|
+
this.ctx.log(
|
|
237
|
+
'warn',
|
|
238
|
+
'could not find entity',
|
|
239
|
+
oid,
|
|
240
|
+
'for undo operation',
|
|
241
|
+
ops,
|
|
242
|
+
);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const inverse = getUndoOperations(oid, viewData.view, patches, getNow);
|
|
246
|
+
inverseOps.unshift(...inverse);
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
return inverseOps;
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { ObjectIdentifier } from '@verdant-web/common';
|
|
2
|
+
import type { Entity } from './Entity.js';
|
|
3
|
+
|
|
4
|
+
export type AccessibleEntityProperty<T> = T extends Array<any>
|
|
5
|
+
? number
|
|
6
|
+
: T extends object
|
|
7
|
+
? keyof T
|
|
8
|
+
: never;
|
|
9
|
+
|
|
10
|
+
export type DataFromInit<Init> = Init extends { [key: string]: any }
|
|
11
|
+
? {
|
|
12
|
+
[Key in keyof Init]: Init[Key];
|
|
13
|
+
}
|
|
14
|
+
: Init extends Array<any>
|
|
15
|
+
? Init
|
|
16
|
+
: any;
|
|
17
|
+
|
|
18
|
+
// reduces keys of an object to only ones with an optional
|
|
19
|
+
// value
|
|
20
|
+
export type DeletableKeys<T> = keyof {
|
|
21
|
+
[Key in keyof T as IfNullableThen<T[Key], Key>]: Key;
|
|
22
|
+
};
|
|
23
|
+
type IfNullableThen<T, Out> = undefined extends T
|
|
24
|
+
? Out
|
|
25
|
+
: null extends T
|
|
26
|
+
? Out
|
|
27
|
+
: never;
|
|
28
|
+
|
|
29
|
+
export type EntityShape<E extends Entity<any, any>> = E extends Entity<
|
|
30
|
+
infer Value,
|
|
31
|
+
any
|
|
32
|
+
>
|
|
33
|
+
? Value
|
|
34
|
+
: never;
|
|
35
|
+
|
|
36
|
+
export type BaseEntityValue = { [Key: string]: any } | any[];
|
|
37
|
+
|
|
38
|
+
export interface EntityChange {
|
|
39
|
+
oid: ObjectIdentifier;
|
|
40
|
+
isLocal: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EntityChangeInfo {
|
|
44
|
+
isLocal?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type EntityEvents = {
|
|
48
|
+
change: (info: EntityChangeInfo) => void;
|
|
49
|
+
changeDeep: (
|
|
50
|
+
target: BaseEntity<any, any, any>,
|
|
51
|
+
info: EntityChangeInfo,
|
|
52
|
+
) => void;
|
|
53
|
+
delete: (info: EntityChangeInfo) => void;
|
|
54
|
+
restore: (info: EntityChangeInfo) => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface BaseEntity<
|
|
58
|
+
Init,
|
|
59
|
+
Value extends BaseEntityValue,
|
|
60
|
+
Snapshot = DataFromInit<Init>,
|
|
61
|
+
> {
|
|
62
|
+
dispose: () => void;
|
|
63
|
+
subscribe<EventName extends keyof EntityEvents>(
|
|
64
|
+
event: EventName,
|
|
65
|
+
callback: EntityEvents[EventName],
|
|
66
|
+
): () => void;
|
|
67
|
+
get<Key extends keyof Value>(key: Key): Value[Key];
|
|
68
|
+
getAll(): Value;
|
|
69
|
+
getSnapshot(): Snapshot;
|
|
70
|
+
readonly deleted: boolean;
|
|
71
|
+
readonly updatedAt: number;
|
|
72
|
+
readonly uid: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type DeepPartial<T> = {
|
|
76
|
+
[P in keyof T]?: T[P] extends Array<infer U>
|
|
77
|
+
? Array<DeepPartial<U>>
|
|
78
|
+
: T[P] extends ReadonlyArray<infer U>
|
|
79
|
+
? ReadonlyArray<DeepPartial<U>>
|
|
80
|
+
: DeepPartial<T[P]>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export interface ObjectEntity<
|
|
84
|
+
Init,
|
|
85
|
+
Value extends BaseEntityValue,
|
|
86
|
+
Snapshot = DataFromInit<Init>,
|
|
87
|
+
> extends BaseEntity<Init, Value, Snapshot> {
|
|
88
|
+
keys(): string[];
|
|
89
|
+
entries(): [string, Exclude<Value[keyof Value], undefined>][];
|
|
90
|
+
values(): Exclude<Value[keyof Value], undefined>[];
|
|
91
|
+
set<Key extends keyof Init>(key: Key, value: Init[Key]): void;
|
|
92
|
+
delete(key: DeletableKeys<Value>): void;
|
|
93
|
+
update(
|
|
94
|
+
value: DeepPartial<Init>,
|
|
95
|
+
options?: { replaceSubObjects?: boolean; merge?: boolean },
|
|
96
|
+
): void;
|
|
97
|
+
readonly isList: false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ListEntity<
|
|
101
|
+
Init,
|
|
102
|
+
Value extends BaseEntityValue,
|
|
103
|
+
Snapshot = DataFromInit<Init>,
|
|
104
|
+
> extends Iterable<ListItemValue<Value>>,
|
|
105
|
+
BaseEntity<Init, Value, Snapshot> {
|
|
106
|
+
readonly isList: true;
|
|
107
|
+
readonly length: number;
|
|
108
|
+
push(value: ListItemInit<Init>): void;
|
|
109
|
+
insert(index: number, value: ListItemInit<Init>): void;
|
|
110
|
+
move(from: number, to: number): void;
|
|
111
|
+
moveItem(item: ListItemValue<Value>, to: number): void;
|
|
112
|
+
/**
|
|
113
|
+
* A Set operation which adds a value if an equivalent value is not already present.
|
|
114
|
+
* Object values are never the same.
|
|
115
|
+
*/
|
|
116
|
+
add(value: ListItemValue<Value>): void;
|
|
117
|
+
removeAll(item: ListItemValue<Value>): void;
|
|
118
|
+
removeFirst(item: ListItemValue<Value>): void;
|
|
119
|
+
removeLast(item: ListItemValue<Value>): void;
|
|
120
|
+
map<U>(callback: (value: ListItemValue<Value>, index: number) => U): U[];
|
|
121
|
+
filter(
|
|
122
|
+
callback: (value: ListItemValue<Value>, index: number) => boolean,
|
|
123
|
+
): ListItemValue<Value>[];
|
|
124
|
+
delete(index: number): void;
|
|
125
|
+
has(value: ListItemValue<Value>): boolean;
|
|
126
|
+
forEach(callback: (value: ListItemValue<Value>, index: number) => void): void;
|
|
127
|
+
some(predicate: (value: ListItemValue<Value>) => boolean): boolean;
|
|
128
|
+
every(predicate: (value: ListItemValue<Value>) => boolean): boolean;
|
|
129
|
+
find(
|
|
130
|
+
predicate: (value: ListItemValue<Value>) => boolean,
|
|
131
|
+
): ListItemValue<Value> | undefined;
|
|
132
|
+
includes(value: ListItemValue<Value>): boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type AnyEntity<
|
|
136
|
+
Init,
|
|
137
|
+
KeyValue extends BaseEntityValue,
|
|
138
|
+
Snapshot extends any,
|
|
139
|
+
> =
|
|
140
|
+
| ListEntity<Init, KeyValue, Snapshot>
|
|
141
|
+
| ObjectEntity<Init, KeyValue, Snapshot>;
|
|
142
|
+
|
|
143
|
+
export type ListItemValue<KeyValue> = KeyValue extends Array<infer T>
|
|
144
|
+
? T
|
|
145
|
+
: never;
|
|
146
|
+
export type ListItemInit<Init> = Init extends Array<infer T> ? T : never;
|
|
147
|
+
|
|
148
|
+
export type EntityDestructured<T extends AnyEntity<any, any, any> | null> =
|
|
149
|
+
| (T extends ListEntity<any, infer KeyValue, any>
|
|
150
|
+
? KeyValue
|
|
151
|
+
: T extends ObjectEntity<any, infer KeyValue, any>
|
|
152
|
+
? KeyValue
|
|
153
|
+
: never)
|
|
154
|
+
| (T extends null ? null : never);
|
package/src/files/EntityFile.ts
CHANGED
|
@@ -17,7 +17,6 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
|
|
|
17
17
|
private _fileData: FileData | null = null;
|
|
18
18
|
private _loading = true;
|
|
19
19
|
private _failed = false;
|
|
20
|
-
private _disposed = false;
|
|
21
20
|
private _downloadRemote = false;
|
|
22
21
|
|
|
23
22
|
constructor(
|
|
@@ -39,7 +38,6 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
|
|
|
39
38
|
[UPDATE] = (fileData: FileData) => {
|
|
40
39
|
this._loading = false;
|
|
41
40
|
this._failed = false;
|
|
42
|
-
this._disposed = false;
|
|
43
41
|
this._fileData = fileData;
|
|
44
42
|
if (fileData.file) {
|
|
45
43
|
if (this._objectUrl) {
|
|
@@ -78,10 +76,17 @@ export class EntityFile extends EventSubscriber<EntityFileEvents> {
|
|
|
78
76
|
return this._failed;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
destroy = () => {
|
|
82
80
|
if (this._objectUrl) {
|
|
83
81
|
URL.revokeObjectURL(this._objectUrl);
|
|
84
82
|
}
|
|
85
|
-
this.
|
|
83
|
+
this.dispose();
|
|
86
84
|
};
|
|
85
|
+
|
|
86
|
+
getSnapshot() {
|
|
87
|
+
return {
|
|
88
|
+
id: this.id,
|
|
89
|
+
url: this.loading || this.failed ? undefined : this.url,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
87
92
|
}
|
package/src/files/FileManager.ts
CHANGED
|
@@ -108,11 +108,11 @@ export class FileManager {
|
|
|
108
108
|
* Immediately returns an EntityFile to use, then either loads
|
|
109
109
|
* the file from cache, local database, or the server.
|
|
110
110
|
*/
|
|
111
|
-
get = (id: string) => {
|
|
111
|
+
get = (id: string, options?: { downloadRemote?: boolean }) => {
|
|
112
112
|
if (this.files.has(id)) {
|
|
113
113
|
return this.files.get(id)!;
|
|
114
114
|
}
|
|
115
|
-
const file = new EntityFile(id);
|
|
115
|
+
const file = new EntityFile(id, options);
|
|
116
116
|
this.files.set(id, file);
|
|
117
117
|
this.load(file);
|
|
118
118
|
return file;
|
|
@@ -120,6 +120,7 @@ export class FileManager {
|
|
|
120
120
|
|
|
121
121
|
private load = async (file: EntityFile, retries = 0) => {
|
|
122
122
|
if (retries > 5) {
|
|
123
|
+
this.context.log('error', 'Failed to load file after 5 retries');
|
|
123
124
|
file[MARK_FAILED]();
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
@@ -137,6 +138,7 @@ export class FileManager {
|
|
|
137
138
|
downloadRemote: file.downloadRemote,
|
|
138
139
|
});
|
|
139
140
|
} else {
|
|
141
|
+
this.context.log('error', 'Failed to load file', result);
|
|
140
142
|
file[MARK_FAILED]();
|
|
141
143
|
if (result.retry) {
|
|
142
144
|
// schedule a retry
|
|
@@ -183,7 +185,7 @@ export class FileManager {
|
|
|
183
185
|
};
|
|
184
186
|
|
|
185
187
|
private handleFileRefsDeleted = async (fileRefs: FileRef[]) => {
|
|
186
|
-
const tx = this.storage.createTransaction(['files'], 'readwrite');
|
|
188
|
+
const tx = this.storage.createTransaction(['files'], { mode: 'readwrite' });
|
|
187
189
|
await Promise.all(
|
|
188
190
|
fileRefs.map(async (fileRef) => {
|
|
189
191
|
try {
|
package/src/files/FileStorage.ts
CHANGED
|
@@ -53,8 +53,7 @@ export class FileStorage extends IDBService {
|
|
|
53
53
|
buffer,
|
|
54
54
|
} as StoredFileData);
|
|
55
55
|
},
|
|
56
|
-
'readwrite',
|
|
57
|
-
transaction,
|
|
56
|
+
{ mode: 'readwrite', transaction },
|
|
58
57
|
);
|
|
59
58
|
};
|
|
60
59
|
|
|
@@ -86,8 +85,7 @@ export class FileStorage extends IDBService {
|
|
|
86
85
|
remote: 'true',
|
|
87
86
|
} as StoredFileData);
|
|
88
87
|
},
|
|
89
|
-
'readwrite',
|
|
90
|
-
transaction,
|
|
88
|
+
{ mode: 'readwrite', transaction },
|
|
91
89
|
);
|
|
92
90
|
};
|
|
93
91
|
|
|
@@ -100,8 +98,7 @@ export class FileStorage extends IDBService {
|
|
|
100
98
|
(store) => {
|
|
101
99
|
return store.get(id);
|
|
102
100
|
},
|
|
103
|
-
'readonly',
|
|
104
|
-
transaction,
|
|
101
|
+
{ mode: 'readonly', transaction },
|
|
105
102
|
);
|
|
106
103
|
if (!raw) {
|
|
107
104
|
return undefined;
|
|
@@ -129,8 +126,7 @@ export class FileStorage extends IDBService {
|
|
|
129
126
|
(store) => {
|
|
130
127
|
return store.delete(id);
|
|
131
128
|
},
|
|
132
|
-
'readwrite',
|
|
133
|
-
transaction,
|
|
129
|
+
{ mode: 'readwrite', transaction },
|
|
134
130
|
);
|
|
135
131
|
}
|
|
136
132
|
|
|
@@ -152,8 +148,7 @@ export class FileStorage extends IDBService {
|
|
|
152
148
|
deletedAt: Date.now(),
|
|
153
149
|
} as StoredFileData);
|
|
154
150
|
},
|
|
155
|
-
'readwrite',
|
|
156
|
-
transaction,
|
|
151
|
+
{ mode: 'readwrite', transaction },
|
|
157
152
|
);
|
|
158
153
|
};
|
|
159
154
|
|
|
@@ -163,7 +158,7 @@ export class FileStorage extends IDBService {
|
|
|
163
158
|
(store) => {
|
|
164
159
|
return store.index('remote').getAll('false');
|
|
165
160
|
},
|
|
166
|
-
'readonly',
|
|
161
|
+
{ mode: 'readonly' },
|
|
167
162
|
);
|
|
168
163
|
return raw.map(this.hydrateFileData);
|
|
169
164
|
};
|
|
@@ -182,8 +177,7 @@ export class FileStorage extends IDBService {
|
|
|
182
177
|
(value, store) => {
|
|
183
178
|
iterator(this.hydrateFileData(value), store);
|
|
184
179
|
},
|
|
185
|
-
'readwrite',
|
|
186
|
-
transaction,
|
|
180
|
+
{ mode: 'readwrite', transaction },
|
|
187
181
|
);
|
|
188
182
|
};
|
|
189
183
|
}
|
package/src/files/utils.ts
CHANGED
|
@@ -12,11 +12,15 @@ export function createFileData(file: File): FileData {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function isFile(value: any): value is File {
|
|
16
|
-
return
|
|
15
|
+
export function isFile(value: any): value is File {
|
|
16
|
+
return (
|
|
17
|
+
value instanceof File ||
|
|
18
|
+
(typeof Blob !== 'undefined' && value instanceof Blob)
|
|
19
|
+
);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
/**
|
|
23
|
+
* MUTATES the value.
|
|
20
24
|
* Replaces File values with refs and returns the normalized value.
|
|
21
25
|
* The list of files passed to the second argument will be populated with the files found in the value.
|
|
22
26
|
*/
|
package/src/idb.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { roughSizeOfObject } from '@verdant-web/common';
|
|
2
2
|
|
|
3
|
+
export function isAbortError(err: unknown) {
|
|
4
|
+
return err instanceof Error && err.name === 'AbortError';
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export function storeRequestPromise<T>(request: IDBRequest<T>) {
|
|
4
8
|
return new Promise<T>((resolve, reject) => {
|
|
5
9
|
request.onsuccess = () => {
|
|
6
10
|
resolve(request.result);
|
|
7
11
|
};
|
|
8
12
|
request.onerror = () => {
|
|
9
|
-
|
|
13
|
+
if (request.error && isAbortError(request.error)) {
|
|
14
|
+
// TODO: is this the right thing to do?
|
|
15
|
+
resolve(request.result);
|
|
16
|
+
} else {
|
|
17
|
+
reject(request.error);
|
|
18
|
+
}
|
|
10
19
|
};
|
|
11
20
|
});
|
|
12
21
|
}
|
|
@@ -29,7 +38,11 @@ export function cursorIterator<T>(
|
|
|
29
38
|
}
|
|
30
39
|
};
|
|
31
40
|
request.onerror = () => {
|
|
32
|
-
|
|
41
|
+
if (request.error && isAbortError(request.error)) {
|
|
42
|
+
resolve();
|
|
43
|
+
} else {
|
|
44
|
+
reject(request.error);
|
|
45
|
+
}
|
|
33
46
|
};
|
|
34
47
|
});
|
|
35
48
|
}
|
|
@@ -53,7 +66,14 @@ export function getSizeOfObjectStore(
|
|
|
53
66
|
}
|
|
54
67
|
};
|
|
55
68
|
cursorReq.onerror = function (e) {
|
|
56
|
-
|
|
69
|
+
if (cursorReq.error && isAbortError(cursorReq.error)) {
|
|
70
|
+
resolve({
|
|
71
|
+
count: count,
|
|
72
|
+
size: size,
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
reject(cursorReq.error);
|
|
76
|
+
}
|
|
57
77
|
};
|
|
58
78
|
tx.oncomplete = function (e) {
|
|
59
79
|
resolve({
|
|
@@ -115,3 +135,31 @@ export async function getAllDatabaseNamesAndVersions(
|
|
|
115
135
|
) {
|
|
116
136
|
return indexedDB.databases();
|
|
117
137
|
}
|
|
138
|
+
|
|
139
|
+
export function createAbortableTransaction(
|
|
140
|
+
db: IDBDatabase,
|
|
141
|
+
storeNames: string[],
|
|
142
|
+
mode: 'readonly' | 'readwrite',
|
|
143
|
+
abortSignal?: AbortSignal,
|
|
144
|
+
log?: (...args: any[]) => void,
|
|
145
|
+
) {
|
|
146
|
+
const tx = db.transaction(storeNames, mode);
|
|
147
|
+
if (abortSignal) {
|
|
148
|
+
const abort = () => {
|
|
149
|
+
log?.('debug', 'aborting transaction');
|
|
150
|
+
try {
|
|
151
|
+
tx.abort();
|
|
152
|
+
} catch (e) {
|
|
153
|
+
log?.('debug', 'aborting transaction failed', e);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
abortSignal.addEventListener('abort', abort);
|
|
157
|
+
tx.addEventListener('error', () => {
|
|
158
|
+
abortSignal.removeEventListener('abort', abort);
|
|
159
|
+
});
|
|
160
|
+
tx.addEventListener('complete', () => {
|
|
161
|
+
abortSignal.removeEventListener('abort', abort);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return tx;
|
|
165
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { ClientDescriptor as StorageDescriptor };
|
|
|
11
11
|
export { Client as Storage };
|
|
12
12
|
export type { ClientDescriptorOptions };
|
|
13
13
|
export type { ClientDescriptorOptions as StorageInitOptions };
|
|
14
|
-
export { Entity } from './entities/Entity.js';
|
|
14
|
+
export { Entity } from './entities/2/Entity.js';
|
|
15
15
|
export type {
|
|
16
16
|
ObjectEntity,
|
|
17
17
|
ListEntity,
|
|
@@ -19,7 +19,7 @@ export type {
|
|
|
19
19
|
AccessibleEntityProperty,
|
|
20
20
|
AnyEntity,
|
|
21
21
|
EntityDestructured,
|
|
22
|
-
} from './entities/
|
|
22
|
+
} from './entities/2/types.js';
|
|
23
23
|
export { ServerSync } from './sync/Sync.js';
|
|
24
24
|
export type { SyncTransportMode } from './sync/Sync.js';
|
|
25
25
|
export { EntityFile } from './files/EntityFile.js';
|