@verdant-web/store 4.6.1 → 5.0.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 +14 -12
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/queries.test.js +3 -3
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/__tests__/schema.test.js +3 -3
- package/dist/esm/__tests__/schema.test.js.map +1 -1
- package/dist/esm/client/Client.d.ts +12 -10
- package/dist/esm/client/Client.js +40 -30
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/context/Time.d.ts +1 -1
- package/dist/esm/context/Time.js +1 -1
- package/dist/esm/context/Time.js.map +1 -1
- package/dist/esm/context/context.d.ts +84 -15
- package/dist/esm/context/context.js +98 -1
- package/dist/esm/context/context.js.map +1 -1
- package/dist/esm/entities/Entity.test.js +0 -1
- package/dist/esm/entities/Entity.test.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.js +11 -5
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.js +9 -7
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/files/FileManager.js +5 -5
- package/dist/esm/files/FileManager.js.map +1 -1
- package/dist/esm/index.d.ts +6 -4
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +3 -4
- package/dist/esm/internal.js +1 -2
- package/dist/esm/internal.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +3 -6
- package/dist/esm/persistence/PersistenceMetadata.js +5 -6
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.d.ts +3 -3
- package/dist/esm/persistence/idb/IdbService.js +0 -1
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/idbPersistence.d.ts +9 -10
- package/dist/esm/persistence/idb/idbPersistence.js +11 -4
- package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +2 -2
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +1 -1
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.d.ts +3 -2
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js +16 -15
- package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -1
- package/dist/esm/persistence/idb/queries/migration/db.js +7 -0
- package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
- package/dist/esm/persistence/idb/util.js +27 -17
- package/dist/esm/persistence/idb/util.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +8 -8
- package/dist/esm/persistence/migration/engine.d.ts +5 -3
- package/dist/esm/persistence/migration/engine.js +23 -14
- package/dist/esm/persistence/migration/engine.js.map +1 -1
- package/dist/esm/persistence/migration/finalize.d.ts +5 -3
- package/dist/esm/persistence/migration/finalize.js +5 -4
- package/dist/esm/persistence/migration/finalize.js.map +1 -1
- package/dist/esm/persistence/migration/migrate.d.ts +8 -5
- package/dist/esm/persistence/migration/migrate.js +10 -4
- package/dist/esm/persistence/migration/migrate.js.map +1 -1
- package/dist/esm/persistence/persistence.d.ts +9 -2
- package/dist/esm/persistence/persistence.js +65 -32
- package/dist/esm/persistence/persistence.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.js +1 -1
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +2 -2
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +1 -1
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +1 -1
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/sync/PresenceManager.d.ts +2 -2
- package/dist/esm/sync/PresenceManager.js +5 -2
- package/dist/esm/sync/PresenceManager.js.map +1 -1
- package/dist/esm/sync/PushPullSync.js +4 -4
- package/dist/esm/sync/PushPullSync.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +2 -2
- package/dist/esm/sync/Sync.js +17 -14
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +12 -7
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/sync/serviceWorker.d.ts +2 -2
- package/dist/esm/sync/serviceWorker.js +3 -4
- package/dist/esm/sync/serviceWorker.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixtures/testStorage.ts +4 -8
- package/src/__tests__/queries.test.ts +3 -5
- package/src/__tests__/schema.test.ts +3 -3
- package/src/client/Client.ts +50 -34
- package/src/context/Time.ts +2 -2
- package/src/context/context.ts +189 -17
- package/src/entities/Entity.test.ts +0 -1
- package/src/entities/EntityMetadata.ts +16 -5
- package/src/entities/EntityStore.ts +14 -10
- package/src/files/FileManager.ts +5 -5
- package/src/index.ts +6 -10
- package/src/internal.ts +10 -11
- package/src/persistence/PersistenceMetadata.ts +9 -11
- package/src/persistence/idb/IdbService.ts +2 -3
- package/src/persistence/idb/idbPersistence.ts +25 -19
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +3 -3
- package/src/persistence/idb/queries/IdbDocumentDb.ts +31 -17
- package/src/persistence/idb/queries/migration/db.ts +10 -0
- package/src/persistence/idb/util.ts +46 -24
- package/src/persistence/interfaces.ts +21 -12
- package/src/persistence/migration/engine.ts +33 -18
- package/src/persistence/migration/finalize.ts +11 -5
- package/src/persistence/migration/migrate.ts +15 -8
- package/src/persistence/persistence.ts +78 -67
- package/src/queries/FindAllQuery.ts +3 -1
- package/src/queries/FindInfiniteQuery.ts +6 -2
- package/src/queries/FindOneQuery.ts +3 -1
- package/src/queries/FindPageQuery.ts +3 -1
- package/src/sync/PresenceManager.ts +10 -7
- package/src/sync/PushPullSync.ts +8 -6
- package/src/sync/Sync.ts +26 -16
- package/src/sync/WebSocketSync.ts +25 -9
- package/src/sync/serviceWorker.ts +4 -6
- package/dist/esm/client/ClientDescriptor.d.ts +0 -87
- package/dist/esm/client/ClientDescriptor.js +0 -133
- package/dist/esm/client/ClientDescriptor.js.map +0 -1
- package/dist/esm/persistence/migration/types.d.ts +0 -3
- package/dist/esm/persistence/migration/types.js +0 -2
- package/dist/esm/persistence/migration/types.js.map +0 -1
- package/src/client/ClientDescriptor.ts +0 -246
- package/src/persistence/migration/types.ts +0 -4
package/src/context/context.ts
CHANGED
|
@@ -3,30 +3,117 @@ import {
|
|
|
3
3
|
EventSubscriber,
|
|
4
4
|
FileData,
|
|
5
5
|
FileRef,
|
|
6
|
+
HybridLogicalClockTimestampProvider,
|
|
6
7
|
Migration,
|
|
7
8
|
ObjectIdentifier,
|
|
8
9
|
Operation,
|
|
9
10
|
PatchCreator,
|
|
10
11
|
StorageSchema,
|
|
12
|
+
VerdantError,
|
|
11
13
|
} from '@verdant-web/common';
|
|
14
|
+
import { FakeWeakRef } from '../FakeWeakRef.js';
|
|
12
15
|
import { UndoHistory } from '../UndoHistory.js';
|
|
13
16
|
import type { Client } from '../client/Client.js';
|
|
14
|
-
import { VerdantLogger } from '../logger.js';
|
|
17
|
+
import { debugLogger, noLogger, VerdantLogger } from '../logger.js';
|
|
15
18
|
import { PersistenceFiles } from '../persistence/PersistenceFiles.js';
|
|
16
19
|
import type { PersistenceMetadata } from '../persistence/PersistenceMetadata.js';
|
|
17
20
|
import type { PersistenceDocuments } from '../persistence/PersistenceQueries.js';
|
|
21
|
+
import { IdbPersistence } from '../persistence/idb/idbPersistence.js';
|
|
18
22
|
import {
|
|
19
23
|
PersistedFileData,
|
|
20
24
|
PersistenceImplementation,
|
|
21
25
|
} from '../persistence/interfaces.js';
|
|
26
|
+
import { initializePersistence } from '../persistence/persistence.js';
|
|
27
|
+
import { ServerSyncOptions } from '../sync/Sync.js';
|
|
22
28
|
import { ShutdownHandler } from './ShutdownHandler.js';
|
|
23
29
|
import { Time } from './Time.js';
|
|
24
30
|
|
|
31
|
+
export interface ContextEnvironment {
|
|
32
|
+
WebSocket: typeof WebSocket;
|
|
33
|
+
fetch: typeof fetch;
|
|
34
|
+
indexedDB: typeof indexedDB;
|
|
35
|
+
location: Location;
|
|
36
|
+
history: History;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const defaultBrowserEnvironment: ContextEnvironment = {
|
|
40
|
+
WebSocket: typeof WebSocket !== 'undefined' ? WebSocket : (undefined as any),
|
|
41
|
+
fetch: typeof window !== 'undefined' ? window.fetch.bind(window) : fetch!,
|
|
42
|
+
indexedDB: typeof indexedDB !== 'undefined' ? indexedDB : (undefined as any),
|
|
43
|
+
location:
|
|
44
|
+
typeof window !== 'undefined' ? window.location : (undefined as any),
|
|
45
|
+
history: typeof window !== 'undefined' ? window.history : (undefined as any),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface ContextInit {
|
|
49
|
+
/** The schema used to create this client */
|
|
50
|
+
schema: StorageSchema<any>;
|
|
51
|
+
oldSchemas: StorageSchema<any>[];
|
|
52
|
+
/** Migrations, in order, to upgrade to each successive version of the schema */
|
|
53
|
+
migrations: Migration<any>[];
|
|
54
|
+
/** Provide a sync config to turn on synchronization with a server */
|
|
55
|
+
sync?: ServerSyncOptions;
|
|
56
|
+
/**
|
|
57
|
+
* Namespaces are used to separate data from different clients in IndexedDB.
|
|
58
|
+
*/
|
|
59
|
+
namespace: string;
|
|
60
|
+
/**
|
|
61
|
+
* Provide your own UndoHistory to have a unified undo system across multiple
|
|
62
|
+
* clients if you so desire.
|
|
63
|
+
*/
|
|
64
|
+
undoHistory?: UndoHistory;
|
|
65
|
+
/**
|
|
66
|
+
* Provide a log function to log internal debug messages
|
|
67
|
+
*/
|
|
68
|
+
log?: VerdantLogger | false;
|
|
69
|
+
disableRebasing?: boolean;
|
|
70
|
+
rebaseTimeout?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Provide a specific schema number to override the schema version
|
|
73
|
+
* in the database. This is useful for testing migrations or recovering
|
|
74
|
+
* from a mistakenly deployed incorrect schema. A specific version is required
|
|
75
|
+
* so that you don't leave this on accidentally for all new schemas.
|
|
76
|
+
*/
|
|
77
|
+
overrideSchemaConflict?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Configuration for file management
|
|
80
|
+
*/
|
|
81
|
+
files?: FileConfig;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Override the default IndexedDB persistence implementation.
|
|
85
|
+
*/
|
|
86
|
+
persistence?: PersistenceImplementation;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Specify the environment dependencies needed for the client.
|
|
90
|
+
* Normally these are provided by the browser, but in other
|
|
91
|
+
* runtimes you may need to provide your own.
|
|
92
|
+
*/
|
|
93
|
+
environment?: Partial<ContextEnvironment>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Enables experimental WeakRef usage to cull documents
|
|
97
|
+
* from cache that aren't being used. This is a performance
|
|
98
|
+
* optimization which has been tested under all Verdant's test
|
|
99
|
+
* suites but I still want to keep testing it in the real world
|
|
100
|
+
* before turning it on.
|
|
101
|
+
*/
|
|
102
|
+
EXPERIMENTAL_weakRefs?: boolean;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Customize querying behavior.
|
|
106
|
+
*/
|
|
107
|
+
queries?: QueryConfig;
|
|
108
|
+
|
|
109
|
+
persistenceShutdownHandler?: ShutdownHandler;
|
|
110
|
+
}
|
|
111
|
+
|
|
25
112
|
/**
|
|
26
113
|
* Common components utilized across various client
|
|
27
114
|
* services.
|
|
28
115
|
*/
|
|
29
|
-
export
|
|
116
|
+
export class Context {
|
|
30
117
|
namespace: string;
|
|
31
118
|
/**
|
|
32
119
|
* when in WIP mode, namespace might be set to a temporary value. This will always point to the
|
|
@@ -35,9 +122,16 @@ export interface Context {
|
|
|
35
122
|
originalNamespace: string;
|
|
36
123
|
time: Time;
|
|
37
124
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
125
|
+
// async initialized services
|
|
126
|
+
get meta(): Promise<PersistenceMetadata> {
|
|
127
|
+
return this.initializedPromise.then((init) => init.meta);
|
|
128
|
+
}
|
|
129
|
+
get documents(): Promise<PersistenceDocuments> {
|
|
130
|
+
return this.initializedPromise.then((init) => init.documents);
|
|
131
|
+
}
|
|
132
|
+
get files(): Promise<PersistenceFiles> {
|
|
133
|
+
return this.initializedPromise.then((init) => init.files);
|
|
134
|
+
}
|
|
41
135
|
|
|
42
136
|
undoHistory: UndoHistory;
|
|
43
137
|
schema: StorageSchema;
|
|
@@ -57,6 +151,7 @@ export interface Context {
|
|
|
57
151
|
fileAdded: (file: FileData) => void;
|
|
58
152
|
[ev: `fileUploaded:${string}`]: (file: FileData) => void;
|
|
59
153
|
fileUploaded: (file: FileData) => void;
|
|
154
|
+
outgoingSyncMessage: (message: ClientMessage) => void;
|
|
60
155
|
}>;
|
|
61
156
|
globalEvents: EventSubscriber<{
|
|
62
157
|
/**
|
|
@@ -86,15 +181,22 @@ export interface Context {
|
|
|
86
181
|
rebase: () => void;
|
|
87
182
|
fileSaved: (file: FileData) => void;
|
|
88
183
|
}>;
|
|
89
|
-
weakRef<T extends object>(value: T): WeakRef<T
|
|
184
|
+
weakRef = <T extends object>(value: T): WeakRef<T> => {
|
|
185
|
+
if (this.init.EXPERIMENTAL_weakRefs) {
|
|
186
|
+
return new WeakRef(value);
|
|
187
|
+
}
|
|
188
|
+
return new FakeWeakRef(value) as any;
|
|
189
|
+
};
|
|
90
190
|
migrations: Migration<any>[];
|
|
91
|
-
closing: boolean;
|
|
92
191
|
/** If this is present, any attempt to close the client should await it first. */
|
|
93
192
|
closeLock?: Promise<void>;
|
|
94
|
-
pauseRebasing: boolean;
|
|
95
193
|
patchCreator: PatchCreator;
|
|
96
194
|
persistenceShutdownHandler: ShutdownHandler;
|
|
97
195
|
|
|
196
|
+
// state
|
|
197
|
+
closing: boolean = false;
|
|
198
|
+
pauseRebasing: boolean = false;
|
|
199
|
+
|
|
98
200
|
config: {
|
|
99
201
|
files?: FileConfig;
|
|
100
202
|
sync?: SyncConfig;
|
|
@@ -102,13 +204,7 @@ export interface Context {
|
|
|
102
204
|
queries?: QueryConfig;
|
|
103
205
|
};
|
|
104
206
|
|
|
105
|
-
environment:
|
|
106
|
-
WebSocket: typeof WebSocket;
|
|
107
|
-
fetch: typeof fetch;
|
|
108
|
-
indexedDB: typeof indexedDB;
|
|
109
|
-
location: Location;
|
|
110
|
-
history: History;
|
|
111
|
-
};
|
|
207
|
+
environment: ContextEnvironment;
|
|
112
208
|
|
|
113
209
|
persistence: PersistenceImplementation;
|
|
114
210
|
|
|
@@ -116,7 +212,80 @@ export interface Context {
|
|
|
116
212
|
* Must be defined by the Client once it exists. Attempts to use this before
|
|
117
213
|
* it's ready will rightfully throw an error.
|
|
118
214
|
*/
|
|
119
|
-
getClient
|
|
215
|
+
getClient = (): Client => {
|
|
216
|
+
throw new VerdantError(
|
|
217
|
+
VerdantError.Code.Unexpected,
|
|
218
|
+
undefined,
|
|
219
|
+
'Client not yet initialized. This is a Verdant bug, please report it.',
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
constructor(
|
|
224
|
+
private init: ContextInit,
|
|
225
|
+
initPersistence?: ReturnType<typeof initializePersistence>,
|
|
226
|
+
) {
|
|
227
|
+
// if server-side and no alternative IndexedDB implementation was provided,
|
|
228
|
+
// we can't initialize the storage
|
|
229
|
+
if (typeof window === 'undefined' && !this.init.environment) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
'A Verdant client was initialized in an environment without a global Window or `environment` configuration. If you are using verdant in a server-rendered framework, you must enforce that all clients are initialized on the client-side, or you must provide some mock interface of the environment to the Client options.',
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
// set static values
|
|
235
|
+
this.namespace = this.init.namespace;
|
|
236
|
+
this.originalNamespace = this.init.namespace;
|
|
237
|
+
this.time = new Time(
|
|
238
|
+
new HybridLogicalClockTimestampProvider(),
|
|
239
|
+
this.init.schema.version,
|
|
240
|
+
);
|
|
241
|
+
this.log =
|
|
242
|
+
this.init.log === false ? noLogger : this.init.log || debugLogger('🌿');
|
|
243
|
+
this.migrations = init.migrations;
|
|
244
|
+
this.undoHistory = init.undoHistory || new UndoHistory();
|
|
245
|
+
this.entityEvents = new EventSubscriber();
|
|
246
|
+
this.internalEvents = new EventSubscriber();
|
|
247
|
+
this.globalEvents = new EventSubscriber();
|
|
248
|
+
this.schema = init.schema;
|
|
249
|
+
this.oldSchemas = init.oldSchemas;
|
|
250
|
+
this.patchCreator = new PatchCreator(() => this.time.now);
|
|
251
|
+
this.persistenceShutdownHandler =
|
|
252
|
+
init.persistenceShutdownHandler || new ShutdownHandler(this.log);
|
|
253
|
+
this.config = {
|
|
254
|
+
files: init.files,
|
|
255
|
+
sync: init.sync,
|
|
256
|
+
persistence: {
|
|
257
|
+
disableRebasing: init.disableRebasing,
|
|
258
|
+
rebaseTimeout: init.rebaseTimeout,
|
|
259
|
+
},
|
|
260
|
+
queries: init.queries,
|
|
261
|
+
};
|
|
262
|
+
this.environment = {
|
|
263
|
+
...defaultBrowserEnvironment,
|
|
264
|
+
...init.environment,
|
|
265
|
+
};
|
|
266
|
+
this.persistence =
|
|
267
|
+
init.persistence || new IdbPersistence(this.environment.indexedDB);
|
|
268
|
+
this.initializedPromise = initPersistence || initializePersistence(this);
|
|
269
|
+
this.initializedPromise.then(() => {
|
|
270
|
+
this.log('info', 'Persistence initialized');
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
private initializedPromise;
|
|
274
|
+
get waitForInitialization(): Promise<void> {
|
|
275
|
+
return this.initializedPromise.then(() => {});
|
|
276
|
+
}
|
|
277
|
+
reinitialize = async (): Promise<void> => {
|
|
278
|
+
this.initializedPromise = initializePersistence(this);
|
|
279
|
+
await this.initializedPromise;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
cloneWithOptions(options: Partial<ContextInit>): Context {
|
|
283
|
+
const copy = new Context(
|
|
284
|
+
{ ...this.init, ...options },
|
|
285
|
+
this.initializedPromise,
|
|
286
|
+
);
|
|
287
|
+
return copy;
|
|
288
|
+
}
|
|
120
289
|
}
|
|
121
290
|
|
|
122
291
|
export interface FileConfig {
|
|
@@ -231,4 +400,7 @@ export interface QueryConfig {
|
|
|
231
400
|
evictionTime?: number;
|
|
232
401
|
}
|
|
233
402
|
|
|
234
|
-
export type
|
|
403
|
+
export type ContextWithoutPersistence = Omit<
|
|
404
|
+
Context,
|
|
405
|
+
'meta' | 'documents' | 'files'
|
|
406
|
+
>;
|
|
@@ -261,7 +261,6 @@ describe('Entity', () => {
|
|
|
261
261
|
entity.update({ string: 'new world' });
|
|
262
262
|
expect(onPendingOperations).toHaveBeenCalledTimes(1);
|
|
263
263
|
const operation = onPendingOperations.mock.calls[0][0][0];
|
|
264
|
-
console.log(operation);
|
|
265
264
|
|
|
266
265
|
entity.__discardPendingOperation__(operation);
|
|
267
266
|
|
|
@@ -274,12 +274,23 @@ export class EntityMetadata {
|
|
|
274
274
|
if (op.data.op === 'delete') {
|
|
275
275
|
deleted = true;
|
|
276
276
|
} else {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
277
|
+
try {
|
|
278
|
+
base = applyPatch(base, op.data);
|
|
279
|
+
if (op.data.op === 'initialize') {
|
|
280
|
+
deleted = false;
|
|
281
|
+
if (op.authz) {
|
|
282
|
+
authz = op.authz;
|
|
283
|
+
}
|
|
282
284
|
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
this.ctx.log(
|
|
287
|
+
'critical',
|
|
288
|
+
`Failed to apply operation to entity ${this.oid}: ${JSON.stringify(
|
|
289
|
+
op,
|
|
290
|
+
)}`,
|
|
291
|
+
err,
|
|
292
|
+
);
|
|
293
|
+
throw err;
|
|
283
294
|
}
|
|
284
295
|
}
|
|
285
296
|
|
|
@@ -134,7 +134,7 @@ export class EntityStore extends Disposable {
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
empty = async () => {
|
|
137
|
-
await this.ctx.documents.reset();
|
|
137
|
+
await (await this.ctx.documents).reset();
|
|
138
138
|
this.events.resetAll.invoke(this);
|
|
139
139
|
this.cache.clear();
|
|
140
140
|
};
|
|
@@ -144,8 +144,8 @@ export class EntityStore extends Disposable {
|
|
|
144
144
|
this.ctx.log('warn', 'EntityStore is disposed, not resetting local data');
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
|
-
await this.ctx.meta.reset();
|
|
148
|
-
await this.ctx.documents.reset();
|
|
147
|
+
await (await this.ctx.meta).reset();
|
|
148
|
+
await (await this.ctx.documents).reset();
|
|
149
149
|
this.events.resetAll.invoke(this);
|
|
150
150
|
};
|
|
151
151
|
|
|
@@ -223,7 +223,7 @@ export class EntityStore extends Disposable {
|
|
|
223
223
|
// TODO: could messages be sent to sync before storage,
|
|
224
224
|
// so that realtime is lower latency? What would happen
|
|
225
225
|
// if the storage failed?
|
|
226
|
-
await this.ctx.meta.insertData(data, abortOptions);
|
|
226
|
+
await (await this.ctx.meta).insertData(data, abortOptions);
|
|
227
227
|
this.ctx.log(
|
|
228
228
|
'debug',
|
|
229
229
|
'Data processing complete, all data saved to metadata db.',
|
|
@@ -247,7 +247,7 @@ export class EntityStore extends Disposable {
|
|
|
247
247
|
);
|
|
248
248
|
try {
|
|
249
249
|
this.ctx.log('debug', 'Saving entities to queryable storage');
|
|
250
|
-
await this.ctx.documents.saveEntities(entities, abortOptions);
|
|
250
|
+
await (await this.ctx.documents).saveEntities(entities, abortOptions);
|
|
251
251
|
} catch (err) {
|
|
252
252
|
if (this.disposed) {
|
|
253
253
|
this.ctx.log(
|
|
@@ -319,6 +319,7 @@ export class EntityStore extends Disposable {
|
|
|
319
319
|
};
|
|
320
320
|
|
|
321
321
|
destroy = async () => {
|
|
322
|
+
this.ctx.log('warn', 'Disposing EntityStore');
|
|
322
323
|
this.dispose();
|
|
323
324
|
await this.batcher.flushAll();
|
|
324
325
|
};
|
|
@@ -469,7 +470,11 @@ export class EntityStore extends Disposable {
|
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
if (this.disposed) {
|
|
472
|
-
|
|
473
|
+
this.ctx.log(
|
|
474
|
+
'warn',
|
|
475
|
+
'Cannot hydrate entity after store has been disposed',
|
|
476
|
+
);
|
|
477
|
+
return null;
|
|
473
478
|
}
|
|
474
479
|
|
|
475
480
|
const metadataFamily = new EntityFamilyMetadata({
|
|
@@ -522,10 +527,9 @@ export class EntityStore extends Disposable {
|
|
|
522
527
|
entity: Entity,
|
|
523
528
|
opts?: { abort: AbortSignal },
|
|
524
529
|
) => {
|
|
525
|
-
const { operations, baselines } = await
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
);
|
|
530
|
+
const { operations, baselines } = await (
|
|
531
|
+
await this.ctx.meta
|
|
532
|
+
).getDocumentData(entity.oid, opts);
|
|
529
533
|
|
|
530
534
|
if (!baselines.length && !Object.keys(operations).length) {
|
|
531
535
|
this.ctx.log('debug', 'No data found for entity', entity.oid);
|
package/src/files/FileManager.ts
CHANGED
|
@@ -37,7 +37,7 @@ export class FileManager extends Disposable {
|
|
|
37
37
|
}
|
|
38
38
|
// this will download any original remote file and trigger a re-upload to the
|
|
39
39
|
// new file's identity, in addition to storing it on disk
|
|
40
|
-
const processedFile = await this.context.files.add(file);
|
|
40
|
+
const processedFile = await (await this.context.files).add(file);
|
|
41
41
|
entityFile[UPDATE](processedFile);
|
|
42
42
|
};
|
|
43
43
|
|
|
@@ -59,7 +59,7 @@ export class FileManager extends Disposable {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
private load = async (file: EntityFile) => {
|
|
62
|
-
const fileData = await this.context.files.get(file.id);
|
|
62
|
+
const fileData = await (await this.context.files).get(file.id);
|
|
63
63
|
if (fileData) {
|
|
64
64
|
file[UPDATE](fileData);
|
|
65
65
|
} else {
|
|
@@ -84,7 +84,7 @@ export class FileManager extends Disposable {
|
|
|
84
84
|
|
|
85
85
|
const result = await this.sync.getFile(file.id);
|
|
86
86
|
if (result.success) {
|
|
87
|
-
await this.context.files.add(result.data);
|
|
87
|
+
await (await this.context.files).add(result.data);
|
|
88
88
|
file[UPDATE](result.data);
|
|
89
89
|
} else {
|
|
90
90
|
this.context.log('error', 'Failed to load file', result);
|
|
@@ -97,8 +97,8 @@ export class FileManager extends Disposable {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
private onFileUploaded = (data: FileData) => {
|
|
100
|
+
private onFileUploaded = async (data: FileData) => {
|
|
101
101
|
this.context.log('debug', 'Marking file as uploaded', data.id);
|
|
102
|
-
this.context.files.onUploaded(data.id);
|
|
102
|
+
(await this.context.files).onUploaded(data.id);
|
|
103
103
|
};
|
|
104
104
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Client } from './client/Client.js';
|
|
2
|
-
import {
|
|
3
|
-
ClientDescriptor,
|
|
4
|
-
ClientDescriptorOptions,
|
|
5
|
-
} from './client/ClientDescriptor.js';
|
|
2
|
+
import type { ContextInit } from './context/context.js';
|
|
6
3
|
export type { ClientWithCollections } from './client/Client.js';
|
|
7
|
-
export { Client,
|
|
4
|
+
export { Client, type ClientDescriptorOptions, type ClientInitOptions };
|
|
5
|
+
type ClientInitOptions = ContextInit;
|
|
6
|
+
/** @deprecated - use ClientInitOptions alias */
|
|
7
|
+
type ClientDescriptorOptions = ClientInitOptions;
|
|
8
8
|
// backward compat
|
|
9
9
|
export { createMigration, schema } from '@verdant-web/common';
|
|
10
10
|
export type {
|
|
@@ -54,8 +54,4 @@ export { ServerSync, type ServerSyncOptions } from './sync/Sync.js';
|
|
|
54
54
|
export type { SyncTransportMode } from './sync/Sync.js';
|
|
55
55
|
export { UndoHistory } from './UndoHistory.js';
|
|
56
56
|
export * from './utils/id.js';
|
|
57
|
-
export { Client as Storage
|
|
58
|
-
export type {
|
|
59
|
-
ClientDescriptorOptions,
|
|
60
|
-
ClientDescriptorOptions as StorageInitOptions,
|
|
61
|
-
};
|
|
57
|
+
export { Client as Storage };
|
package/src/internal.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
export type { Context, InitialContext } from './context/context.js';
|
|
2
1
|
export {
|
|
3
|
-
|
|
2
|
+
assert,
|
|
3
|
+
createCompoundIndexValue,
|
|
4
|
+
createLowerBoundIndexValue,
|
|
4
5
|
createOid,
|
|
6
|
+
createUpperBoundIndexValue,
|
|
7
|
+
decomposeOid,
|
|
5
8
|
generateId,
|
|
6
|
-
|
|
9
|
+
getIndexValues,
|
|
7
10
|
isCompoundIndexFilter,
|
|
8
11
|
isDirectSynthetic,
|
|
9
12
|
isMatchIndexFilter,
|
|
13
|
+
isMultiValueIndex,
|
|
14
|
+
isRangeIndexFilter,
|
|
10
15
|
isSortIndexFilter,
|
|
11
16
|
isStartsWithIndexFilter,
|
|
12
|
-
isMultiValueIndex,
|
|
13
|
-
assert,
|
|
14
|
-
createCompoundIndexValue,
|
|
15
|
-
createLowerBoundIndexValue,
|
|
16
|
-
createUpperBoundIndexValue,
|
|
17
|
-
getIndexValues,
|
|
18
17
|
} from '@verdant-web/common';
|
|
19
18
|
export type {
|
|
20
19
|
CollectionCompoundIndexFilter,
|
|
20
|
+
CollectionIndexFilter,
|
|
21
21
|
MatchCollectionIndexFilter,
|
|
22
22
|
RangeCollectionIndexFilter,
|
|
23
|
-
CollectionIndexFilter,
|
|
24
23
|
} from '@verdant-web/common';
|
|
24
|
+
export type { Context, ContextWithoutPersistence } from './context/context.js';
|
|
25
25
|
export * from './persistence/migration/paths.js';
|
|
26
|
-
export * from './persistence/migration/types.js';
|
|
27
26
|
export { Disposable } from './utils/Disposable.js';
|
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
applyPatch,
|
|
3
3
|
assert,
|
|
4
4
|
assignOid,
|
|
5
|
-
ClientMessage,
|
|
6
5
|
DocumentBaseline,
|
|
7
|
-
EventSubscriber,
|
|
8
6
|
getOidRoot,
|
|
9
7
|
ObjectIdentifier,
|
|
10
8
|
Operation,
|
|
11
9
|
substituteRefsWithObjects,
|
|
12
10
|
} from '@verdant-web/common';
|
|
11
|
+
import cuid from 'cuid';
|
|
12
|
+
import { ContextWithoutPersistence } from '../context/context.js';
|
|
13
13
|
import {
|
|
14
14
|
AbstractTransaction,
|
|
15
15
|
ClientOperation,
|
|
@@ -18,20 +18,18 @@ import {
|
|
|
18
18
|
MetadataExport,
|
|
19
19
|
PersistenceMetadataDb,
|
|
20
20
|
} from './interfaces.js';
|
|
21
|
-
import { InitialContext } from '../context/context.js';
|
|
22
|
-
import { PersistenceRebaser } from './PersistenceRebaser.js';
|
|
23
21
|
import { MessageCreator } from './MessageCreator.js';
|
|
24
|
-
import
|
|
22
|
+
import { PersistenceRebaser } from './PersistenceRebaser.js';
|
|
25
23
|
|
|
26
24
|
export class PersistenceMetadata {
|
|
27
25
|
private rebaser: PersistenceRebaser;
|
|
28
26
|
/** Available to others, like sync... */
|
|
29
27
|
readonly messageCreator: MessageCreator;
|
|
30
|
-
readonly events = new EventSubscriber<{
|
|
31
|
-
syncMessage: (message: ClientMessage) => void;
|
|
32
|
-
}>();
|
|
33
28
|
|
|
34
|
-
constructor(
|
|
29
|
+
constructor(
|
|
30
|
+
private db: PersistenceMetadataDb,
|
|
31
|
+
private ctx: ContextWithoutPersistence,
|
|
32
|
+
) {
|
|
35
33
|
this.rebaser = new PersistenceRebaser(db, this, ctx);
|
|
36
34
|
this.messageCreator = new MessageCreator(db, this, ctx);
|
|
37
35
|
}
|
|
@@ -82,7 +80,7 @@ export class PersistenceMetadata {
|
|
|
82
80
|
`Inserted ${operations.length} local operations; sending sync message`,
|
|
83
81
|
);
|
|
84
82
|
const message = await this.messageCreator.createOperation({ operations });
|
|
85
|
-
this.
|
|
83
|
+
this.ctx.internalEvents.emit('outgoingSyncMessage', message);
|
|
86
84
|
};
|
|
87
85
|
|
|
88
86
|
private insertRemoteOperations = async (
|
|
@@ -500,7 +498,7 @@ export class PersistenceMetadata {
|
|
|
500
498
|
// can't ack timestamps from the future.
|
|
501
499
|
if (timestamp > this.ctx.time.now) return;
|
|
502
500
|
|
|
503
|
-
this.
|
|
501
|
+
this.ctx.internalEvents.emit('outgoingSyncMessage', {
|
|
504
502
|
type: 'ack',
|
|
505
503
|
replicaId: localReplicaInfo.id,
|
|
506
504
|
timestamp,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context
|
|
1
|
+
import { Context } from '../../context/context.js';
|
|
2
2
|
import { Disposable } from '../../utils/Disposable.js';
|
|
3
3
|
import {
|
|
4
4
|
createAbortableTransaction,
|
|
@@ -13,11 +13,10 @@ export class IdbService extends Disposable {
|
|
|
13
13
|
|
|
14
14
|
constructor(
|
|
15
15
|
protected db: IDBDatabase,
|
|
16
|
-
protected readonly ctx:
|
|
16
|
+
protected readonly ctx: Pick<Context, 'log' | 'environment'>,
|
|
17
17
|
) {
|
|
18
18
|
super();
|
|
19
19
|
const abortController = new AbortController();
|
|
20
|
-
const abort = abortController.abort.bind(abortController);
|
|
21
20
|
this.globalAbortController = abortController;
|
|
22
21
|
// FIXME: replace with event? I can't get this to work.
|
|
23
22
|
// this.addDispose(abort);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Migration } from '@verdant-web/common';
|
|
2
|
+
import { ContextWithoutPersistence } from '../../context/context.js';
|
|
2
3
|
import {
|
|
3
|
-
PersistenceImplementation,
|
|
4
4
|
PersistenceFileDb,
|
|
5
|
+
PersistenceImplementation,
|
|
5
6
|
PersistenceNamespace,
|
|
6
7
|
} from '../interfaces.js';
|
|
7
8
|
import { IdbPersistenceFileDb } from './files/IdbPersistenceFileDb.js';
|
|
8
9
|
import { IdbMetadataDb } from './metadata/IdbMetadataDb.js';
|
|
9
10
|
import { openMetadataDatabase } from './metadata/openMetadataDatabase.js';
|
|
10
11
|
import { IdbDocumentDb } from './queries/IdbDocumentDb.js';
|
|
12
|
+
import { openDatabase, upgradeDatabase } from './queries/migration/db.js';
|
|
11
13
|
import {
|
|
12
14
|
closeDatabase,
|
|
13
15
|
deleteDatabase,
|
|
@@ -16,9 +18,6 @@ import {
|
|
|
16
18
|
getNamespaceFromDatabaseInfo,
|
|
17
19
|
overwriteDatabase,
|
|
18
20
|
} from './util.js';
|
|
19
|
-
import { openDatabase, upgradeDatabase } from './queries/migration/db.js';
|
|
20
|
-
import { Migration } from '@verdant-web/common';
|
|
21
|
-
import { OpenDocumentDbContext } from '../migration/types.js';
|
|
22
21
|
|
|
23
22
|
export class IdbPersistence implements PersistenceImplementation {
|
|
24
23
|
name = 'IdbPersistence';
|
|
@@ -45,10 +44,7 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
45
44
|
return 0;
|
|
46
45
|
};
|
|
47
46
|
|
|
48
|
-
deleteNamespace = async (
|
|
49
|
-
namespace: string,
|
|
50
|
-
ctx: InitialContext,
|
|
51
|
-
): Promise<void> => {
|
|
47
|
+
deleteNamespace = async (namespace: string): Promise<void> => {
|
|
52
48
|
await Promise.all([
|
|
53
49
|
deleteDatabase(getMetadataDbName(namespace), this.indexedDB),
|
|
54
50
|
deleteDatabase([namespace, 'collections'].join('_'), this.indexedDB),
|
|
@@ -64,10 +60,14 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
64
60
|
copyNamespace = async (
|
|
65
61
|
from: string,
|
|
66
62
|
to: string,
|
|
67
|
-
ctx:
|
|
63
|
+
ctx: ContextWithoutPersistence,
|
|
68
64
|
): Promise<void> => {
|
|
69
|
-
const fromCtx = {
|
|
70
|
-
|
|
65
|
+
const fromCtx = ctx.cloneWithOptions({
|
|
66
|
+
namespace: from,
|
|
67
|
+
}) as ContextWithoutPersistence;
|
|
68
|
+
const toCtx = ctx.cloneWithOptions({
|
|
69
|
+
namespace: to,
|
|
70
|
+
}) as ContextWithoutPersistence;
|
|
71
71
|
const { db: fromMetaDb } = await openMetadataDatabase({
|
|
72
72
|
indexedDB: this.indexedDB,
|
|
73
73
|
log: fromCtx.log,
|
|
@@ -108,12 +108,13 @@ export class IdbPersistence implements PersistenceImplementation {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
111
|
-
constructor(
|
|
111
|
+
constructor(
|
|
112
|
+
private indexedDB: IDBFactory,
|
|
113
|
+
private namespace: string,
|
|
114
|
+
) {}
|
|
112
115
|
private metadataDb: IDBDatabase | undefined;
|
|
113
116
|
|
|
114
|
-
openFiles(
|
|
115
|
-
ctx: Omit<Context, 'files' | 'documents'>,
|
|
116
|
-
): Promise<PersistenceFileDb> {
|
|
117
|
+
openFiles(ctx: ContextWithoutPersistence): Promise<PersistenceFileDb> {
|
|
117
118
|
if (!this.metadataDb) {
|
|
118
119
|
throw new Error(
|
|
119
120
|
'Metadata database must be opened first. This is a bug in Verdant.',
|
|
@@ -122,7 +123,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
122
123
|
return Promise.resolve(new IdbPersistenceFileDb(this.metadataDb, ctx));
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
openMetadata = async (ctx:
|
|
126
|
+
openMetadata = async (ctx: ContextWithoutPersistence) => {
|
|
126
127
|
const { db } = await openMetadataDatabase({
|
|
127
128
|
indexedDB: this.indexedDB,
|
|
128
129
|
log: ctx.log,
|
|
@@ -133,7 +134,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
133
134
|
return new IdbMetadataDb(db, ctx);
|
|
134
135
|
};
|
|
135
136
|
|
|
136
|
-
openDocuments = async (ctx:
|
|
137
|
+
openDocuments = async (ctx: ContextWithoutPersistence) => {
|
|
137
138
|
const db = await openDatabase({
|
|
138
139
|
version: ctx.schema.version,
|
|
139
140
|
indexedDB: this.indexedDB,
|
|
@@ -145,7 +146,7 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
applyMigration = async (
|
|
148
|
-
ctx:
|
|
149
|
+
ctx: ContextWithoutPersistence,
|
|
149
150
|
migration: Migration<any>,
|
|
150
151
|
): Promise<void> => {
|
|
151
152
|
ctx.log(
|
|
@@ -167,6 +168,11 @@ class IdbPersistenceNamespace implements PersistenceNamespace {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
for (const collection of migration.allCollections) {
|
|
171
|
+
if (!db.objectStoreNames.contains(collection)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Expected object store for collection ${collection} to exist during migration, but it did not`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
170
176
|
const store = transaction.objectStore(collection);
|
|
171
177
|
// apply new indexes
|
|
172
178
|
for (const newIndex of migration.addedIndexes[collection] || []) {
|