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

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 (275) hide show
  1. package/CHANGELOG.md +177 -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 +71 -18
  6. package/dist/storage/MongoBucketStorage.js +516 -202
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
  9. package/dist/storage/implementation/MongoBucketBatch.js +140 -66
  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 +28 -90
  15. package/dist/storage/implementation/MongoCompactor.js +87 -481
  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 +82 -8
  21. package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
  22. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  23. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
  24. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -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 +125 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +41 -4
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
  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 +23 -11
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  44. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  45. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  46. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +28 -0
  47. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  48. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  49. package/dist/storage/implementation/common/PersistedBatch.d.ts +34 -2
  50. package/dist/storage/implementation/common/PersistedBatch.js +63 -10
  51. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  52. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  53. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  54. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  55. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  56. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  57. package/dist/storage/implementation/db.d.ts +4 -1
  58. package/dist/storage/implementation/db.js +31 -16
  59. package/dist/storage/implementation/db.js.map +1 -1
  60. package/dist/storage/implementation/models.d.ts +55 -13
  61. package/dist/storage/implementation/models.js.map +1 -1
  62. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
  63. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
  64. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  65. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
  66. package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
  67. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  68. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
  69. package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -8
  70. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  71. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  72. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  73. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  74. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +33 -27
  75. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
  76. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  77. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  78. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  79. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  80. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  81. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  82. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  83. package/dist/storage/implementation/v1/models.d.ts +10 -1
  84. package/dist/storage/implementation/v1/models.js +1 -0
  85. package/dist/storage/implementation/v1/models.js.map +1 -1
  86. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  87. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  88. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  89. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  90. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  91. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  92. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +16 -5
  93. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
  94. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  95. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
  96. package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
  97. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  98. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
  99. package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
  100. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  101. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
  102. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
  103. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
  104. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
  105. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
  106. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  107. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
  108. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
  109. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  110. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  111. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  112. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  113. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +8 -3
  114. package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
  115. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  116. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  117. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  118. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  119. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +26 -14
  120. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
  121. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  122. package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
  123. package/dist/storage/implementation/v3/bucket-format.js +79 -0
  124. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  125. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  126. package/dist/storage/implementation/v3/chunking.js +30 -0
  127. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  128. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  129. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  130. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  131. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  132. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  133. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  134. package/dist/storage/implementation/v3/models.d.ts +187 -23
  135. package/dist/storage/implementation/v3/models.js +11 -28
  136. package/dist/storage/implementation/v3/models.js.map +1 -1
  137. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
  138. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
  139. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  140. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
  141. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  142. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  143. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
  144. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
  145. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  146. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
  147. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
  148. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  149. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +67 -0
  150. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
  151. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  152. package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
  153. package/dist/storage/implementation/v3/source-table-utils.js +206 -0
  154. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  155. package/dist/storage/storage-index.d.ts +5 -4
  156. package/dist/storage/storage-index.js +5 -4
  157. package/dist/storage/storage-index.js.map +1 -1
  158. package/dist/types/types.d.ts +71 -3
  159. package/dist/types/types.js +58 -2
  160. package/dist/types/types.js.map +1 -1
  161. package/dist/utils/test-utils.d.ts +1 -2
  162. package/dist/utils/test-utils.js +7 -1
  163. package/dist/utils/test-utils.js.map +1 -1
  164. package/dist/utils/util.d.ts +9 -2
  165. package/dist/utils/util.js +131 -5
  166. package/dist/utils/util.js.map +1 -1
  167. package/package.json +13 -19
  168. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  169. package/src/storage/MongoBucketStorage.ts +728 -242
  170. package/src/storage/implementation/MongoBucketBatch.ts +196 -91
  171. package/src/storage/implementation/MongoChecksums.ts +93 -153
  172. package/src/storage/implementation/MongoCompactor.ts +106 -631
  173. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  174. package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
  175. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
  176. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  177. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
  178. package/src/storage/implementation/MongoStorageProvider.ts +48 -4
  179. package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
  180. package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
  181. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
  182. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  183. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
  184. package/src/storage/implementation/common/PersistedBatch.ts +83 -15
  185. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  186. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  187. package/src/storage/implementation/db.ts +40 -22
  188. package/src/storage/implementation/models.ts +58 -14
  189. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
  190. package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
  191. package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
  192. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  193. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
  194. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  195. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  196. package/src/storage/implementation/v1/models.ts +12 -1
  197. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  198. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  199. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
  200. package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
  201. package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
  202. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
  203. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
  204. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
  205. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  206. package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
  207. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  208. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
  209. package/src/storage/implementation/v3/bucket-format.ts +99 -0
  210. package/src/storage/implementation/v3/chunking.ts +40 -0
  211. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  212. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  213. package/src/storage/implementation/v3/models.ts +228 -58
  214. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
  215. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
  216. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
  217. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
  218. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
  219. package/src/storage/implementation/v3/source-table-utils.ts +398 -0
  220. package/src/storage/storage-index.ts +14 -4
  221. package/src/types/types.ts +76 -2
  222. package/src/utils/test-utils.ts +8 -3
  223. package/src/utils/util.ts +188 -7
  224. package/test/src/__snapshots__/storage.test.ts.snap +4 -54
  225. package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
  226. package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
  227. package/test/src/compact-utils.test.ts +226 -0
  228. package/test/src/helpers/MemoryObjectStorage.ts +61 -0
  229. package/test/src/helpers/s3TestFactory.ts +69 -0
  230. package/test/src/object_storage_lifecycle.test.ts +59 -0
  231. package/test/src/object_storage_usage.test.ts +255 -0
  232. package/test/src/parameter_compacting_v1.test.ts +120 -0
  233. package/test/src/parameter_compacting_v3.test.ts +192 -0
  234. package/test/src/parameter_compaction_fence.test.ts +276 -0
  235. package/test/src/setup.ts +6 -1
  236. package/test/src/storage.test.ts +478 -13
  237. package/test/src/storage_compacting.test.ts +2516 -107
  238. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  239. package/test/src/storage_s3_checksums.test.ts +190 -0
  240. package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
  241. package/test/src/storage_s3_reading.test.ts +768 -0
  242. package/test/src/storage_s3_writing.test.ts +136 -0
  243. package/test/src/storage_sync.test.ts +1507 -76
  244. package/test/src/storeCurrentData.test.ts +227 -0
  245. package/test/src/util.ts +3 -2
  246. package/tsconfig.json +1 -4
  247. package/tsconfig.tsbuildinfo +1 -1
  248. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  249. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  250. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  251. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  252. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  253. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  254. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  255. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  256. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  257. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  258. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  259. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  260. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  261. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  262. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  263. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  264. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  265. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  266. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  267. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
  268. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  269. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  270. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  271. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  272. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  273. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  274. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
  275. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
