@powersync/service-module-mongodb-storage 0.0.0-dev-20260602073133 → 0.0.0-dev-20260803144812

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 (239) hide show
  1. package/CHANGELOG.md +101 -5
  2. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
  3. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
  4. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
  5. package/dist/storage/MongoBucketStorage.d.ts +41 -16
  6. package/dist/storage/MongoBucketStorage.js +226 -71
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +56 -25
  9. package/dist/storage/implementation/MongoBucketBatch.js +110 -62
  10. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  11. package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
  12. package/dist/storage/implementation/MongoChecksums.js +36 -121
  13. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  14. package/dist/storage/implementation/MongoCompactor.d.ts +17 -19
  15. package/dist/storage/implementation/MongoCompactor.js +80 -263
  16. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  17. package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
  18. package/dist/storage/implementation/MongoHydrationState.js +49 -0
  19. package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
  20. package/dist/storage/implementation/MongoParameterCompactor.d.ts +7 -6
  21. package/dist/storage/implementation/MongoParameterCompactor.js +25 -2
  22. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  23. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
  24. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
  25. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  27. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  28. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  29. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  30. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +123 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +39 -4
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +57 -29
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +87 -61
  36. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  38. package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +171 -24
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +8 -0
  44. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  45. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  46. package/dist/storage/implementation/common/PersistedBatch.d.ts +7 -2
  47. package/dist/storage/implementation/common/PersistedBatch.js +12 -0
  48. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  49. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  50. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  51. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  52. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  53. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  54. package/dist/storage/implementation/db.js +17 -15
  55. package/dist/storage/implementation/db.js.map +1 -1
  56. package/dist/storage/implementation/models.d.ts +20 -13
  57. package/dist/storage/implementation/models.js.map +1 -1
  58. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
  59. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +61 -29
  60. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  61. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
  62. package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
  63. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  64. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
  65. package/dist/storage/implementation/v1/MongoCompactorV1.js +251 -2
  66. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  67. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -19
  68. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +82 -58
  69. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  70. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  71. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  72. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  73. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  74. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  75. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  76. package/dist/storage/implementation/v1/models.d.ts +10 -1
  77. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  78. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  79. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  80. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
  81. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +309 -259
  82. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  83. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
  84. package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
  85. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  86. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +40 -2
  87. package/dist/storage/implementation/v3/MongoCompactorV3.js +647 -23
  88. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  89. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +67 -0
  90. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +351 -0
  91. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  92. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +45 -30
  93. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +272 -140
  94. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  95. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +6 -3
  96. package/dist/storage/implementation/v3/PersistedBatchV3.js +84 -21
  97. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  98. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  99. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  100. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  101. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +15 -13
  102. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
  103. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  104. package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
  105. package/dist/storage/implementation/v3/bucket-format.js +77 -0
  106. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  107. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  108. package/dist/storage/implementation/v3/chunking.js +30 -0
  109. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  110. package/dist/storage/implementation/v3/models.d.ts +101 -21
  111. package/dist/storage/implementation/v3/models.js +11 -28
  112. package/dist/storage/implementation/v3/models.js.map +1 -1
  113. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +21 -0
  114. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
  115. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  116. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -0
  117. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  118. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  119. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +70 -0
  120. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
  121. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  122. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
  123. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
  124. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  125. package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
  126. package/dist/storage/implementation/v3/source-table-utils.js +214 -0
  127. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  128. package/dist/storage/storage-index.d.ts +4 -4
  129. package/dist/storage/storage-index.js +4 -4
  130. package/dist/storage/storage-index.js.map +1 -1
  131. package/dist/types/types.d.ts +49 -3
  132. package/dist/types/types.js +48 -1
  133. package/dist/types/types.js.map +1 -1
  134. package/dist/utils/test-utils.d.ts +1 -2
  135. package/dist/utils/test-utils.js +7 -1
  136. package/dist/utils/test-utils.js.map +1 -1
  137. package/dist/utils/util.d.ts +9 -2
  138. package/dist/utils/util.js +128 -4
  139. package/dist/utils/util.js.map +1 -1
  140. package/package.json +12 -19
  141. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  142. package/src/storage/MongoBucketStorage.ts +347 -88
  143. package/src/storage/implementation/MongoBucketBatch.ts +147 -82
  144. package/src/storage/implementation/MongoChecksums.ts +93 -153
  145. package/src/storage/implementation/MongoCompactor.ts +102 -316
  146. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  147. package/src/storage/implementation/MongoParameterCompactor.ts +25 -6
  148. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
  149. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  150. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +154 -0
  151. package/src/storage/implementation/MongoStorageProvider.ts +46 -4
  152. package/src/storage/implementation/MongoSyncBucketStorage.ts +121 -87
  153. package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
  154. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +202 -28
  155. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +9 -0
  156. package/src/storage/implementation/common/PersistedBatch.ts +19 -3
  157. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  158. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  159. package/src/storage/implementation/db.ts +19 -20
  160. package/src/storage/implementation/models.ts +20 -14
  161. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +75 -37
  162. package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
  163. package/src/storage/implementation/v1/MongoCompactorV1.ts +294 -9
  164. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +142 -105
  165. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  166. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  167. package/src/storage/implementation/v1/models.ts +11 -1
  168. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  169. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +382 -321
  170. package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
  171. package/src/storage/implementation/v3/MongoCompactorV3.ts +847 -32
  172. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +482 -0
  173. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +359 -187
  174. package/src/storage/implementation/v3/PersistedBatchV3.ts +118 -37
  175. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  176. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +46 -36
  177. package/src/storage/implementation/v3/bucket-format.ts +97 -0
  178. package/src/storage/implementation/v3/chunking.ts +40 -0
  179. package/src/storage/implementation/v3/models.ts +132 -55
  180. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
  181. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
  182. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
  183. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
  184. package/src/storage/implementation/v3/source-table-utils.ts +392 -0
  185. package/src/storage/storage-index.ts +12 -4
  186. package/src/types/types.ts +58 -1
  187. package/src/utils/test-utils.ts +8 -3
  188. package/src/utils/util.ts +185 -6
  189. package/test/src/__snapshots__/storage.test.ts.snap +0 -75
  190. package/test/src/__snapshots__/storage_sync.test.ts.snap +35 -617
  191. package/test/src/cleanup-stopped-sync-configs.test.ts +671 -0
  192. package/test/src/helpers/MemoryObjectStorage.ts +53 -0
  193. package/test/src/helpers/s3TestFactory.ts +69 -0
  194. package/test/src/object_storage_lifecycle.test.ts +59 -0
  195. package/test/src/setup.ts +6 -1
  196. package/test/src/storage.test.ts +227 -13
  197. package/test/src/storage_compacting.test.ts +1951 -12
  198. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  199. package/test/src/storage_s3_checksums.test.ts +191 -0
  200. package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
  201. package/test/src/storage_s3_reading.test.ts +526 -0
  202. package/test/src/storage_s3_writing.test.ts +135 -0
  203. package/test/src/storage_sync.test.ts +1180 -73
  204. package/test/src/storeCurrentData.test.ts +227 -0
  205. package/test/src/util.ts +2 -1
  206. package/tsconfig.json +1 -4
  207. package/tsconfig.tsbuildinfo +1 -1
  208. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  209. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  210. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  211. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  212. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  213. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  214. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  215. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  216. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  217. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  218. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  219. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  220. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  221. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  222. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  223. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
  224. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
  225. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
  226. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  227. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  228. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  229. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  230. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
  231. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  232. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  233. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  234. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  235. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  236. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  237. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
  238. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
  239. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
