@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.
Files changed (105) hide show
  1. package/dist/bundle/index.js +9 -9
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/cjs/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
  4. package/dist/cjs/{entities/FakeWeakRef.js → FakeWeakRef.js} +4 -4
  5. package/dist/cjs/FakeWeakRef.js.map +1 -0
  6. package/dist/cjs/IDBService.d.ts +1 -1
  7. package/dist/cjs/IDBService.js +18 -1
  8. package/dist/cjs/IDBService.js.map +1 -1
  9. package/dist/cjs/__tests__/documents.test.js +17 -0
  10. package/dist/cjs/__tests__/documents.test.js.map +1 -1
  11. package/dist/cjs/__tests__/fixtures/testStorage.d.ts +1 -1
  12. package/dist/cjs/__tests__/fixtures/testStorage.js +3 -2
  13. package/dist/cjs/__tests__/fixtures/testStorage.js.map +1 -1
  14. package/dist/cjs/__tests__/mutations.test.d.ts +1 -0
  15. package/dist/cjs/__tests__/mutations.test.js +42 -0
  16. package/dist/cjs/__tests__/mutations.test.js.map +1 -0
  17. package/dist/cjs/client/Client.d.ts +6 -4
  18. package/dist/cjs/client/Client.js +1 -5
  19. package/dist/cjs/client/Client.js.map +1 -1
  20. package/dist/cjs/client/ClientDescriptor.d.ts +12 -4
  21. package/dist/cjs/client/ClientDescriptor.js +17 -0
  22. package/dist/cjs/client/ClientDescriptor.js.map +1 -1
  23. package/dist/cjs/context.d.ts +1 -0
  24. package/dist/cjs/entities/DocumentFamiliyCache.d.ts +2 -1
  25. package/dist/cjs/entities/DocumentFamiliyCache.js +6 -3
  26. package/dist/cjs/entities/DocumentFamiliyCache.js.map +1 -1
  27. package/dist/cjs/entities/Entity.d.ts +7 -2
  28. package/dist/cjs/entities/Entity.js +33 -3
  29. package/dist/cjs/entities/Entity.js.map +1 -1
  30. package/dist/cjs/entities/EntityStore.js +14 -13
  31. package/dist/cjs/entities/EntityStore.js.map +1 -1
  32. package/dist/cjs/index.d.ts +1 -1
  33. package/dist/cjs/index.js +2 -1
  34. package/dist/cjs/index.js.map +1 -1
  35. package/dist/cjs/metadata/BaselinesStore.js +19 -5
  36. package/dist/cjs/metadata/BaselinesStore.js.map +1 -1
  37. package/dist/cjs/migration/openDatabase.js +5 -16
  38. package/dist/cjs/migration/openDatabase.js.map +1 -1
  39. package/dist/cjs/sync/Sync.d.ts +6 -5
  40. package/dist/cjs/sync/Sync.js.map +1 -1
  41. package/dist/esm/{entities/FakeWeakRef.d.ts → FakeWeakRef.d.ts} +2 -2
  42. package/dist/esm/{entities/FakeWeakRef.js → FakeWeakRef.js} +2 -2
  43. package/dist/esm/FakeWeakRef.js.map +1 -0
  44. package/dist/esm/IDBService.d.ts +1 -1
  45. package/dist/esm/IDBService.js +18 -1
  46. package/dist/esm/IDBService.js.map +1 -1
  47. package/dist/esm/__tests__/documents.test.js +17 -0
  48. package/dist/esm/__tests__/documents.test.js.map +1 -1
  49. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
  50. package/dist/esm/__tests__/fixtures/testStorage.js +4 -3
  51. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  52. package/dist/esm/__tests__/mutations.test.d.ts +1 -0
  53. package/dist/esm/__tests__/mutations.test.js +40 -0
  54. package/dist/esm/__tests__/mutations.test.js.map +1 -0
  55. package/dist/esm/client/Client.d.ts +6 -4
  56. package/dist/esm/client/Client.js +2 -6
  57. package/dist/esm/client/Client.js.map +1 -1
  58. package/dist/esm/client/ClientDescriptor.d.ts +12 -4
  59. package/dist/esm/client/ClientDescriptor.js +17 -0
  60. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  61. package/dist/esm/context.d.ts +1 -0
  62. package/dist/esm/entities/DocumentFamiliyCache.d.ts +2 -1
  63. package/dist/esm/entities/DocumentFamiliyCache.js +6 -3
  64. package/dist/esm/entities/DocumentFamiliyCache.js.map +1 -1
  65. package/dist/esm/entities/Entity.d.ts +7 -2
  66. package/dist/esm/entities/Entity.js +33 -3
  67. package/dist/esm/entities/Entity.js.map +1 -1
  68. package/dist/esm/entities/EntityStore.js +15 -14
  69. package/dist/esm/entities/EntityStore.js.map +1 -1
  70. package/dist/esm/index.d.ts +1 -1
  71. package/dist/esm/index.js +1 -1
  72. package/dist/esm/index.js.map +1 -1
  73. package/dist/esm/metadata/BaselinesStore.js +20 -6
  74. package/dist/esm/metadata/BaselinesStore.js.map +1 -1
  75. package/dist/esm/migration/openDatabase.js +6 -17
  76. package/dist/esm/migration/openDatabase.js.map +1 -1
  77. package/dist/esm/sync/Sync.d.ts +6 -5
  78. package/dist/esm/sync/Sync.js.map +1 -1
  79. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +9 -4
  82. package/src/{entities/FakeWeakRef.ts → FakeWeakRef.ts} +2 -2
  83. package/src/IDBService.ts +20 -2
  84. package/src/__tests__/documents.test.ts +19 -0
  85. package/src/__tests__/fixtures/testStorage.ts +4 -7
  86. package/src/__tests__/mutations.test.ts +51 -0
  87. package/src/client/Client.ts +5 -11
  88. package/src/client/ClientDescriptor.ts +34 -7
  89. package/src/context.ts +1 -0
  90. package/src/entities/DocumentFamiliyCache.ts +7 -2
  91. package/src/entities/Entity.ts +41 -6
  92. package/src/entities/EntityStore.ts +23 -15
  93. package/src/index.ts +1 -0
  94. package/src/metadata/BaselinesStore.ts +22 -6
  95. package/src/migration/openDatabase.ts +11 -21
  96. package/src/sync/Sync.ts +13 -9
  97. package/dist/cjs/entities/FakeWeakRef.js.map +0 -1
  98. package/dist/cjs/indexes.d.ts +0 -3
  99. package/dist/cjs/indexes.js +0 -20
  100. package/dist/cjs/indexes.js.map +0 -1
  101. package/dist/esm/entities/FakeWeakRef.js.map +0 -1
  102. package/dist/esm/indexes.d.ts +0 -3
  103. package/dist/esm/indexes.js +0 -15
  104. package/dist/esm/indexes.js.map +0 -1
  105. 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<Presence = any, Profile = any> {
69
- private readonly _readyPromise: Promise<Client>;
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: Client) => void;
85
+ private resolveReady!: (storage: ClientImpl) => void;
73
86
  private rejectReady!: (err: Error) => void;
