@peers-app/peers-device 0.15.0 → 0.15.2

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 (68) hide show
  1. package/dist/chunk-download-manager.d.ts +2 -2
  2. package/dist/chunk-download-manager.js +1 -1
  3. package/dist/chunk-download-manager.js.map +1 -1
  4. package/dist/chunk-download-manager.test.js +57 -57
  5. package/dist/chunk-download-manager.test.js.map +1 -1
  6. package/dist/chunk-download.types.d.ts +1 -1
  7. package/dist/connection-manager/connection-manager-priorities.d.ts +1 -1
  8. package/dist/connection-manager/connection-manager-priorities.js +10 -6
  9. package/dist/connection-manager/connection-manager-priorities.js.map +1 -1
  10. package/dist/connection-manager/connection-manager-priorities.test.js +192 -194
  11. package/dist/connection-manager/connection-manager-priorities.test.js.map +1 -1
  12. package/dist/connection-manager/connection-manager.d.ts +2 -2
  13. package/dist/connection-manager/connection-manager.js +71 -55
  14. package/dist/connection-manager/connection-manager.js.map +1 -1
  15. package/dist/connection-manager/connection-manager.test.js +165 -147
  16. package/dist/connection-manager/connection-manager.test.js.map +1 -1
  17. package/dist/connection-manager/connection-state.type.d.ts +1 -1
  18. package/dist/connection-manager/device-message-handler.types.d.ts +6 -6
  19. package/dist/connection-manager/device-messages.d.ts +4 -4
  20. package/dist/connection-manager/device-messages.js +56 -40
  21. package/dist/connection-manager/device-messages.js.map +1 -1
  22. package/dist/connection-manager/group-invite-messages.d.ts +2 -2
  23. package/dist/connection-manager/group-invite-messages.js +36 -47
  24. package/dist/connection-manager/group-invite-messages.js.map +1 -1
  25. package/dist/connection-manager/hops-map.js +4 -4
  26. package/dist/connection-manager/hops-map.js.map +1 -1
  27. package/dist/connection-manager/hops-map.test.js +3 -3
  28. package/dist/connection-manager/hops-map.test.js.map +1 -1
  29. package/dist/connection-manager/network-manager.d.ts +2 -2
  30. package/dist/connection-manager/network-manager.js +81 -75
  31. package/dist/connection-manager/network-manager.js.map +1 -1
  32. package/dist/index.d.ts +12 -12
  33. package/dist/json-diff.d.ts +2 -2
  34. package/dist/json-diff.js +30 -27
  35. package/dist/json-diff.js.map +1 -1
  36. package/dist/local.data-source.d.ts +1 -1
  37. package/dist/local.data-source.js +23 -23
  38. package/dist/local.data-source.js.map +1 -1
  39. package/dist/local.data-source.test.js +17 -17
  40. package/dist/local.data-source.test.js.map +1 -1
  41. package/dist/machine-stats.js +57 -51
  42. package/dist/machine-stats.js.map +1 -1
  43. package/dist/machine-stats.test.js +42 -42
  44. package/dist/machine-stats.test.js.map +1 -1
  45. package/dist/main.d.ts +2 -2
  46. package/dist/main.js +10 -8
  47. package/dist/main.js.map +1 -1
  48. package/dist/packages.tracked-data-source.d.ts +1 -1
  49. package/dist/packages.tracked-data-source.js.map +1 -1
  50. package/dist/persistent-vars.test.js +148 -148
  51. package/dist/persistent-vars.test.js.map +1 -1
  52. package/dist/pvars.tracked-data-source.d.ts +1 -1
  53. package/dist/pvars.tracked-data-source.js +12 -10
  54. package/dist/pvars.tracked-data-source.js.map +1 -1
  55. package/dist/sync-group.d.ts +2 -2
  56. package/dist/sync-group.js +110 -88
  57. package/dist/sync-group.js.map +1 -1
  58. package/dist/sync-group.test.js +157 -120
  59. package/dist/sync-group.test.js.map +1 -1
  60. package/dist/tracked-data-source.d.ts +1 -1
  61. package/dist/tracked-data-source.js +61 -62
  62. package/dist/tracked-data-source.js.map +1 -1
  63. package/dist/tracked-data-source.test.js +507 -299
  64. package/dist/tracked-data-source.test.js.map +1 -1
  65. package/dist/websocket-client.d.ts +1 -1
  66. package/dist/websocket-client.js +50 -41
  67. package/dist/websocket-client.js.map +1 -1
  68. package/package.json +3 -3
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const peers_sdk_1 = require("@peers-app/peers-sdk");
7
7
  const tracked_data_source_1 = require("./tracked-data-source");
8
8
  const local_data_source_1 = require("./local.data-source");