@@ -1,5 +1,5 @@
1
1
  import { ReplicationAssertionError } from '@powersync/lib-services-framework';
2
- import { ColumnDescriptor, SourceTable, storage } from '@powersync/service-core';
2
+ import { ColumnDescriptor, InternalOpId, SourceTable, storage } from '@powersync/service-core';
3
3
  import * as bson from 'bson';
4
4
  import { mongoTableId } from '../../../utils/util.js';
5
5
  import { calculateCheckpointState } from '../CheckpointState.js';
@@ -9,22 +9,35 @@ import { SourceRecordStore } from '../common/SourceRecordStore.js';
9
9
  import { PersistedBatchV1 } from './PersistedBatchV1.js';
10
10
  import { SourceRecordStoreV1 } from './SourceRecordStoreV1.js';
11
11
  import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
12
- import { SourceTableDocumentV1, SyncRuleDocumentV1 } from './models.js';
12
+ import { SyncRuleDocumentV1 } from './models.js';
13
13
 
14
14
  export class MongoBucketBatchV1 extends MongoBucketBatch {
15
- declare public readonly db: VersionedPowerSyncMongoV1;
15
+ declare readonly db: VersionedPowerSyncMongoV1;
16
16
 
17
17
  private readonly store: SourceRecordStore;
18
18
  private needsActivation = true;
19
19
  private lastWaitingLogThrottled = 0;
20
20
 
21
+ /**
22
+ * The last persisted op that is not yet covered by a checkpoint, if any.
23
+ *
24
+ * Seeded from the persisted keepalive_op, so that ops persisted before a restart can still be
25
+ * included in the next checkpoint.
26
+ */
27
+ private persisted_op: InternalOpId | null = null;
28
+
21
29
  constructor(options: MongoBucketBatchOptions) {
22
30
  super(options);
23
- this.store = new SourceRecordStoreV1(this.db, this.group_id);
31
+ this.persisted_op = options.keepaliveOp ?? null;
32
+ this.store = new SourceRecordStoreV1(this.db, this.replicationStreamId);
33
+ }
34
+
35
+ protected override recordPersistedOp(lastOp: InternalOpId): void {
36
+ this.persisted_op = lastOp;
24
37
  }
25
38
 
26
39
  protected createPersistedBatch(writtenSize: number): PersistedBatch {
27
- return new PersistedBatchV1(this.db, this.group_id, this.mapping, writtenSize, {
40
+ return new PersistedBatchV1(this.db, this.replicationStreamId, this.mapping, writtenSize, {
28
41
  logger: this.logger
29
42
  });
30
43
  }
@@ -32,15 +45,14 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
32
45
  protected get sourceRecordStore(): SourceRecordStore {
33
46
  return this.store;
34
47
  }
35
-
36
48
  protected async cleanupDroppedSourceTables(_tables: SourceTable[]) {
37
49
  // No-op for V1: source records live in a shared collection.
38
50
  }
39
51
 
40
52
  async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
41
- const syncRules = options.syncRules ?? this.sync_rules;
53
+ const syncRules = options.parsedSyncConfig?.hydratedSyncConfig ?? this.sync_rules;
42
54
  const { connection_id, source } = options;
43
- const { schema, name, objectId, replicaIdColumns, connectionTag } = source;
55
+ const { schema, name, objectId, replicaIdColumns, connectionTag, sendsCompleteRows } = source;
44
56
 
45
57
  const normalizedReplicaIdColumns = replicaIdColumns.map((column) => ({
46
58
  name: column.name,
@@ -50,9 +62,9 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
50
62
 
51
63
  let result: storage.ResolveTablesResult | null = null;
52
64
  await this.db.client.withSession(async (session) => {
53
- const col = this.db.commonSourceTables(this.group_id);
65
+ const col = this.db.sourceTablesV1(this.replicationStreamId);
54
66
  const filter: any = {
55
- group_id: this.group_id,
67
+ group_id: this.replicationStreamId,
56
68
  connection_id,
57
69
  schema_name: schema,
58
70
  table_name: name,
@@ -66,7 +78,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
66
78
  if (doc == null) {
67
79
  doc = {
68
80
  _id: options.idGenerator ? (options.idGenerator() as bson.ObjectId) : new bson.ObjectId(),
69
- group_id: this.group_id,
81
+ group_id: this.replicationStreamId,
70
82
  connection_id,
71
83
  relation_id: objectId,
72
84
  schema_name: schema,
@@ -90,6 +102,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
90
102
  sourceTable.syncEvent = syncRules.tableTriggersEvent(source);
91
103
  sourceTable.syncData = sourceTable.bucketDataSources.length > 0;
92
104
  sourceTable.syncParameters = sourceTable.parameterLookupSources.length > 0;
105
+ sourceTable.storeCurrentData = sendsCompleteRows !== true;
93
106
  sourceTable.snapshotStatus =
94
107
  doc.snapshot_status == null
95
108
  ? undefined
@@ -106,7 +119,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
106
119
  const truncate = await col
107
120
  .find(
108
121
  {
109
- group_id: this.group_id,
122
+ group_id: this.replicationStreamId,
110
123
  connection_id,
111
124
  _id: { $ne: doc._id },
112
125
  $or: truncateFilter
@@ -144,13 +157,13 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
144
157
  }
145
158
 
146
159
  async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
147
- const doc = (await this.db.commonSourceTables(this.group_id).findOne(
160
+ const doc = await this.db.sourceTablesV1(this.replicationStreamId).findOne(
148
161
  {
149
- group_id: this.group_id,
162
+ group_id: this.replicationStreamId,
150
163
  _id: mongoTableId(table.id)
151
164
  },
152
165
  { session: this.session }
153
- )) as SourceTableDocumentV1 | null;
166
+ );
154
167
  if (doc == null) {
155
168
  return null;
156
169
  }
@@ -190,6 +203,8 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
190
203
 
191
204
  await this.flush(options);
192
205
 
206
+ using _ = this.tracer.span('storage', 'commit');
207
+
193
208
  const now = new Date();
194
209
 
195
210
  await this.db.write_checkpoints.updateMany(
@@ -242,7 +257,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
242
257
  };
243
258
 
244
259
  const preUpdateDocument = (await this.db.sync_rules.findOneAndUpdate(
245
- { _id: this.group_id },
260
+ { _id: this.replicationStreamId },
246
261
  [
247
262
  {
248
263
  $set: {
@@ -295,7 +310,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
295
310
 
296
311
  if (preUpdateDocument == null) {
297
312
  throw new ReplicationAssertionError(
298
- 'Failed to update checkpoint - no matching sync_rules document for _id: ' + this.group_id
313
+ 'Failed to update checkpoint - no matching sync_rules document for _id: ' + this.replicationStreamId
299
314
  );
300
315
  }
301
316
 
@@ -329,7 +344,6 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
329
344
  await this.autoActivate(lsn);
330
345
  await this.db.notifyCheckpoint();
331
346
  this.persisted_op = null;
332
- this.last_checkpoint_lsn = lsn;
333
347
  if (checkpointState.newLastCheckpoint != null) {
334
348
  await this.sourceRecordStore.postCommitCleanup(checkpointState.newLastCheckpoint, this.logger);
335
349
  }
@@ -346,23 +360,30 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
346
360
  }
347
361
 
348
362
  async setResumeLsn(lsn: string): Promise<void> {
363
+ using _ = this.tracer.span('storage', 'set_resume_lsn');
349
364
  await this.db.sync_rules.updateOne(
350
365
  {
351
- _id: this.group_id
366
+ _id: this.replicationStreamId
352
367
  },
353
368
  {
354
369
  $set: {
355
370
  snapshot_lsn: lsn
356
371
  }
357
372
  },
358
- { session: this.session }
373
+ {
374
+ session: this.session,
375
+ // Losing occasional resume LSN is fine. That may mean reprocessing
376
+ // some source changes in some edge cases, which is not an issue since
377
+ // changes are processed in an idempotent way.
378
+ writeConcern: { w: 1 }
379
+ }
359
380
  );
360
381
  }
361
382
 
362
383
  async markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void> {
363
384
  await this.db.sync_rules.updateOne(
364
385
  {
365
- _id: this.group_id
386
+ _id: this.replicationStreamId
366
387
  },
367
388
  {
368
389
  $set: {
@@ -380,17 +401,24 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
380
401
  async markSnapshotDone(no_checkpoint_before_lsn: string, options?: { throwOnConflict?: boolean }): Promise<void> {
381
402
  await this.withTransaction(async () => {
382
403
  // Protect against race conditions
383
- const count = await this.db.commonSourceTables(this.group_id).countDocuments(
384
- {
385
- group_id: this.group_id,
386
- snapshot_done: false
387
- },
388
- { session: this.session }
389
- );
390
- if (count > 0) {
404
+ const blockingTables = await this.db
405
+ .sourceTablesV1(this.replicationStreamId)
406
+ .find(
407
+ {
408
+ group_id: this.replicationStreamId,
409
+ snapshot_done: false
410
+ },
411
+ {
412
+ session: this.session,
413
+ projection: { schema_name: 1, table_name: 1 }
414
+ }
415
+ )
416
+ .toArray();
417
+
418
+ if (blockingTables.length > 0) {
391
419
  if (options?.throwOnConflict ?? true) {
392
420
  throw new ReplicationAssertionError(
393
- `Cannot mark snapshot done while ${count} source table${count == 1 ? '' : 's'} still require snapshotting`
421
+ `Cannot mark snapshot done while source tables still require snapshotting. Tables: ${blockingTables.map((t) => `${t.schema_name}.${t.table_name}`).join(', ')}`
394
422
  );
395
423
  } else {
396
424
  return;
@@ -404,7 +432,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
404
432
  async markTableSnapshotRequired(_table: storage.SourceTable): Promise<void> {
405
433
  await this.db.sync_rules.updateOne(
406
434
  {
407
- _id: this.group_id
435
+ _id: this.replicationStreamId
408
436
  },
409
437
  {
410
438
  $set: {
@@ -423,7 +451,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
423
451
  const ids = tables.map((table) => mongoTableId(table.id));
424
452
 
425
453
  await this.withTransaction(async () => {
426
- await this.db.commonSourceTables(this.group_id).updateMany(
454
+ await this.db.sourceTablesV1(this.replicationStreamId).updateMany(
427
455
  { _id: { $in: ids } },
428
456
  {
429
457
  $set: {
@@ -439,7 +467,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
439
467
  if (no_checkpoint_before_lsn != null) {
440
468
  await this.db.sync_rules.updateOne(
441
469
  {
442
- _id: this.group_id
470
+ _id: this.replicationStreamId
443
471
  },
444
472
  {
445
473
  $set: {
@@ -467,12 +495,20 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
467
495
 
468
496
  const session = this.session;
469
497
  let activated = false;
498
+ let needsFutureActivationCheck = true;
470
499
  await session.withTransaction(async () => {
471
- const doc = (await this.db.sync_rules.findOne({ _id: this.group_id }, { session })) as SyncRuleDocumentV1;
500
+ // Reset on transaction retries.
501
+ activated = false;
502
+ needsFutureActivationCheck = true;
503
+
504
+ const doc = (await this.db.sync_rules.findOne(
505
+ { _id: this.replicationStreamId },
506
+ { session }
507
+ )) as SyncRuleDocumentV1;
472
508
  if (doc && doc.state == storage.SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
473
509
  await this.db.sync_rules.updateOne(
474
510
  {
475
- _id: this.group_id
511
+ _id: this.replicationStreamId
476
512
  },
477
513
  {
478
514
  $set: {
@@ -484,7 +520,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
484
520
 
485
521
  await this.db.sync_rules.updateMany(
486
522
  {
487
- _id: { $ne: this.group_id },
523
+ _id: { $ne: this.replicationStreamId },
488
524
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
489
525
  },
490
526
  {
@@ -496,13 +532,15 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
496
532
  );
497
533
  activated = true;
498
534
  } else if (doc?.state != storage.SyncRuleState.PROCESSING) {
499
- this.needsActivation = false;
535
+ needsFutureActivationCheck = false;
500
536
  }
501
537
  });
502
538
  if (activated) {
503
539
  this.logger.info(`Activated new replication stream at ${lsn}`);
504
540
  await this.db.notifyCheckpoint();
505
541
  this.needsActivation = false;
542
+ } else if (!needsFutureActivationCheck) {
543
+ this.needsActivation = false;
506
544
  }
507
545
  }
508
546
  }
@@ -1,20 +1,31 @@
1
+ import * as lib_mongo from '@powersync/lib-service-mongodb';
1
2
  import {
3
+ addPartialChecksums,
2
4
  bson,
3
5
  BucketChecksum,
4
6
  FetchPartialBucketChecksum,
5
7
  InternalOpId,
6
- PartialChecksumMap
8
+ isPartialChecksum,
9
+ PartialChecksumMap,
10
+ PartialOrFullChecksum
7
11
  } from '@powersync/service-core';
8
- import { FetchPartialBucketChecksumByBucket, MongoChecksums } from '../MongoChecksums.js';
12
+ import {
13
+ checksumFromAggregate,
14
+ DEFAULT_OPERATION_BATCH_LIMIT,
15
+ FetchPartialBucketChecksumByBucket,
16
+ MongoChecksums,
17
+ MongoChecksumSessionContext
18
+ } from '../MongoChecksums.js';
9
19
  import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
10
-
11
20
  export class MongoChecksumsV1 extends MongoChecksums {
12
21
  declare protected readonly db: VersionedPowerSyncMongoV1;
13
22
 
14
23
  async computePartialChecksumsDirectByBucket(
15
- batch: FetchPartialBucketChecksumByBucket[]
24
+ batch: FetchPartialBucketChecksumByBucket[],
25
+ context: MongoChecksumSessionContext
16
26
  ): Promise<PartialChecksumMap> {
17
- return this.computePartialChecksumsForCollection(batch, this.db.bucketDataV1, (request) => ({
27
+ const collection = this.db.bucketDataV1;
28
+ const createFilter = (request: FetchPartialBucketChecksumByBucket) => ({
18
29
  _id: {
19
30
  $gt: {
20
31
  g: this.group_id,
@@ -27,11 +38,134 @@ export class MongoChecksumsV1 extends MongoChecksums {
27
38
  o: request.end
28
39
  }
29
40
  }
30
- }));
41
+ });
42
+
43
+ const batchLimit = this.options?.operationBatchLimit ?? DEFAULT_OPERATION_BATCH_LIMIT;
44
+
45
+ // Map requests by bucket. We adjust this as we get partial results.
46
+ let requests = new Map<string, FetchPartialBucketChecksumByBucket>();
47
+ for (let request of batch) {
48
+ requests.set(request.bucket, request);
49
+ }
50
+
51
+ const partialChecksums = new Map<string, PartialOrFullChecksum>();
52
+
53
+ while (requests.size > 0) {
54
+ const filters = Array.from(requests.values(), createFilter);
55
+
56
+ // Historically, checksum may be stored as 'int' or 'double'.
57
+ // More recently, this should be a 'long'.
58
+ // $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
59
+ const checksumLong = this.storageConfig.longChecksums ? '$checksum' : { $toLong: '$checksum' };
60
+
61
+ // Aggregate over a max of `batchLimit` operations at a time.
62
+ // Let's say we have 3 buckets (A, B, C), each with 10 operations, and our batch limit is 12.
63
+ // Then we'll do three batches:
64
+ // 1. Query: A[1-end], B[1-end], C[1-end]
65
+ // Returns: A[1-10], B[1-2]
66
+ // 2. Query: B[3-end], C[1-end]
67
+ // Returns: B[3-10], C[1-4]
68
+ // 3. Query: C[5-end]
69
+ // Returns: C[5-10]
70
+ const aggregate = await collection
71
+ .aggregate(
72
+ [
73
+ {
74
+ $match: {
75
+ $or: filters
76
+ }
77
+ },
78
+ // sort and limit _before_ grouping
79
+ { $sort: { _id: 1 } },
80
+ { $limit: batchLimit },
81
+ {
82
+ $group: {
83
+ _id: '$_id.b',
84
+ checksum_total: { $sum: checksumLong },
85
+ count: { $sum: 1 },
86
+ has_clear_op: {
87
+ $max: {
88
+ $cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
89
+ }
90
+ },
91
+ last_op: { $max: '$_id.o' }
92
+ }
93
+ },
94
+ // Sort the aggregated results (100 max, so should be fast).
95
+ // This is important to identify which buckets we have partial data for.
96
+ { $sort: { _id: 1 } }
97
+ ],
98
+ {
99
+ ...context.readOptions,
100
+ maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS
101
+ }
102
+ )
103
+ .toArray()
104
+ .catch((e) => {
105
+ throw lib_mongo.mapQueryError(e, 'while reading checksums');
106
+ });
107
+
108
+ let batchCount = 0;
109
+ let limitReached = false;
110
+ for (let doc of aggregate) {
111
+ const bucket = doc._id;
112
+ const checksum = checksumFromAggregate(doc);
113
+
114
+ const existing = partialChecksums.get(bucket);
115
+ if (existing != null) {
116
+ partialChecksums.set(bucket, addPartialChecksums(bucket, existing, checksum));
117
+ } else {
118
+ partialChecksums.set(bucket, checksum);
119
+ }
120
+
121
+ batchCount += doc.count;
122
+ if (batchCount == batchLimit) {
123
+ // Limit reached. Request more in the next batch.
124
+ // Note that this only affects the _last_ bucket in a batch.
125
+ limitReached = true;
126
+ const req = requests.get(bucket);
127
+ requests.set(bucket, {
128
+ ...req!,
129
+ start: doc.last_op
130
+ });
131
+ } else {
132
+ // All done for this bucket
133
+ requests.delete(bucket);
134
+ }
135
+ }
136
+ if (!limitReached) {
137
+ break;
138
+ }
139
+ }
140
+
141
+ return new Map<string, PartialOrFullChecksum>(
142
+ batch.map((request) => {
143
+ const bucket = request.bucket;
144
+ let partialChecksum = partialChecksums.get(bucket);
145
+ // Could be null if we got no data
146
+ if (partialChecksum == null) {
147
+ partialChecksum = {
148
+ bucket,
149
+ partialCount: 0,
150
+ partialChecksum: 0
151
+ };
152
+ }
153
+ if (request.start == null && isPartialChecksum(partialChecksum)) {
154
+ partialChecksum = {
155
+ bucket,
156
+ count: partialChecksum.partialCount,
157
+ checksum: partialChecksum.partialChecksum
158
+ };
159
+ }
160
+
161
+ return [bucket, partialChecksum];
162
+ })
163
+ );
31
164
  }
32
165
 
33
166
  protected async fetchPreStates(
34
- batch: FetchPartialBucketChecksum[]
167
+ batch: FetchPartialBucketChecksum[],
168
+ context: MongoChecksumSessionContext
35
169
  ): Promise<Map<string, { opId: InternalOpId; checksum: BucketChecksum }>> {
36
170
  const preFilters = batch
37
171
  .filter((request) => request.start == null)
@@ -49,9 +183,14 @@ export class MongoChecksumsV1 extends MongoChecksums {
49
183
  }
50
184
 
51
185
  const states = await this.db.bucketStateV1
52
- .find({
53
- $or: preFilters
54
- })
186
+ .find(
187
+ {
188
+ $or: preFilters
189
+ },
190
+ {
191
+ ...context.readOptions
192
+ }
193
+ )
55
194
  .toArray();
56
195
 
57
196
  for (const state of states) {
@@ -69,7 +208,10 @@ export class MongoChecksumsV1 extends MongoChecksums {
69
208
  return preStates;
70
209
  }
71
210
 
72
- protected async computePartialChecksumsInternal(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
73
- return this.computePartialChecksumsDirectByBucket(batch);
211
+ protected async computePartialChecksumsInternal(
212
+ batch: FetchPartialBucketChecksum[],
213
+ context: MongoChecksumSessionContext
214
+ ): Promise<PartialChecksumMap> {
215
+ return this.computePartialChecksumsDirectByBucket(batch, context);
74
216
  }
75
217
  }