@powersync/service-module-mongodb-storage 0.16.0 → 0.17.0

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 (102) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +6 -4
  3. package/dist/storage/MongoBucketStorage.js +110 -36
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +4 -6
  6. package/dist/storage/implementation/BucketDefinitionMapping.js +3 -3
  7. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -1
  8. package/dist/storage/implementation/CheckpointState.d.ts +20 -0
  9. package/dist/storage/implementation/CheckpointState.js +31 -0
  10. package/dist/storage/implementation/CheckpointState.js.map +1 -0
  11. package/dist/storage/implementation/MongoBucketBatch.d.ts +33 -22
  12. package/dist/storage/implementation/MongoBucketBatch.js +45 -271
  13. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  14. package/dist/storage/implementation/MongoChecksums.d.ts +2 -1
  15. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  16. package/dist/storage/implementation/MongoCompactor.d.ts +1 -1
  17. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +4 -4
  18. package/dist/storage/implementation/MongoPersistedSyncRules.js +11 -8
  19. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -1
  20. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +19 -5
  21. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +53 -19
  22. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  23. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +21 -10
  24. package/dist/storage/implementation/MongoSyncBucketStorage.js +18 -163
  25. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  26. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
  27. package/dist/storage/implementation/MongoSyncRulesLock.js +7 -3
  28. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  29. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
  30. package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
  31. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
  32. package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
  33. package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -2
  34. package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
  35. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
  36. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  37. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  38. package/dist/storage/implementation/db.d.ts +10 -2
  39. package/dist/storage/implementation/db.js.map +1 -1
  40. package/dist/storage/implementation/models.d.ts +31 -47
  41. package/dist/storage/implementation/models.js.map +1 -1
  42. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
  43. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +385 -0
  44. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  45. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
  46. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +16 -7
  47. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +77 -6
  48. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  49. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
  50. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  51. package/dist/storage/implementation/v1/models.d.ts +12 -1
  52. package/dist/storage/implementation/v1/models.js.map +1 -1
  53. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +17 -0
  54. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +429 -0
  55. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  56. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +1 -1
  57. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +1 -2
  58. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -1
  59. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +29 -7
  60. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +117 -16
  61. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  62. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -2
  63. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  64. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +3 -2
  65. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
  66. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  67. package/dist/storage/implementation/v3/models.d.ts +61 -3
  68. package/dist/storage/implementation/v3/models.js.map +1 -1
  69. package/package.json +6 -6
  70. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
  71. package/src/storage/MongoBucketStorage.ts +166 -44
  72. package/src/storage/implementation/BucketDefinitionMapping.ts +12 -9
  73. package/src/storage/implementation/CheckpointState.ts +59 -0
  74. package/src/storage/implementation/MongoBucketBatch.ts +81 -355
  75. package/src/storage/implementation/MongoChecksums.ts +2 -1
  76. package/src/storage/implementation/MongoCompactor.ts +1 -1
  77. package/src/storage/implementation/MongoPersistedSyncRules.ts +13 -7
  78. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +69 -24
  79. package/src/storage/implementation/MongoSyncBucketStorage.ts +40 -215
  80. package/src/storage/implementation/MongoSyncRulesLock.ts +9 -3
  81. package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
  82. package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
  83. package/src/storage/implementation/common/PersistedBatch.ts +2 -2
  84. package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
  85. package/src/storage/implementation/createMongoSyncBucketStorage.ts +2 -2
  86. package/src/storage/implementation/db.ts +5 -2
  87. package/src/storage/implementation/models.ts +35 -58
  88. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +478 -1
  89. package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
  90. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +111 -16
  91. package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
  92. package/src/storage/implementation/v1/models.ts +15 -0
  93. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +564 -1
  94. package/src/storage/implementation/v3/MongoCompactorV3.ts +1 -1
  95. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +1 -2
  96. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +150 -22
  97. package/src/storage/implementation/v3/PersistedBatchV3.ts +1 -2
  98. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +7 -2
  99. package/src/storage/implementation/v3/models.ts +70 -2
  100. package/test/src/storage_sync.test.ts +422 -6
  101. package/test/src/storeCurrentData.test.ts +211 -0
  102. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import { GetIntanceOptions, storage } from '@powersync/service-core';
1
+ import { GetIntanceOptions, LEGACY_STORAGE_VERSION, storage } from '@powersync/service-core';
2
2
 
3
3
  import { DO_NOT_LOG, ErrorCode, ServiceError } from '@powersync/lib-services-framework';
4
4
  import { v4 as uuid } from 'uuid';
