@verdant-web/store 2.5.8 → 2.7.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 +15 -10
- package/dist/bundle/index.js.map +4 -4
- package/dist/cjs/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
- package/dist/cjs/{entities/FakeWeakRef.js → FakeWeakRef.js} +4 -4
- package/dist/cjs/FakeWeakRef.js.map +1 -0
- package/dist/cjs/IDBService.d.ts +1 -1
- package/dist/cjs/IDBService.js +18 -1
- package/dist/cjs/IDBService.js.map +1 -1
- package/dist/cjs/__tests__/documents.test.js +17 -0
- package/dist/cjs/__tests__/documents.test.js.map +1 -1
- package/dist/cjs/__tests__/fixtures/testStorage.d.ts +1 -1
- package/dist/cjs/__tests__/fixtures/testStorage.js +3 -2
- package/dist/cjs/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/cjs/__tests__/mutations.test.d.ts +1 -0
- package/dist/cjs/__tests__/mutations.test.js +42 -0
- package/dist/cjs/__tests__/mutations.test.js.map +1 -0
- package/dist/cjs/__tests__/queries.test.js +2 -0
- package/dist/cjs/__tests__/queries.test.js.map +1 -1
- package/dist/cjs/client/Client.d.ts +6 -4
- package/dist/cjs/client/Client.js +24 -16
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/client/ClientDescriptor.d.ts +15 -4
- package/dist/cjs/client/ClientDescriptor.js +117 -36
- package/dist/cjs/client/ClientDescriptor.js.map +1 -1
- package/dist/cjs/context.d.ts +1 -0
- package/dist/cjs/entities/DocumentFamiliyCache.d.ts +22 -2
- package/dist/cjs/entities/DocumentFamiliyCache.js +39 -21
- package/dist/cjs/entities/DocumentFamiliyCache.js.map +1 -1
- package/dist/cjs/entities/Entity.d.ts +7 -2
- package/dist/cjs/entities/Entity.js +33 -3
- package/dist/cjs/entities/Entity.js.map +1 -1
- package/dist/cjs/entities/EntityStore.d.ts +2 -1
- package/dist/cjs/entities/EntityStore.js +50 -20
- package/dist/cjs/entities/EntityStore.js.map +1 -1
- package/dist/cjs/idb.d.ts +2 -0
- package/dist/cjs/idb.js +9 -1
- package/dist/cjs/idb.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/metadata/BaselinesStore.js +15 -5
- package/dist/cjs/metadata/BaselinesStore.js.map +1 -1
- package/dist/cjs/metadata/openMetadataDatabase.d.ts +11 -2
- package/dist/cjs/metadata/openMetadataDatabase.js +56 -3
- package/dist/cjs/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/cjs/migration/db.d.ts +1 -1
- package/dist/cjs/migration/db.js +5 -2
- package/dist/cjs/migration/db.js.map +1 -1
- package/dist/cjs/migration/openDatabase.d.ts +8 -0
- package/dist/cjs/migration/openDatabase.js +217 -165
- package/dist/cjs/migration/openDatabase.js.map +1 -1
- package/dist/cjs/queries/BaseQuery.js +12 -1
- package/dist/cjs/queries/BaseQuery.js.map +1 -1
- package/dist/cjs/sync/Sync.d.ts +6 -5
- package/dist/cjs/sync/Sync.js.map +1 -1
- package/dist/cjs/sync/WebSocketSync.js +4 -3
- package/dist/cjs/sync/WebSocketSync.js.map +1 -1
- package/dist/esm/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
- package/dist/esm/{entities/FakeWeakRef.js → FakeWeakRef.js} +2 -2
- package/dist/esm/FakeWeakRef.js.map +1 -0
- package/dist/esm/IDBService.d.ts +1 -1
- package/dist/esm/IDBService.js +18 -1
- package/dist/esm/IDBService.js.map +1 -1
- package/dist/esm/__tests__/documents.test.js +17 -0
- package/dist/esm/__tests__/documents.test.js.map +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.js +4 -3
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/__tests__/mutations.test.d.ts +1 -0
- package/dist/esm/__tests__/mutations.test.js +40 -0
- package/dist/esm/__tests__/mutations.test.js.map +1 -0
- package/dist/esm/__tests__/queries.test.js +2 -0
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/client/Client.d.ts +6 -4
- package/dist/esm/client/Client.js +25 -17
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +15 -4
- package/dist/esm/client/ClientDescriptor.js +121 -40
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context.d.ts +1 -0
- package/dist/esm/entities/DocumentFamiliyCache.d.ts +22 -2
- package/dist/esm/entities/DocumentFamiliyCache.js +39 -21
- package/dist/esm/entities/DocumentFamiliyCache.js.map +1 -1
- package/dist/esm/entities/Entity.d.ts +7 -2
- package/dist/esm/entities/Entity.js +33 -3
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/EntityStore.d.ts +2 -1
- package/dist/esm/entities/EntityStore.js +51 -21
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/idb.d.ts +2 -0
- package/dist/esm/idb.js +6 -0
- package/dist/esm/idb.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/metadata/BaselinesStore.js +16 -6
- package/dist/esm/metadata/BaselinesStore.js.map +1 -1
- package/dist/esm/metadata/openMetadataDatabase.d.ts +11 -2
- package/dist/esm/metadata/openMetadataDatabase.js +54 -2
- package/dist/esm/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/migration/db.d.ts +1 -1
- package/dist/esm/migration/db.js +5 -2
- package/dist/esm/migration/db.js.map +1 -1
- package/dist/esm/migration/openDatabase.d.ts +8 -0
- package/dist/esm/migration/openDatabase.js +215 -164
- package/dist/esm/migration/openDatabase.js.map +1 -1
- package/dist/esm/queries/BaseQuery.js +12 -1
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -5
- package/dist/esm/sync/Sync.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +4 -3
- 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 +9 -4
- package/src/{entities/FakeWeakRef.ts → FakeWeakRef.ts} +2 -2
- package/src/IDBService.ts +20 -2
- package/src/__tests__/documents.test.ts +19 -0
- package/src/__tests__/fixtures/testStorage.ts +4 -7
- package/src/__tests__/mutations.test.ts +51 -0
- package/src/__tests__/queries.test.ts +3 -0
- package/src/client/Client.ts +29 -21
- package/src/client/ClientDescriptor.ts +176 -53
- package/src/context.ts +1 -0
- package/src/entities/DocumentFamiliyCache.ts +66 -21
- package/src/entities/Entity.ts +41 -6
- package/src/entities/EntityStore.ts +68 -21
- package/src/idb.ts +10 -0
- package/src/index.ts +1 -0
- package/src/metadata/BaselinesStore.ts +17 -6
- package/src/metadata/openMetadataDatabase.ts +96 -13
- package/src/migration/db.ts +14 -1
- package/src/migration/openDatabase.ts +350 -219
- package/src/queries/BaseQuery.ts +14 -1
- package/src/sync/Sync.ts +13 -9
- package/src/sync/WebSocketSync.ts +1 -0
- package/dist/cjs/entities/FakeWeakRef.js.map +0 -1
- package/dist/cjs/indexes.d.ts +0 -3
- package/dist/cjs/indexes.js +0 -20
- package/dist/cjs/indexes.js.map +0 -1
- package/dist/esm/entities/FakeWeakRef.js.map +0 -1
- package/dist/esm/indexes.d.ts +0 -3
- package/dist/esm/indexes.js +0 -15
- package/dist/esm/indexes.js.map +0 -1
- package/src/indexes.ts +0 -31
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EventSubscriber,
|
|
3
|
+
Migration,
|
|
4
|
+
StorageSchema,
|
|
5
|
+
hashObject,
|
|
6
|
+
} from '@verdant-web/common';
|
|
2
7
|
import { Context } from '../context.js';
|
|
3
8
|
import { FileManagerConfig } from '../files/FileManager.js';
|
|
4
9
|
import { Metadata } from '../metadata/Metadata.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
10
|
+
import {
|
|
11
|
+
openMetadataDatabase,
|
|
12
|
+
openWIPMetadataDatabase,
|
|
13
|
+
} from '../metadata/openMetadataDatabase.js';
|
|
14
|
+
import {
|
|
15
|
+
openDocumentDatabase,
|
|
16
|
+
openWIPDocumentDatabase,
|
|
17
|
+
} from '../migration/openDatabase.js';
|
|
7
18
|
import { ServerSyncOptions } from '../sync/Sync.js';
|
|
8
19
|
import { UndoHistory } from '../UndoHistory.js';
|
|
9
20
|
import { Client } from './Client.js';
|
|
10
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
deleteAllDatabases,
|
|
23
|
+
deleteDatabase,
|
|
24
|
+
getAllDatabaseNamesAndVersions,
|
|
25
|
+
} from '../idb.js';
|
|
26
|
+
import { FakeWeakRef } from '../FakeWeakRef.js';
|
|
11
27
|
|
|
12
28
|
export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
13
29
|
/** The schema used to create this client */
|
|
@@ -43,6 +59,14 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
|
43
59
|
* Configuration for file management
|
|
44
60
|
*/
|
|
45
61
|
files?: FileManagerConfig;
|
|
62
|
+
/**
|
|
63
|
+
* Enables experimental WeakRef usage to cull documents
|
|
64
|
+
* from cache that aren't being used. This is a performance
|
|
65
|
+
* optimization which has been tested under all Verdant's test
|
|
66
|
+
* suites but I still want to keep testing it in the real world
|
|
67
|
+
* before turning it on.
|
|
68
|
+
*/
|
|
69
|
+
EXPERIMENTAL_weakRefs?: boolean;
|
|
46
70
|
}
|
|
47
71
|
|
|
48
72
|
/**
|
|
@@ -50,13 +74,17 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
|
50
74
|
* Storage creation promise and exposes some metadata which can
|
|
51
75
|
* be useful immediately.
|
|
52
76
|
*/
|
|
53
|
-
export class ClientDescriptor<
|
|
54
|
-
|
|
77
|
+
export class ClientDescriptor<
|
|
78
|
+
Presence = any,
|
|
79
|
+
Profile = any,
|
|
80
|
+
ClientImpl extends Client = Client,
|
|
81
|
+
> {
|
|
82
|
+
private readonly _readyPromise: Promise<ClientImpl>;
|
|
55
83
|
// assertions because these are defined by plucking them from
|
|
56
84
|
// Promise initializer
|
|
57
|
-
private resolveReady!: (storage:
|
|
85
|
+
private resolveReady!: (storage: ClientImpl) => void;
|
|
58
86
|
private rejectReady!: (err: Error) => void;
|
|
59
|
-
private _resolvedValue:
|
|
87
|
+
private _resolvedValue: ClientImpl | undefined;
|
|
60
88
|
private _initializing = false;
|
|
61
89
|
private _namespace: string;
|
|
62
90
|
|
|
@@ -88,51 +116,13 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
88
116
|
}
|
|
89
117
|
this._initializing = true;
|
|
90
118
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const context: Omit<Context, 'documentDb'> = {
|
|
99
|
-
namespace: this._namespace,
|
|
100
|
-
metaDb,
|
|
101
|
-
schema: init.schema,
|
|
102
|
-
log: init.log || (() => {}),
|
|
103
|
-
undoHistory: init.undoHistory || new UndoHistory(),
|
|
104
|
-
entityEvents: new EventSubscriber(),
|
|
105
|
-
globalEvents: new EventSubscriber(),
|
|
106
|
-
};
|
|
107
|
-
const meta = new Metadata({
|
|
108
|
-
context,
|
|
109
|
-
disableRebasing: init.disableRebasing,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// verify schema integrity
|
|
113
|
-
await meta.updateSchema(init.schema, init.overrideSchemaConflict);
|
|
114
|
-
|
|
115
|
-
const documentDb = await openDocumentDatabase({
|
|
116
|
-
context,
|
|
117
|
-
version: init.schema.version,
|
|
118
|
-
meta,
|
|
119
|
-
migrations: init.migrations,
|
|
120
|
-
indexedDB: init.indexedDb,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const fullContext: Context = Object.assign(context, { documentDb });
|
|
124
|
-
|
|
125
|
-
const storage = new Client(
|
|
126
|
-
{
|
|
127
|
-
syncConfig: init.sync,
|
|
128
|
-
migrations: init.migrations,
|
|
129
|
-
files: init.files,
|
|
130
|
-
},
|
|
131
|
-
fullContext,
|
|
132
|
-
{
|
|
133
|
-
meta,
|
|
134
|
-
},
|
|
135
|
-
);
|
|
119
|
+
let storage: ClientImpl;
|
|
120
|
+
if (init.schema.wip) {
|
|
121
|
+
storage = await this.initializeWIPDatabases(init);
|
|
122
|
+
} else {
|
|
123
|
+
storage = await this.initializeDatabases(init);
|
|
124
|
+
this.cleanupWIPDatabases(init);
|
|
125
|
+
}
|
|
136
126
|
|
|
137
127
|
this.resolveReady(storage);
|
|
138
128
|
this._resolvedValue = storage;
|
|
@@ -145,6 +135,139 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
145
135
|
}
|
|
146
136
|
};
|
|
147
137
|
|
|
138
|
+
private initializeDatabases = async (init: ClientDescriptorOptions) => {
|
|
139
|
+
const { db: metaDb } = await openMetadataDatabase({
|
|
140
|
+
indexedDB: init.indexedDb,
|
|
141
|
+
log: init.log,
|
|
142
|
+
namespace: init.namespace,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const context: Omit<Context, 'documentDb'> = {
|
|
146
|
+
namespace: this._namespace,
|
|
147
|
+
metaDb,
|
|
148
|
+
schema: init.schema,
|
|
149
|
+
log: init.log || (() => {}),
|
|
150
|
+
undoHistory: init.undoHistory || new UndoHistory(),
|
|
151
|
+
entityEvents: new EventSubscriber(),
|
|
152
|
+
globalEvents: new EventSubscriber(),
|
|
153
|
+
weakRef: (value) => {
|
|
154
|
+
if (init.EXPERIMENTAL_weakRefs) {
|
|
155
|
+
return new WeakRef(value);
|
|
156
|
+
} else {
|
|
157
|
+
return new FakeWeakRef(value) as unknown as WeakRef<typeof value>;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const meta = new Metadata({
|
|
162
|
+
context,
|
|
163
|
+
disableRebasing: init.disableRebasing,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// verify schema integrity
|
|
167
|
+
await meta.updateSchema(init.schema, init.overrideSchemaConflict);
|
|
168
|
+
|
|
169
|
+
const documentDb = await openDocumentDatabase({
|
|
170
|
+
context,
|
|
171
|
+
version: init.schema.version,
|
|
172
|
+
meta,
|
|
173
|
+
migrations: init.migrations,
|
|
174
|
+
indexedDB: init.indexedDb,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const fullContext: Context = Object.assign(context, { documentDb });
|
|
178
|
+
|
|
179
|
+
const storage = new Client(
|
|
180
|
+
{
|
|
181
|
+
syncConfig: init.sync,
|
|
182
|
+
migrations: init.migrations,
|
|
183
|
+
files: init.files,
|
|
184
|
+
},
|
|
185
|
+
fullContext,
|
|
186
|
+
{
|
|
187
|
+
meta,
|
|
188
|
+
},
|
|
189
|
+
) as ClientImpl;
|
|
190
|
+
|
|
191
|
+
return storage;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
private initializeWIPDatabases = async (init: ClientDescriptorOptions) => {
|
|
195
|
+
const schemaHash = hashObject(init.schema);
|
|
196
|
+
console.info(`WIP schema in use. Opening database with hash ${schemaHash}`);
|
|
197
|
+
|
|
198
|
+
const wipNamespace = `@@wip_${init.namespace}_${schemaHash}`;
|
|
199
|
+
const { db: metaDb } = await openWIPMetadataDatabase({
|
|
200
|
+
indexedDB: init.indexedDb,
|
|
201
|
+
log: init.log,
|
|
202
|
+
namespace: init.namespace,
|
|
203
|
+
wipNamespace: wipNamespace,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const context: Omit<Context, 'documentDb'> = {
|
|
207
|
+
namespace: this._namespace,
|
|
208
|
+
metaDb,
|
|
209
|
+
schema: init.schema,
|
|
210
|
+
log: init.log || (() => {}),
|
|
211
|
+
undoHistory: init.undoHistory || new UndoHistory(),
|
|
212
|
+
entityEvents: new EventSubscriber(),
|
|
213
|
+
globalEvents: new EventSubscriber(),
|
|
214
|
+
weakRef: (value) => {
|
|
215
|
+
if (init.EXPERIMENTAL_weakRefs) {
|
|
216
|
+
return new WeakRef(value);
|
|
217
|
+
} else {
|
|
218
|
+
return new FakeWeakRef(value) as unknown as WeakRef<typeof value>;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const meta = new Metadata({
|
|
223
|
+
context,
|
|
224
|
+
disableRebasing: init.disableRebasing,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// verify schema integrity
|
|
228
|
+
await meta.updateSchema(init.schema, init.overrideSchemaConflict);
|
|
229
|
+
|
|
230
|
+
const documentDb = await openWIPDocumentDatabase({
|
|
231
|
+
context,
|
|
232
|
+
version: init.schema.version,
|
|
233
|
+
meta,
|
|
234
|
+
migrations: init.migrations,
|
|
235
|
+
indexedDB: init.indexedDb,
|
|
236
|
+
wipNamespace,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const fullContext: Context = Object.assign(context, { documentDb });
|
|
240
|
+
|
|
241
|
+
const storage = new Client(
|
|
242
|
+
{
|
|
243
|
+
syncConfig: init.sync,
|
|
244
|
+
migrations: init.migrations,
|
|
245
|
+
files: init.files,
|
|
246
|
+
},
|
|
247
|
+
fullContext,
|
|
248
|
+
{
|
|
249
|
+
meta,
|
|
250
|
+
},
|
|
251
|
+
) as ClientImpl;
|
|
252
|
+
|
|
253
|
+
return storage;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
private cleanupWIPDatabases = async (init: ClientDescriptorOptions) => {
|
|
257
|
+
const databaseInfo = await getAllDatabaseNamesAndVersions(init.indexedDb);
|
|
258
|
+
const wipDatabases = databaseInfo
|
|
259
|
+
.filter((db) => db.name?.startsWith('@@wip_'))
|
|
260
|
+
.map((db) => db.name!);
|
|
261
|
+
// don't clear a current WIP database.
|
|
262
|
+
const wipDatabasesToDelete = wipDatabases.filter(
|
|
263
|
+
(db) =>
|
|
264
|
+
!db.startsWith(`@@wip_${init.namespace}_${hashObject(init.schema)}`),
|
|
265
|
+
);
|
|
266
|
+
for (const db of wipDatabasesToDelete) {
|
|
267
|
+
await deleteDatabase(db, init.indexedDb);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
148
271
|
get current() {
|
|
149
272
|
// exposing an immediate value if already resolved lets us
|
|
150
273
|
// skip the promise microtask when accessing this externally if
|
package/src/context.ts
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
} from '@verdant-web/common';
|
|
12
12
|
import { Entity, refreshEntity, StoreTools } from './Entity.js';
|
|
13
13
|
import type { EntityStore } from './EntityStore.js';
|
|
14
|
-
import { WeakRef } from './FakeWeakRef.js';
|
|
15
14
|
import { Context } from '../context.js';
|
|
16
15
|
import { TaggedOperation } from '../types.js';
|
|
16
|
+
import { Resolvable } from '../utils/Resolvable.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Local operations: operations on this client that haven't
|
|
@@ -44,6 +44,14 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
44
44
|
private context;
|
|
45
45
|
private storeTools: StoreTools;
|
|
46
46
|
|
|
47
|
+
private _initialized = new Resolvable<boolean>();
|
|
48
|
+
get initializedPromise() {
|
|
49
|
+
return this._initialized.promise;
|
|
50
|
+
}
|
|
51
|
+
setInitialized = () => {
|
|
52
|
+
this._initialized.resolve(true);
|
|
53
|
+
};
|
|
54
|
+
|
|
47
55
|
constructor({
|
|
48
56
|
oid,
|
|
49
57
|
store,
|
|
@@ -69,6 +77,10 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
69
77
|
this.context = context;
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
get weakRef() {
|
|
81
|
+
return this.context.weakRef;
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
insertLocalOperations = (operations: Operation[]) => {
|
|
73
85
|
const oidSet = new Set<ObjectIdentifier>();
|
|
74
86
|
for (const operation of operations) {
|
|
@@ -236,6 +248,14 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
236
248
|
};
|
|
237
249
|
|
|
238
250
|
computeView = (oid: ObjectIdentifier) => {
|
|
251
|
+
if (
|
|
252
|
+
this.baselinesMap.size === 0 &&
|
|
253
|
+
this.operationsMap.size === 0 &&
|
|
254
|
+
this.localOperationsMap.size === 0
|
|
255
|
+
) {
|
|
256
|
+
this.context.log('debug', `Entity ${oid} accessed with no data at all`);
|
|
257
|
+
return { view: null, deleted: true, lastTimestamp: null };
|
|
258
|
+
}
|
|
239
259
|
const confirmed = this.computeConfirmedView(oid);
|
|
240
260
|
const unconfirmedOperations = this.localOperationsMap.get(oid) || [];
|
|
241
261
|
if (confirmed.empty && !unconfirmedOperations.length) {
|
|
@@ -303,6 +323,7 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
303
323
|
oid: ObjectIdentifier,
|
|
304
324
|
schema: StorageFieldSchema,
|
|
305
325
|
parent?: Entity,
|
|
326
|
+
readonlyKeys?: string[],
|
|
306
327
|
): Entity => {
|
|
307
328
|
let entityRef = this.entities.get(oid);
|
|
308
329
|
let entity = entityRef?.deref();
|
|
@@ -313,10 +334,11 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
313
334
|
fieldSchema: schema,
|
|
314
335
|
store: this.storeTools,
|
|
315
336
|
parent,
|
|
337
|
+
readonlyKeys,
|
|
316
338
|
});
|
|
317
339
|
|
|
318
340
|
// immediately add to cache and queue a removal if nobody subscribed
|
|
319
|
-
this.entities.set(oid,
|
|
341
|
+
this.entities.set(oid, this.context.weakRef(entity));
|
|
320
342
|
}
|
|
321
343
|
|
|
322
344
|
return entity as any;
|
|
@@ -331,33 +353,56 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
331
353
|
this.entities.clear();
|
|
332
354
|
};
|
|
333
355
|
|
|
334
|
-
reset = (
|
|
335
|
-
operations
|
|
336
|
-
baselines
|
|
356
|
+
reset = ({
|
|
357
|
+
operations,
|
|
358
|
+
baselines,
|
|
359
|
+
dropExistingUnconfirmed: dropUnconfirmed = false,
|
|
360
|
+
unconfirmedOperations,
|
|
361
|
+
dropAll,
|
|
362
|
+
}: {
|
|
363
|
+
operations: TaggedOperation[];
|
|
364
|
+
unconfirmedOperations?: Operation[];
|
|
365
|
+
baselines: DocumentBaseline[];
|
|
337
366
|
/**
|
|
338
367
|
* Whether to drop operations which are only in-memory. Unconfirmed operations
|
|
339
368
|
* will not be restored from storage until they are persisted, so it's not advisable
|
|
340
369
|
* to use this unless the intention is to completely clear the entities.
|
|
341
370
|
*/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
371
|
+
dropExistingUnconfirmed?: boolean;
|
|
372
|
+
/**
|
|
373
|
+
* Drop unconfirmed and confirmed data before resetting to incoming data.
|
|
374
|
+
* This is dangerous due to race conditions. Only use when a full reset is
|
|
375
|
+
* required.
|
|
376
|
+
*/
|
|
377
|
+
dropAll?: boolean;
|
|
378
|
+
}) => {
|
|
379
|
+
this.context.log(
|
|
380
|
+
'debug',
|
|
381
|
+
`Resetting cache for ${this.oid} with ${operations.length} ops and ${baselines.length} baselines, dropUnconfirmed=${dropUnconfirmed}`,
|
|
347
382
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
383
|
+
const info = { isLocal: false, affectedOids: new Set<ObjectIdentifier>() };
|
|
384
|
+
|
|
385
|
+
// NOTE: not clearing these maps... there are even more
|
|
386
|
+
// race conditions where we begin opening a cache, queue up a
|
|
387
|
+
// reset, then receive incoming operations before the reset
|
|
388
|
+
// actually hits this function, so those incoming ops are
|
|
389
|
+
// dropped.
|
|
390
|
+
// FIXME: include this in a future refactor of this
|
|
391
|
+
// whole system.
|
|
392
|
+
|
|
393
|
+
if (dropAll) this.baselinesMap.clear();
|
|
394
|
+
this.insertBaselines(baselines, info);
|
|
395
|
+
|
|
396
|
+
if (dropAll) this.operationsMap.clear();
|
|
397
|
+
this.insertOperations(operations, info);
|
|
398
|
+
|
|
399
|
+
if (unconfirmedOperations || dropUnconfirmed) {
|
|
400
|
+
this.localOperationsMap.clear();
|
|
401
|
+
if (unconfirmedOperations) {
|
|
402
|
+
this.insertLocalOperations(unconfirmedOperations);
|
|
358
403
|
}
|
|
359
404
|
}
|
|
360
|
-
|
|
405
|
+
|
|
361
406
|
for (const oid of this.entities.keys()) {
|
|
362
407
|
const entityRef = this.entities.get(oid);
|
|
363
408
|
const entity = entityRef?.deref();
|
package/src/entities/Entity.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
decomposeOid,
|
|
7
7
|
EventSubscriber,
|
|
8
8
|
FileData,
|
|
9
|
+
FileRef,
|
|
9
10
|
isFileRef,
|
|
10
11
|
isObjectRef,
|
|
11
12
|
maybeGetOid,
|
|
@@ -19,7 +20,6 @@ import {
|
|
|
19
20
|
} from '@verdant-web/common';
|
|
20
21
|
import { EntityFile } from '../files/EntityFile.js';
|
|
21
22
|
import { processValueFiles } from '../files/utils.js';
|
|
22
|
-
import { WeakRef } from './FakeWeakRef.js';
|
|
23
23
|
|
|
24
24
|
export const ADD_OPERATIONS = '@@addOperations';
|
|
25
25
|
export const DELETE = '@@delete';
|
|
@@ -39,6 +39,7 @@ export interface CacheTools {
|
|
|
39
39
|
parent?: Entity,
|
|
40
40
|
): Entity;
|
|
41
41
|
hasOid(oid: ObjectIdentifier): boolean;
|
|
42
|
+
weakRef<T extends object>(value: T): WeakRef<T>;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export interface StoreTools {
|
|
@@ -103,10 +104,11 @@ type EntityEvents = {
|
|
|
103
104
|
type BaseEntityValue = { [Key: string]: any } | any[];
|
|
104
105
|
|
|
105
106
|
export class Entity<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
>
|
|
107
|
+
Init = any,
|
|
108
|
+
KeyValue extends BaseEntityValue = any,
|
|
109
|
+
Snapshot extends any = DataFromInit<Init>,
|
|
110
|
+
>
|
|
111
|
+
implements
|
|
110
112
|
ObjectEntity<Init, KeyValue, Snapshot>,
|
|
111
113
|
ListEntity<Init, KeyValue, Snapshot>
|
|
112
114
|
{
|
|
@@ -120,6 +122,7 @@ export class Entity<
|
|
|
120
122
|
protected readonly cache: CacheTools;
|
|
121
123
|
protected _deleted = false;
|
|
122
124
|
protected parent: WeakRef<Entity<any, any>> | undefined;
|
|
125
|
+
protected readonly readonlyKeys: (keyof Init)[];
|
|
123
126
|
|
|
124
127
|
private cachedSnapshot: any = null;
|
|
125
128
|
private cachedDestructure: KeyValue | null = null;
|
|
@@ -205,6 +208,7 @@ export class Entity<
|
|
|
205
208
|
cache,
|
|
206
209
|
parent,
|
|
207
210
|
onAllUnsubscribed,
|
|
211
|
+
readonlyKeys = [],
|
|
208
212
|
}: {
|
|
209
213
|
oid: ObjectIdentifier;
|
|
210
214
|
store: StoreTools;
|
|
@@ -212,14 +216,16 @@ export class Entity<
|
|
|
212
216
|
cache: CacheTools;
|
|
213
217
|
parent?: Entity<any, any>;
|
|
214
218
|
onAllUnsubscribed?: () => void;
|
|
219
|
+
readonlyKeys?: (keyof Init)[];
|
|
215
220
|
}) {
|
|
216
221
|
this.oid = oid;
|
|
217
222
|
const { collection } = decomposeOid(oid);
|
|
218
223
|
this.collection = collection;
|
|
219
|
-
this.parent = parent && new WeakRef(parent);
|
|
220
224
|
this.store = store;
|
|
221
225
|
this.fieldSchema = fieldSchema;
|
|
226
|
+
this.readonlyKeys = readonlyKeys;
|
|
222
227
|
this.cache = cache;
|
|
228
|
+
this.parent = parent && this.cache.weakRef(parent);
|
|
223
229
|
const { view, deleted, lastTimestamp } = this.cache.computeView(oid);
|
|
224
230
|
this._current = view;
|
|
225
231
|
this._deleted = deleted;
|
|
@@ -402,6 +408,17 @@ export class Entity<
|
|
|
402
408
|
return result;
|
|
403
409
|
};
|
|
404
410
|
|
|
411
|
+
private getFileSnapshot(item: FileRef) {
|
|
412
|
+
const file = this.store.getFile(item.id);
|
|
413
|
+
if (file.url) {
|
|
414
|
+
return { id: item.id, url: file.url };
|
|
415
|
+
} else if (file.loading || file.failed) {
|
|
416
|
+
return { id: item.id, url: undefined };
|
|
417
|
+
} else {
|
|
418
|
+
return { id: item.id, url: null };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
405
422
|
/**
|
|
406
423
|
* Returns a copy of the entity and all sub-objects as
|
|
407
424
|
* a plain object or array.
|
|
@@ -421,6 +438,8 @@ export class Entity<
|
|
|
421
438
|
snapshot = this.value.map((item, idx) => {
|
|
422
439
|
if (isObjectRef(item)) {
|
|
423
440
|
return this.getSubObject(item.id, idx)?.getSnapshot();
|
|
441
|
+
} else if (isFileRef(item)) {
|
|
442
|
+
return this.getFileSnapshot(item);
|
|
424
443
|
}
|
|
425
444
|
return item;
|
|
426
445
|
}) as Snapshot;
|
|
@@ -429,6 +448,8 @@ export class Entity<
|
|
|
429
448
|
for (const [key, value] of Object.entries(snapshot)) {
|
|
430
449
|
if (isObjectRef(value)) {
|
|
431
450
|
snapshot[key] = this.getSubObject(value.id, key)?.getSnapshot();
|
|
451
|
+
} else if (isFileRef(value)) {
|
|
452
|
+
snapshot[key] = this.getFileSnapshot(value);
|
|
432
453
|
}
|
|
433
454
|
}
|
|
434
455
|
}
|
|
@@ -451,6 +472,9 @@ export class Entity<
|
|
|
451
472
|
return Object.values(this.getAll());
|
|
452
473
|
};
|
|
453
474
|
set = <Key extends keyof Init>(key: Key, value: Init[Key]) => {
|
|
475
|
+
if (this.readonlyKeys.includes(key)) {
|
|
476
|
+
throw new Error(`Cannot set readonly key ${key.toString()}`);
|
|
477
|
+
}
|
|
454
478
|
this.addPatches(
|
|
455
479
|
this.store.patchCreator.createSet(
|
|
456
480
|
this.oid,
|
|
@@ -480,6 +504,9 @@ export class Entity<
|
|
|
480
504
|
}
|
|
481
505
|
};
|
|
482
506
|
private getDeleteMode = (key: any) => {
|
|
507
|
+
if (this.readonlyKeys.includes(key)) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
483
510
|
// 'any' is always deletable, and map values can be removed completely
|
|
484
511
|
if (this.fieldSchema.type === 'any' || this.fieldSchema.type === 'map') {
|
|
485
512
|
return 'delete';
|
|
@@ -532,6 +559,9 @@ export class Entity<
|
|
|
532
559
|
);
|
|
533
560
|
}
|
|
534
561
|
for (const [key, field] of Object.entries(value)) {
|
|
562
|
+
if (this.readonlyKeys.includes(key as any)) {
|
|
563
|
+
throw new Error(`Cannot set readonly key ${key.toString()}`);
|
|
564
|
+
}
|
|
535
565
|
const fieldSchema = this.getChildFieldSchema(key);
|
|
536
566
|
if (fieldSchema) {
|
|
537
567
|
traverseCollectionFieldsAndApplyDefaults(field, fieldSchema);
|
|
@@ -718,6 +748,10 @@ export class Entity<
|
|
|
718
748
|
find = (predicate: (value: ListItemValue<KeyValue>) => boolean) => {
|
|
719
749
|
return this.getAsWrapped().find(predicate);
|
|
720
750
|
};
|
|
751
|
+
|
|
752
|
+
includes = (item: ListItemValue<KeyValue>) => {
|
|
753
|
+
return this.has(item);
|
|
754
|
+
};
|
|
721
755
|
}
|
|
722
756
|
|
|
723
757
|
export interface BaseEntity<
|
|
@@ -786,6 +820,7 @@ export interface ListEntity<
|
|
|
786
820
|
find(
|
|
787
821
|
predicate: (value: ListItemValue<Value>) => boolean,
|
|
788
822
|
): ListItemValue<Value> | undefined;
|
|
823
|
+
includes(value: ListItemValue<Value>): boolean;
|
|
789
824
|
}
|
|
790
825
|
|
|
791
826
|
export type AnyEntity<
|