@verdant-web/store 2.7.6 → 2.7.7

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 (41) hide show
  1. package/dist/bundle/index.js +9 -9
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/cjs/__tests__/fixtures/testStorage.d.ts +2 -1
  4. package/dist/cjs/__tests__/fixtures/testStorage.js +3 -1
  5. package/dist/cjs/__tests__/fixtures/testStorage.js.map +1 -1
  6. package/dist/cjs/__tests__/legacyOids.test.js +231 -45
  7. package/dist/cjs/__tests__/legacyOids.test.js.map +1 -1
  8. package/dist/cjs/client/ClientDescriptor.d.ts +2 -0
  9. package/dist/cjs/client/ClientDescriptor.js +3 -0
  10. package/dist/cjs/client/ClientDescriptor.js.map +1 -1
  11. package/dist/cjs/client/constants.d.ts +1 -0
  12. package/dist/cjs/client/constants.js +5 -0
  13. package/dist/cjs/client/constants.js.map +1 -0
  14. package/dist/cjs/metadata/openMetadataDatabase.d.ts +4 -2
  15. package/dist/cjs/metadata/openMetadataDatabase.js +65 -4
  16. package/dist/cjs/metadata/openMetadataDatabase.js.map +1 -1
  17. package/dist/cjs/sync/Sync.js.map +1 -1
  18. package/dist/esm/__tests__/fixtures/testStorage.d.ts +2 -1
  19. package/dist/esm/__tests__/fixtures/testStorage.js +3 -1
  20. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  21. package/dist/esm/__tests__/legacyOids.test.js +232 -46
  22. package/dist/esm/__tests__/legacyOids.test.js.map +1 -1
  23. package/dist/esm/client/ClientDescriptor.d.ts +2 -0
  24. package/dist/esm/client/ClientDescriptor.js +3 -0
  25. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  26. package/dist/esm/client/constants.d.ts +1 -0
  27. package/dist/esm/client/constants.js +2 -0
  28. package/dist/esm/client/constants.js.map +1 -0
  29. package/dist/esm/metadata/openMetadataDatabase.d.ts +4 -2
  30. package/dist/esm/metadata/openMetadataDatabase.js +65 -4
  31. package/dist/esm/metadata/openMetadataDatabase.js.map +1 -1
  32. package/dist/esm/sync/Sync.js.map +1 -1
  33. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +2 -2
  36. package/src/__tests__/fixtures/testStorage.ts +8 -1
  37. package/src/__tests__/legacyOids.test.ts +240 -49
  38. package/src/client/ClientDescriptor.ts +6 -0
  39. package/src/client/constants.ts +1 -0
  40. package/src/metadata/openMetadataDatabase.ts +65 -2
  41. package/src/sync/Sync.ts +1 -0
@@ -1,9 +1,6 @@
1
- // import { IDBFactory } from 'fake-indexeddb';
2
-
3
1
  import {
4
2
  HybridLogicalClockTimestampProvider,
5
3
  OperationPatch,
6
- createCompoundIndexValue,
7
4
  getIndexValues,
8
5
  makeObjectRef,
9
6
  } from '@verdant-web/common';
@@ -11,67 +8,213 @@ import { storeRequestPromise } from '../idb.js';
11
8
  import { openMetadataDatabase } from '../metadata/openMetadataDatabase.js';
12
9
  import { createTestStorage, todoCollection } from './fixtures/testStorage.js';
13
10
  import { describe, expect, it } from 'vitest';
11
+ import { OperationsStore } from '../metadata/OperationsStore.js';
12
+ import { BaselinesStore } from '../metadata/BaselinesStore.js';
14
13
 
15
14
  const idb = new IDBFactory();
16
15
 