@@ -6,14 +6,22 @@ import { v4 as uuid } from 'uuid';
6
6
  import * as lib_mongo from '@powersync/lib-service-mongodb';
7
7
  import { mongo } from '@powersync/lib-service-mongodb';
8
8
 
9
+ import { ObjectId } from 'bson';
9
10
  import { generateSlotName } from '../utils/util.js';
10
11
  import { BucketDefinitionMapping } from './implementation/BucketDefinitionMapping.js';
11
12
  import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
12
13
  import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
13
14
  import { PowerSyncMongo } from './implementation/db.js';
14
- import { getMongoStorageConfig, SyncRuleDocument } from './implementation/models.js';
15
+ import { getMongoStorageConfig, StorageConfig, SyncRuleDocumentBase } from './implementation/models.js';
15
16
  import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
16
- import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
17
+ import {
18
+ MongoPersistedSyncRulesContentV1,
19
+ MongoPersistedSyncRulesContentV3
20
+ } from './implementation/MongoPersistedSyncRulesContent.js';
21
+ import { syncRuleStateUpdatePipeline } from './implementation/SyncRuleStateUpdate.js';
22
+ import { SyncRuleDocumentV1 } from './implementation/v1/models.js';
23
+ import { VersionedPowerSyncMongoV3 } from './implementation/v3/VersionedPowerSyncMongoV3.js';
24
+ import { ReplicationStreamDocumentV3, SyncConfigDefinition } from './storage-index.js';
17
25
 
18
26
  export interface MongoBucketStorageOptions {
19
27
  checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
@@ -54,11 +62,11 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
54
62
  if ((typeof id as any) == 'bigint') {
55
63
  id = Number(id);
56
64
  }
57
- const storageConfig = (syncRules as MongoPersistedSyncRulesContent).getStorageConfig();
65
+ const storageConfig = (syncRules as MongoPersistedSyncRulesContentV1).getStorageConfig();
58
66
  const storage = createMongoSyncBucketStorage(
59
67
  this,
60
68
  id,
61
- syncRules as MongoPersistedSyncRulesContent,
69
+ syncRules as MongoPersistedSyncRulesContentV1,
62
70
  slot_name,
63
71
  undefined,
64
72
  {
@@ -110,11 +118,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
110
118
  _id: next.id,
111
119
  state: storage.SyncRuleState.PROCESSING
112
120
  },
113
- {
114
- $set: {
115
- state: storage.SyncRuleState.STOP
116
- }
117
- }
121
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP)
118
122
  );
119
123
  await this.db.notifyCheckpoint();
120
124
  } else if (next == null && active?.id == sync_rules_group_id) {
@@ -130,11 +134,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
130
134
  _id: active.id,
131
135
  state: storage.SyncRuleState.ACTIVE
132
136
  },
133
- {
134
- $set: {
135
- state: storage.SyncRuleState.ERRORED
136
- }
137
- }
137
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
138
138
  );
139
139
  await this.db.notifyCheckpoint();
140
140
  } else if (next != null && active?.id == sync_rules_group_id) {
@@ -145,30 +145,122 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
145
145
  _id: active.id,
146
146
  state: storage.SyncRuleState.ACTIVE
147
147
  },