@@ -1,5 +1,11 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
- import { HydratedSyncConfig, SqlEventDescriptor, SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
2
+ import {
3
+ EventDefinitionId,
4
+ HydratedEventDescriptor,
5
+ HydratedSyncConfig,
6
+ SqliteRow,
7
+ SqliteValue
8
+ } from '@powersync/service-sync-rules';
3
9
  import * as bson from 'bson';
4
10
 
5
11
  import {
@@ -12,6 +18,7 @@ import {
12
18
  ServiceError
13
19
  } from '@powersync/lib-services-framework';
14
20
  import {
21
+ BucketDefinitionMapping,
15
22
  BucketStorageMarkRecordUnavailable,
16
23
  deserializeBson,
17
24
  InternalOpId,
@@ -23,14 +30,15 @@ import {
23
30
  } from '@powersync/service-core';
24
31
  import * as timers from 'node:timers/promises';
25
32
  import { mongoTableId } from '../../utils/util.js';
26
- import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
27
33
  import { PersistedBatch } from './common/PersistedBatch.js';
28
34
  import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
29
35
  import type { VersionedPowerSyncMongo } from './db.js';
30
36
  import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
31
37
  import { MongoIdSequence } from './MongoIdSequence.js';
32
- import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
38
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
33
39
  import { OperationBatch, RecordOperation } from './OperationBatch.js';
40
+ import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
41
+ import { createObjectStorageUsageWriterId } from './v3/object-storage/ObjectStorageUsage.js';
34
42
 
35
43
  // Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
36
44
  // While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
@@ -41,15 +49,25 @@ const replicationMutex = new utils.Mutex();
41
49
 
42
50
  export interface MongoBucketBatchOptions {
43
51
  db: VersionedPowerSyncMongo;
44
- syncRules: HydratedSyncConfig;
45
- groupId: number;
46
- slotName: string;
47
- syncConfigId?: bson.ObjectId | null;
48
- lastCheckpointLsn: string | null;
49
- keepaliveOp: InternalOpId | null;
52
+ /**
53
+ * The parsed sync config set for this batch.
54
+ *
55
+ * The batch derives both the hydrated sync rules and the bucket definition mapping from
56
+ * this single set, so they always come from the same parse. Do not add separate
57
+ * syncRules/mapping options - pairing values from different parses is exactly the bug
58
+ * this shape prevents.
59
+ */
60
+ parsedSyncConfig: MongoParsedSyncConfigSet;
61
+ replicationStreamId: number;
62
+ replicationStreamName: string;
63
+ syncConfigIds?: bson.ObjectId[];
64
+ /**
65
+ * Seeds the in-memory persisted-op tracking for v1 storage. Not used by v3 storage, which
66
+ * tracks the persisted-op head durably on the replication stream document instead.
67
+ */
68
+ keepaliveOp?: InternalOpId | null;
50
69
  resumeFromLsn: string | null;
51
70
  storeCurrentData: boolean;
52
- mapping: BucketDefinitionMapping;
53
71
  /**
54
72
  * Set to true for initial replication.
55
73
  */
@@ -60,6 +78,13 @@ export interface MongoBucketBatchOptions {
60
78
 
61
79
  logger: Logger;
62
80
  tracer?: PerformanceTracer<'storage' | 'evaluate'>;
81
+ /**
82
+ * Aborts in-flight object storage uploads when replication stops.
83
+ */
84
+ signal?: AbortSignal;
85
+
86
+ objectStorage?: ObjectStorage;
87
+ inlineThresholdBytes?: number;
63
88
  }
64
89
 
65
90
  export abstract class MongoBucketBatch
@@ -70,35 +95,34 @@ export abstract class MongoBucketBatch
70
95
  protected logger: Logger;
71
96
 
72
97
  private readonly client: mongo.MongoClient;
73
- public readonly db: VersionedPowerSyncMongo;
98
+ readonly db: VersionedPowerSyncMongo;
74
99
  public readonly session: mongo.ClientSession;
75
100
  protected readonly sync_rules: HydratedSyncConfig;
76
101
 
77
- protected readonly group_id: number;
102
+ protected readonly replicationStreamId: number;
78
103
 
79
- private readonly slot_name: string;
104
+ private readonly replicationStreamName: string;
105
+ /**
106
+ * Source-level setting for whether raw row data should be stored in current_data.
107
+ *
108
+ * Some sources always send complete rows (MongoDB, MySQL with binlog_row_image=full),
109
+ * in which case this is false for the whole batch. For sources where it depends on the
110
+ * table (Postgres REPLICA IDENTITY), this is true and the decision is refined per-table
111
+ * via SourceTable.storeCurrentData. The effective per-record value is the conjunction of
112
+ * the two.
113
+ */
80
114
  private readonly storeCurrentData: boolean;
81
115
  public readonly skipExistingRows: boolean;
82
116
  protected readonly mapping: BucketDefinitionMapping;
117
+ protected readonly objectStorageUsageWriterId = createObjectStorageUsageWriterId();
83
118
 
84
119
  private batch: OperationBatch | null = null;
85
- private write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
120
+ protected write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
86
121
  private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
87
122
  private hooks: storage.StorageHooks | undefined;
88
123
  private clearedError = false;
89
124
 
90
- private tracer: PerformanceTracer<'storage' | 'evaluate'>;
91
-
92
- /**
93
- * Last LSN received associated with a checkpoint.
94
- *
95
- * This could be either:
96
- * 1. A commit LSN.
97
- * 2. A keepalive message LSN.
98
- */
99
- protected last_checkpoint_lsn: string | null = null;
100
-
101
- protected persisted_op: InternalOpId | null = null;
125
+ protected tracer: PerformanceTracer<'storage' | 'evaluate'>;
102
126
 
103
127
  /**
104
128
  * Last written op, if any. This may not reflect a consistent checkpoint.
@@ -106,9 +130,9 @@ export abstract class MongoBucketBatch
106
130
  public last_flushed_op: InternalOpId | null = null;
107
131
 
108
132
  /**
109
- * lastCheckpointLsn is the last consistent commit.
133
+ * LSN to resume replication from.
110
134
  *
111
- * While that is generally a "safe" point to resume from, there are cases where we may want to resume from a different point:
135
+ * This is typically the last commit LSN, but there are cases where it differs:
112
136
  * 1. After an initial snapshot, we don't have a consistent commit yet, but need to resume from the snapshot LSN.
113
137
  * 2. If "no_checkpoint_before_lsn" is set far in advance, it may take a while to reach that point. We
114
138
  * may want to resume at incremental points before that.
@@ -123,34 +147,28 @@ export abstract class MongoBucketBatch
123
147
  this.options = options;
124
148
  this.client = options.db.client;
125
149
  this.db = options.db;
126
- this.group_id = options.groupId;
127
- this.last_checkpoint_lsn = options.lastCheckpointLsn;
150
+ this.replicationStreamId = options.replicationStreamId;
128
151
  this.resumeFromLsn = options.resumeFromLsn;
129
152
  this.session = this.client.startSession();
130
- this.slot_name = options.slotName;
131
- this.sync_rules = options.syncRules;
153
+ this.replicationStreamName = options.replicationStreamName;
154
+ this.sync_rules = options.parsedSyncConfig.hydratedSyncConfig;
132
155
  this.storeCurrentData = options.storeCurrentData;
133
- this.mapping = options.mapping;
156
+ this.mapping = options.parsedSyncConfig.mapping;
134
157
  this.skipExistingRows = options.skipExistingRows;
135
158
  this.markRecordUnavailable = options.markRecordUnavailable;
136
159
  this.hooks = options.hooks;
137
160
  this.batch = new OperationBatch();
138
161
 
139
- this.persisted_op = options.keepaliveOp ?? null;
140
162
  this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
141
163
  }
142
164
 
143
165
  addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void {
144
166
  this.write_checkpoint_batch.push({
145
167
  ...checkpoint,
146
- sync_rules_id: this.group_id
168
+ sync_rules_id: this.replicationStreamId
147
169
  });
148
170
  }
149
171
 
150
- get lastCheckpointLsn() {
151
- return this.last_checkpoint_lsn;
152
- }
153
-
154
172
  abstract resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
155
173
 
156
174
  protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
@@ -178,6 +196,11 @@ export abstract class MongoBucketBatch
178
196
  no_checkpoint_before_lsn?: string
179
197
  ): Promise<storage.SourceTable[]>;
180
198
 
199
+ protected abstract batchCreateCustomWriteCheckpoints(session: mongo.ClientSession, opId: InternalOpId): Promise<void>;
200
+
201
+ /** Perform any version-specific setup required before writing custom checkpoints. */
202
+ protected async prepareCustomWriteCheckpoints(): Promise<void> {}
203
+
181
204
  async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
182
205
  let result: storage.FlushedResult | null = null;
183
206
  // One flush may be split over multiple transactions.
@@ -195,25 +218,37 @@ export abstract class MongoBucketBatch
195
218
  const batch = this.batch;
196
219
  let last_op: InternalOpId | null = null;
197
220
  let resumeBatch: OperationBatch | null = null;
221
+ let clearedError = false;
198
222
 
199
223
  using _ = this.tracer.span('storage', 'flush');
200
224
 
201
225
  await this.hooks?.beforeBatchFlush?.(this);
226
+ if (this.write_checkpoint_batch.length > 0) {
227
+ // Collection/index creation cannot run inside the replication transaction.
228
+ await this.prepareCustomWriteCheckpoints();
229
+ }
202
230
 
203
231
  await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
232
+ clearedError = false;
204
233
  if (batch != null) {
205
- resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
234
+ const result = await this.replicateBatch(session, batch, opSeq, options);
235
+ resumeBatch = result.resumeBatch;
236
+ clearedError ||= result.clearedError;
206
237
  }
207
238
 
208
239
  if (this.write_checkpoint_batch.length > 0) {
209
240
  this.logger.info(`Writing ${this.write_checkpoint_batch.length} custom write checkpoints`);
210
- await batchCreateCustomWriteCheckpoints(this.db, session, this.write_checkpoint_batch, opSeq.next());
241
+ await this.batchCreateCustomWriteCheckpoints(session, opSeq.next());
211
242
  this.write_checkpoint_batch = [];
212
243
  }
213
244
 
214
245
  last_op = opSeq.last();
215
246
  });
216
247
 
248
+ if (clearedError) {
249
+ this.clearedError = true;
250
+ }
251
+
217
252
  // null if done, set if we need another flush
218
253
  this.batch = resumeBatch;
219
254
 
@@ -221,7 +256,7 @@ export abstract class MongoBucketBatch
221
256
  throw new ReplicationAssertionError('Unexpected last_op == null');
222
257
  }
223
258
 
224
- this.persisted_op = last_op;
259
+ this.recordPersistedOp(last_op);
225
260
  this.last_flushed_op = last_op;
226
261
  await this.hooks?.afterBatchFlush?.(this);
227
262
  return { flushed_op: last_op };
@@ -232,11 +267,15 @@ export abstract class MongoBucketBatch
232
267
  batch: OperationBatch,
233
268
  op_seq: MongoIdSequence,
234
269
  options?: storage.BucketBatchCommitOptions
235
- ): Promise<OperationBatch | null> {
270
+ ): Promise<{ resumeBatch: OperationBatch | null; clearedError: boolean }> {
236
271
  let sizes: Map<string, number> | undefined = undefined;
237
272
  using _ = this.tracer.span('storage', 'replicate_batch');
238
- if (this.storeCurrentData && !this.skipExistingRows) {
239
- // We skip this step if we don't store current_data, since the sizes will
273
+ // Only look up current_data sizes if the batch stores current_data and at least one
274
+ // table in it does too (per-table can disable it, e.g. Postgres REPLICA IDENTITY FULL).
275
+ const anyTableStoresCurrentData =
276
+ this.storeCurrentData && batch.batch.some((r) => r.record.sourceTable.storeCurrentData);
277
+ if (anyTableStoresCurrentData && !this.skipExistingRows) {
278
+ // We skip this step if no tables store current_data, since the sizes will
240
279
  // always be small in that case.
241
280
 
242
281
  // With skipExistingRows, we don't load the full documents into memory,
@@ -249,10 +288,14 @@ export abstract class MongoBucketBatch
249
288
  // (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
250
289
  // the order of processing, which then becomes really tricky to manage.
251
290
  // This now takes 2+ queries, but doesn't have any issues with order of operations.
252
- const sizeLookups = batch.batch.map((r) => ({
253
- sourceTableId: mongoTableId(r.record.sourceTable.id),
254
- replicaId: r.beforeId
255
- }));
291
+ // Within this branch this.storeCurrentData is true, so the per-table flag is the
292
+ // effective value - only look up sizes for tables that actually store current_data.
293
+ const sizeLookups = batch.batch
294
+ .filter((r) => r.record.sourceTable.storeCurrentData)
295
+ .map((r) => ({
296
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
297
+ replicaId: r.beforeId
298
+ }));
256
299
 
257
300
  sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
258
301
  }
@@ -329,12 +372,17 @@ export abstract class MongoBucketBatch
329
372
  }
330
373
  }