74
- private _resolvedValue: Client | undefined;
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: Client;
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
@@ -26,4 +26,5 @@ export interface Context {
26
26
  */
27
27
  futureSeen: (timestamp: string) => void;
28
28
  }>;
29
+ weakRef<T extends object>(value: T): WeakRef<T>;
29
30
  }
@@ -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, new WeakRef(entity));
341
+ this.entities.set(oid, this.context.weakRef(entity));
337
342
  }
338
343
 
339
344
  return entity as any;
@@ -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
- Init = any,
107
- KeyValue extends BaseEntityValue = any,
108
- Snapshot extends any = DataFromInit<Init>,
109
- > implements
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 = (oid: ObjectIdentifier) => {
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
- type: 'object',
109
- properties: this.schema.collections[collection].fields as any,
110
- } as const;
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 = cloneDeep(snapshot);
223
- assignIndexValues(this.schema.collections[collection], stored);
224
- // IMPORTANT! this property must be assigned
225
- assignOidPropertiesToAllSubObjects(stored);
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
@@ -28,6 +28,7 @@ export {
28
28
  schema,
29
29
  createDefaultMigration,
30
30
  migrate,
31
+ createMigration,
31
32
  } from '@verdant-web/common';
32
33
  export type {
33
34
  StorageDocument,
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  DocumentBaseline,
3
- getOidRange,
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 [start, end] = getOidRange(oid);
42
- return store.openCursor(IDBKeyRange.bound(start, end, false, false));
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.map((oid) => {
58
- const [start, end] = getOidRange(oid);
59
- return store.getAll(IDBKeyRange.bound(start, end, false, false));
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<any, any>;
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 = assignIndexValues(
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<any, any> {
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<StorageSchema, StorageSchema> = {
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<any, any> {
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<StorageSchema, StorageSchema> = {
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 extends EventSubscriber<SyncEvents> implements Sync {
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<Profile = any, Presence = any>
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<Profile, Presence>({
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"}
@@ -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;
@@ -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
@@ -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"}
@@ -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;