148
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
149
+ );
150
+ await this.db.notifyCheckpoint();
151
+ }
152
+ }
153
+
154
+ private async updateSyncRulesV3(
155
+ options: storage.UpdateSyncRulesOptions,
156
+ storageVersion: number,
157
+ storageConfig: StorageConfig
158
+ ): Promise<MongoPersistedSyncRulesContentV3> {
159
+ let rules: MongoPersistedSyncRulesContentV3 | undefined = undefined;
160
+ const versioned = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
161
+
162
+ const session = this.session;
163
+
164
+ await session.withTransaction(async () => {
165
+ // Only have a single replication stream with PROCESSING.
166
+ await this.db.sync_rules.updateMany(
148
167
  {
149
- $set: {
150
- state: storage.SyncRuleState.ERRORED
168
+ state: storage.SyncRuleState.PROCESSING
169
+ },
170
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
171
+ { session }
172
+ );
173
+
174
+ const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
175
+ {
176
+ _id: 'sync_rules'
177
+ },
178
+ {
179
+ $inc: {
180
+ op_id: 1n
151
181
  }
182
+ },
183
+ {
184
+ upsert: true,
185
+ returnDocument: 'after',
186
+ session
152
187
  }
153
188
  );
189
+
190
+ const id = Number(id_doc!.op_id);
191
+ const slot_name = generateSlotName(this.slot_name_prefix, id);
192
+
193
+ const mapping = BucketDefinitionMapping.fromParsedSyncRules(options.config.parsed);
194
+
195
+ const syncConfigDoc: SyncConfigDefinition = {
196
+ _id: new ObjectId(),
197
+ replication_stream_id: id,
198
+ created_at: new Date(),
199
+ storage_version: storageVersion,
200
+ content: options.config.yaml,
201
+ serialized_plan: options.config.plan,
202
+ rule_mapping: mapping.serialize()
203
+ };
204
+ await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
205
+
206
+ const doc: ReplicationStreamDocumentV3 = {
207
+ _id: id,
208
+ storage_version: storageVersion,
209
+ sync_configs: [
210
+ {
211
+ _id: syncConfigDoc._id,
212
+ state: storage.SyncRuleState.PROCESSING,
213
+ keepalive_op: null,
214
+ last_checkpoint: null,
215
+ last_checkpoint_lsn: null,
216
+ no_checkpoint_before: null,
217
+ snapshot_done: false
218
+ }
219
+ ],
220
+ snapshot_lsn: undefined,
221
+ state: storage.SyncRuleState.PROCESSING,
222
+ slot_name: slot_name,
223
+ last_checkpoint_ts: null,
224
+ last_fatal_error: null,
225
+ last_fatal_error_ts: null,
226
+ last_keepalive_ts: null
227
+ };
228
+
229
+ await this.db.sync_rules.insertOne(doc, { session });
154
230
  await this.db.notifyCheckpoint();
155
- }
231
+ rules = new MongoPersistedSyncRulesContentV3(this.db, doc, syncConfigDoc);
232
+ if (options.lock) {
233
+ // The lock is persisted on rules.current_lock
234
+ await rules.lock(session);
235
+ }
236
+ });
237
+
238
+ return rules!;
156
239
  }
157
240
 
158
- async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedSyncRulesContent> {
241
+ async updateSyncRules(
242
+ options: storage.UpdateSyncRulesOptions
243
+ ): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3> {
159
244
  const storageVersion =
160
245
  options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
246
+
161
247
  const storageConfig = getMongoStorageConfig(storageVersion);
248
+ if (storageConfig.incrementalReprocessing) {
249
+ return this.updateSyncRulesV3(options, storageVersion, storageConfig);
250
+ }
251
+
252
+ let rules: MongoPersistedSyncRulesContentV1 | undefined = undefined;
162
253
 
163
- let rules: MongoPersistedSyncRulesContent | undefined = undefined;
254
+ const session = this.session;
164
255
 
165
- await this.session.withTransaction(async () => {
256
+ await session.withTransaction(async () => {
166
257
  // Only have a single replication stream with PROCESSING.
167
258
  await this.db.sync_rules.updateMany(
168
259
  {
169
260
  state: storage.SyncRuleState.PROCESSING
170
261
  },
171
- { $set: { state: storage.SyncRuleState.STOP } }
262
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
263
+ { session }
172
264
  );
173
265
 
174
266
  const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
@@ -182,14 +274,15 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
182
274
  },
183
275
  {
184
276
  upsert: true,
185
- returnDocument: 'after'
277
+ returnDocument: 'after',
278
+ session
186
279
  }
187
280
  );
188
281
 
189
282
  const id = Number(id_doc!.op_id);
190
283
  const slot_name = generateSlotName(this.slot_name_prefix, id);
191
284
 
192
- const doc: SyncRuleDocument = {
285
+ const doc: SyncRuleDocumentV1 = {
193
286
  _id: id,
194
287
  storage_version: storageVersion,
195
288
  content: options.config.yaml,
@@ -207,47 +300,68 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
207
300
  last_fatal_error_ts: null,
208
301
  last_keepalive_ts: null
209
302
  };
210
- if (storageConfig.incrementalReprocessing) {
211
- const parsed = options.config.parsed;
212
- doc.rule_mapping = BucketDefinitionMapping.fromParsedSyncRules(parsed).serialize();
213
- }
214
- await this.db.sync_rules.insertOne(doc);
303
+
304
+ await this.db.sync_rules.insertOne(doc, { session });
215
305
  await this.db.notifyCheckpoint();
216
- rules = new MongoPersistedSyncRulesContent(this.db, doc);
306
+ rules = new MongoPersistedSyncRulesContentV1(this.db, doc);
217
307
  if (options.lock) {
218
- const lock = await rules.lock();
308
+ // The lock is persisted on rules.current_lock
309
+ await rules.lock(session);
219
310
  }
220
311
  });
221
312
 
222
313
  return rules!;
223
314
  }
224
315
 
225
- async getActiveSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
316
+ async getActiveSyncRulesContent(): Promise<
317
+ MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null
318
+ > {
226
319
  const doc = await this.db.sync_rules.findOne(
227
320
  {
228
321
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
229
322
  },
230
323
  { sort: { _id: -1 }, limit: 1 }
231
324
  );
325
+
326
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED]);
327
+ }
328
+
329
+ private async getSyncRulesContent(doc: SyncRuleDocumentBase | null, stateFilter: storage.SyncRuleState[]) {
232
330
  if (doc == null) {
233
331
  return null;
234
332
  }
333
+ const storageConfig = getMongoStorageConfig(doc.storage_version ?? LEGACY_STORAGE_VERSION);
235
334
 
236
- return new MongoPersistedSyncRulesContent(this.db, doc);
335
+ if (storageConfig.incrementalReprocessing) {
336
+ const v3 = doc as ReplicationStreamDocumentV3;
337
+ const active = v3.sync_configs.find((c) => stateFilter.includes(c.state));
338
+ if (active == null) {
339
+ return null;
340
+ }
341
+
342
+ // TODO: cache the config. It could specifically help for the main replication loop
343
+ // that checks for active replication streams.
344
+ // It is not a major bottleneck though, since it only runs once every couple of seconds at most.
345
+ const db = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
346
+ const syncConfigDoc = await db.syncConfigDefinitions.findOne({ _id: active._id });
347
+ if (syncConfigDoc == null) {
348
+ return null;
349
+ }
350
+ return new MongoPersistedSyncRulesContentV3(this.db, v3, syncConfigDoc);
351
+ }
352
+
353
+ return new MongoPersistedSyncRulesContentV1(this.db, doc as SyncRuleDocumentV1);
237
354
  }
