@peers-app/peers-device 0.14.1 → 0.15.1

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