@verdant-web/store 2.6.0 → 2.7.1
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 -9
- 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/client/Client.d.ts +6 -4
- package/dist/cjs/client/Client.js +1 -5
- package/dist/cjs/client/Client.js.map +1 -1
- package/dist/cjs/client/ClientDescriptor.d.ts +12 -4
- package/dist/cjs/client/ClientDescriptor.js +17 -0
- package/dist/cjs/client/ClientDescriptor.js.map +1 -1
- package/dist/cjs/context.d.ts +1 -0
- package/dist/cjs/entities/DocumentFamiliyCache.d.ts +2 -1
- package/dist/cjs/entities/DocumentFamiliyCache.js +6 -3
- 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.js +14 -13
- package/dist/cjs/entities/EntityStore.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 +19 -5
- package/dist/cjs/metadata/BaselinesStore.js.map +1 -1
- package/dist/cjs/migration/openDatabase.js +5 -16
- package/dist/cjs/migration/openDatabase.js.map +1 -1
- package/dist/cjs/sync/Sync.d.ts +6 -5
- package/dist/cjs/sync/Sync.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/client/Client.d.ts +6 -4
- package/dist/esm/client/Client.js +2 -6
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +12 -4
- package/dist/esm/client/ClientDescriptor.js +17 -0
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/context.d.ts +1 -0
- package/dist/esm/entities/DocumentFamiliyCache.d.ts +2 -1
- package/dist/esm/entities/DocumentFamiliyCache.js +6 -3
- 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.js +15 -14
- package/dist/esm/entities/EntityStore.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 +20 -6
- package/dist/esm/metadata/BaselinesStore.js.map +1 -1
- package/dist/esm/migration/openDatabase.js +6 -17
- package/dist/esm/migration/openDatabase.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +6 -5
- package/dist/esm/sync/Sync.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/client/Client.ts +5 -11
- package/src/client/ClientDescriptor.ts +34 -7
- package/src/context.ts +1 -0
- package/src/entities/DocumentFamiliyCache.ts +7 -2
- package/src/entities/Entity.ts +41 -6
- package/src/entities/EntityStore.ts +23 -15
- package/src/index.ts +1 -0
- package/src/metadata/BaselinesStore.ts +22 -6
- package/src/migration/openDatabase.ts +11 -21
- package/src/sync/Sync.ts +13 -9
- 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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
deleteDatabase,
|
|
24
24
|
getAllDatabaseNamesAndVersions,
|
|
25
25
|
} from '../idb.js';
|
|
26
|
+
import { FakeWeakRef } from '../FakeWeakRef.js';
|
|
26
27
|
|
|
27
28
|
export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
28
29
|
/** The schema used to create this client */
|
|
@@ -58,6 +59,14 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
|
58
59
|
* Configuration for file management
|
|
59
60
|
*/
|
|
60
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;
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/**
|
|
@@ -65,13 +74,17 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
|
65
74
|
* Storage creation promise and exposes some metadata which can
|
|
66
75
|
* be useful immediately.
|
|
67
76
|
*/
|
|
68
|
-
export class ClientDescriptor<
|
|
69
|
-
|
|
77
|
+
export class ClientDescriptor<
|
|
78
|
+
Presence = any,
|
|
79
|
+
Profile = any,
|
|
80
|
+
ClientImpl extends Client = Client,
|
|
81
|
+
> {
|
|
82
|
+
private readonly _readyPromise: Promise<ClientImpl>;
|
|
70
83
|
// assertions because these are defined by plucking them from
|
|
71
84
|
// Promise initializer
|
|
72
|
-
private resolveReady!: (storage:
|
|
85
|
+
private resolveReady!: (storage: ClientImpl) => void;
|
|
73
86
|
private rejectReady!: (err: Error) => void;
|
|
74
|
-
private _resolvedValue:
|
|
87
|
+
private _resolvedValue: ClientImpl | undefined;
|
|
75
88
|
private _initializing = false;
|
|
76
89
|
private _namespace: string;
|
|
77
90
|
|
|
@@ -103,7 +116,7 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
103
116
|
}
|
|
104
117
|
this._initializing = true;
|
|
105
118
|
try {
|
|
106
|
-
let storage:
|
|
119
|
+
let storage: ClientImpl;
|
|
107
120
|
if (init.schema.wip) {
|
|
108
121
|
storage = await this.initializeWIPDatabases(init);
|
|
109
122
|
} else {
|
|
@@ -137,6 +150,13 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
137
150
|
undoHistory: init.undoHistory || new UndoHistory(),
|
|
138
151
|
entityEvents: new EventSubscriber(),
|
|
139
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
|
+
},
|
|
140
160
|
};
|
|
141
161
|
const meta = new Metadata({
|
|
142
162
|
context,
|
|
@@ -166,7 +186,7 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
166
186
|
{
|
|
167
187
|
meta,
|
|
168
188
|
},
|
|
169
|
-
);
|
|
189
|
+
) as ClientImpl;
|
|
170
190
|
|
|
171
191
|
return storage;
|
|
172
192
|
};
|
|
@@ -191,6 +211,13 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
191
211
|
undoHistory: init.undoHistory || new UndoHistory(),
|
|
192
212
|
entityEvents: new EventSubscriber(),
|
|
193
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
|
+
},
|
|
194
221
|
};
|
|
195
222
|
const meta = new Metadata({
|
|
196
223
|
context,
|
|
@@ -221,7 +248,7 @@ export class ClientDescriptor<Presence = any, Profile = any> {
|
|
|
221
248
|
{
|
|
222
249
|
meta,
|
|
223
250
|
},
|
|
224
|
-
);
|
|
251
|
+
) as ClientImpl;
|
|
225
252
|
|
|
226
253
|
return storage;
|
|
227
254
|
};
|
package/src/context.ts
CHANGED
|
@@ -11,7 +11,6 @@ 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';
|
|
17
16
|
import { Resolvable } from '../utils/Resolvable.js';
|
|
@@ -78,6 +77,10 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
78
77
|
this.context = context;
|
|
79
78
|
}
|
|
80
79
|
|
|
80
|
+
get weakRef() {
|
|
81
|
+
return this.context.weakRef;
|
|
82
|
+
}
|
|
83
|
+
|
|
81
84
|
insertLocalOperations = (operations: Operation[]) => {
|
|
82
85
|
const oidSet = new Set<ObjectIdentifier>();
|
|
83
86
|
for (const operation of operations) {
|
|
@@ -320,6 +323,7 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
320
323
|
oid: ObjectIdentifier,
|
|
321
324
|
schema: StorageFieldSchema,
|
|
322
325
|
parent?: Entity,
|
|
326
|
+
readonlyKeys?: string[],
|
|
323
327
|
): Entity => {
|
|
324
328
|
let entityRef = this.entities.get(oid);
|
|
325
329
|
let entity = entityRef?.deref();
|
|
@@ -330,10 +334,11 @@ export class DocumentFamilyCache extends EventSubscriber<
|
|
|
330
334
|
fieldSchema: schema,
|
|
331
335
|
store: this.storeTools,
|
|
332
336
|
parent,
|
|
337
|
+
readonlyKeys,
|
|
333
338
|
});
|
|
334
339
|
|
|
335
340
|
// immediately add to cache and queue a removal if nobody subscribed
|
|
336
|
-
this.entities.set(oid,
|
|
341
|
+
this.entities.set(oid, this.context.weakRef(entity));
|
|
337
342
|
}
|
|
338
343
|
|
|
339
344
|
return entity as any;
|
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<
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
DocumentBaseline,
|
|
10
10
|
EventSubscriber,
|
|
11
11
|
generateId,
|
|
12
|
+
getIndexValues,
|
|
12
13
|
getOidRoot,
|
|
13
14
|
getUndoOperations,
|
|
14
15
|
groupBaselinesByRootOid,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
Operation,
|
|
19
20
|
removeOidsFromAllSubObjects,
|
|
20
21
|
StorageCollectionSchema,
|
|
22
|
+
StorageObjectFieldSchema,
|
|
21
23
|
} from '@verdant-web/common';
|
|
22
24
|
import { Context } from '../context.js';
|
|
23
25
|
import { FileManager } from '../files/FileManager.js';
|
|
@@ -98,16 +100,22 @@ export class EntityStore {
|
|
|
98
100
|
this.context = context;
|
|
99
101
|
};
|
|
100
102
|
|
|
101
|
-
private getDocumentSchema = (
|
|
103
|
+
private getDocumentSchema = (
|
|
104
|
+
oid: ObjectIdentifier,
|
|
105
|
+
): { schema: StorageObjectFieldSchema | null; readonlyKeys: string[] } => {
|
|
102
106
|
const { collection } = decomposeOid(oid);
|
|
103
107
|
if (!this.schema.collections[collection]) {
|
|
104
108
|
this.log('warn', `Missing schema for collection: ${collection}`);
|
|
105
|
-
return null;
|
|
109
|
+
return { schema: null, readonlyKeys: [] };
|
|
106
110
|
}
|
|
111
|
+
const schema = this.schema.collections[collection];
|
|
107
112
|
return {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
readonlyKeys: [schema.primaryKey],
|
|
114
|
+
schema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: schema.fields as any,
|
|
117
|
+
} as const,
|
|
118
|
+
};
|
|
111
119
|
};
|
|
112
120
|
|
|
113
121
|
private refreshFamilyCache = async (
|
|
@@ -219,10 +227,10 @@ export class EntityStore {
|
|
|
219
227
|
|
|
220
228
|
const snapshot = entity?.getSnapshot();
|
|
221
229
|
if (snapshot) {
|
|
222
|
-
const stored =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
const stored = getIndexValues(
|
|
231
|
+
this.schema.collections[collection],
|
|
232
|
+
snapshot,
|
|
233
|
+
);
|
|
226
234
|
try {
|
|
227
235
|
const tx = this.db.transaction(collection, 'readwrite');
|
|
228
236
|
const store = tx.objectStore(collection);
|
|
@@ -248,11 +256,11 @@ export class EntityStore {
|
|
|
248
256
|
|
|
249
257
|
get = async (oid: ObjectIdentifier) => {
|
|
250
258
|
const familyCache = await this.openFamilyCache(oid);
|
|
251
|
-
const schema = this.getDocumentSchema(oid);
|
|
259
|
+
const { schema, readonlyKeys } = this.getDocumentSchema(oid);
|
|
252
260
|
if (!schema) {
|
|
253
261
|
return null;
|
|
254
262
|
}
|
|
255
|
-
return familyCache.getEntity(oid, schema);
|
|
263
|
+
return familyCache.getEntity(oid, schema, undefined, readonlyKeys);
|
|
256
264
|
};
|
|
257
265
|
|
|
258
266
|
/**
|
|
@@ -263,11 +271,11 @@ export class EntityStore {
|
|
|
263
271
|
getCached = (oid: ObjectIdentifier) => {
|
|
264
272
|
const cache = this.documentFamilyCaches.get(oid);
|
|
265
273
|
if (cache) {
|
|
266
|
-
const schema = this.getDocumentSchema(oid);
|
|
274
|
+
const { schema, readonlyKeys } = this.getDocumentSchema(oid);
|
|
267
275
|
if (!schema) {
|
|
268
276
|
return null;
|
|
269
277
|
}
|
|
270
|
-
return cache.getEntity(oid, schema);
|
|
278
|
+
return cache.getEntity(oid, schema, undefined, readonlyKeys);
|
|
271
279
|
}
|
|
272
280
|
return null;
|
|
273
281
|
};
|
|
@@ -295,7 +303,7 @@ export class EntityStore {
|
|
|
295
303
|
// only holding it in memory would introduce lag before it shows up
|
|
296
304
|
// in other queries.
|
|
297
305
|
await this.submitOperations(operations, options);
|
|
298
|
-
const schema = this.getDocumentSchema(oid);
|
|
306
|
+
const { schema, readonlyKeys } = this.getDocumentSchema(oid);
|
|
299
307
|
if (!schema) {
|
|
300
308
|
throw new Error(
|
|
301
309
|
`Cannot create a document in the ${
|
|
@@ -303,7 +311,7 @@ export class EntityStore {
|
|
|
303
311
|
} collection; it is not defined in the current schema version.`,
|
|
304
312
|
);
|
|
305
313
|
}
|
|
306
|
-
return familyCache.getEntity(oid, schema);
|
|
314
|
+
return familyCache.getEntity(oid, schema, undefined, readonlyKeys);
|
|
307
315
|
};
|
|
308
316
|
|
|
309
317
|
private addOperationsToOpenCaches = async (
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DocumentBaseline,
|
|
3
|
-
|
|
3
|
+
getOidSubIdRange,
|
|
4
|
+
getOidRoot,
|
|
4
5
|
ObjectIdentifier,
|
|
6
|
+
isLegacyDotOid,
|
|
5
7
|
} from '@verdant-web/common';
|
|
6
8
|
import { IDBService } from '../IDBService.js';
|
|
7
9
|
|
|
@@ -38,8 +40,16 @@ export class BaselinesStore extends IDBService {
|
|
|
38
40
|
return this.iterate(
|
|
39
41
|
'baselines',
|
|
40
42
|
(store) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
+
const root = getOidRoot(oid);
|
|
44
|
+
// FIXME: get rid of legacy dot OIDs...
|
|
45
|
+
const isDot = isLegacyDotOid(oid);
|
|
46
|
+
const [start, end] = getOidSubIdRange(oid, isDot);
|
|
47
|
+
return [
|
|
48
|
+
// first the root itself
|
|
49
|
+
store.openCursor(IDBKeyRange.only(root)),
|
|
50
|
+
// then the range of its possible subdocuments
|
|
51
|
+
store.openCursor(IDBKeyRange.bound(start, end, false, false)),
|
|
52
|
+
];
|
|
43
53
|
},
|
|
44
54
|
iterator,
|
|
45
55
|
mode,
|
|
@@ -54,9 +64,15 @@ export class BaselinesStore extends IDBService {
|
|
|
54
64
|
const result = await this.runAll<DocumentBaseline[]>(
|
|
55
65
|
'baselines',
|
|
56
66
|
(store) => {
|
|
57
|
-
return docOids.
|
|
58
|
-
const
|
|
59
|
-
|
|
67
|
+
return docOids.flatMap((oid) => {
|
|
68
|
+
const root = getOidRoot(oid);
|
|
69
|
+
// FIXME: get rid of legacy dot OIDs...
|
|
70
|
+
const isDot = isLegacyDotOid(oid);
|
|
71
|
+
const [start, end] = getOidSubIdRange(oid, isDot);
|
|
72
|
+
return [
|
|
73
|
+
store.get(root),
|
|
74
|
+
store.getAll(IDBKeyRange.bound(start, end, false, false)),
|
|
75
|
+
];
|
|
60
76
|
});
|
|
61
77
|
},
|
|
62
78
|
mode,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createOid,
|
|
14
14
|
decomposeOid,
|
|
15
15
|
diffToPatches,
|
|
16
|
+
getIndexValues,
|
|
16
17
|
getOidRoot,
|
|
17
18
|
hasOid,
|
|
18
19
|
initialToPatches,
|
|
@@ -219,7 +220,7 @@ async function runMigrations({
|
|
|
219
220
|
for (const migration of toRun) {
|
|
220
221
|
// special case: if this is the version 1 migration, we have no pre-existing database
|
|
221
222
|
// to use for the migration.
|
|
222
|
-
let engine: MigrationEngine
|
|
223
|
+
let engine: MigrationEngine;
|
|
223
224
|
// migrations from 0 (i.e. initial migrations) don't attempt to open an existing db
|
|
224
225
|
if (migration.oldSchema.version === 0) {
|
|
225
226
|
engine = getInitialMigrationEngine({
|
|
@@ -379,12 +380,10 @@ async function runMigrations({
|
|
|
379
380
|
.filter((s): s is [string, any] => !!s)
|
|
380
381
|
.map(([oid, snapshot]) => {
|
|
381
382
|
if (!snapshot) return [oid, undefined];
|
|
382
|
-
const view =
|
|
383
|
+
const view = getIndexValues(
|
|
383
384
|
migration.newSchema.collections[collection],
|
|
384
385
|
snapshot,
|
|
385
386
|
);
|
|
386
|
-
// TODO: remove the need for this by only storing index values!
|
|
387
|
-
assignOidPropertiesToAllSubObjects(view);
|
|
388
387
|
return [oid, view];
|
|
389
388
|
});
|
|
390
389
|
|
|
@@ -397,7 +396,10 @@ async function runMigrations({
|
|
|
397
396
|
await Promise.all(
|
|
398
397
|
views.map(([oid, view]) => {
|
|
399
398
|
if (view) {
|
|
400
|
-
return putView(writeStore, view)
|
|
399
|
+
return putView(writeStore, view).catch((err) => {
|
|
400
|
+
view;
|
|
401
|
+
throw err;
|
|
402
|
+
});
|
|
401
403
|
} else {
|
|
402
404
|
const { id } = decomposeOid(oid);
|
|
403
405
|
return deleteView(writeStore, id);
|
|
@@ -488,7 +490,6 @@ function getMigrationQueries({
|
|
|
488
490
|
// only get the snapshot up to the previous version (newer operations may have synced)
|
|
489
491
|
to: meta.time.now(migration.oldSchema.version),
|
|
490
492
|
});
|
|
491
|
-
// removeOidsFromAllSubObjects(doc);
|
|
492
493
|
return doc;
|
|
493
494
|
},
|
|
494
495
|
findOne: async (filter: CollectionFilter) => {
|
|
@@ -502,7 +503,6 @@ function getMigrationQueries({
|
|
|
502
503
|
// only get the snapshot up to the previous version (newer operations may have synced)
|
|
503
504
|
to: meta.time.now(migration.oldSchema.version),
|
|
504
505
|
});
|
|
505
|
-
// removeOidsFromAllSubObjects(doc);
|
|
506
506
|
return doc;
|
|
507
507
|
},
|
|
508
508
|
findAll: async (filter: CollectionFilter) => {
|
|
@@ -519,7 +519,6 @@ function getMigrationQueries({
|
|
|
519
519
|
}),
|
|
520
520
|
),
|
|
521
521
|
);
|
|
522
|
-
// docs.forEach((doc) => removeOidsFromAllSubObjects(doc));
|
|
523
522
|
return docs;
|
|
524
523
|
},
|
|
525
524
|
};
|
|
@@ -536,7 +535,7 @@ function getMigrationEngine({
|
|
|
536
535
|
migration: Migration;
|
|
537
536
|
meta: Metadata;
|
|
538
537
|
context: Context;
|
|
539
|
-
}): MigrationEngine
|
|
538
|
+
}): MigrationEngine {
|
|
540
539
|
function getMigrationNow() {
|
|
541
540
|
return meta.time.zero(migration.version);
|
|
542
541
|
}
|
|
@@ -555,7 +554,7 @@ function getMigrationEngine({
|
|
|
555
554
|
meta,
|
|
556
555
|
});
|
|
557
556
|
const awaitables = new Array<Promise<any>>();
|
|
558
|
-
const engine: MigrationEngine
|
|
557
|
+
const engine: MigrationEngine = {
|
|
559
558
|
log: context.log,
|
|
560
559
|
newOids,
|
|
561
560
|
migrate: async (collection, strategy) => {
|
|
@@ -568,15 +567,6 @@ function getMigrationEngine({
|
|
|
568
567
|
`Document is missing an OID: ${JSON.stringify(doc)}`,
|
|
569
568
|
);
|
|
570
569
|
const original = cloneDeep(doc);
|
|
571
|
-
// remove any indexes before computing the diff
|
|
572
|
-
// const collectionSpec = migration.oldSchema.collections[collection];
|
|
573
|
-
// const indexKeys = [
|
|
574
|
-
// ...Object.keys(collectionSpec.synthetics || {}),
|
|
575
|
-
// ...Object.keys(collectionSpec.compounds || {}),
|
|
576
|
-
// ];
|
|
577
|
-
// indexKeys.forEach((key) => {
|
|
578
|
-
// delete doc[key];
|
|
579
|
-
// });
|
|
580
570
|
// @ts-ignore - excessive type resolution
|
|
581
571
|
const newValue = await strategy(doc);
|
|
582
572
|
if (newValue) {
|
|
@@ -618,7 +608,7 @@ function getInitialMigrationEngine({
|
|
|
618
608
|
context: OpenDocumentDbContext;
|
|
619
609
|
migration: Migration;
|
|
620
610
|
meta: Metadata;
|
|
621
|
-
}): MigrationEngine
|
|
611
|
+
}): MigrationEngine {
|
|
622
612
|
function getMigrationNow() {
|
|
623
613
|
return meta.time.zero(migration.version);
|
|
624
614
|
}
|
|
@@ -639,7 +629,7 @@ function getInitialMigrationEngine({
|
|
|
639
629
|
newOids,
|
|
640
630
|
meta,
|
|
641
631
|
});
|
|
642
|
-
const engine: MigrationEngine
|
|
632
|
+
const engine: MigrationEngine = {
|
|
643
633
|
log: context.log,
|
|
644
634
|
newOids,
|
|
645
635
|
migrate: () => {
|
package/src/sync/Sync.ts
CHANGED
|
@@ -48,15 +48,19 @@ export interface SyncTransport {
|
|
|
48
48
|
readonly status: 'active' | 'paused';
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export interface Sync extends SyncTransport {
|
|
51
|
+
export interface Sync<Presence = any, Profile = any> extends SyncTransport {
|
|
52
52
|
setMode(mode: SyncTransportMode): void;
|
|
53
53
|
setPullInterval(interval: number): void;
|
|
54
54
|
readonly pullInterval: number;
|
|
55
55
|
uploadFile(data: FileData): Promise<FileUploadResult>;
|
|
56
56
|
getFile(fileId: string): Promise<FilePullResult>;
|
|
57
|
+
readonly presence: PresenceManager<Profile, Presence>;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
export class NoSync
|
|
60
|
+
export class NoSync<Presence = any, Profile = any>
|
|
61
|
+
extends EventSubscriber<SyncEvents>
|
|
62
|
+
implements Sync<Presence, Profile>
|
|
63
|
+
{
|
|
60
64
|
readonly mode = 'pull';
|
|
61
65
|
|
|
62
66
|
public send(): void {}
|
|
@@ -76,9 +80,9 @@ export class NoSync extends EventSubscriber<SyncEvents> implements Sync {
|
|
|
76
80
|
public readonly status = 'paused';
|
|
77
81
|
public readonly pullInterval = 0;
|
|
78
82
|
|
|
79
|
-
public readonly presence = new PresenceManager({
|
|
80
|
-
initialPresence: {},
|
|
81
|
-
defaultProfile: {},
|
|
83
|
+
public readonly presence = new PresenceManager<Profile, Presence>({
|
|
84
|
+
initialPresence: {} as any,
|
|
85
|
+
defaultProfile: {} as any,
|
|
82
86
|
});
|
|
83
87
|
|
|
84
88
|
uploadFile = async () => {
|
|
@@ -152,11 +156,11 @@ export interface ServerSyncOptions<Profile = any, Presence = any>
|
|
|
152
156
|
useBroadcastChannel?: boolean;
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
export class ServerSync<
|
|
159
|
+
export class ServerSync<Presence = any, Profile = any>
|
|
156
160
|
extends EventSubscriber<
|
|
157
161
|
SyncEvents & { syncingChange: (syncing: boolean) => void }
|
|
158
162
|
>
|
|
159
|
-
implements Sync
|
|
163
|
+
implements Sync<Presence, Profile>
|
|
160
164
|
{
|
|
161
165
|
private webSocketSync: WebSocketSync;
|
|
162
166
|
private pushPullSync: PushPullSync;
|
|
@@ -173,7 +177,7 @@ export class ServerSync<Profile = any, Presence = any>
|
|
|
173
177
|
|
|
174
178
|
private meta: Metadata;
|
|
175
179
|
|
|
176
|
-
readonly presence
|
|
180
|
+
readonly presence: PresenceManager<Profile, Presence>;
|
|
177
181
|
|
|
178
182
|
private log;
|
|
179
183
|
|
|
@@ -208,7 +212,7 @@ export class ServerSync<Profile = any, Presence = any>
|
|
|
208
212
|
this.meta = meta;
|
|
209
213
|
this.onData = onData;
|
|
210
214
|
this.log = log || (() => {});
|
|
211
|
-
this.presence = new PresenceManager
|
|
215
|
+
this.presence = new PresenceManager({
|
|
212
216
|
initialPresence,
|
|
213
217
|
defaultProfile,
|
|
214
218
|
updateBatchTimeout: presenceUpdateBatchTimeout,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FakeWeakRef.js","sourceRoot":"","sources":["../../../src/entities/FakeWeakRef.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,MAAa,OAAO;IACnB,YAAY,KAAQ;QACnB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAID,KAAK;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;CACD;AAVD,0BAUC"}
|
package/dist/cjs/indexes.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { StorageCollectionSchema } from '@verdant-web/common';
|
|
2
|
-
export declare function computeSynthetics(schema: StorageCollectionSchema, obj: any): Record<string, any>;
|
|
3
|
-
export declare function computeCompoundIndices(schema: StorageCollectionSchema<any, any, any>, doc: any): any;
|
package/dist/cjs/indexes.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.computeCompoundIndices = exports.computeSynthetics = void 0;
|
|
4
|
-
const common_1 = require("@verdant-web/common");
|
|
5
|
-
function computeSynthetics(schema, obj) {
|
|
6
|
-
const result = {};
|
|
7
|
-
for (const [name, property] of Object.entries(schema.synthetics || {})) {
|
|
8
|
-
result[name] = property.compute(obj);
|
|
9
|
-
}
|
|
10
|
-
return result;
|
|
11
|
-
}
|
|
12
|
-
exports.computeSynthetics = computeSynthetics;
|
|
13
|
-
function computeCompoundIndices(schema, doc) {
|
|
14
|
-
return Object.entries(schema.compounds || {}).reduce((acc, [indexKey, index]) => {
|
|
15
|
-
acc[indexKey] = (0, common_1.createCompoundIndexValue)(...index.of.map((key) => doc[key]));
|
|
16
|
-
return acc;
|
|
17
|
-
}, {});
|
|
18
|
-
}
|
|
19
|
-
exports.computeCompoundIndices = computeCompoundIndices;
|
|
20
|
-
//# sourceMappingURL=indexes.js.map
|
package/dist/cjs/indexes.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"indexes.js","sourceRoot":"","sources":["../../src/indexes.ts"],"names":[],"mappings":";;;AAAA,gDAM6B;AAE7B,SAAgB,iBAAiB,CAAC,MAA+B,EAAE,GAAQ;IAC1E,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE;QACvE,MAAM,CAAC,IAAI,CAAC,GAAI,QAA6C,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;KAC3E;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAND,8CAMC;AAED,SAAgB,sBAAsB,CACrC,MAA8C,EAC9C,GAAQ;IAER,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,MAAM,CAElD,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE;QAC5B,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAA,iCAAwB,EACvC,GAAI,KAA2C,CAAC,EAAE,CAAC,GAAG,CACrD,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAoB,CACpC,CACD,CAAC;QACF,OAAO,GAAG,CAAC;IACZ,CAAC,EAAE,EAAwC,CAAC,CAAC;AAC9C,CAAC;AAdD,wDAcC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FakeWeakRef.js","sourceRoot":"","sources":["../../../src/entities/FakeWeakRef.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,OAAO,OAAO;IACnB,YAAY,KAAQ;QACnB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAID,KAAK;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;CACD"}
|
package/dist/esm/indexes.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { StorageCollectionSchema } from '@verdant-web/common';
|
|
2
|
-
export declare function computeSynthetics(schema: StorageCollectionSchema, obj: any): Record<string, any>;
|
|
3
|
-
export declare function computeCompoundIndices(schema: StorageCollectionSchema<any, any, any>, doc: any): any;
|