238
355
 
239
- async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
356
+ async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null> {
240
357
  const doc = await this.db.sync_rules.findOne(
241
358
  {
242
359
  state: storage.SyncRuleState.PROCESSING
243
360
  },
244
361
  { sort: { _id: -1 }, limit: 1 }
245
362
  );
246
- if (doc == null) {
247
- return null;
248
- }
249
363
 
250
- return new MongoPersistedSyncRulesContent(this.db, doc);
364
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.PROCESSING]);
251
365
  }
252
366
 
253
367
  async getReplicatingSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
@@ -257,9 +371,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
257
371
  })
258
372
  .toArray();
259
373
 
260
- return docs.map((doc) => {
261
- return new MongoPersistedSyncRulesContent(this.db, doc);
262
- });
374
+ return (
375
+ await Promise.all(
376
+ docs.map((doc) => {
377
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE]);
378
+ })
379
+ )
380
+ ).filter((r) => r != null);
263
381
  }
264
382
 
265
383
  async getStoppedSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
@@ -269,9 +387,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
269
387
  })
270
388
  .toArray();
271
389
 
272
- return docs.map((doc) => {
273
- return new MongoPersistedSyncRulesContent(this.db, doc);
274
- });
390
+ return (
391
+ await Promise.all(
392
+ docs.map((doc) => {
393
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.STOP]);
394
+ })
395
+ )
396
+ ).filter((d) => d != null);
275
397
  }
276
398
 