331
374
 
332
- if (didFlush) {
375
+ const clearedError = didFlush && !this.clearedError;
376
+ if (clearedError) {
377
+ // No need to clear an error more than once per batch, since an error would always result in restarting the batch.
333
378
  using _ = this.tracer.span('storage', 'clear_error');
334
- await this.clearError();
379
+ await this.clearError(this.session);
335
380
  }
336
381
 
337
- return resumeBatch?.hasData() ? resumeBatch : null;
382
+ return {
383
+ resumeBatch: resumeBatch?.hasData() ? resumeBatch : null,
384
+ clearedError
385
+ };
338
386
  }
339
387
 
340
388
  private saveOperation(
@@ -348,6 +396,9 @@ export abstract class MongoBucketBatch
348
396
  const afterId = operation.afterId;
349
397
  let after = record.after;
350
398
  const sourceTable = record.sourceTable;
399
+ // Effective per-record flag: store current_data only if both the batch (source-level,
400
+ // e.g. Postgres) and the table (e.g. non-FULL replica identity) require it.
401
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
351
402
 
352
403
  let existing_buckets: LoadedSourceRecord['buckets'] = [];
353
404
  let new_buckets: LoadedSourceRecord['buckets'] = [];
@@ -376,7 +427,7 @@ export abstract class MongoBucketBatch
376
427
  // Not an error if we re-apply a transaction
377
428
  existing_buckets = [];
378
429
  existing_lookups = [];
379
- if (!isCompleteRow(this.storeCurrentData, after!)) {
430
+ if (!isCompleteRow(storeCurrentData, after!)) {
380
431
  if (this.markRecordUnavailable != null) {
381
432
  // This will trigger a "resnapshot" of the record.
382
433
  // This is not relevant if storeCurrentData is false, since we'll get the full row
@@ -392,7 +443,7 @@ export abstract class MongoBucketBatch
392
443
  } else {
393
444
  existing_buckets = result.buckets;
394
445
  existing_lookups = result.lookups;
395
- if (this.storeCurrentData && result.data != null) {
446
+ if (storeCurrentData && result.data != null) {
396
447
  const data = deserializeBson(result.data.buffer) as SqliteRow;
397
448
  after = storage.mergeToast<SqliteValue>(after!, data);
398
449
  }
@@ -403,7 +454,9 @@ export abstract class MongoBucketBatch
403
454
  // Not an error if we re-apply a transaction
404
455
  existing_buckets = [];
405
456
  existing_lookups = [];
406
- // Log to help with debugging if there was a consistency issue
457
+ // Log to help with debugging if there was a consistency issue.
458
+ // Gate on the batch-level flag: FULL tables (per-record flag false) still get a
459
+ // current_data entry, so a missing record on DELETE is meaningful for them too.
407
460
  if (this.storeCurrentData && this.markRecordUnavailable == null) {
408
461
  this.logger.warn(
409
462
  `Cannot find previous record for delete on ${record.sourceTable.qualifiedName}: ${beforeId} / ${record.before?.id}`
@@ -416,7 +469,7 @@ export abstract class MongoBucketBatch
416
469
  }
417
470
 
418
471
  let afterData: bson.Binary | null = null;
419
- if (afterId != null && !this.storeCurrentData) {
472
+ if (afterId != null && !storeCurrentData) {
420
473
  afterData = null;
421
474
  } else if (afterId != null) {
422
475
  try {
@@ -440,7 +493,7 @@ export abstract class MongoBucketBatch
440
493
  {
441
494
  level: errors.ErrorSeverity.WARNING,
442
495
  metadata: {
443
- replication_slot: this.slot_name,
496
+ replication_slot: this.replicationStreamName,
444
497
  table: record.sourceTable.qualifiedName
445
498
  }
446
499
  }
@@ -483,7 +536,7 @@ export abstract class MongoBucketBatch
483
536
  // However, it will be valid by the end of the transaction.
484
537
  //
485
538
  // In this case, we don't save the op, but we do save the current data.
486
- if (afterId && after && utils.isCompleteRow(this.storeCurrentData, after)) {
539
+ if (afterId && after && utils.isCompleteRow(storeCurrentData, after)) {
487
540
  // Insert or update
488
541
  if (sourceTable.syncData) {
489
542
  const { results, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
@@ -499,7 +552,7 @@ export abstract class MongoBucketBatch
499
552
  {
500
553
  level: errors.ErrorSeverity.WARNING,
501
554
  metadata: {
502
- replication_slot: this.slot_name,
555
+ replication_slot: this.replicationStreamName,
503
556
  table: record.sourceTable.qualifiedName
504
557
  }
505
558
  }
@@ -534,7 +587,7 @@ export abstract class MongoBucketBatch
534
587
  {
535
588
  level: errors.ErrorSeverity.WARNING,
536
589
  metadata: {
537
- replication_slot: this.slot_name,
590
+ replication_slot: this.replicationStreamName,
538
591
  table: record.sourceTable.qualifiedName
539
592
  }
540
593
  }
@@ -669,7 +722,7 @@ export abstract class MongoBucketBatch
669
722
 
670
723
  await this.db.sync_rules.updateOne(
671
724
  {
672
- _id: this.group_id
725
+ _id: this.replicationStreamId
673
726
  },
674
727
  {
675
728
  $set: {
@@ -678,12 +731,41 @@ export abstract class MongoBucketBatch
678
731
  },
679
732
  { session }
680
733
  );
734
+
735
+ // Allow subclasses to persist additional flush-time state in the same transaction
736
+ // (e.g. v3 advances the stream-level last_persisted_op).
737
+ await this.onReplicationTransactionFlush(session, opSeq.last());
681
738
  // We don't notify checkpoint here - we don't make any checkpoint updates directly
682
739
  });
683
740
  }
684
741
 
742
+ /**
743
+ * Hook called inside the replication flush transaction, after ops have been persisted.
744
+ *
745
+ * The base implementation does nothing; v3 storage overrides this to `$max` the stream-level
746
+ * `last_persisted_op` durably within the same transaction.
747
+ */
748
+ protected async onReplicationTransactionFlush(_session: mongo.ClientSession, _lastOp: InternalOpId): Promise<void> {
749
+ // No-op by default (v1 behaviour unchanged).
750
+ }
751
+
752
+ /**
753
+ * Called after a replication transaction has successfully committed, with the last persisted op id.
754
+ *
755
+ * v1 storage tracks this in memory to fold into the next checkpoint. v3 storage does not need it:
756
+ * the stream-level `last_persisted_op` is already advanced durably by
757
+ * {@link onReplicationTransactionFlush} within the same transaction, and checkpoints read it
758
+ * from the document.
759
+ *
760
+ * Keep calls to this adjacent to {@link withReplicationTransaction} usage - both must observe
761
+ * every path that persists ops.
762
+ */
763
+ protected recordPersistedOp(_lastOp: InternalOpId): void {
764
+ // No-op by default.
765
+ }
766
+
685
767
  async [Symbol.asyncDispose]() {
686
- if (this.batch != null || this.write_checkpoint_batch.length > 0) {
768
+ if (this.batch?.hasData() || this.write_checkpoint_batch.length > 0) {
687
769
  // We don't error here, since:
688
770
  // 1. In error states, this is expected (we can't distinguish between disposing after success or error).
689
771
  // 2. SuppressedError is messy to deal with.
@@ -699,19 +781,27 @@ export abstract class MongoBucketBatch
699
781
 
700
782
  async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
701
783
  const { after, before, sourceTable, tag } = record;
702
- for (const event of this.getTableEvents(sourceTable)) {
703
- this.iterateListeners((cb) =>
704
- cb.replicationEvent?.({
705
- batch: this,
706
- table: sourceTable,
707
- data: {
708
- op: tag,
709
- after: after && utils.isCompleteRow(this.storeCurrentData, after) ? after : undefined,
710
- before: before && utils.isCompleteRow(this.storeCurrentData, before) ? before : undefined
711
- },
712
- event
713
- })
714
- );
784
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
785
+ // V3 source tables own disjoint event-definition ids for each physical table. Multiple
786
+ // SourceTables may evaluate different events, but each definition is evaluated through
787
+ // at most one record for this row change.
788
+ // Legacy storage leaves eventDefinitionIds undefined and selects by table ref.
789
+ if (sourceTable.syncEvent) {
790
+ for (const { event, eventId } of this.getTableEvents(sourceTable)) {
791
+ this.iterateListeners((cb) =>
792
+ cb.replicationEvent?.({
793
+ batch: this,
794
+ table: sourceTable,
795
+ data: {
796
+ op: tag,
797
+ after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
798
+ before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
799
+ },
800
+ event,
801
+ event_id: eventId
802
+ })
803
+ );
804
+ }
715
805
  }
716
806
 
717
807
  /**
@@ -744,7 +834,7 @@ export abstract class MongoBucketBatch
744
834
 
745
835
  await this.withTransaction(async () => {
746
836
  for (let table of sourceTables) {
747
- await this.db.commonSourceTables(this.group_id).deleteOne({ _id: mongoTableId(table.id) });
837
+ await this.db.commonSourceTables(this.replicationStreamId).deleteOne({ _id: mongoTableId(table.id) });
748
838
  }
749
839
  });
750
840
 
@@ -761,7 +851,7 @@ export abstract class MongoBucketBatch
761
851
  }
762
852
 
763
853
  if (last_op) {
764
- this.persisted_op = last_op;
854
+ this.recordPersistedOp(last_op);
765
855
  return {
766
856
  flushed_op: last_op
767
857
  };
@@ -830,7 +920,7 @@ export abstract class MongoBucketBatch
830
920
  copy.snapshotStatus = snapshotStatus;
831
921
 
832
922
  await this.withTransaction(async () => {
833
- await this.db.commonSourceTables(this.group_id).updateOne(
923
+ await this.db.commonSourceTables(this.replicationStreamId).updateOne(
834
924
  { _id: mongoTableId(table.id) },
835
925
  {
836
926
  $set: {
@@ -848,32 +938,47 @@ export abstract class MongoBucketBatch
848
938
  return copy;
849
939
  }
850
940
 
851
- protected async clearError(): Promise<void> {
852
- // No need to clear an error more than once per batch, since an error would always result in restarting the batch.
853
- if (this.clearedError) {
854
- return;
855
- }
856
-
941
+ protected async clearError(session: mongo.ClientSession): Promise<void> {
857
942
  await this.db.sync_rules.updateOne(
858
943
  {
859
- _id: this.group_id
944
+ _id: this.replicationStreamId
860
945
  },
861
946
  {
862
947
  $set: {
863
948
  last_fatal_error: null,
864
949
  last_fatal_error_ts: null
865
950
  }
951
+ },
952
+ {
953
+ session
866
954
  }
867
955
  );
868
- this.clearedError = true;
869
956
  }
870
957
 
871
958
  /**
872
- * Gets relevant {@link SqlEventDescriptor}s for the given {@link SourceTable}
959
+ * Gets relevant {@link HydratedEventDescriptor}s for the given {@link SourceTable}
873
960
  */
874
- protected getTableEvents(table: storage.SourceTable): SqlEventDescriptor[] {
875
- return this.sync_rules.eventDescriptors.filter((evt) =>
876
- [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table.ref))
877
- );
961
+ protected getTableEvents(
962
+ table: storage.SourceTable
963
+ ): { event: HydratedEventDescriptor; eventId?: EventDefinitionId }[] {
964
+ // V3 storage assigns event-definition ids to each source table, so membership is authoritative.
965
+ // Iterate the table's distinct ids and resolve each through the stream's deduped event map, so a
966
+ // definition reused across configs has one evaluator for that id. The evaluator may still return
967
+ // multiple payloads from matching queries. Legacy storage leaves this undefined and selects by table ref.
968
+ if (table.eventDefinitionIds != null) {
969
+ const eventById = this.options.parsedSyncConfig.eventById;
970
+ const events: { event: HydratedEventDescriptor; eventId: EventDefinitionId }[] = [];
971
+ for (const id of table.eventDefinitionIds) {
972
+ const event = eventById.get(id);
973
+ if (event != null && event.tableTriggersEvent(table.ref)) {
974
+ events.push({ event, eventId: id });
975
+ }
976
+ }
977
+ return events;
978
+ }
979
+
980
+ return this.sync_rules.eventDescriptors
981
+ .filter((event) => event.tableTriggersEvent(table.ref))
982
+ .map((event) => ({ event }));
878
983
  }
879
984
  }