@powersync/service-module-mongodb-storage 0.20.0 → 0.22.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 (192) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +36 -3
  3. package/dist/storage/MongoBucketStorage.js +372 -207
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
  6. package/dist/storage/implementation/MongoBucketBatch.js +44 -17
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
  9. package/dist/storage/implementation/MongoCompactor.js +27 -238
  10. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  11. package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
  12. package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
  13. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
  15. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
  16. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  17. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
  18. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  19. package/dist/storage/implementation/MongoStorageProvider.js +7 -3
  20. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  21. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
  22. package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
  23. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  24. package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
  25. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  26. package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
  27. package/dist/storage/implementation/MongoWriteBatch.js +149 -0
  28. package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
  29. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
  30. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
  31. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  32. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  33. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  34. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  35. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
  36. package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
  37. package/dist/storage/implementation/common/PersistedBatch.js +49 -6
  38. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  39. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
  40. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
  41. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  42. package/dist/storage/implementation/db.d.ts +9 -1
  43. package/dist/storage/implementation/db.js +27 -1
  44. package/dist/storage/implementation/db.js.map +1 -1
  45. package/dist/storage/implementation/models.d.ts +31 -0
  46. package/dist/storage/implementation/models.js.map +1 -1
  47. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
  48. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
  49. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  50. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
  51. package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
  52. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  53. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  54. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  55. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  56. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
  57. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
  58. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  59. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
  60. package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
  61. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  62. package/dist/storage/implementation/v1/models.js +1 -0
  63. package/dist/storage/implementation/v1/models.js.map +1 -1
  64. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  65. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  66. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  67. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +10 -2
  68. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
  69. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
  71. package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
  72. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
  74. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
  75. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  76. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
  77. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
  78. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  79. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
  80. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
  81. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  83. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  84. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  85. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
  86. package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
  87. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  88. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
  89. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
  90. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  91. package/dist/storage/implementation/v3/bucket-format.js +2 -0
  92. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  93. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  94. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  95. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  96. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  97. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  98. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  99. package/dist/storage/implementation/v3/models.d.ts +84 -4
  100. package/dist/storage/implementation/v3/models.js.map +1 -1
  101. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
  102. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
  103. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  104. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
  105. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
  106. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
  107. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  108. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
  109. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
  110. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  111. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
  112. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
  113. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  114. package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
  115. package/dist/storage/implementation/v3/source-table-utils.js +30 -37
  116. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  117. package/dist/storage/storage-index.d.ts +2 -1
  118. package/dist/storage/storage-index.js +1 -0
  119. package/dist/storage/storage-index.js.map +1 -1
  120. package/dist/types/types.d.ts +28 -1
  121. package/dist/types/types.js +28 -7
  122. package/dist/types/types.js.map +1 -1
  123. package/dist/utils/test-utils.js +1 -0
  124. package/dist/utils/test-utils.js.map +1 -1
  125. package/dist/utils/util.js +3 -1
  126. package/dist/utils/util.js.map +1 -1
  127. package/package.json +10 -9
  128. package/src/storage/MongoBucketStorage.ts +507 -269
  129. package/src/storage/implementation/MongoBucketBatch.ts +67 -24
  130. package/src/storage/implementation/MongoCompactor.ts +35 -346
  131. package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
  132. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
  133. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
  134. package/src/storage/implementation/MongoStorageProvider.ts +15 -3
  135. package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
  136. package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
  137. package/src/storage/implementation/MongoWriteBatch.ts +172 -0
  138. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
  139. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  140. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
  141. package/src/storage/implementation/common/PersistedBatch.ts +66 -10
  142. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
  143. package/src/storage/implementation/db.ts +35 -2
  144. package/src/storage/implementation/models.ts +34 -0
  145. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
  146. package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
  147. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  148. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
  149. package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
  150. package/src/storage/implementation/v1/models.ts +1 -0
  151. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  152. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
  153. package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
  154. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
  155. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
  156. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
  157. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  158. package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
  159. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
  160. package/src/storage/implementation/v3/bucket-format.ts +2 -0
  161. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  162. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  163. package/src/storage/implementation/v3/models.ts +91 -3
  164. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
  165. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
  166. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
  167. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
  168. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
  169. package/src/storage/implementation/v3/source-table-utils.ts +39 -41
  170. package/src/storage/storage-index.ts +2 -0
  171. package/src/types/types.ts +42 -7
  172. package/src/utils/test-utils.ts +1 -0
  173. package/src/utils/util.ts +3 -1
  174. package/test/src/__snapshots__/storage.test.ts.snap +25 -0
  175. package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
  176. package/test/src/chunk_compaction_config.test.ts +38 -0
  177. package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
  178. package/test/src/compact-utils.test.ts +226 -0
  179. package/test/src/helpers/MemoryObjectStorage.ts +14 -6
  180. package/test/src/object_storage_usage.test.ts +267 -0
  181. package/test/src/parameter_compacting_v1.test.ts +120 -0
  182. package/test/src/parameter_compacting_v3.test.ts +192 -0
  183. package/test/src/parameter_compaction_fence.test.ts +276 -0
  184. package/test/src/storage.test.ts +273 -22
  185. package/test/src/storage_compacting.test.ts +968 -246
  186. package/test/src/storage_s3_checksums.test.ts +3 -4
  187. package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
  188. package/test/src/storage_s3_reading.test.ts +290 -2
  189. package/test/src/storage_s3_writing.test.ts +1 -0
  190. package/test/src/storage_sync.test.ts +336 -41
  191. package/test/src/util.ts +1 -1
  192. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,34 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { bson, InternalOpId } from '@powersync/service-core';