9
- describe('TrackedDataSource', () => {
9
+ describe("TrackedDataSource", () => {
10
10
  let db;
11
11
  let sqlDataSource;
12
12
  // let Notes: Table<INote>;
@@ -21,13 +21,13 @@ describe('TrackedDataSource', () => {
21
21
  links: peers_sdk_1.zodAnyObject.optional(),
22
22
  });
23
23
  const NotesMetaData = {
24
- name: 'notes',
25
- description: '',
26
- primaryKeyName: 'noteId',
24
+ name: "notes",
25
+ description: "",
26
+ primaryKeyName: "noteId",
27
27
  fields: (0, peers_sdk_1.schemaToFields)(notesSchema),
28
28
  };
29
29
  beforeAll(async () => {
30
- db = new local_data_source_1.DBLocal(':memory:');
30
+ db = new local_data_source_1.DBLocal(":memory:");
31
31
  });
32
32
  afterEach(() => {
33
33
  trackedTable.preserveHistory = false;
@@ -38,9 +38,9 @@ describe('TrackedDataSource', () => {
38
38
  }
39
39
  });
40
40
  async function dataSourceFactory(db) {
41
- let createDb = !db;
41
+ const createDb = !db;
42
42
  if (!db) {
43
- db = new local_data_source_1.DBLocal(':memory:');
43
+ db = new local_data_source_1.DBLocal(":memory:");
44
44
  }
45
45
  // Create fresh instances for each test
46
46
  sqlDataSource = new peers_sdk_1.SQLDataSource(db, NotesMetaData, notesSchema);
@@ -71,19 +71,23 @@ describe('TrackedDataSource', () => {
71
71
  beforeEach(async () => {
72
72
  [changeTrackingTable, trackedTable] = await dataSourceFactory(db);
73
73
  });
74
- describe('Insert Operations', () => {
75
- it('should insert a record and create a single full-object change', async () => {
76
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test note', completed: false });
74
+ describe("Insert Operations", () => {
75
+ it("should insert a record and create a single full-object change", async () => {
76
+ const note = await trackedTable.insert({
77
+ noteId: (0, peers_sdk_1.newid)(),
78
+ title: "test note",
79
+ completed: false,
80
+ });
77
81
  expect(note.noteId).toBeDefined();
78
- expect(note.title).toBe('test note');
82
+ expect(note.title).toBe("test note");
79
83
  // Check that changes were recorded
80
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
84
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
81
85
  // Should have exactly one change at path "/"
82
86
  expect(changes.length).toBe(1);
83
- expect(changes[0].op).toBe('set');
84
- expect(changes[0].path).toBe('/');
87
+ expect(changes[0].op).toBe("set");
88
+ expect(changes[0].path).toBe("/");
85
89
  expect(changes[0].value).toBeNull(); // value is null in the changes table; resolved at query time via listChanges
86
- expect(changes[0].tableName).toBe('notes');
90
+ expect(changes[0].tableName).toBe("notes");
87
91
  expect(changes[0].recordId).toBe(note.noteId);
88
92
  expect(changes[0].createdAt).toBeDefined();
89
93
  expect(changes[0].appliedAt).toBeDefined();
@@ -94,59 +98,78 @@ describe('TrackedDataSource', () => {
94
98
  const resolvedChanges = await trackedTable.listChanges({ recordId: note.noteId });
95
99
  expect(resolvedChanges[0].value).toEqual(note);
96
100
  });
97
- it('should auto-generate noteId if not provided', async () => {
98
- const note = await trackedTable.insert({ title: 'test note' });
101
+ it("should auto-generate noteId if not provided", async () => {
102
+ const note = await trackedTable.insert({ title: "test note" });
99
103
  expect(note.noteId).toBeDefined();
100
104
  expect(note.noteId.length).toBeGreaterThan(0);
101
105
  });
102
- it('should store operations in sequential timestamp order', async () => {
103
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test note', completed: false });
104
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId }, { sortBy: ['createdAt'] });
106
+ it("should store operations in sequential timestamp order", async () => {
107
+ const note = await trackedTable.insert({
108
+ noteId: (0, peers_sdk_1.newid)(),
109
+ title: "test note",
110
+ completed: false,
111
+ });
112
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId }, { sortBy: ["createdAt"] });
105
113
  // Verify timestamps are sequential
106
114
  for (let i = 1; i < changes.length; i++) {
107
115
  expect(changes[i].createdAt).toBeGreaterThan(changes[i - 1].createdAt);
108
116
  }
109
117
  });
110
118
  });
111
- describe('Update Operations', () => {
112
- it('should update a record and store granular change records', async () => {
119
+ describe("Update Operations", () => {
120
+ it("should update a record and store granular change records", async () => {
113
121
  // Insert
114
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original' });
122
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original" });
115
123
  // Count initial changes
116
- const initialChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
124
+ const initialChanges = await changeTrackingTable.list({
125
+ tableName: "notes",
126
+ recordId: note.noteId,
127
+ });
117
128
  const initialCount = initialChanges.length;
118
129
  // Update
119
- note.title = 'updated';
130
+ note.title = "updated";
120
131
  note.completed = true;
121
132
  await trackedTable.update(note);
122
133
  // Check that new changes were added
123
- const allChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
134
+ const allChanges = await changeTrackingTable.list({
135
+ tableName: "notes",
136
+ recordId: note.noteId,
137
+ });
124
138
  expect(allChanges.length).toBeGreaterThan(initialCount);
125
139
  // The new changes should be 'replace' or 'add' operations
126
140
  const updateChanges = allChanges.slice(initialCount);
127
141
  updateChanges.forEach((change) => {
128
- expect(['set', 'delete', 'patch-text']).toContain(change.op);
142
+ expect(["set", "delete", "patch-text"]).toContain(change.op);
129
143
  });
130
144
  // Verify the record was actually updated
131
145
  const retrieved = await trackedTable.get(note.noteId);
132
- expect(retrieved?.title).toBe('updated');
146
+ expect(retrieved?.title).toBe("updated");
133
147
  expect(retrieved?.completed).toBe(true);
134
148
  });
135
- it('should handle no-op updates gracefully', async () => {
136
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
137
- const changesBeforeUpdate = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
149
+ it("should handle no-op updates gracefully", async () => {
150
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
151
+ const changesBeforeUpdate = await changeTrackingTable.list({
152
+ tableName: "notes",
153
+ recordId: note.noteId,
154
+ });
138
155
  // Update with same values
139
156
  await trackedTable.update(note);
140
- const changesAfterUpdate = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
157
+ const changesAfterUpdate = await changeTrackingTable.list({
158
+ tableName: "notes",
159
+ recordId: note.noteId,
160
+ });
141
161
  expect(changesAfterUpdate.length).toBe(changesBeforeUpdate.length); // No new changes
142
162
  });
143
- it('should mark old changes as superseded when saveAsSnapshot is true', async () => {
144
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original' });
163
+ it("should mark old changes as superseded when saveAsSnapshot is true", async () => {
164
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original" });
145
165
  // Get initial changes
146
- const initialChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
166
+ const initialChanges = await changeTrackingTable.list({
167
+ tableName: "notes",
168
+ recordId: note.noteId,
169
+ });
147
170
  expect(initialChanges.every((c) => !c.supersededAt)).toBe(true);
148
171
  // Update with saveAsSnapshot
149
- note.title = 'updated';
172
+ note.title = "updated";
150
173
  await trackedTable.save(note, { saveAsSnapshot: true });
151
174
  // Check that old changes are marked as superseded
152
175
  const oldChanges = await changeTrackingTable.list({
@@ -168,30 +191,30 @@ describe('TrackedDataSource', () => {
168
191
  });
169
192
  });
170
193
  });
171
- describe('Delete Operations', () => {
172
- it('should delete a record and create a remove operation', async () => {
173
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
194
+ describe("Delete Operations", () => {
195
+ it("should delete a record and create a remove operation", async () => {
196
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
174
197
  await trackedTable.delete(note);
175
198
  // Check for remove operation
176
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
177
- const removeOp = changes.find((c) => c.op === 'delete');
199
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
200
+ const removeOp = changes.find((c) => c.op === "delete");
178
201
  expect(removeOp).toBeDefined();
179
- expect(removeOp?.op).toBe('delete');
202
+ expect(removeOp?.op).toBe("delete");
180
203
  // Verify record was deleted
181
204
  const retrieved = await trackedTable.get(note.noteId);
182
205
  expect(retrieved).toBeUndefined();
183
206
  });
184
- it('should delete by string ID', async () => {
185
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
207
+ it("should delete by string ID", async () => {
208
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
186
209
  await trackedTable.delete(note.noteId);
187
210
  const retrieved = await trackedTable.get(note.noteId);
188
211
  expect(retrieved).toBeUndefined();
189
212
  });
190
- it('should mark old changes as superseded after delete', async () => {
191
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
213
+ it("should mark old changes as superseded after delete", async () => {
214
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
192
215
  // Initial changes should not be superseded
193
216
  const beforeDelete = await changeTrackingTable.list({
194
- tableName: 'notes',
217
+ tableName: "notes",
195
218
  recordId: note.noteId,
196
219
  supersededAt: { $exists: false },
197
220
  });
@@ -199,16 +222,16 @@ describe('TrackedDataSource', () => {
199
222
  await trackedTable.delete(note);
200
223
  // Old changes should now be superseded (except the remove operation)
201
224
  const superseded = await changeTrackingTable.list({
202
- tableName: 'notes',
225
+ tableName: "notes",
203
226
  recordId: note.noteId,
204
227
  supersededAt: { $exists: true },
205
228
  });
206
229
  expect(superseded.length).toBeGreaterThan(0);
207
230
  });
208
231
  });
209
- describe('Restore Operations', () => {
210
- it('should restore a deleted record', async () => {
211
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
232
+ describe("Restore Operations", () => {
233
+ it("should restore a deleted record", async () => {
234
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
212
235
  await trackedTable.delete(note);
213
236
  // Restore
214
237
  const restored = await trackedTable.restore(note);
@@ -218,52 +241,58 @@ describe('TrackedDataSource', () => {
218
241
  const retrieved = await trackedTable.get(note.noteId);
219
242
  expect(retrieved).toEqual(note);
220
243
  });
221
- it('should create a single add operation for restored record', async () => {
222
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
244
+ it("should create a single add operation for restored record", async () => {
245
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
223
246
  const noteId = note.noteId;
224
247
  await trackedTable.delete(note);
225
248
  // Count changes before restore
226
- const beforeRestore = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId });
249
+ const beforeRestore = await changeTrackingTable.list({
250
+ tableName: "notes",
251
+ recordId: noteId,
252
+ });
227
253
  await trackedTable.restore(note);
228
- const afterRestore = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId });
254
+ const afterRestore = await changeTrackingTable.list({ tableName: "notes", recordId: noteId });
229
255
  expect(afterRestore.length).toBe(beforeRestore.length + 1);
230
256
  // Find the new 'add' operation from restore (should be exactly one at path "/")
231
257
  const restoreOps = afterRestore.filter((c) => !c.supersededAt);
232
258
  expect(restoreOps.length).toBe(1);
233
- expect(restoreOps[0].op).toBe('set');
234
- expect(restoreOps[0].path).toBe('/');
259
+ expect(restoreOps[0].op).toBe("set");
260
+ expect(restoreOps[0].path).toBe("/");
235
261
  expect(restoreOps[0].value).toBeNull(); // value is null in the changes table; resolved at query time
236
262
  });
237
- it('should mark old changes as superseded after restore', async () => {
238
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
263
+ it("should mark old changes as superseded after restore", async () => {
264
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
239
265
  await trackedTable.delete(note);
240
266
  await trackedTable.restore(note);
241
267
  const superseded = await changeTrackingTable.list({
242
- tableName: 'notes',
268
+ tableName: "notes",
243
269
  recordId: note.noteId,
244
270
  supersededAt: { $exists: true },
245
271
  });
246
272
  expect(superseded.length).toBeGreaterThan(0);
247
273
  });
248
274
  });
249
- describe('Save Operations', () => {
250
- it('should route to insert for new records', async () => {
251
- const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
275
+ describe("Save Operations", () => {
276
+ it("should route to insert for new records", async () => {
277
+ const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
252
278
  expect(note.noteId).toBeDefined();
253
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
279
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
254
280
  expect(changes.length).toBeGreaterThan(0);
255
- expect(changes.every((c) => c.op === 'set')).toBe(true);
281
+ expect(changes.every((c) => c.op === "set")).toBe(true);
256
282
  });
257
- it('should route to update for existing records', async () => {
258
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original' });
259
- const initialCount = (await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId })).length;
260
- note.title = 'updated';
283
+ it("should route to update for existing records", async () => {
284
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original" });
285
+ const initialCount = (await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId })).length;
286
+ note.title = "updated";
261
287
  await trackedTable.save(note);
262
- const allChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
288
+ const allChanges = await changeTrackingTable.list({
289
+ tableName: "notes",
290
+ recordId: note.noteId,
291
+ });
263
292
  expect(allChanges.length).toBeGreaterThan(initialCount);
264
293
  });
265
- it('should restore deleted record when restoreIfDeleted is true', async () => {
266
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
294
+ it("should restore deleted record when restoreIfDeleted is true", async () => {
295
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
267
296
  await trackedTable.delete(note);
268
297
  // Try to save with restoreIfDeleted option
269
298
  const restored = await trackedTable.save(note, { restoreIfDeleted: true });
@@ -271,15 +300,15 @@ describe('TrackedDataSource', () => {
271
300
  const retrieved = await trackedTable.get(note.noteId);
272
301
  expect(retrieved).toBeDefined();
273
302
  });
274
- it('should throw error when saving deleted record without restoreIfDeleted', async () => {
275
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
303
+ it("should throw error when saving deleted record without restoreIfDeleted", async () => {
304
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
276
305
  await trackedTable.delete(note);
277
- await expect(trackedTable.save(note)).rejects.toThrow('has been deleted');
306
+ await expect(trackedTable.save(note)).rejects.toThrow("has been deleted");
278
307
  });
279
- it('should save snapshots as a single add at root path', async () => {
280
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original', count: 1 });
281
- const updatedNote = await trackedTable.save({ ...note, title: 'updated', count: 2 }, { saveAsSnapshot: true });
282
- const changes = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ['createdAt'] });
308
+ it("should save snapshots as a single add at root path", async () => {
309
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original", count: 1 });
310
+ const updatedNote = await trackedTable.save({ ...note, title: "updated", count: 2 }, { saveAsSnapshot: true });
311
+ const changes = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ["createdAt"] });
283
312
  expect(changes.length).toBe(2); // One for insert, one for snapshot save
284
313
  // Both insert and snapshot changes store null value (resolved at query time)
285
314
  expect(changes[0].value).toBeNull();
@@ -290,84 +319,84 @@ describe('TrackedDataSource', () => {
290
319
  expect(resolvedChanges[0].value).toEqual(updatedNote);
291
320
  expect(resolvedChanges[1].value).toEqual(updatedNote);
292
321
  });
293
- it('should save non-snapshot updates as individual changes', async () => {
294
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original', count: 1 });
295
- await trackedTable.save({ ...note, title: 'updated', count: 2 });
296
- const changes = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ['createdAt'] });
322
+ it("should save non-snapshot updates as individual changes", async () => {
323
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original", count: 1 });
324
+ await trackedTable.save({ ...note, title: "updated", count: 2 });
325
+ const changes = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ["createdAt"] });
297
326
  expect(changes.length).toBe(3); // One for insert, two for field updates
298
327
  expect(changes[0].value).toBeNull(); // Insert change stores null; resolved at query time
299
328
  // Granular field updates still store their values inline
300
- const titleChange = changes.find(c => c.path === '/title');
301
- const countChange = changes.find(c => c.path === '/count');
302
- expect(titleChange?.value).toBe('updated');
329
+ const titleChange = changes.find((c) => c.path === "/title");
330
+ const countChange = changes.find((c) => c.path === "/count");
331
+ expect(titleChange?.value).toBe("updated");
303
332
  expect(countChange?.value).toBe(2);
304
333
  });
305
334
  });
306
- describe('weakInsert Option', () => {
307
- it('should use weak timestamp (1 + Math.random()) for insert with weakInsert: true', async () => {
308
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' }, { weakInsert: true });
335
+ describe("weakInsert Option", () => {
336
+ it("should use weak timestamp (1 + Math.random()) for insert with weakInsert: true", async () => {
337
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" }, { weakInsert: true });
309
338
  const now = Date.now() - 2;
310
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
339
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
311
340
  expect(changes.length).toBe(1);
312
341
  const change = changes[0];
313
342
  expect(change.createdAt).toBeGreaterThanOrEqual(1);
314
343
  expect(change.createdAt).toBeLessThan(2); // 1 + Math.random() is between 1 and 2
315
- expect(change.appliedAt).toBeGreaterThanOrEqual(now); // appliedAt should be the current datetime
344
+ expect(change.appliedAt).toBeGreaterThanOrEqual(now); // appliedAt should be the current datetime
316
345
  });
317
- it('should use normal timestamp for insert without weakInsert', async () => {
318
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
319
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
346
+ it("should use normal timestamp for insert without weakInsert", async () => {
347
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
348
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
320
349
  expect(changes.length).toBe(1);
321
350
  const change = changes[0];
322
351
  // Normal timestamp should be much larger (performance.timeOrigin + performance.now() or Date.now())
323
352
  expect(change.createdAt).toBeGreaterThan(1000);
324
353
  });
325
- it('should use normal timestamp for insert with weakInsert: false', async () => {
326
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' }, { weakInsert: false });
327
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
354
+ it("should use normal timestamp for insert with weakInsert: false", async () => {
355
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" }, { weakInsert: false });
356
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
328
357
  expect(changes.length).toBe(1);
329
358
  const change = changes[0];
330
359
  // weakInsert: false should use normal timestamp
331
360
  expect(change.createdAt).toBeGreaterThan(1000);
332
361
  });
333
- it('should use weak timestamp for save (new record) with weakInsert: true', async () => {
334
- const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: 'test' }, { weakInsert: true });
335
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId });
362
+ it("should use weak timestamp for save (new record) with weakInsert: true", async () => {
363
+ const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: "test" }, { weakInsert: true });
364
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId });
336
365
  expect(changes.length).toBe(1);
337
366
  const change = changes[0];
338
367
  expect(change.createdAt).toBeGreaterThanOrEqual(1);
339
368
  expect(change.createdAt).toBeLessThan(2);
340
369
  });
341
- it('should not affect update operations (weakInsert ignored for updates)', async () => {
370
+ it("should not affect update operations (weakInsert ignored for updates)", async () => {
342
371
  // Insert with normal timestamp
343
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original' });
344
- const initialChange = (await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId }))[0];
372
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original" });
373
+ const initialChange = (await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId }))[0];
345
374
  const initialTimestamp = initialChange.createdAt;
346
375
  // Update with weakInsert - should not affect the update timestamp
347
- note.title = 'updated';
376
+ note.title = "updated";
348
377
  await trackedTable.save(note, { weakInsert: true });
349
- const allChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: note.noteId }, { sortBy: ['createdAt'] });
378
+ const allChanges = await changeTrackingTable.list({ tableName: "notes", recordId: note.noteId }, { sortBy: ["createdAt"] });
350
379
  // Should have more changes (the update)
351
380
  expect(allChanges.length).toBeGreaterThan(1);
352
381
  // The update changes should have normal timestamps (not weak)
353
382
  const updateChanges = allChanges.slice(1);
354
- updateChanges.forEach(change => {
383
+ updateChanges.forEach((change) => {
355
384
  expect(change.createdAt).toBeGreaterThan(initialTimestamp);
356
385
  expect(change.createdAt).toBeGreaterThan(1000); // Normal timestamp
357
386
  });
358
387
  });
359
- it('should use weak timestamp when save routes to insert (existing ID but no record)', async () => {
388
+ it("should use weak timestamp when save routes to insert (existing ID but no record)", async () => {
360
389
  const noteId = (0, peers_sdk_1.newid)();
361
390
  // Save with an ID but no existing record - this routes to _insert
362
- const note = await trackedTable.save({ noteId, title: 'test' }, { weakInsert: true });
363
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId });
391
+ const note = await trackedTable.save({ noteId, title: "test" }, { weakInsert: true });
392
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: noteId });
364
393
  expect(changes.length).toBe(1);
365
394
  const change = changes[0];
366
395
  expect(change.createdAt).toBeGreaterThanOrEqual(1);
367
396
  expect(change.createdAt).toBeLessThan(2);
368
397
  });
369
- it('should work with restoreIfDeleted option', async () => {
370
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
398
+ it("should work with restoreIfDeleted option", async () => {
399
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
371
400
  await trackedTable.delete(note);
372
401
  // Restore with weakInsert - note: restore doesn't use weakInsert, but we can test the combination
373
402
  const restored = await trackedTable.save(note, { restoreIfDeleted: true, weakInsert: true });
@@ -377,34 +406,34 @@ describe('TrackedDataSource', () => {
377
406
  const retrieved = await trackedTable.get(note.noteId);
378
407
  expect(retrieved).toBeDefined();
379
408
  });
380
- it('should work with saveAsSnapshot option', async () => {
381
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'original' });
409
+ it("should work with saveAsSnapshot option", async () => {
410
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "original" });
382
411
  // Save as snapshot with weakInsert
383
- const updated = await trackedTable.save({ ...note, title: 'updated' }, { saveAsSnapshot: true, weakInsert: true });
412
+ const updated = await trackedTable.save({ ...note, title: "updated" }, { saveAsSnapshot: true, weakInsert: true });
384
413
  // The snapshot save is an update, so weakInsert won't affect it
385
414
  // But we can verify the operation completed successfully
386
415
  const retrieved = await trackedTable.get(note.noteId);
387
- expect(retrieved?.title).toBe('updated');
416
+ expect(retrieved?.title).toBe("updated");
388
417
  });
389
- it('should allow weak writes to be superseded by normal writes', async () => {
418
+ it("should allow weak writes to be superseded by normal writes", async () => {
390
419
  const noteId = (0, peers_sdk_1.newid)();
391
420
  // First, insert with weakInsert (simulating a new device creating the record)
392
- const note1 = await trackedTable.insert({ noteId, title: 'weak write 1' }, { weakInsert: true });
393
- const change1 = (await changeTrackingTable.list({ tableName: 'notes', recordId: noteId }))[0];
421
+ const note1 = await trackedTable.insert({ noteId, title: "weak write 1" }, { weakInsert: true });
422
+ const change1 = (await changeTrackingTable.list({ tableName: "notes", recordId: noteId }))[0];
394
423
  expect(change1.createdAt).toBeLessThan(2);
395
424
  expect(change1.createdAt).toBeGreaterThanOrEqual(1);
396
425
  // Wait a bit to ensure normal timestamp is later
397
426
  await (0, peers_sdk_1.sleep)(10);
398
427
  // Then, update with normal write (simulating another device with authoritative data)
399
428
  // This simulates the scenario where an existing device updates the record
400
- note1.title = 'normal write';
429
+ note1.title = "normal write";
401
430
  await trackedTable.save(note1); // Normal save without weakInsert
402
- const allChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId }, { sortBy: ['createdAt'] });
431
+ const allChanges = await changeTrackingTable.list({ tableName: "notes", recordId: noteId }, { sortBy: ["createdAt"] });
403
432
  // Should have more than 1 change (initial insert + update changes)
404
433
  expect(allChanges.length).toBeGreaterThan(1);
405
434
  // The update changes should have normal timestamps
406
435
  const updateChanges = allChanges.slice(1);
407
- updateChanges.forEach(change => {
436
+ updateChanges.forEach((change) => {
408
437
  expect(change.createdAt).toBeGreaterThan(1000); // Normal timestamp
409
438
  });
410
439
  // Verify that the initial weak write has a weak timestamp
@@ -412,45 +441,45 @@ describe('TrackedDataSource', () => {
412
441
  expect(change1.createdAt).toBeGreaterThanOrEqual(1);
413
442
  // The final record should reflect the normal write (update takes precedence)
414
443
  const final = await trackedTable.get(noteId);
415
- expect(final?.title).toBe('normal write');
444
+ expect(final?.title).toBe("normal write");
416
445
  // Note: The initial change at path "/" may or may not be superseded depending on the update logic.
417
- // The key point is that weak writes have very small timestamps (1 + Math.random()) and normal writes
418
- // have large timestamps, so when syncing between devices, the normal writes will take precedence
446
+ // The key point is that weak writes have very small timestamps (1 + Math.random()) and normal writes
447
+ // have large timestamps, so when syncing between devices, the normal writes will take precedence
419
448
  // due to their later timestamps.
420
449
  });
421
- it('should use weak timestamp for insert called via save when recordId is provided but record does not exist', async () => {
450
+ it("should use weak timestamp for insert called via save when recordId is provided but record does not exist", async () => {
422
451
  const noteId = (0, peers_sdk_1.newid)();
423
452
  // Save with an ID but no existing record - routes to _insert with skipDeletedCheck=true
424
- const note = await trackedTable.save({ noteId, title: 'test' }, { weakInsert: true });
425
- const changes = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId });
453
+ const note = await trackedTable.save({ noteId, title: "test" }, { weakInsert: true });
454
+ const changes = await changeTrackingTable.list({ tableName: "notes", recordId: noteId });
426
455
  expect(changes.length).toBe(1);
427
456
  const change = changes[0];
428
457
  expect(change.createdAt).toBeGreaterThanOrEqual(1);
429
458
  expect(change.createdAt).toBeLessThan(2);
430
459
  });
431
460
  });
432
- describe('Read Operations', () => {
433
- it('should get a record by id', async () => {
434
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
461
+ describe("Read Operations", () => {
462
+ it("should get a record by id", async () => {
463
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
435
464
  const retrieved = await trackedTable.get(note.noteId);
436
465
  expect(retrieved).toEqual(note);
437
466
  });
438
- it('should list records with filters', async () => {
439
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note1', completed: true });
440
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note2', completed: false });
467
+ it("should list records with filters", async () => {
468
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note1", completed: true });
469
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note2", completed: false });
441
470
  const completed = await trackedTable.list({ completed: true });
442
471
  expect(completed.length).toBe(1);
443
- expect(completed[0].title).toBe('note1');
472
+ expect(completed[0].title).toBe("note1");
444
473
  });
445
- it('should count records', async () => {
446
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note1' });
447
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note2' });
474
+ it("should count records", async () => {
475
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note1" });
476
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note2" });
448
477
  const count = await trackedTable.count();
449
478
  expect(count).toBe(2);
450
479
  });
451
- it('should iterate records with cursor', async () => {
452
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note1' });
453
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note2' });
480
+ it("should iterate records with cursor", async () => {
481
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note1" });
482
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note2" });
454
483
  const cursor = trackedTable.cursor();
455
484
  const notes = [];
456
485
  for await (const note of cursor) {
@@ -459,25 +488,25 @@ describe('TrackedDataSource', () => {
459
488
  expect(notes.length).toBe(2);
460
489
  });
461
490
  });
462
- describe('Change Tracking Methods', () => {
463
- it('should list changes for this table', async () => {
464
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
491
+ describe("Change Tracking Methods", () => {
492
+ it("should list changes for this table", async () => {
493
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
465
494
  const changes = await trackedTable.listChanges();
466
495
  expect(changes.length).toBeGreaterThan(0);
467
496
  changes.forEach((c) => {
468
- expect(c.tableName).toBe('notes');
497
+ expect(c.tableName).toBe("notes");
469
498
  });
470
499
  });
471
- it('should filter changes by recordId', async () => {
472
- const note1 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note1' });
473
- const note2 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note2' });
500
+ it("should filter changes by recordId", async () => {
501
+ const note1 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note1" });
502
+ const note2 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note2" });
474
503
  const changes1 = await trackedTable.listChanges({ recordId: note1.noteId });
475
504
  expect(changes1.every((c) => c.recordId === note1.noteId)).toBe(true);
476
505
  const changes2 = await trackedTable.listChanges({ recordId: note2.noteId });
477
506
  expect(changes2.every((c) => c.recordId === note2.noteId)).toBe(true);
478
507
  });
479
- it('should use cursor for changes', async () => {
480
- await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
508
+ it("should use cursor for changes", async () => {
509
+ await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
481
510
  const cursor = await trackedTable.cursorChanges();
482
511
  const changes = [];
483
512
  for await (const change of cursor) {
@@ -486,36 +515,41 @@ describe('TrackedDataSource', () => {
486
515
  expect(changes.length).toBeGreaterThan(0);
487
516
  });
488
517
  });
489
- describe('Helper Methods', () => {
490
- it('should identify deleted records', async () => {
491
- const note1 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note1' });
492
- const note2 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note2' });
518
+ describe("Helper Methods", () => {
519
+ it("should identify deleted records", async () => {
520
+ const note1 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note1" });
521
+ const note2 = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note2" });
493
522
  await trackedTable.delete(note1);
494
523
  const deletedIds = await changeTrackingTable.filterToDeletedIds([note1.noteId, note2.noteId]);
495
524
  expect(deletedIds).toContain(note1.noteId);
496
525
  expect(deletedIds).not.toContain(note2.noteId);
497
526
  });
498
- it('should handle empty array in filterToDeletedIds', async () => {
527
+ it("should handle empty array in filterToDeletedIds", async () => {
499
528
  const deletedIds = await changeTrackingTable.filterToDeletedIds([]);
500
529
  expect(deletedIds).toEqual([]);
501
530
  });
502
531
  });
503
- describe('Edge Cases', () => {
504
- it('should handle insert with all optional fields', async () => {
505
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'minimal' });
532
+ describe("Edge Cases", () => {
533
+ it("should handle insert with all optional fields", async () => {
534
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "minimal" });
506
535
  expect(note.completed).toBeUndefined();
507
536
  expect(note.count).toBeUndefined();
508
537
  });
509
- it('should handle update removing optional fields', async () => {
510
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test', completed: true, count: 5 });
538
+ it("should handle update removing optional fields", async () => {
539
+ const note = await trackedTable.insert({
540
+ noteId: (0, peers_sdk_1.newid)(),
541
+ title: "test",
542
+ completed: true,
543
+ count: 5,
544
+ });
511
545
  // Update to remove optional fields
512
- const updated = { noteId: note.noteId, title: 'test' };
546
+ const updated = { noteId: note.noteId, title: "test" };
513
547
  await trackedTable.update(updated);
514
548
  const retrieved = await trackedTable.get(note.noteId);
515
- expect(retrieved?.title).toBe('test');
549
+ expect(retrieved?.title).toBe("test");
516
550
  // The optional fields behavior depends on the underlying implementation
517
551
  });
518
- it('should handle concurrent operations on different records', async () => {
552
+ it("should handle concurrent operations on different records", async () => {
519
553
  const promises = [];
520
554
  for (let i = 0; i < 5; i++) {
521
555
  promises.push(trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: `note${i}` }));
@@ -525,12 +559,12 @@ describe('TrackedDataSource', () => {
525
559
  const count = await trackedTable.count();
526
560
  expect(count).toBe(5);
527
561
  });
528
- it('should handle distributed deletion scenario', async () => {
562
+ it("should handle distributed deletion scenario", async () => {
529
563
  // Scenario: One peer deletes a record, another peer (disconnected) makes changes
530
564
  const noteId = (0, peers_sdk_1.newid)();
531
565
  trackedTable.preserveHistory = true;
532
566
  // Peer 1: Create record
533
- await trackedTable.insert({ noteId, title: 'original', completed: false });
567
+ await trackedTable.insert({ noteId, title: "original", completed: false });
534
568
  // Peer 1: Delete record at timestamp 2000
535
569
  await trackedTable.delete(noteId);
536
570
  // Peer 2: Makes changes AFTER deletion (while disconnected)
@@ -539,20 +573,20 @@ describe('TrackedDataSource', () => {
539
573
  // Peer 2 writes to specific paths (doesn't know about deletion)
540
574
  const laterChange1 = {
541
575
  changeId: (0, peers_sdk_1.newid)(),
542
- tableName: 'notes',
576
+ tableName: "notes",
543
577
  recordId: noteId,
544
- op: 'set',
545
- path: '/title',
546
- value: 'updated by peer 2',
578
+ op: "set",
579
+ path: "/title",
580
+ value: "updated by peer 2",
547
581
  createdAt: laterTimestamp,
548
582
  appliedAt: laterTimestamp,
549
583
  };
550
584
  const laterChange2 = {
551
585
  changeId: (0, peers_sdk_1.newid)(),
552
- tableName: 'notes',
586
+ tableName: "notes",
553
587
  recordId: noteId,
554
- op: 'set',
555
- path: '/completed',
588
+ op: "set",
589
+ path: "/completed",
556
590
  value: true,
557
591
  createdAt: laterTimestamp + 1,
558
592
  appliedAt: laterTimestamp + 1,
@@ -563,7 +597,7 @@ describe('TrackedDataSource', () => {
563
597
  const deletedIds = await changeTrackingTable.filterToDeletedIds([noteId]);
564
598
  expect(deletedIds).toContain(noteId);
565
599
  // Verify the later changes are marked as superseded
566
- const allChanges = await changeTrackingTable.list({ tableName: 'notes', recordId: noteId }, { sortBy: ['createdAt'] });
600
+ const allChanges = await changeTrackingTable.list({ tableName: "notes", recordId: noteId }, { sortBy: ["createdAt"] });
567
601
  // Find the changes with the later timestamps (the ones we manually added)
568
602
  const change1InDb = allChanges.find((c) => c.changeId === laterChange1.changeId);
569
603
  const change2InDb = allChanges.find((c) => c.changeId === laterChange2.changeId);
@@ -576,49 +610,49 @@ describe('TrackedDataSource', () => {
576
610
  const record = await trackedTable.get(noteId);
577
611
  expect(record).toBeUndefined();
578
612
  });
579
- it('should handle insert with existing deleted record', async () => {
613
+ it("should handle insert with existing deleted record", async () => {
580
614
  const noteId = (0, peers_sdk_1.newid)();
581
615
  // Insert and then delete a record
582
- await trackedTable.insert({ noteId, title: 'original note' });
616
+ await trackedTable.insert({ noteId, title: "original note" });
583
617
  await trackedTable.delete(noteId);
584
618
  // Try to insert with same ID - should fail
585
- await expect(trackedTable.insert({ noteId, title: 'new note' })).rejects.toThrow('because it has been deleted');
619
+ await expect(trackedTable.insert({ noteId, title: "new note" })).rejects.toThrow("because it has been deleted");
586
620
  });
587
- it('should handle update with missing record', async () => {
621
+ it("should handle update with missing record", async () => {
588
622
  const noteId = (0, peers_sdk_1.newid)();
589
623
  // Try to update non-existent record
590
- await expect(trackedTable.update({ noteId, title: 'new note' })).rejects.toThrow('No record found to update');
624
+ await expect(trackedTable.update({ noteId, title: "new note" })).rejects.toThrow("No record found to update");
591
625
  });
592
- it('should handle delete with string ID', async () => {
626
+ it("should handle delete with string ID", async () => {
593
627
  const noteId = (0, peers_sdk_1.newid)();
594
628
  // Insert a record first
595
- await trackedTable.insert({ noteId, title: 'test note' });
629
+ await trackedTable.insert({ noteId, title: "test note" });
596
630
  // Delete by string ID should work
597
631
  await trackedTable.delete(noteId);
598
632
  // Verify it was deleted
599
633
  const note = await trackedTable.get(noteId);
600
634
  expect(note).toBeUndefined();
601
635
  });
602
- it('should find and track untracked records', async () => {
636
+ it("should find and track untracked records", async () => {
603
637
  // Insert records directly into the underlying data source (bypassing change tracking)
604
- const note1 = { noteId: (0, peers_sdk_1.newid)(), title: 'untracked 1' };
605
- const note2 = { noteId: (0, peers_sdk_1.newid)(), title: 'untracked 2' };
606
- const note3 = { noteId: (0, peers_sdk_1.newid)(), title: 'untracked 3' };
638
+ const note1 = { noteId: (0, peers_sdk_1.newid)(), title: "untracked 1" };
639
+ const note2 = { noteId: (0, peers_sdk_1.newid)(), title: "untracked 2" };
640
+ const note3 = { noteId: (0, peers_sdk_1.newid)(), title: "untracked 3" };
607
641
  await sqlDataSource.insert(note1);
608
642
  await sqlDataSource.insert(note2);
609
643
  await sqlDataSource.insert(note3);
610
644
  // Verify no change records exist for these
611
- let changes = await changeTrackingTable.list({ tableName: 'notes' });
645
+ let changes = await changeTrackingTable.list({ tableName: "notes" });
612
646
  expect(changes.length).toBe(0);
613
647
  // Run findAndTrackRecords
614
648
  await trackedTable.findAndTrackRecords();
615
649
  // Verify change records were created
616
- changes = await changeTrackingTable.list({ tableName: 'notes' });
650
+ changes = await changeTrackingTable.list({ tableName: "notes" });
617
651
  expect(changes.length).toBe(3);
618
652
  // All should be 'set' operations at path "/"
619
- changes.forEach(change => {
620
- expect(change.op).toBe('set');
621
- expect(change.path).toBe('/');
653
+ changes.forEach((change) => {
654
+ expect(change.op).toBe("set");
655
+ expect(change.path).toBe("/");
622
656
  expect(change.createdAt).toBeLessThan(10); // Low timestamp
623
657
  expect(change.appliedAt).toBeGreaterThan(Date.now() - 1000); // Recent appliedAt
624
658
  });
@@ -626,21 +660,21 @@ describe('TrackedDataSource', () => {
626
660
  const allNotes = await trackedTable.list();
627
661
  expect(allNotes.length).toBe(3);
628
662
  });
629
- it('should compact superseded changes older than given timestamp', async () => {
630
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
663
+ it("should compact superseded changes older than given timestamp", async () => {
664
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
631
665
  const noteId = note.noteId;
632
666
  // Make several updates to create multiple changes
633
- await trackedTable.update({ noteId, title: 'update 1' });
667
+ await trackedTable.update({ noteId, title: "update 1" });
634
668
  await (0, peers_sdk_1.sleep)(10);
635
- await trackedTable.update({ noteId, title: 'update 2' });
669
+ await trackedTable.update({ noteId, title: "update 2" });
636
670
  await (0, peers_sdk_1.sleep)(10);
637
- await trackedTable.update({ noteId, title: 'update 3' });
671
+ await trackedTable.update({ noteId, title: "update 3" });
638
672
  await (0, peers_sdk_1.sleep)(10);
639
673
  // Get all changes before compacting
640
674
  const changesBefore = await changeTrackingTable.list({ recordId: noteId });
641
675
  expect(changesBefore.length).toBeGreaterThan(1);
642
676
  // Count superseded changes
643
- const supersededBefore = changesBefore.filter(c => c.supersededAt).length;
677
+ const supersededBefore = changesBefore.filter((c) => c.supersededAt).length;
644
678
  expect(supersededBefore).toBeGreaterThan(0);
645
679
  // Compact (remove all superseded changes by using future timestamp)
646
680
  await trackedTable.compact(Date.now() + 1000);
@@ -649,25 +683,25 @@ describe('TrackedDataSource', () => {
649
683
  // Should have fewer changes now
650
684
  expect(changesAfter.length).toBeLessThan(changesBefore.length);
651
685
  // Should have no superseded changes left
652
- const supersededAfter = changesAfter.filter(c => c.supersededAt).length;
686
+ const supersededAfter = changesAfter.filter((c) => c.supersededAt).length;
653
687
  expect(supersededAfter).toBe(0);
654
688
  // Verify the record is still accessible and correct
655
689
  const retrieved = await trackedTable.get(noteId);
656
- expect(retrieved?.title).toBe('update 3');
690
+ expect(retrieved?.title).toBe("update 3");
657
691
  });
658
- it('should compact only changes superseded before given timestamp', async () => {
659
- const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'test' });
692
+ it("should compact only changes superseded before given timestamp", async () => {
693
+ const note = await trackedTable.insert({ noteId: (0, peers_sdk_1.newid)(), title: "test" });
660
694
  const noteId = note.noteId;
661
695
  // Make updates
662
- await trackedTable.update({ noteId, title: 'update 1' });
696
+ await trackedTable.update({ noteId, title: "update 1" });
663
697
  await (0, peers_sdk_1.sleep)(10);
664
698
  const midTimestamp = Date.now();
665
699
  await (0, peers_sdk_1.sleep)(10);
666
- await trackedTable.update({ noteId, title: 'update 2' });
700
+ await trackedTable.update({ noteId, title: "update 2" });
667
701
  await (0, peers_sdk_1.sleep)(10);
668
- await trackedTable.update({ noteId, title: 'update 3' });
669
- const changesBefore = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ['createdAt'] });
670
- const supersededBefore = changesBefore.filter(c => c.supersededAt).length;
702
+ await trackedTable.update({ noteId, title: "update 3" });
703
+ const changesBefore = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ["createdAt"] });
704
+ const supersededBefore = changesBefore.filter((c) => c.supersededAt).length;
671
705
  // Compact only changes superseded before midTimestamp
672
706
  await trackedTable.compact(midTimestamp);
673
707
  const changesAfter = await changeTrackingTable.list({ recordId: noteId });
@@ -677,138 +711,312 @@ describe('TrackedDataSource', () => {
677
711
  // Therefore, no changes should be removed.
678
712
  expect(changesAfter.length).toBe(changesBefore.length); // No changes should be removed
679
713
  // There should still be superseded changes (the ones after midTimestamp)
680
- const supersededAfter = changesAfter.filter(c => c.supersededAt).length;
714
+ const supersededAfter = changesAfter.filter((c) => c.supersededAt).length;
681
715
  expect(supersededAfter).toBe(supersededBefore); // Same number of superseded changes
682
716
  });
683
717
  });
684
- describe('applyChanges', () => {
685
- it('should save but supersede remote changes that are older', async () => {
718
+ describe("applyChanges", () => {
719
+ it("should save but supersede remote changes that are older", async () => {
686
720
  const noteId = `000000000000000000note001`;
687
721
  trackedTable.preserveHistory = true;
688
- const note = await trackedTable.save({ noteId, title: 'original' });
722
+ const note = await trackedTable.save({ noteId, title: "original" });
689
723
  const timeUpdatedRemote = (0, peers_sdk_1.getTimestamp)();
690
- note.title = 'updated1';
724
+ note.title = "updated1";
691
725
  await trackedTable.save(note);
692
726
  const changes = await changeTrackingTable.list({ recordId: note.noteId });
693
727
  expect(changes.length).toBe(2);
694
728
  // Create changes to apply
695
729
  const remoteChange = {
696
730
  changeId: (0, peers_sdk_1.newid)(),
697
- tableName: 'notes',
731
+ tableName: "notes",
698
732
  recordId: note.noteId,
699
- op: 'set',
700
- path: '/title',
701
- value: 'updated2',
733
+ op: "set",
734
+ path: "/title",
735
+ value: "updated2",
702
736
  createdAt: timeUpdatedRemote,
703
737
  appliedAt: timeUpdatedRemote,
704
738
  };
705
739
  await trackedTable.applyChanges([remoteChange]);
706
740
  const finalRecord = await trackedTable.get(note.noteId);
707
741
  expect(finalRecord).toBeDefined();
708
- expect(finalRecord?.title).toBe('updated1'); // because remote change is superseded
709
- const finalChanges = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ['createdAt'] });
742
+ expect(finalRecord?.title).toBe("updated1"); // because remote change is superseded
743
+ const finalChanges = await changeTrackingTable.list({ recordId: note.noteId }, { sortBy: ["createdAt"] });
710
744
  expect(finalChanges.length).toBe(3);
711
745
  const remoteChangeInDb = finalChanges.find((c) => c.changeId === remoteChange.changeId);
712
746
  expect(remoteChangeInDb).toBeDefined();
713
747
  expect(remoteChangeInDb?.supersededAt).toBeDefined();
714
748
  });
715
- it('should replace the entire array when an item in the array is added or removed', async () => {
716
- const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: 'original', tags: ['tag1', 'tag2'] });
717
- note.tags = ['tag1', 'tag2', 'tag3'];
749
+ it("should replace the entire array when an item in the array is added or removed", async () => {
750
+ const note = await trackedTable.save({
751
+ noteId: (0, peers_sdk_1.newid)(),
752
+ title: "original",
753
+ tags: ["tag1", "tag2"],
754
+ });
755
+ note.tags = ["tag1", "tag2", "tag3"];
718
756
  await trackedTable.save(note);
719
757
  const changes = await changeTrackingTable.list({ recordId: note.noteId });
720
758
  expect(changes.length).toBe(2);
721
- const addTagChange = changes.find((c) => c.op === 'set' && c.path === '/tags');
759
+ const addTagChange = changes.find((c) => c.op === "set" && c.path === "/tags");
722
760
  expect(addTagChange).toBeDefined();
723
- expect(addTagChange?.value).toEqual(['tag1', 'tag2', 'tag3']);
761
+ expect(addTagChange?.value).toEqual(["tag1", "tag2", "tag3"]);
724
762
  });
725
- it('should updating two similarly named fields', async () => {
726
- const note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: 'original', links: { a: 1, aa: 2 } });
763
+ it("should updating two similarly named fields", async () => {
764
+ const note = await trackedTable.save({
765
+ noteId: (0, peers_sdk_1.newid)(),
766
+ title: "original",
767
+ links: { a: 1, aa: 2 },
768
+ });
727
769
  const ts = (0, peers_sdk_1.getTimestamp)();
728
770
  await trackedTable.applyChanges([
729
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: note.noteId, op: 'set', path: '/links/aa', value: 3, createdAt: ts, appliedAt: ts },
730
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: note.noteId, op: 'set', path: '/links/a', value: 4, createdAt: ts + 1, appliedAt: ts + 1 },
771
+ {
772
+ changeId: (0, peers_sdk_1.newid)(),
773
+ tableName: "notes",
774
+ recordId: note.noteId,
775
+ op: "set",
776
+ path: "/links/aa",
777
+ value: 3,
778
+ createdAt: ts,
779
+ appliedAt: ts,
780
+ },
781
+ {
782
+ changeId: (0, peers_sdk_1.newid)(),
783
+ tableName: "notes",
784
+ recordId: note.noteId,
785
+ op: "set",
786
+ path: "/links/a",
787
+ value: 4,
788
+ createdAt: ts + 1,
789
+ appliedAt: ts + 1,
790
+ },
731
791
  ]);
732
- const dbNote = await trackedTable.get(note.noteId);
792
+ const dbNote = (await trackedTable.get(note.noteId));
733
793
  expect(dbNote).toBeDefined();
734
794
  expect(dbNote?.links).toBeDefined();
735
795
  expect(dbNote?.links?.aa).toBe(3);
736
796
  expect(dbNote?.links?.a).toBe(4);
737
797
  });
738
- it('should apply deep changes after higher changes', async () => {
798
+ it("should apply deep changes after higher changes", async () => {
739
799
  const noteId = `000000000000000000note001`;
740
800
  let note = {
741
801
  noteId,
742
- title: 'original',
802
+ title: "original",
743
803
  };
744
804
  const ts = (0, peers_sdk_1.getTimestamp)();
745
805
  await trackedTable.applyChanges([
746
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/', value: note, createdAt: ts, appliedAt: ts },
747
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links', value: { a: 1 }, createdAt: ts + 1, appliedAt: ts + 1 },
748
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links/a', value: '2', createdAt: ts + 2, appliedAt: ts + 2 },
806
+ {
807
+ changeId: (0, peers_sdk_1.newid)(),
808
+ tableName: "notes",
809
+ recordId: noteId,
810
+ op: "set",
811
+ path: "/",
812
+ value: note,
813
+ createdAt: ts,
814
+ appliedAt: ts,
815
+ },
816
+ {
817
+ changeId: (0, peers_sdk_1.newid)(),
818
+ tableName: "notes",
819
+ recordId: noteId,
820
+ op: "set",
821
+ path: "/links",
822
+ value: { a: 1 },
823
+ createdAt: ts + 1,
824
+ appliedAt: ts + 1,
825
+ },
826
+ {
827
+ changeId: (0, peers_sdk_1.newid)(),
828
+ tableName: "notes",
829
+ recordId: noteId,
830
+ op: "set",
831
+ path: "/links/a",
832
+ value: "2",
833
+ createdAt: ts + 2,
834
+ appliedAt: ts + 2,
835
+ },
749
836
  ]);
750
- note = await trackedTable.get(noteId);
837
+ note = (await trackedTable.get(noteId));
751
838
  expect(note).toBeDefined();
752
839
  expect(note?.links).toBeDefined();
753
- expect(note?.links?.a).toBe('2');
840
+ expect(note?.links?.a).toBe("2");
754
841
  });
755
- it('should correctly apply and supersede changes from a single batch', async () => {
842
+ it("should correctly apply and supersede changes from a single batch", async () => {
756
843
  const noteId = `000000000000000000note001`;
757
844
  let note = {
758
845
  noteId,
759
- title: 'original',
846
+ title: "original",
760
847
  };
761
848
  const ts = (0, peers_sdk_1.getTimestamp)();
762
849
  await trackedTable.applyChanges([
763
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/', value: note, createdAt: ts, appliedAt: ts },
764
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links', value: { a: 1 }, createdAt: ts + 1, appliedAt: ts + 1 },
765
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links/a', value: '2', createdAt: ts + 2, appliedAt: ts + 2 },
766
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'delete', path: '/links', createdAt: ts + 3, appliedAt: ts + 3 },
850
+ {
851
+ changeId: (0, peers_sdk_1.newid)(),
852
+ tableName: "notes",
853
+ recordId: noteId,
854
+ op: "set",
855
+ path: "/",
856
+ value: note,
857
+ createdAt: ts,
858
+ appliedAt: ts,
859
+ },
860
+ {
861
+ changeId: (0, peers_sdk_1.newid)(),
862
+ tableName: "notes",
863
+ recordId: noteId,
864
+ op: "set",
865
+ path: "/links",
866
+ value: { a: 1 },
867
+ createdAt: ts + 1,
868
+ appliedAt: ts + 1,
869
+ },
870
+ {
871
+ changeId: (0, peers_sdk_1.newid)(),
872
+ tableName: "notes",
873
+ recordId: noteId,
874
+ op: "set",
875
+ path: "/links/a",
876
+ value: "2",
877
+ createdAt: ts + 2,
878
+ appliedAt: ts + 2,
879
+ },
880
+ {
881
+ changeId: (0, peers_sdk_1.newid)(),
882
+ tableName: "notes",
883
+ recordId: noteId,
884
+ op: "delete",
885
+ path: "/links",
886
+ createdAt: ts + 3,
887
+ appliedAt: ts + 3,
888
+ },
767
889
  ]);
768
- note = await trackedTable.get(noteId);
890
+ note = (await trackedTable.get(noteId));
769
891
  expect(note).toBeDefined();
770
892
  expect(note?.links).toBeUndefined();
771
893
  });
772
- it('should delete records on a delete change on path /', async () => {
773
- let note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: 'to be deleted' });
894
+ it("should delete records on a delete change on path /", async () => {
895
+ let note = await trackedTable.save({ noteId: (0, peers_sdk_1.newid)(), title: "to be deleted" });
774
896
  const ts = (0, peers_sdk_1.getTimestamp)();
775
897
  await trackedTable.applyChanges([
776
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: note.noteId, op: 'delete', path: '/', createdAt: ts + 4, appliedAt: ts + 4 },
898
+ {
899
+ changeId: (0, peers_sdk_1.newid)(),
900
+ tableName: "notes",
901
+ recordId: note.noteId,
902
+ op: "delete",
903
+ path: "/",
904
+ createdAt: ts + 4,
905
+ appliedAt: ts + 4,
906
+ },
777
907
  ]);
778
- note = await trackedTable.get(note.noteId);
908
+ note = (await trackedTable.get(note.noteId));
779
909
  expect(note).toBeUndefined();
780
910
  });
781
- it('should correctly apply and supersede changes from a single batch that includes both creating and deleting the object', async () => {
911
+ it("should correctly apply and supersede changes from a single batch that includes both creating and deleting the object", async () => {
782
912
  const noteId = `000000000000000000note001`;
783
913
  let note = {
784
914
  noteId,
785
- title: 'original',
915
+ title: "original",
786
916
  };
787
917
  const ts = (0, peers_sdk_1.getTimestamp)();
788
918
  await trackedTable.applyChanges([
789
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/', value: note, createdAt: ts, appliedAt: ts },
790
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links', value: { a: 1 }, createdAt: ts + 1, appliedAt: ts + 1 },
791
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links/a', value: '2', createdAt: ts + 2, appliedAt: ts + 2 },
792
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'delete', path: '/links', createdAt: ts + 3, appliedAt: ts + 3 },
793
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'delete', path: '/', createdAt: ts + 4, appliedAt: ts + 4 },
919
+ {
920
+ changeId: (0, peers_sdk_1.newid)(),
921
+ tableName: "notes",
922
+ recordId: noteId,
923
+ op: "set",
924
+ path: "/",
925
+ value: note,
926
+ createdAt: ts,
927
+ appliedAt: ts,
928
+ },
929
+ {
930
+ changeId: (0, peers_sdk_1.newid)(),
931
+ tableName: "notes",
932
+ recordId: noteId,
933
+ op: "set",
934
+ path: "/links",
935
+ value: { a: 1 },
936
+ createdAt: ts + 1,
937
+ appliedAt: ts + 1,
938
+ },
939
+ {
940
+ changeId: (0, peers_sdk_1.newid)(),
941
+ tableName: "notes",
942
+ recordId: noteId,
943
+ op: "set",
944
+ path: "/links/a",
945
+ value: "2",
946
+ createdAt: ts + 2,
947
+ appliedAt: ts + 2,
948
+ },
949
+ {
950
+ changeId: (0, peers_sdk_1.newid)(),
951
+ tableName: "notes",
952
+ recordId: noteId,
953
+ op: "delete",
954
+ path: "/links",
955
+ createdAt: ts + 3,
956
+ appliedAt: ts + 3,
957
+ },
958
+ {
959
+ changeId: (0, peers_sdk_1.newid)(),
960
+ tableName: "notes",
961
+ recordId: noteId,
962
+ op: "delete",
963
+ path: "/",
964
+ createdAt: ts + 4,
965
+ appliedAt: ts + 4,
966
+ },
794
967
  ]);
795
- note = await trackedTable.get(noteId);
968
+ note = (await trackedTable.get(noteId));
796
969
  expect(note).toBeUndefined();
797
970
  });
798
- it('should correctly apply and supersede changes from a single batch that includes both creating, deleting the object, and restoring the object', async () => {
971
+ it("should correctly apply and supersede changes from a single batch that includes both creating, deleting the object, and restoring the object", async () => {
799
972
  const noteId = `000000000000000000note001`;
800
973
  const note = {
801
974
  noteId,
802
- title: 'original',
975
+ title: "original",
803
976
  };
804
977
  const ts = (0, peers_sdk_1.getTimestamp)();
805
978
  await trackedTable.applyChanges([
806
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/', value: note, createdAt: ts, appliedAt: ts },
807
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/links', value: { a: 1 }, createdAt: ts + 2, appliedAt: ts + 2 },
808
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'delete', path: '/', createdAt: ts + 4, appliedAt: ts + 4 },
809
- { changeId: (0, peers_sdk_1.newid)(), tableName: 'notes', recordId: noteId, op: 'set', path: '/', value: note, createdAt: ts + 5, appliedAt: ts + 5 },
979
+ {
980
+ changeId: (0, peers_sdk_1.newid)(),
981
+ tableName: "notes",
982
+ recordId: noteId,
983
+ op: "set",
984
+ path: "/",
985
+ value: note,
986
+ createdAt: ts,
987
+ appliedAt: ts,
988
+ },
989
+ {
990
+ changeId: (0, peers_sdk_1.newid)(),
991
+ tableName: "notes",
992
+ recordId: noteId,
993
+ op: "set",
994
+ path: "/links",
995
+ value: { a: 1 },
996
+ createdAt: ts + 2,
997
+ appliedAt: ts + 2,
998
+ },
999
+ {
1000
+ changeId: (0, peers_sdk_1.newid)(),
1001
+ tableName: "notes",
1002
+ recordId: noteId,
1003
+ op: "delete",
1004
+ path: "/",
1005
+ createdAt: ts + 4,
1006
+ appliedAt: ts + 4,
1007
+ },
1008
+ {
1009
+ changeId: (0, peers_sdk_1.newid)(),
1010
+ tableName: "notes",
1011
+ recordId: noteId,
1012
+ op: "set",
1013
+ path: "/",
1014
+ value: note,
1015
+ createdAt: ts + 5,
1016
+ appliedAt: ts + 5,
1017
+ },
810
1018
  ]);
811
- const dbNote = await trackedTable.get(noteId);
1019
+ const dbNote = (await trackedTable.get(noteId));
812
1020
  expect(dbNote).toEqual(note);
813
1021
  });
814
1022
  // // not sure we want to support this behavior
@@ -826,61 +1034,61 @@ describe('TrackedDataSource', () => {
826
1034
  // const dbNote = await trackedTable.get(noteId) as INote;
827
1035
  // expect(dbNote?.links?.a).toEqual(1);
828
1036
  // });
829
- it('should handle two peers adding items to an array separately (last write wins)', async () => {
1037
+ it("should handle two peers adding items to an array separately (last write wins)", async () => {
830
1038
  const [changeTrackingTable, trackedTable] = await dataSourceFactory();
831
1039
  const [changeTrackingTableRemote, trackedTableRemote] = await dataSourceFactory();
832
1040
  const noteId = `000000000000000000note001`;
833
- const note = await trackedTable.save({ noteId, title: 'original', tags: ['tag1', 'tag2'] });
1041
+ const note = await trackedTable.save({ noteId, title: "original", tags: ["tag1", "tag2"] });
834
1042
  // Use listChanges (resolves insert change values) instead of changeTrackingTable.list
835
1043
  await trackedTableRemote.applyChanges(await trackedTable.listChanges({ recordId: note.noteId }));
836
1044
  let noteLocal = await trackedTable.get(note.noteId);
837
1045
  let noteRemote = await trackedTableRemote.get(note.noteId);
838
1046
  expect(noteLocal).toEqual(noteRemote);
839
- noteLocal.tags.push('tag3');
1047
+ noteLocal.tags.push("tag3");
840
1048
  await trackedTable.save(noteLocal);
841
- noteRemote.tags.push('tag4');
1049
+ noteRemote.tags.push("tag4");
842
1050
  await trackedTableRemote.save(noteRemote);
843
1051
  await trackedTableRemote.applyChanges(await trackedTable.listChanges({ recordId: note.noteId }));
844
1052
  await trackedTable.applyChanges(await trackedTableRemote.listChanges({ recordId: note.noteId }));
845
1053
  noteRemote = await trackedTableRemote.get(note.noteId);
846
1054
  noteLocal = await trackedTable.get(note.noteId);
847
1055
  // changes to arrays are treated as full replacements, so last write wins
848
- expect(noteRemote?.tags).toEqual(['tag1', 'tag2', 'tag4']);
849
- expect(noteLocal?.tags).toEqual(['tag1', 'tag2', 'tag4']);
1056
+ expect(noteRemote?.tags).toEqual(["tag1", "tag2", "tag4"]);
1057
+ expect(noteLocal?.tags).toEqual(["tag1", "tag2", "tag4"]);
850
1058
  });
851
- it('should treat changes to objects nested in arrays as a full replacement of the array', async () => {
1059
+ it("should treat changes to objects nested in arrays as a full replacement of the array", async () => {
852
1060
  const noteId = `000000000000000000note001`;
853
- const note = { noteId, title: 'original', links: { a: [{ b: 1 }, { c: 2 }] }, };
1061
+ const note = { noteId, title: "original", links: { a: [{ b: 1 }, { c: 2 }] } };
854
1062
  const noteInserted = await trackedTable.save(note);
855
1063
  noteInserted.links.a[0].b = 2;
856
1064
  const noteUpdated = await trackedTable.save(noteInserted);
857
1065
  expect(noteUpdated).toEqual(noteInserted);
858
- const changes = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ['createdAt'] });
1066
+ const changes = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ["createdAt"] });
859
1067
  expect(changes.length).toBe(2);
860
1068
  const arrayChange = changes[1];
861
- expect(arrayChange.path).toBe('/links/a');
1069
+ expect(arrayChange.path).toBe("/links/a");
862
1070
  expect(arrayChange.value).toEqual([{ b: 2 }, { c: 2 }]);
863
1071
  });
864
- it('should treat changes to arrays nested in another array as a full replacement of the top array', async () => {
1072
+ it("should treat changes to arrays nested in another array as a full replacement of the top array", async () => {
865
1073
  const noteId = `000000000000000000note001`;
866
- const note = { noteId, title: 'original', links: { a: [[{ b: 1 }, { c: 2 }, 2], 3] }, };
1074
+ const note = { noteId, title: "original", links: { a: [[{ b: 1 }, { c: 2 }, 2], 3] } };
867
1075
  const noteInserted = await trackedTable.save(note);
868
1076
  noteInserted.links.a[0][0].b = 2;
869
1077
  const noteUpdated = await trackedTable.save(noteInserted);
870
1078
  expect(noteUpdated).toEqual(noteInserted);
871
- const changes = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ['createdAt'] });
1079
+ const changes = await changeTrackingTable.list({ recordId: noteId }, { sortBy: ["createdAt"] });
872
1080
  expect(changes.length).toBe(2);
873
1081
  const arrayChange = changes[1];
874
- expect(arrayChange.path).toBe('/links/a');
1082
+ expect(arrayChange.path).toBe("/links/a");
875
1083
  expect(arrayChange.value).toEqual([[{ b: 2 }, { c: 2 }, 2], 3]);
876
1084
  });
877
1085
  });
878
- describe('Transaction Support', () => {
879
- it('should support runInTransaction on DBLocal', async () => {
1086
+ describe("Transaction Support", () => {
1087
+ it("should support runInTransaction on DBLocal", async () => {
880
1088
  // Verify that DBLocal supports transactions
881
- expect(typeof db.runInTransaction).toBe('function');
1089
+ expect(typeof db.runInTransaction).toBe("function");
882
1090
  });
883
- it('should apply multiple changes in a single transaction via applyChanges', async () => {
1091
+ it("should apply multiple changes in a single transaction via applyChanges", async () => {
884
1092
  // Create a separate "remote" data source to generate changes
885
1093
  const [changeTrackingTableRemote, trackedTableRemote] = await dataSourceFactory();
886
1094
  // Insert multiple records on the remote
@@ -889,7 +1097,7 @@ describe('TrackedDataSource', () => {
889
1097
  const note = await trackedTableRemote.insert({
890
1098
  noteId: (0, peers_sdk_1.newid)(),
891
1099
  title: `note ${i}`,
892
- completed: false
1100
+ completed: false,
893
1101
  });
894
1102
  records.push(note);
895
1103
  }
@@ -906,7 +1114,7 @@ describe('TrackedDataSource', () => {
906
1114
  const localChanges = await changeTrackingTable.list({});
907
1115
  expect(localChanges.length).toBe(remoteChanges.length);
908
1116
  });
909
- it('should handle large batches of changes efficiently', async () => {
1117
+ it("should handle large batches of changes efficiently", async () => {
910
1118
  // Create a separate "remote" data source
911
1119
  const [changeTrackingTableRemote, trackedTableRemote] = await dataSourceFactory();
912
1120
  // Insert many records
@@ -940,16 +1148,16 @@ describe('TrackedDataSource', () => {
940
1148
  // Log performance (informational)
941
1149
  console.log(`Applied ${remoteChanges.length} changes in ${endTime - startTime}ms`);
942
1150
  });
943
- it('should bulk insert changes correctly', async () => {
1151
+ it("should bulk insert changes correctly", async () => {
944
1152
  const changes = [];
945
1153
  for (let i = 0; i < 5; i++) {
946
1154
  const noteId = (0, peers_sdk_1.newid)();
947
1155
  changes.push({
948
1156
  changeId: (0, peers_sdk_1.newid)(),
949
- tableName: 'notes',
1157
+ tableName: "notes",
950
1158
  recordId: noteId,
951
- op: 'set',
952
- path: '/',
1159
+ op: "set",
1160
+ path: "/",
953
1161
  value: { noteId, title: `bulk note ${i}`, completed: false },
954
1162
  createdAt: (0, peers_sdk_1.getTimestamp)(),
955
1163
  appliedAt: (0, peers_sdk_1.getTimestamp)(),
@@ -961,20 +1169,20 @@ describe('TrackedDataSource', () => {
961
1169
  const insertedChanges = await changeTrackingTable.list({});
962
1170
  expect(insertedChanges.length).toBeGreaterThanOrEqual(5);
963
1171
  for (const change of changes) {
964
- const found = insertedChanges.find(c => c.changeId === change.changeId);
1172
+ const found = insertedChanges.find((c) => c.changeId === change.changeId);
965
1173
  expect(found).toBeDefined();
966
- expect(found?.tableName).toBe('notes');
1174
+ expect(found?.tableName).toBe("notes");
967
1175
  }
968
1176
  });
969
- it('should handle mixed insert/update/delete in applyChanges transaction', async () => {
1177
+ it("should handle mixed insert/update/delete in applyChanges transaction", async () => {
970
1178
  // Create a separate "remote" data source
971
1179
  const [changeTrackingTableRemote, trackedTableRemote] = await dataSourceFactory();
972
1180
  // Insert some records
973
- const note1 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note 1' });
974
- const note2 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note 2' });
975
- const note3 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: 'note 3' });
1181
+ const note1 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note 1" });
1182
+ const note2 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note 2" });
1183
+ const note3 = await trackedTableRemote.insert({ noteId: (0, peers_sdk_1.newid)(), title: "note 3" });
976
1184
  // Update one
977
- note1.title = 'updated note 1';
1185
+ note1.title = "updated note 1";
978
1186
  await trackedTableRemote.update(note1);
979
1187
  // Delete one
980
1188
  await trackedTableRemote.delete(note2.noteId);
@@ -984,19 +1192,19 @@ describe('TrackedDataSource', () => {
984
1192
  await trackedTable.applyChanges(remoteChanges);
985
1193
  // Verify results
986
1194
  const localNote1 = await trackedTable.get(note1.noteId);
987
- expect(localNote1?.title).toBe('updated note 1');
1195
+ expect(localNote1?.title).toBe("updated note 1");
988
1196
  const localNote2 = await trackedTable.get(note2.noteId);
989
1197
  expect(localNote2).toBeUndefined();
990
1198
  const localNote3 = await trackedTable.get(note3.noteId);
991
- expect(localNote3?.title).toBe('note 3');
1199
+ expect(localNote3?.title).toBe("note 3");
992
1200
  });
993
- it('should support sync methods on SQLDataSource', async () => {
1201
+ it("should support sync methods on SQLDataSource", async () => {
994
1202
  // Test sync methods directly on sqlDataSource
995
1203
  await sqlDataSource.initTable();
996
1204
  // Test insertSync via bulkInsert
997
1205
  const records = [
998
- { noteId: (0, peers_sdk_1.newid)(), title: 'sync test 1' },
999
- { noteId: (0, peers_sdk_1.newid)(), title: 'sync test 2' },
1206
+ { noteId: (0, peers_sdk_1.newid)(), title: "sync test 1" },
1207
+ { noteId: (0, peers_sdk_1.newid)(), title: "sync test 2" },
1000
1208
  ];
1001
1209
  const inserted = await sqlDataSource.bulkInsert(records);
1002
1210
  expect(inserted.length).toBe(2);
@@ -1006,30 +1214,30 @@ describe('TrackedDataSource', () => {
1006
1214
  expect(found?.title).toBe(record.title);
1007
1215
  }
1008
1216
  // Test bulkDelete
1009
- await sqlDataSource.bulkDelete(inserted.map(r => r.noteId));
1217
+ await sqlDataSource.bulkDelete(inserted.map((r) => r.noteId));
1010
1218
  // Verify records are deleted
1011
1219
  for (const record of inserted) {
1012
1220
  const found = await sqlDataSource.get(record.noteId);
1013
1221
  expect(found).toBeUndefined();
1014
1222
  }
1015
1223
  });
1016
- it('should support bulkSave on SQLDataSource', async () => {
1224
+ it("should support bulkSave on SQLDataSource", async () => {
1017
1225
  await sqlDataSource.initTable();
1018
1226
  // Insert some records
1019
1227
  const records = [
1020
- { noteId: (0, peers_sdk_1.newid)(), title: 'bulk save 1' },
1021
- { noteId: (0, peers_sdk_1.newid)(), title: 'bulk save 2' },
1228
+ { noteId: (0, peers_sdk_1.newid)(), title: "bulk save 1" },
1229
+ { noteId: (0, peers_sdk_1.newid)(), title: "bulk save 2" },
1022
1230
  ];
1023
1231
  // First bulkSave should insert
1024
1232
  const inserted = await sqlDataSource.bulkSave(records);
1025
1233
  expect(inserted.length).toBe(2);
1026
1234
  // Modify and bulkSave again should update
1027
- inserted[0].title = 'updated bulk save 1';
1235
+ inserted[0].title = "updated bulk save 1";
1028
1236
  inserted[1].completed = true;
1029
1237
  const updated = await sqlDataSource.bulkSave(inserted);
1030
1238
  // Verify updates
1031
1239
  const found1 = await sqlDataSource.get(inserted[0].noteId);
1032
- expect(found1?.title).toBe('updated bulk save 1');
1240
+ expect(found1?.title).toBe("updated bulk save 1");
1033
1241
  const found2 = await sqlDataSource.get(inserted[1].noteId);
1034
1242
  expect(found2?.completed).toBe(true);
1035
1243
  });