17
16
  async function seedDatabaseAndCreateClient() {
18
- const client1 = await createTestStorage({ idb });
17
+ const client1 = await createTestStorage({ idb, metadataVersion: 4 });
19
18
  await client1.close();
20
19
  const { db } = await openMetadataDatabase({
21
20
  indexedDB: idb,
22
21
  namespace: 'test',
22
+ metadataVersion: 4,
23
23
  });
24
+ expect(db.version).toBe(4);
24
25
  const tx = db.transaction(['baselines', 'operations'], 'readwrite');
25
26
  const clock = new HybridLogicalClockTimestampProvider();
26
- const results = await Promise.all(
27
- [
28
- tx.objectStore('baselines').put({
29
- oid: 'todos/a',
30
- snapshot: {
31
- id: 'a',
32
- content: 'item a',
33
- tags: makeObjectRef('todos/a.tags:foo'),
34
- done: false,
35
- category: 'default',
36
- attachments: makeObjectRef('todos/a.attachments:bar'),
37
- },
38
- timestamp: clock.now(1),
39
- }),
40
- tx.objectStore('baselines').put({
41
- oid: 'todos/a.tags:foo',
42
- snapshot: ['tag1'],
43
- timestamp: clock.now(1),
44
- }),
45
- tx.objectStore('baselines').put({
46
- oid: 'todos/a.attachments:bar',
47
- snapshot: [makeObjectRef('todos/a.attachments.#:baz')],
48
- timestamp: clock.now(1),
49
- }),
50
- tx.objectStore('baselines').put({
51
- oid: 'todos/a.attachments.#:baz',
52
- snapshot: {
53
- name: 'baz',
54
- test: 1,
55
- },
56
- timestamp: clock.now(1),
57
- }),
58
- (() => {
59
- const ts = clock.now(1);
60
- return tx.objectStore('operations').put({
27
+ const ops = new OperationsStore(db);
28
+ const baselines = new BaselinesStore(db);
29
+ await Promise.all([
30
+ baselines.setAll(
31
+ [
32
+ {
33
+ oid: 'weirds/b.objectMap.one:ghi',
34
+ snapshot: { content: 'ghi' },
35
+ timestamp: clock.now(1),
36
+ },
37
+ {
38
+ oid: 'weirds/b.objectMap.two:jkl',
39
+ snapshot: { content: 'jkl' },
40
+ timestamp: clock.now(1),
41
+ },
42
+ {
43
+ oid: 'weirds/b.map:abc',
44
+ snapshot: {},
45
+ timestamp: clock.now(1),
46
+ },
47
+ {
48
+ oid: 'weirds/b.objectMap:def',
49
+ snapshot: {
50
+ one: makeObjectRef('weirds/b.objectMap.one:ghi'),
51
+ two: makeObjectRef('weirds/b.objectMap.two:jkl'),
52
+ },
53
+ timestamp: clock.now(1),
54
+ },
55
+ {
56
+ oid: 'weirds/b.weird.ok:opq',
57
+ snapshot: {
58
+ message: 'ok',
59
+ },
60
+ timestamp: clock.now(1),
61
+ },
62
+ {
63
+ oid: 'weirds/b.weird.cool:rst',
64
+ snapshot: {
65
+ message: 'cool',
66
+ },
67
+ timestamp: clock.now(1),
68
+ },
69
+ {
70
+ oid: 'weirds/b.weird.list.#:xyz',
71
+ snapshot: {
72
+ name: 'xyz',
73
+ test: 1,
74
+ },
75
+ timestamp: clock.now(1),
76
+ },
77
+ {
78
+ oid: 'weirds/b.weird.list.#:123',
79
+ snapshot: {
80
+ name: '123',
81
+ test: 2,
82
+ },
83
+ timestamp: clock.now(1),
84
+ },
85
+ {
86
+ oid: 'weirds/b.weird.list:uvw',
87
+ snapshot: [
88
+ makeObjectRef('weirds/b/weird.list.#:xyz'),
89
+ makeObjectRef('weirds/b/weird.list.#:123'),
90
+ ],
91
+ timestamp: clock.now(1),
92
+ },
93
+ {
94
+ oid: 'weirds/b.weird:lmn',
95
+ snapshot: {
96
+ ok: makeObjectRef('weirds/b.weird.ok:opq'),
97
+ cool: makeObjectRef('weirds/b.weird.cool:rst'),
98
+ list: makeObjectRef('weirds/b.weird.list:uvw'),
99
+ },
100
+ timestamp: clock.now(1),
101
+ },
102
+ {
103
+ oid: 'weirds/b',
104
+ snapshot: {
105
+ id: 'b',
106
+ map: makeObjectRef('weirds/b.map:abc'),
107
+ objectMap: makeObjectRef('weirds/b.objectMap:def'),
108
+ weird: makeObjectRef('weirds/b.weird:lmn'),
109
+ },
110
+ timestamp: clock.now(1),
111
+ },
112
+ {
113
+ oid: 'todos/a.attachments.#:baz',
114
+ snapshot: {
115
+ name: 'baz',
116
+ test: 1,
117
+ },
118
+ timestamp: clock.now(1),
119
+ },
120
+ {
121
+ oid: 'todos/a.attachments.#:qux',
122
+ snapshot: {
123
+ name: 'qux',
124
+ test: 2,
125
+ },
126
+ timestamp: clock.now(1),
127
+ },
128
+ {
129
+ oid: 'todos/a.attachments:bar',
130
+ snapshot: [
131
+ makeObjectRef('todos/a.attachments.#:baz'),
132
+ makeObjectRef('todos/a.attachments.#:qux'),
133
+ ],
134
+ timestamp: clock.now(1),
135
+ },
136
+ {
137
+ oid: 'todos/a.tags:foo',
138
+ snapshot: ['tag1'],
139
+ timestamp: clock.now(1),
140
+ },
141
+ {
142
+ oid: 'todos/a',
143
+ snapshot: {
144
+ id: 'a',
145
+ content: 'item a',
146
+ tags: makeObjectRef('todos/a.tags:foo'),
147
+ done: false,
148
+ category: 'default',
149
+ attachments: makeObjectRef('todos/a.attachments:bar'),
150
+ },
151
+ timestamp: clock.now(1),
152
+ },
153
+ ],
154
+ { transaction: tx },
155
+ ),
156
+ ops.addOperations(
157
+ [
158
+ // trying to cover all types of operations which use refs here
159
+ {
61
160
  oid: 'todos/a.tags:foo',
62
- timestamp: ts,
161
+ timestamp: clock.now(1),
63
162
  data: {
64
163
  op: 'list-push',
65
164
  value: 'tag2',
66
165
  } as OperationPatch,
67
166
  isLocal: true,
68
- oid_timestamp: createCompoundIndexValue('todos/a.tags:foo', ts),
69
- l_t: createCompoundIndexValue(true, ts),
70
- d_t: createCompoundIndexValue('todos/a', ts),
71
- });
72
- })(),
73
- ].map(storeRequestPromise),
74
- );
167
+ },
168
+ {
169
+ oid: 'todos/a.attachments.#:bin',
170
+ timestamp: clock.now(1),
171
+ data: {
172
+ op: 'initialize',
173
+ value: {
174
+ name: 'bin',
175
+ test: 2,
176
+ },
177
+ },
178
+ isLocal: true,
179
+ },
180
+ {
181
+ oid: 'todos/a.attachments:bar',
182
+ timestamp: clock.now(1),
183
+ data: {
184
+ op: 'list-push',
185
+ value: makeObjectRef('todos/a.attachments.#:bin'),
186
+ },
187
+ isLocal: true,
188
+ },
189
+ {
190
+ oid: 'todos/a.attachments:bar',
191
+ timestamp: clock.now(1),
192
+ isLocal: true,
193
+ data: {
194
+ op: 'list-remove',
195
+ value: makeObjectRef('todos/a.attachments.#:baz'),
196
+ },
197
+ },
198
+ {
199
+ oid: 'weirds/b.objectMap:def',
200
+ timestamp: clock.now(1),
201
+ data: {
202
+ op: 'delete',
203
+ },
204
+ isLocal: true,
205
+ },
206
+ {
207
+ oid: 'weirds/b.weird.list:uvw',
208
+ timestamp: clock.now(1),
209
+ data: {
210
+ op: 'delete',
211
+ },
212
+ isLocal: true,
213
+ },
214
+ ],
215
+ { transaction: tx },
216
+ ),
217
+ ]);
75
218
  db.close();
76
219
  // documents are a little trickier.
77
220
  const docDbReq = idb.open('test_collections', 1);
@@ -108,18 +251,41 @@ describe('clients with stored legacy oids', () => {
108
251
  it('can still function', async () => {
109
252
  const client = await seedDatabaseAndCreateClient();
110
253
  const todo = await client.todos.get('a').resolved;
254
+ const weird = await client.weirds.get('b').resolved;
255
+
256
+ // first: does the data look right?
111
257
  expect(todo.getSnapshot()).toMatchObject({
112
258
  id: 'a',
113
259
  content: 'item a',
114
260
  tags: ['tag1', 'tag2'],
261
+ done: false,
115
262
  category: 'default',
116
263
  attachments: [
117
264
  {
118
- name: 'baz',
119
- test: 1,
265
+ name: 'qux',
266
+ test: 2,
267
+ },
268
+ {
269
+ name: 'bin',
270
+ test: 2,
120
271
  },
121
272
  ],
122
273
  });
274
+ expect(weird.getSnapshot()).toMatchObject({
275
+ id: 'b',
276
+ map: {},
277
+ objectMap: null,
278
+ weird: {
279
+ list: null,
280
+ cool: {
281
+ message: 'cool',
282
+ },
283
+ ok: {
284
+ message: 'ok',
285
+ },
286
+ },
287
+ });
288
+
123
289
  todo.get('attachments').push({
124
290
  name: 'qux',
125
291
  });
@@ -144,5 +310,30 @@ describe('clients with stored legacy oids', () => {
144
310
  },
145
311
  ]
146
312
  `);
313
+
314
+ weird.get('weird').set('another', 'bar');
315
+ weird.get('weird').set('list', ['a', 'b', 'c']);
316
+ weird.get('weird').delete('cool');
317
+ weird.get('map').set('foo', 'bar');
318
+ expect(weird.getSnapshot()).toMatchInlineSnapshot(`
319
+ {
320
+ "id": "b",
321
+ "map": {
322
+ "foo": "bar",
323
+ },
324
+ "objectMap": null,
325
+ "weird": {
326
+ "another": "bar",
327
+ "list": [
328
+ "a",
329
+ "b",
330
+ "c",
331
+ ],
332
+ "ok": {
333
+ "message": "ok",
334
+ },
335
+ },
336
+ }
337
+ `);
147
338
  });
148
339
  });
@@ -24,6 +24,7 @@ import {
24
24
  getAllDatabaseNamesAndVersions,
25
25
  } from '../idb.js';
26
26
  import { FakeWeakRef } from '../FakeWeakRef.js';
27
+ import { METADATA_VERSION_KEY } from './constants.js';
27
28
 
28
29
  export interface ClientDescriptorOptions<Presence = any, Profile = any> {
29
30
  /** The schema used to create this client */
@@ -67,6 +68,9 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
67
68
  * before turning it on.
68
69
  */
69
70
  EXPERIMENTAL_weakRefs?: boolean;
71
+
72
+ // not for public use
73
+ [METADATA_VERSION_KEY]?: number;
70
74
  }
71
75
 
72
76
  /**
@@ -136,10 +140,12 @@ export class ClientDescriptor<
136
140
  };
137
141
 
138
142
  private initializeDatabases = async (init: ClientDescriptorOptions) => {
143
+ const metadataVersion = init[METADATA_VERSION_KEY];
139
144
  const { db: metaDb } = await openMetadataDatabase({
140
145
  indexedDB: init.indexedDb,
141
146
  log: init.log,
142
147
  namespace: init.namespace,
148
+ metadataVersion,
143
149
  });
144
150
 
145
151
  const context: Omit<Context, 'documentDb'> = {
@@ -0,0 +1 @@
1
+ export const METADATA_VERSION_KEY = Symbol('metadataVersion');
@@ -1,19 +1,22 @@
1
+ import { replaceLegacyOidsInObject } from '@verdant-web/common';
1
2
  import { closeDatabase, storeRequestPromise } from '../idb.js';
2
3
 
3
- const migrations = [version1, version2, version3, version4];
4
+ const migrations = [version1, version2, version3, version4, version5];
4
5
 
5
6
  export function openMetadataDatabase({
6
7
  indexedDB = window.indexedDB,
7
8
  namespace,
8
9
  log,
10
+ metadataVersion = 5,
9
11
  }: {
10
12
  indexedDB?: IDBFactory;
11
13
  namespace: string;
12
14
  log?: (...args: any[]) => void;
15
+ metadataVersion?: number;
13
16
  }): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
14
17
  return new Promise<{ wasInitialized: boolean; db: IDBDatabase }>(
15
18
  (resolve, reject) => {
16
- const request = indexedDB.open(`${namespace}_meta`, 4);
19
+ const request = indexedDB.open(`${namespace}_meta`, metadataVersion);
17
20
  let wasInitialized = false;
18
21
  request.onupgradeneeded = async (event) => {
19
22
  const db = request.result;
@@ -24,6 +27,11 @@ export function openMetadataDatabase({
24
27
  await migration(db, tx);
25
28
  }
26
29
 
30
+ await new Promise((resolve, reject) => {
31
+ tx.addEventListener('complete', resolve);
32
+ tx.addEventListener('error', reject);
33
+ });
34
+
27
35
  if (!event.oldVersion) {
28
36
  wasInitialized = true;
29
37
  }
@@ -44,16 +52,19 @@ export async function openWIPMetadataDatabase({
44
52
  namespace,
45
53
  indexedDB,
46
54
  log,
55
+ metadataVersion,
47
56
  }: {
48
57
  indexedDB?: IDBFactory;
49
58
  namespace: string;
50
59
  wipNamespace: string;
51
60
  log?: (...args: any[]) => void;
61
+ metadataVersion?: number;
52
62
  }): Promise<{ wasInitialized: boolean; db: IDBDatabase }> {
53
63
  const result = await openMetadataDatabase({
54
64
  namespace: wipNamespace,
55
65
  indexedDB,
56
66
  log,
67
+ metadataVersion,
57
68
  });
58
69
 
59
70
  // this WIP database was already set up.
@@ -67,6 +78,7 @@ export async function openWIPMetadataDatabase({
67
78
  namespace,
68
79
  indexedDB,
69
80
  log,
81
+ metadataVersion,
70
82
  });
71
83
 
72
84
  const tx = prodDb.transaction(
@@ -195,3 +207,54 @@ async function version4(db: IDBDatabase, tx: IDBTransaction) {
195
207
  files.createIndex('remote', 'remote');
196
208
  files.createIndex('deletedAt', 'deletedAt');
197
209
  }
210
+
211
+ async function version5(db: IDBDatabase, tx: IDBTransaction) {
212
+ // rewrites all baselines and operations to replace legacy OIDs
213
+ // with new ones.
214
+ const operations = tx.objectStore('operations');
215
+ await new Promise<void>((resolve, reject) => {
216
+ const cursorReq = operations.openCursor();
217
+ cursorReq.onsuccess = () => {
218
+ const cursor = cursorReq.result;
219
+ if (cursor) {
220
+ const converted = replaceLegacyOidsInObject(cursor.value);
221
+ // conversion may change the primary key, so we need to put the
222
+ // object back to the store
223
+ if (converted.oid_timestamp !== cursor.primaryKey) {
224
+ cursor.delete();
225
+ operations.put(converted);
226
+ } else {
227
+ cursor.update(converted);
228
+ }
229
+ cursor.continue();
230
+ } else {
231
+ resolve();
232
+ }
233
+ };
234
+ cursorReq.onerror = (event) => {
235
+ reject(cursorReq.error);
236
+ };
237
+ });
238
+ const baselines = tx.objectStore('baselines');
239
+ await new Promise<void>((resolve, reject) => {
240
+ const cursorReq = baselines.openCursor();
241
+ cursorReq.onsuccess = () => {
242
+ const cursor = cursorReq.result;
243
+ if (cursor) {
244
+ const converted = replaceLegacyOidsInObject(cursor.value);
245
+ if (converted.oid !== cursor.primaryKey) {
246
+ cursor.delete();
247
+ baselines.put(converted);
248
+ } else {
249
+ cursor.update(converted);
250
+ }
251
+ cursor.continue();
252
+ } else {
253
+ resolve();
254
+ }
255
+ };
256
+ cursorReq.onerror = (event) => {
257
+ reject(cursorReq.error);
258
+ };
259
+ });
260
+ }
package/src/sync/Sync.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  Operation,
7
7
  ReplicaType,
8
8
  ServerMessage,
9
+ replaceLegacyOidsInObject,
9
10
  } from '@verdant-web/common';
10
11
  import { Metadata } from '../metadata/Metadata.js';
11
12
  import { HANDLE_MESSAGE, PresenceManager } from './PresenceManager.js';