3
+ import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
4
+ import type { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
5
+
6
+ /**
7
+ * Incrementally compacts V3 parameter indexes using the stream operation sequence as a work
8
+ * cursor. The cursor is advanced only after every parameter index has completed the same range.
9
+ */
10
+ export class MongoParameterCompactorV3 extends MongoParameterCompactor {
11
+ declare protected readonly db: VersionedPowerSyncMongoV3;
12
+
13
+ protected async getCollections(): Promise<mongo.Collection<mongo.Document>[]> {
14
+ const collections = await this.db.listParameterIndexCollections(this.replicationStreamId);
15
+ return collections.map(({ collection }) => collection as unknown as mongo.Collection<mongo.Document>);
16
+ }
17
+
18
+ protected shouldCompactDocument(_doc: { _id: bigint; key: mongo.Document }): boolean {
19
+ return true;
20
+ }
21
+
22
+ /** Uses the `{ lookup: 1, key: 1, _id: -1 }` `lookup_op_id` index. */
23
+ protected leadingHistoryDeleteFilter(
24
+ lookup: bson.Binary,
25
+ keys: mongo.Document[],
26
+ before: InternalOpId
27
+ ): mongo.Document {
28
+ return {
29
+ lookup,
30
+ key: { $in: keys },
31
+ _id: { $lt: before }
32
+ };
33
+ }
34
+ }
@@ -1,7 +1,7 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
2
  import { Logger, ReplicationAbortedError } from '@powersync/lib-services-framework';
3
3
  import { SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
4
- import { BucketDefinitionId, ParameterIndexId, SyncConfig } from '@powersync/service-sync-rules';
4
+ import { BucketDefinitionId, EventDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
5
5
  import * as bson from 'bson';
6
6
  import { clearCollectionInIdRanges, idPrefixFilter } from '../../../utils/util.js';
7
7
  import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
@@ -13,6 +13,7 @@ import {
13
13
  } from './models.js';
14
14
  import { ObjectStorage } from './object-storage/ObjectStorage.js';
15
15
  import { ObjectStorageLifecycle } from './object-storage/ObjectStorageLifecycle.js';
16
+ import { ObjectStorageUsage } from './object-storage/ObjectStorageUsage.js';
16
17
 
17
18
  type SyncConfigState = ReplicationStreamDocumentV3['sync_configs'][number];
18
19
 
@@ -46,8 +47,6 @@ export class MongoStoppedSyncConfigCleanup {
46
47
  private readonly signal: AbortSignal | undefined;
47
48
  private readonly logger: Logger;
48
49
  private readonly objectStorage: ObjectStorage | undefined;
49
- private readonly defaultSchema: string;
50
- private readonly sourceConnectionTag: string;
51
50
  private readonly clearBatchThrottleRate: number;
52
51
 
53
52
  constructor(options: MongoStoppedSyncConfigCleanupOptions) {
@@ -56,8 +55,6 @@ export class MongoStoppedSyncConfigCleanup {
56
55
  this.signal = options.signal;
57
56
  this.logger = options.logger;
58
57
  this.objectStorage = options.objectStorage;
59
- this.defaultSchema = options.defaultSchema;
60
- this.sourceConnectionTag = options.sourceConnectionTag;
61
58
  this.clearBatchThrottleRate = options.clearBatchThrottleRate;
62
59
  }
63
60
 
@@ -91,28 +88,32 @@ export class MongoStoppedSyncConfigCleanup {
91
88
  liveStorageIds.bucketDefinitionIds
92
89
  );
93
90
  const unusedParameterIndexIds = difference(stoppedStorageIds.parameterIndexIds, liveStorageIds.parameterIndexIds);
91
+ const unusedEventDefinitionIds = difference(
92
+ stoppedStorageIds.eventDefinitionIds,
93
+ liveStorageIds.eventDefinitionIds
94
+ );
94
95
 
95
96
  const result: storage.CleanupStoppedSyncConfigsResult = {
96
97
  ...EMPTY_RESULT
97
98
  };
98
99
 
99
- if (unusedBucketDefinitionIds.length > 0 || unusedParameterIndexIds.length > 0) {
100
+ if (
101
+ unusedBucketDefinitionIds.length > 0 ||
102
+ unusedParameterIndexIds.length > 0 ||
103
+ unusedEventDefinitionIds.length > 0
104
+ ) {
100
105
  await this.cleanupSourceTableMemberships(
101
106
  unusedBucketDefinitionIds,
102
107
  unusedParameterIndexIds,
103
- liveConfigDocs,
108
+ unusedEventDefinitionIds,
104
109
  result
105
110
  );
111
+ await this.dropCustomCheckpointRequestCollections(unusedEventDefinitionIds);
106
112
  result.bucketDataCollectionsDropped = await this.dropBucketDataCollections(unusedBucketDefinitionIds);
107
113
  result.bucketStateDocumentsDeleted = await this.deleteBucketStateDocuments(unusedBucketDefinitionIds);
108
114
  result.parameterIndexCollectionsDropped = await this.dropParameterIndexCollections(unusedParameterIndexIds);
109
115
  }
110
116
 
111
- // Event-only source tables carry empty membership arrays, so they are never selected by the
112
- // membership filter above. Clean them up separately, regardless of whether any bucket or
113
- // parameter ids became unused (a stopped config may have contributed only an event trigger).
114
- await this.cleanupEventOnlySourceTables(liveConfigDocs, result);
115
-
116
117
  result.stoppedSyncConfigsRemoved = await this.pruneStoppedSyncConfigStates(stoppedStates);
117
118
 
118
119
  if (result.stoppedSyncConfigsRemoved > 0) {
@@ -132,20 +133,21 @@ export class MongoStoppedSyncConfigCleanup {
132
133
  return this.db.syncConfigDefinitions.find({ _id: { $in: ids } }).toArray();
133
134
  }
134
135
 
135
- private storageIdsFor(configs: Pick<SyncConfigDefinition, 'rule_mapping'>[]) {
136
+ private storageIdsFor(configs: SyncConfigDefinition[]) {
136
137
  const mappings = configs.map((config) =>
137
138
  SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(config.rule_mapping)
138
139
  );
139
140
  return {
140
141
  bucketDefinitionIds: [...new Set(mappings.flatMap((mapping) => mapping.allBucketDefinitionIds()))],
141
- parameterIndexIds: [...new Set(mappings.flatMap((mapping) => mapping.allParameterIndexIds()))]
142
+ parameterIndexIds: [...new Set(mappings.flatMap((mapping) => mapping.allParameterIndexIds()))],
143
+ eventDefinitionIds: [...new Set(mappings.flatMap((mapping) => mapping.allEventDefinitionIds()))]
142
144
  };
143
145
  }
144
146
 
145
147
  private async cleanupSourceTableMemberships(
146
148
  unusedBucketDefinitionIds: BucketDefinitionId[],
147
149
  unusedParameterIndexIds: ParameterIndexId[],
148
- liveConfigDocs: SyncConfigDefinition[],
150
+ unusedEventDefinitionIds: EventDefinitionId[],
149
151
  result: storage.CleanupStoppedSyncConfigsResult
150
152
  ) {
151
153
  const update: Record<string, unknown> = {};
@@ -155,11 +157,18 @@ export class MongoStoppedSyncConfigCleanup {
155
157
  if (unusedParameterIndexIds.length > 0) {
156
158
  update.parameter_lookup_source_ids = { $in: unusedParameterIndexIds };
157
159
  }
160
+ if (unusedEventDefinitionIds.length > 0) {
161
+ update.event_definition_ids = { $in: unusedEventDefinitionIds };
162
+ }
158
163
 
159
164
  // Keep obsolete membership ids as the durable cleanup marker until each source table is
160
165
  // either deleted or retained. If interrupted after dropping a source_records collection,
161
166
  // the next run can still rediscover the source table from these obsolete ids and retry.
162
- const filter = this.sourceTableMembershipFilter(unusedBucketDefinitionIds, unusedParameterIndexIds);
167
+ const filter = this.sourceTableMembershipFilter(
168
+ unusedBucketDefinitionIds,
169
+ unusedParameterIndexIds,
170
+ unusedEventDefinitionIds
171
+ );
163
172
  const candidateSourceTables = await this.db
164
173
  .sourceTables(this.replicationStreamId)
165
174
  .find(filter, {
@@ -167,6 +176,7 @@ export class MongoStoppedSyncConfigCleanup {
167
176
  _id: 1,
168
177
  bucket_data_source_ids: 1,
169
178
  parameter_lookup_source_ids: 1,
179
+ event_definition_ids: 1,
170
180
  schema_name: 1,
171
181
  table_name: 1
172
182
  }
@@ -175,12 +185,13 @@ export class MongoStoppedSyncConfigCleanup {
175
185
  if (candidateSourceTables.length == 0) {
176
186
  return;
177
187
  }
178
- const liveSyncConfigs = this.parseSyncConfigs(liveConfigDocs);
179
-
180
- const deletableSourceTables = candidateSourceTables.filter(
181
- (sourceTable) =>
182
- this.membershipsBecomeEmpty(sourceTable, unusedBucketDefinitionIds, unusedParameterIndexIds) &&
183
- !this.triggersLiveEvent(sourceTable, liveSyncConfigs)
188
+ const deletableSourceTables = candidateSourceTables.filter((sourceTable) =>
189
+ this.membershipsBecomeEmpty(
190
+ sourceTable,
191
+ unusedBucketDefinitionIds,
192
+ unusedParameterIndexIds,
193
+ unusedEventDefinitionIds
194
+ )
184
195
  );
185
196
  const retainedSourceTables = candidateSourceTables.filter(
186
197
  (sourceTable) => !deletableSourceTables.some((deletable) => deletable._id.equals(sourceTable._id))
@@ -189,19 +200,24 @@ export class MongoStoppedSyncConfigCleanup {
189
200
 
190
201
  await this.deleteSourceTables(
191
202
  deletableSourceTables.map((table) => table._id),
192
- (ids) => this.deletableSourceTableFilter(ids, unusedBucketDefinitionIds, unusedParameterIndexIds),
203
+ (ids) =>
204
+ this.deletableSourceTableFilter(
205
+ ids,
206
+ unusedBucketDefinitionIds,
207
+ unusedParameterIndexIds,
208
+ unusedEventDefinitionIds
209
+ ),
193
210
  result
194
211
  );
195
212
 
196
- // A retained source table whose memberships become empty is kept alive only by a live event
197
- // (otherwise it would be deletable). It becomes event-only, and event-only save() never reads
198
- // or writes current_data, so its source_records collection is now dead weight. Drop it before
199
- // the $pull below, so the obsolete membership ids remain as a recovery marker if interrupted.
200
- // Existing source-table docs only ever shrink their memberships, so this table cannot resume
201
- // data sync on the same doc and need current_data again.
213
+ // A retained source table whose data and parameter memberships become empty is event-only.
214
+ // Event-only save() never reads or writes current_data, so its source_records collection is now
215
+ // dead weight. Drop it before the $pull below, so the obsolete membership ids remain as a recovery
216
+ // marker if interrupted. Existing source-table docs only ever shrink their memberships, so this
217
+ // table cannot resume data sync on the same doc and need current_data again.
202
218
  const becomingEventOnlySourceTableIds = retainedSourceTables
203
219
  .filter((sourceTable) =>
204
- this.membershipsBecomeEmpty(sourceTable, unusedBucketDefinitionIds, unusedParameterIndexIds)
220
+ this.dataMembershipsBecomeEmpty(sourceTable, unusedBucketDefinitionIds, unusedParameterIndexIds)
205
221
  )
206
222
  .map((sourceTable) => sourceTable._id);
207
223
  for (const sourceTableId of becomingEventOnlySourceTableIds) {
@@ -223,42 +239,6 @@ export class MongoStoppedSyncConfigCleanup {
223
239
  }
224
240
  }
225
241
 
226
- /**
227
- * Clean up event-only source tables that no live sync config still triggers events for.
228
- *
229
- * Event-only source tables carry empty membership arrays (see source-table-utils:
230
- * "Empty memberships indicate an event-only table"), so the membership filter never selects
231
- * them. Without this, the source_tables row and its source_records collection leak when the
232
- * config that contributed the event trigger is stopped.
233
- */
234
- private async cleanupEventOnlySourceTables(
235
- liveConfigDocs: SyncConfigDefinition[],
236
- result: storage.CleanupStoppedSyncConfigsResult
237
- ) {
238
- const candidateSourceTables = await this.db
239
- .sourceTables(this.replicationStreamId)
240
- .find(this.eventOnlySourceTableFilter(), {
241
- projection: {
242
- _id: 1,
243
- bucket_data_source_ids: 1,
244
- parameter_lookup_source_ids: 1,
245
- schema_name: 1,
246
- table_name: 1
247
- }
248
- })
249
- .toArray();
250
- if (candidateSourceTables.length == 0) {
251
- return;
252
- }
253
-
254
- const liveSyncConfigs = this.parseSyncConfigs(liveConfigDocs);
255
- const deletableSourceTableIds = candidateSourceTables
256
- .filter((sourceTable) => !this.triggersLiveEvent(sourceTable, liveSyncConfigs))
257
- .map((sourceTable) => sourceTable._id);
258
-
259
- await this.deleteSourceTables(deletableSourceTableIds, (ids) => this.eventOnlyDeletableFilter(ids), result);
260
- }
261
-
262
242
  /**
263
243
  * Drop the given source tables and their source_records collections.
264
244
  *
@@ -298,6 +278,25 @@ export class MongoStoppedSyncConfigCleanup {
298
278
  }
299
279
 
300
280
  private membershipsBecomeEmpty(
281
+ sourceTable: Pick<
282
+ SourceTableDocumentV3,
283
+ 'bucket_data_source_ids' | 'parameter_lookup_source_ids' | 'event_definition_ids'
284
+ >,
285
+ unusedBucketDefinitionIds: BucketDefinitionId[],
286
+ unusedParameterIndexIds: ParameterIndexId[],
287
+ unusedEventDefinitionIds: EventDefinitionId[]
288
+ ): boolean {
289
+ const unusedBucketDefinitionIdSet = new Set(unusedBucketDefinitionIds);
290
+ const unusedParameterIndexIdSet = new Set(unusedParameterIndexIds);
291
+ const unusedEventDefinitionIdSet = new Set(unusedEventDefinitionIds);
292
+ return (
293
+ sourceTable.bucket_data_source_ids.every((id) => unusedBucketDefinitionIdSet.has(id)) &&
294
+ sourceTable.parameter_lookup_source_ids.every((id) => unusedParameterIndexIdSet.has(id)) &&
295
+ sourceTable.event_definition_ids.every((id) => unusedEventDefinitionIdSet.has(id))
296
+ );
297
+ }
298
+
299
+ private dataMembershipsBecomeEmpty(
301
300
  sourceTable: Pick<SourceTableDocumentV3, 'bucket_data_source_ids' | 'parameter_lookup_source_ids'>,
302
301
  unusedBucketDefinitionIds: BucketDefinitionId[],
303
302
  unusedParameterIndexIds: ParameterIndexId[]
@@ -313,57 +312,21 @@ export class MongoStoppedSyncConfigCleanup {
313
312
  private deletableSourceTableFilter(
314
313
  ids: bson.ObjectId[],
315
314
  unusedBucketDefinitionIds: BucketDefinitionId[],
316
- unusedParameterIndexIds: ParameterIndexId[]
315
+ unusedParameterIndexIds: ParameterIndexId[],
316
+ unusedEventDefinitionIds: EventDefinitionId[]
317
317
  ): Record<string, unknown> {
318
318
  return {
319
319
  _id: { $in: ids },
320
320
  bucket_data_source_ids: { $not: { $elemMatch: { $nin: unusedBucketDefinitionIds } } },
321
- parameter_lookup_source_ids: { $not: { $elemMatch: { $nin: unusedParameterIndexIds } } }
322
- };
323
- }
324
-
325
- private eventOnlySourceTableFilter(): Record<string, unknown> {
326
- return {
327
- bucket_data_source_ids: { $size: 0 },
328
- parameter_lookup_source_ids: { $size: 0 }
321
+ parameter_lookup_source_ids: { $not: { $elemMatch: { $nin: unusedParameterIndexIds } } },
322
+ event_definition_ids: { $not: { $elemMatch: { $nin: unusedEventDefinitionIds } } }
329
323
  };
330
324
  }
331
325
 
332
- private eventOnlyDeletableFilter(ids: bson.ObjectId[]): Record<string, unknown> {
333
- return {
334
- _id: { $in: ids },
335
- ...this.eventOnlySourceTableFilter()
336
- };
337
- }
338
-
339
- private parseSyncConfigs(configDocs: SyncConfigDefinition[]): SyncConfig[] {
340
- // This is ugly - we should not need to re-parse to achieve this.
341
- // Revisit persistence for this later.
342
- return configDocs.map((config) => {
343
- return storage.parsePersistedSyncConfigContent({
344
- content: config.content,
345
- compiledPlan: config.serialized_plan ?? null,
346
- storageVersion: config.storage_version,
347
- parseOptions: {
348
- defaultSchema: this.defaultSchema
349
- }
350
- }).config;
351
- });
352
- }
353
-
354
- private triggersLiveEvent(sourceTable: SourceTableDocumentV3, liveSyncConfigs: SyncConfig[]): boolean {
355
- return liveSyncConfigs.some((syncConfig) =>
356
- syncConfig.tableTriggersEvent({
357
- connectionTag: this.sourceConnectionTag,
358
- schema: sourceTable.schema_name,
359
- name: sourceTable.table_name
360
- })
361
- );
362
- }
363
-
364
326
  private sourceTableMembershipFilter(
365
327
  bucketDefinitionIds: BucketDefinitionId[],
366
- parameterIndexIds: ParameterIndexId[]
328
+ parameterIndexIds: ParameterIndexId[],
329
+ eventDefinitionIds: EventDefinitionId[]
367
330
  ): Partial<SourceTableDocumentV3> | Record<string, unknown> {
368
331
  const clauses: Record<string, unknown>[] = [];
369
332
  if (bucketDefinitionIds.length > 0) {
@@ -372,12 +335,27 @@ export class MongoStoppedSyncConfigCleanup {
372
335
  if (parameterIndexIds.length > 0) {
373
336
  clauses.push({ parameter_lookup_source_ids: { $in: parameterIndexIds } });
374
337
  }
338
+ if (eventDefinitionIds.length > 0) {
339
+ clauses.push({ event_definition_ids: { $in: eventDefinitionIds } });
340
+ }
375
341
  if (clauses.length == 0) {
376
342
  return { _id: { $exists: false } };
377
343
  }
378
344
  return { $or: clauses };
379
345
  }
380
346
 
347
+ private async dropCustomCheckpointRequestCollections(eventDefinitionIds: EventDefinitionId[]) {
348
+ for (const eventId of eventDefinitionIds) {
349
+ this.throwIfAborted();
350
+ await this.dropCollection(
351
+ this.db.customCheckpointRequests({
352
+ replicationStreamId: this.replicationStreamId,
353
+ eventId
354
+ })
355
+ );
356
+ }
357
+ }
358
+
381
359
  private async dropBucketDataCollections(definitionIds: BucketDefinitionId[]): Promise<number> {
382
360
  const lifecycle = this.objectStorage
383
361
  ? new ObjectStorageLifecycle(this.db, this.replicationStreamId, this.objectStorage)
@@ -385,6 +363,19 @@ export class MongoStoppedSyncConfigCleanup {
385
363
  for (const definitionId of definitionIds) {
386
364
  this.throwIfAborted();
387
365
  await this.dropCollection(this.db.bucketData(this.replicationStreamId, definitionId));
366
+ }
367
+ if (definitionIds.length > 0 && this.objectStorage != null) {
368
+ const usage = new ObjectStorageUsage(this.db, this.replicationStreamId);
369
+ await this.db.client.withSession((session) =>
370
+ session.withTransaction(async () => {
371
+ for (const definitionId of definitionIds) {
372
+ await usage.removeDefinition(definitionId, session);
373
+ }
374
+ })
375
+ );
376
+ }
377
+ for (const definitionId of definitionIds) {
378
+ this.throwIfAborted();
388
379
  await lifecycle?.deletePrefix(lifecycle.definitionPrefix(definitionId), { signal: this.signal });
389
380
  }
390
381
  return definitionIds.length;
@@ -431,7 +422,7 @@ export class MongoStoppedSyncConfigCleanup {
431
422
  collection: lib_mongo.mongo.Collection<T>
432
423
  ): Promise<void> {
433
424
  await collection.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }).catch((error) => {
434
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
425
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
435
426
  return;
436
427
  }
437
428
  throw error;