277
399
  async getActiveStorage(): Promise<MongoSyncBucketStorage | null> {
@@ -1,19 +1,22 @@
1
1
  import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
- import { BucketDataSource, ParameterIndexLookupCreator, SyncConfigWithErrors } from '@powersync/service-sync-rules';
3
- import { SyncRuleDocument } from './models.js';
4
-
5
- export type BucketDefinitionId = string;
6
- export type ParameterIndexId = string;
2
+ import {
3
+ BucketDataSource,
4
+ BucketDefinitionId,
5
+ ParameterIndexId,
6
+ ParameterIndexLookupCreator,
7
+ SyncConfigWithErrors
8
+ } from '@powersync/service-sync-rules';
9
+ import { SyncConfigDefinition } from '../storage-index.js';
7
10
 
8
11
  export class BucketDefinitionMapping {
9
- static fromSyncRules(doc: Pick<SyncRuleDocument, 'rule_mapping'>): BucketDefinitionMapping {
12
+ static fromSyncConfig(doc: Pick<SyncConfigDefinition, 'rule_mapping'>): BucketDefinitionMapping {
10
13
  return new BucketDefinitionMapping(doc.rule_mapping?.definitions ?? {}, doc.rule_mapping?.parameter_indexes ?? {});
11
14
  }
12
15
 
13
16
  static fromParsedSyncRules(syncRules: SyncConfigWithErrors): BucketDefinitionMapping {
14
17
  const definitionNames = syncRules.config.bucketDataSources.map((source) => source.uniqueName).sort();
15
18
  const parameterKeys = syncRules.config.bucketParameterLookupSources
16
- .map((source) => `${source.defaultLookupScope.lookupName}#${source.defaultLookupScope.queryId}`)
19
+ .map((source) => `${source.sourceId.lookupName}#${source.sourceId.queryId}`)
17
20
  .sort();
18
21
 
19
22
  const definitions: Record<string, BucketDefinitionId> = {};
@@ -51,7 +54,7 @@ export class BucketDefinitionMapping {
51
54
  }
52
55
 
53
56
  parameterLookupId(source: ParameterIndexLookupCreator): ParameterIndexId {
54
- const key = this.parameterLookupKey(source.defaultLookupScope.lookupName, source.defaultLookupScope.queryId);
57
+ const key = this.parameterLookupKey(source.sourceId.lookupName, source.sourceId.queryId);
55
58
  const defId = this.parameterLookupMapping[key];
56
59
  if (defId == null) {
57
60
  throw new ServiceAssertionError(`No mapping found for parameter lookup source ${key}`);
@@ -63,7 +66,7 @@ export class BucketDefinitionMapping {
63
66
  return `${lookupName}#${queryId}`;
64
67
  }
65
68
 
66
- serialize(): NonNullable<SyncRuleDocument['rule_mapping']> {
69
+ serialize(): SyncConfigDefinition['rule_mapping'] {
67
70
  return {
68
71
  definitions: { ...this.definitions },
69
72
  parameter_indexes: { ...this.parameterLookupMapping }
@@ -0,0 +1,59 @@
1
+ export interface CheckpointStateInput {
2
+ lsn: string;
3
+ snapshotDone: boolean;
4
+ lastCheckpointLsn: string | null;
5
+ noCheckpointBefore: string | null;
6
+ keepaliveOp: bigint | null;
7
+ lastCheckpoint: bigint | null;
8
+ persistedOp: bigint | null;
9
+ createEmptyCheckpoints: boolean;
10
+ }
11
+
12
+ export interface CheckpointStateResult {
13
+ canCheckpoint: boolean;
14
+ checkpointBlocked: boolean;
15
+ checkpointCreated: boolean;
16
+ notEmpty: boolean;
17
+ newKeepaliveOp: bigint | null;
18
+ newLastCheckpoint: bigint | null;
19
+ }
20
+
21
+ function maxOpId(...values: (bigint | null | undefined)[]): bigint {
22
+ let max = 0n;
23
+ for (const value of values) {
24
+ if (value != null && value > max) {
25
+ max = value;
26
+ }
27
+ }
28
+ return max;
29
+ }
30
+
31
+ export function canCheckpointState(
32
+ lsn: string,
33
+ state: Pick<CheckpointStateInput, 'snapshotDone' | 'lastCheckpointLsn' | 'noCheckpointBefore'>
34
+ ): boolean {
35
+ return (
36
+ state.snapshotDone === true &&
37
+ (state.lastCheckpointLsn == null || state.lastCheckpointLsn <= lsn) &&
38
+ (state.noCheckpointBefore == null || state.noCheckpointBefore <= lsn)
39
+ );
40
+ }
41
+
42
+ export function calculateCheckpointState(input: CheckpointStateInput): CheckpointStateResult {
43
+ const canCheckpoint = canCheckpointState(input.lsn, input);
44
+ const newKeepaliveOp = canCheckpoint ? null : maxOpId(input.keepaliveOp, input.persistedOp);
45
+ const newLastCheckpoint = canCheckpoint
46
+ ? maxOpId(input.lastCheckpoint, input.persistedOp, input.keepaliveOp)
47
+ : input.lastCheckpoint;
48
+ const notEmpty =
49
+ input.createEmptyCheckpoints || input.keepaliveOp !== newKeepaliveOp || input.lastCheckpoint !== newLastCheckpoint;
50
+
51
+ return {
52
+ canCheckpoint,
53
+ checkpointBlocked: !canCheckpoint,
54
+ checkpointCreated: canCheckpoint && notEmpty,
55
+ notEmpty,
56
+ newKeepaliveOp,
57
+ newLastCheckpoint
58
+ };
59
+ }