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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +34 -16
  3. package/dist/storage/MongoBucketStorage.js +219 -70
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +42 -23
  6. package/dist/storage/implementation/MongoBucketBatch.js +61 -44
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoChecksums.d.ts +10 -9
  9. package/dist/storage/implementation/MongoChecksums.js +3 -111
  10. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  11. package/dist/storage/implementation/MongoCompactor.d.ts +2 -0
  12. package/dist/storage/implementation/MongoCompactor.js +18 -9
  13. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
  15. package/dist/storage/implementation/MongoHydrationState.js +49 -0
  16. package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
  17. package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
  18. package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
  19. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  20. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
  21. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
  22. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  23. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  24. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  25. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  27. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +123 -0
  28. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  29. package/dist/storage/implementation/MongoStorageProvider.js +5 -2
  30. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  31. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +41 -23
  32. package/dist/storage/implementation/MongoSyncBucketStorage.js +51 -43
  33. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  35. package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
  36. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  37. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
  38. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  39. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  40. package/dist/storage/implementation/common/PersistedBatch.d.ts +1 -2
  41. package/dist/storage/implementation/common/PersistedBatch.js +5 -0
  42. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  43. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  44. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  45. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  46. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  47. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  48. package/dist/storage/implementation/db.js +9 -14
  49. package/dist/storage/implementation/db.js.map +1 -1
  50. package/dist/storage/implementation/models.d.ts +4 -13
  51. package/dist/storage/implementation/models.js.map +1 -1
  52. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
  53. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +42 -26
  54. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  55. package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
  56. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  57. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +15 -17
  58. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +47 -45
  59. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  60. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  61. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  62. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  63. package/dist/storage/implementation/v1/models.d.ts +10 -1
  64. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
  65. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +286 -259
  66. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  67. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
  68. package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
  69. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +19 -2
  71. package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
  72. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
  74. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
  75. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  76. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +40 -28
  77. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +232 -116
  78. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  79. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -1
  80. package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
  81. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
  83. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
  84. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  85. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  86. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  87. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +13 -12
  88. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +29 -27
  89. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  90. package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
  91. package/dist/storage/implementation/v3/bucket-format.js +58 -0
  92. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  93. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  94. package/dist/storage/implementation/v3/chunking.js +28 -0
  95. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  96. package/dist/storage/implementation/v3/models.d.ts +64 -21
  97. package/dist/storage/implementation/v3/models.js +11 -28
  98. package/dist/storage/implementation/v3/models.js.map +1 -1
  99. package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
  100. package/dist/storage/implementation/v3/source-table-utils.js +214 -0
  101. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  102. package/dist/storage/storage-index.d.ts +4 -4
  103. package/dist/storage/storage-index.js +4 -4
  104. package/dist/storage/storage-index.js.map +1 -1
  105. package/dist/types/types.d.ts +2 -2
  106. package/dist/utils/test-utils.d.ts +1 -2
  107. package/dist/utils/test-utils.js +5 -1
  108. package/dist/utils/test-utils.js.map +1 -1
  109. package/dist/utils/util.d.ts +1 -1
  110. package/dist/utils/util.js +3 -2
  111. package/dist/utils/util.js.map +1 -1
  112. package/package.json +9 -19
  113. package/src/storage/MongoBucketStorage.ts +334 -87
  114. package/src/storage/implementation/MongoBucketBatch.ts +86 -58
  115. package/src/storage/implementation/MongoChecksums.ts +6 -138
  116. package/src/storage/implementation/MongoCompactor.ts +19 -9
  117. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  118. package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
  119. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
  120. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  121. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +154 -0
  122. package/src/storage/implementation/MongoStorageProvider.ts +5 -2
  123. package/src/storage/implementation/MongoSyncBucketStorage.ts +70 -56
  124. package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
  125. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
  126. package/src/storage/implementation/common/PersistedBatch.ts +7 -3
  127. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  128. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  129. package/src/storage/implementation/db.ts +11 -19
  130. package/src/storage/implementation/models.ts +4 -14
  131. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +55 -34
  132. package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
  133. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +70 -64
  134. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  135. package/src/storage/implementation/v1/models.ts +11 -1
  136. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +358 -314
  137. package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
  138. package/src/storage/implementation/v3/MongoCompactorV3.ts +646 -29
  139. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
  140. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +315 -157
  141. package/src/storage/implementation/v3/PersistedBatchV3.ts +62 -36
  142. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
  143. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  144. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +41 -36
  145. package/src/storage/implementation/v3/bucket-format.ts +70 -0
  146. package/src/storage/implementation/v3/chunking.ts +38 -0
  147. package/src/storage/implementation/v3/models.ts +93 -55
  148. package/src/storage/implementation/v3/source-table-utils.ts +392 -0
  149. package/src/storage/storage-index.ts +12 -4
  150. package/src/utils/test-utils.ts +6 -3
  151. package/src/utils/util.ts +3 -2
  152. package/test/src/__snapshots__/storage.test.ts.snap +0 -75
  153. package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
  154. package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
  155. package/test/src/storage.test.ts +11 -11
  156. package/test/src/storage_compacting.test.ts +1956 -6
  157. package/test/src/storage_sync.test.ts +1077 -68
  158. package/test/src/storeCurrentData.test.ts +17 -7
  159. package/test/src/util.ts +2 -1
  160. package/tsconfig.json +1 -4
  161. package/tsconfig.tsbuildinfo +1 -1
  162. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  163. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  164. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  165. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  166. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  167. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  168. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  169. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  170. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  171. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  172. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  173. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  174. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
  175. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
  176. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
  177. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  178. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  179. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  180. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  181. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  182. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  183. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  184. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
  185. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
@@ -1,41 +1,61 @@
1
+ import * as lib_mongo from '@powersync/lib-service-mongodb';
1
2
  import {
2
3
  bson,
3
4
  BucketChecksum,
4
5
  FetchPartialBucketChecksum,
5
6
  InternalOpId,
7
+ isPartialChecksum,
6
8
  PartialChecksumMap,
7
- PartialOrFullChecksum
9
+ PartialOrFullChecksum,
10
+ SingleSyncConfigBucketDefinitionMapping
8
11
  } from '@powersync/service-core';
9
- import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
10
12
  import {
13
+ checksumFromAggregate,
11
14
  emptyChecksumForRequest,
12
- FetchPartialBucketChecksumV3,
15
+ FetchPartialBucketChecksumByBucket,
16
+ FetchPartialBucketChecksumByDefinition,
13
17
  MongoChecksumOptions,
14
18
  MongoChecksums
15
19
  } from '../MongoChecksums.js';
16
20
  import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
21
+ import { BucketDataDocumentV3 } from './models.js';
17
22
 
18
- export class MongoChecksumsV3 extends MongoChecksums {
23
+ /**
24
+ * Checksum operations addressed by persisted definition id.
25
+ *
26
+ * Safe on any storage instance, including multi-config replication-side instances - no
27
+ * source resolution is involved. This is the only checksum surface the compactor may use.
28
+ */
29
+ export interface DefinitionChecksumOperations {
30
+ computePartialChecksumsDirectByDefinition(
31
+ batch: FetchPartialBucketChecksumByDefinition[]
32
+ ): Promise<PartialChecksumMap>;
33
+ }
34
+
35
+ export interface MongoChecksumOptionsV3 extends MongoChecksumOptions {
36
+ /**
37
+ * The persisted mapping of the single sync config that reads are served from.
38
+ *
39
+ * A thunk rather than a plain value: checksums are constructed eagerly with the storage
40
+ * instance, but the single-config requirement may only be asserted when a read happens.
41
+ */
42
+ syncConfigMapping: () => SingleSyncConfigBucketDefinitionMapping;
43
+ }
44
+
45
+ export class MongoChecksumsV3 extends MongoChecksums implements DefinitionChecksumOperations {
19
46
  declare protected readonly db: VersionedPowerSyncMongoV3;
20
- private readonly mapping: BucketDefinitionMapping;
47
+ private readonly syncConfigMapping: () => SingleSyncConfigBucketDefinitionMapping;
21
48
 
22
- constructor(db: VersionedPowerSyncMongoV3, group_id: number, options: MongoChecksumOptions) {
49
+ constructor(db: VersionedPowerSyncMongoV3, group_id: number, options: MongoChecksumOptionsV3) {
23
50
  super(db, group_id, options);
24
- this.mapping = options.mapping!;
51
+ this.syncConfigMapping = options.syncConfigMapping;
25
52
  }
26
53
 
27
- private normalizeBatch(batch: FetchPartialBucketChecksum[]): FetchPartialBucketChecksumV3[] {
28
- return batch.map((request) => ({
29
- bucket: request.bucket,
30
- definitionId: this.mapping.bucketSourceId(request.source),
31
- start: request.start,
32
- end: request.end
33
- }));
34
- }
35
-
36
- async computePartialChecksumsDirectByDefinition(batch: FetchPartialBucketChecksumV3[]): Promise<PartialChecksumMap> {
54
+ async computePartialChecksumsDirectByDefinition(
55
+ batch: FetchPartialBucketChecksumByDefinition[]
56
+ ): Promise<PartialChecksumMap> {
37
57
  const results = new Map<string, PartialOrFullChecksum>();
38
- const requestsByDefinition = new Map<string, FetchPartialBucketChecksumV3[]>();
58
+ const requestsByDefinition = new Map<string, FetchPartialBucketChecksumByDefinition[]>();
39
59
 
40
60
  for (const request of batch) {
41
61
  const existing = requestsByDefinition.get(request.definitionId) ?? [];
@@ -44,10 +64,9 @@ export class MongoChecksumsV3 extends MongoChecksums {
44
64
  }
45
65
 
46
66
  for (const [definitionId, requests] of requestsByDefinition.entries()) {
47
- const groupResults = await this.computePartialChecksumsForCollection(
67
+ const groupResults = await this.computeChecksumsByDefinition(
48
68
  requests,
49
- this.db.bucketDataV3(this.group_id, definitionId),
50
- createV3BucketFilter
69
+ this.db.bucketData(this.group_id, definitionId)
51
70
  );
52
71
  for (const checksum of groupResults.values()) {
53
72
  results.set(checksum.bucket, checksum);
@@ -62,7 +81,14 @@ export class MongoChecksumsV3 extends MongoChecksums {
62
81
  protected async fetchPreStates(
63
82
  batch: FetchPartialBucketChecksum[]
64
83
  ): Promise<Map<string, { opId: InternalOpId; checksum: BucketChecksum }>> {
65
- const preFilters = this.normalizeBatch(batch)
84
+ const normalizedBatch = batch.map((request) => ({
85
+ bucket: request.bucket,
86
+ definitionId: this.syncConfigMapping().bucketSourceId(request.source),
87
+ start: request.start,
88
+ end: request.end
89
+ }));
90
+
91
+ const preFilters = normalizedBatch
66
92
  .filter((request) => request.start == null)
67
93
  .map((request) => ({
68
94
  _id: {
@@ -78,7 +104,7 @@ export class MongoChecksumsV3 extends MongoChecksums {
78
104
  }
79
105
 
80
106
  const states = await this.db
81
- .bucketStateV3(this.group_id)
107
+ .bucketState(this.group_id)
82
108
  .find({
83
109
  $or: preFilters
84
110
  })
@@ -100,11 +126,218 @@ export class MongoChecksumsV3 extends MongoChecksums {
100
126
  }
101
127
 
102
128
  protected async computePartialChecksumsInternal(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
103
- return this.computePartialChecksumsDirectByDefinition(this.normalizeBatch(batch));
129
+ const normalized = batch.map((request) => ({
130
+ bucket: request.bucket,
131
+ definitionId: this.syncConfigMapping().bucketSourceId(request.source),
132
+ start: request.start,
133
+ end: request.end
134
+ }));
135
+
136
+ const results = new Map<string, PartialOrFullChecksum>();
137
+ const requestsByDefinition = new Map<string, FetchPartialBucketChecksumByDefinition[]>();
138
+
139
+ for (const request of normalized) {
140
+ const existing = requestsByDefinition.get(request.definitionId) ?? [];
141
+ existing.push(request);
142
+ requestsByDefinition.set(request.definitionId, existing);
143
+ }
144
+
145
+ for (const [definitionId, requests] of requestsByDefinition.entries()) {
146
+ const groupResults = await this.computeChecksumsByDefinition(
147
+ requests,
148
+ this.db.bucketData(this.group_id, definitionId)
149
+ );
150
+ for (const checksum of groupResults.values()) {
151
+ results.set(checksum.bucket, checksum);
152
+ }
153
+ }
154
+
155
+ return new Map<string, PartialOrFullChecksum>(
156
+ normalized.map((request) => [request.bucket, results.get(request.bucket) ?? emptyChecksumForRequest(request)])
157
+ );
158
+ }
159
+
160
+ private async computeChecksumsByDefinition(
161
+ batch: FetchPartialBucketChecksumByBucket[],
162
+ collection: lib_mongo.mongo.Collection<BucketDataDocumentV3>
163
+ ): Promise<PartialChecksumMap> {
164
+ const requests = new Map<string, FetchPartialBucketChecksumByBucket>();
165
+ for (let request of batch) {
166
+ requests.set(request.bucket, request);
167
+ }
168
+
169
+ const pipeline = this.buildPartialChecksumPipeline(requests);
170
+ const aggregate = await collection
171
+ .aggregate(pipeline, {
172
+ session: undefined,
173
+ readConcern: 'snapshot',
174
+ maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS
175
+ })
176
+ .toArray()
177
+ .catch((e) => {
178
+ throw lib_mongo.mapQueryError(e, 'while reading checksums');
179
+ });
180
+
181
+ return this.normalizePartialChecksumResults(batch, aggregate);
182
+ }
183
+
184
+ private buildPartialChecksumPipeline(requests: Map<string, FetchPartialBucketChecksumByBucket>): bson.Document[] {
185
+ const filters = Array.from(requests.values(), createBucketFilter);
186
+
187
+ return [
188
+ // $match with $or filters
189
+ {
190
+ $match: {
191
+ $or: filters
192
+ }
193
+ },
194
+ {
195
+ $addFields: {
196
+ bucket_start: {
197
+ $switch: {
198
+ branches: Array.from(requests.entries()).map(([bucket, req]) => ({
199
+ case: { $eq: ['$_id.b', bucket] },
200
+ then: req.start ?? new bson.MinKey()
201
+ })),
202
+ default: new bson.MinKey()
203
+ }
204
+ },
205
+ bucket_end: {
206
+ $switch: {
207
+ branches: Array.from(requests.entries()).map(([bucket, req]) => ({
208
+ case: { $eq: ['$_id.b', bucket] },
209
+ then: req.end
210
+ })),
211
+ default: new bson.MaxKey()
212
+ }
213
+ }
214
+ }
215
+ },
216
+ // Determine if document is fully included within [start, end] range
217
+ {
218
+ $project: {
219
+ _id: 1,
220
+ min_op: 1,
221
+ checksum: 1,
222
+ count: 1,
223
+ ops: 1,
224
+ bucket_start: 1,
225
+ bucket_end: 1,
226
+ is_fully_included: {
227
+ $and: [{ $gt: ['$min_op', '$bucket_start'] }, { $lte: ['$_id.o', '$bucket_end'] }]
228
+ }
229
+ }
230
+ },
231
+ // Compute included checksum, count, and clear op detection
232
+ {
233
+ $project: {
234
+ _id: 1,
235
+ checksum_total: {
236
+ $cond: {
237
+ if: '$is_fully_included',
238
+ then: '$checksum',
239
+ else: {
240
+ $sum: {
241
+ $map: {
242
+ input: {
243
+ $filter: {
244
+ input: '$ops',
245
+ cond: {
246
+ $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
247
+ }
248
+ }
249
+ },
250
+ in: '$$this.checksum'
251
+ }
252
+ }
253
+ }
254
+ }
255
+ },
256
+ count_total: {
257
+ $cond: {
258
+ if: '$is_fully_included',
259
+ then: '$count',
260
+ else: {
261
+ $size: {
262
+ $filter: {
263
+ input: '$ops',
264
+ cond: {
265
+ $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ },
272
+ has_clear_op: {
273
+ $max: {
274
+ $map: {
275
+ input: {
276
+ $filter: {
277
+ input: '$ops',
278
+ cond: {
279
+ $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
280
+ }
281
+ }
282
+ },
283
+ in: { $cond: [{ $eq: ['$$this.op', 'CLEAR'] }, 1, 0] }
284
+ }
285
+ }
286
+ },
287
+ last_op: { $max: '$_id.o' }
288
+ }
289
+ },
290
+ // Group by bucket
291
+ {
292
+ $group: {
293
+ _id: '$_id.b',
294
+ checksum_total: { $sum: '$checksum_total' },
295
+ count: { $sum: '$count_total' },
296
+ has_clear_op: { $max: '$has_clear_op' },
297
+ last_op: { $max: '$last_op' }
298
+ }
299
+ },
300
+ // $sort results
301
+ { $sort: { _id: 1 } }
302
+ ];
303
+ }
304
+
305
+ private normalizePartialChecksumResults(
306
+ batch: FetchPartialBucketChecksumByBucket[],
307
+ aggregate: bson.Document[]
308
+ ): PartialChecksumMap {
309
+ const partialChecksums = new Map<string, PartialOrFullChecksum>();
310
+ for (let doc of aggregate) {
311
+ const bucket = doc._id;
312
+ partialChecksums.set(bucket, checksumFromAggregate(doc));
313
+ }
314
+
315
+ return new Map<string, PartialOrFullChecksum>(
316
+ batch.map((request) => {
317
+ const bucket = request.bucket;
318
+ let partialChecksum = partialChecksums.get(bucket);
319
+ if (partialChecksum == null) {
320
+ partialChecksum = {
321
+ bucket,
322
+ partialCount: 0,
323
+ partialChecksum: 0
324
+ };
325
+ }
326
+ if (request.start == null && isPartialChecksum(partialChecksum)) {
327
+ partialChecksum = {
328
+ bucket,
329
+ count: partialChecksum.partialCount,
330
+ checksum: partialChecksum.partialChecksum
331
+ };
332
+ }
333
+
334
+ return [bucket, partialChecksum];
335
+ })
336
+ );
104
337
  }
105
338
  }
106
339
 
107
- function createV3BucketFilter(request: Pick<FetchPartialBucketChecksumV3, 'bucket' | 'start' | 'end'>) {
340
+ function createBucketFilter(request: Pick<FetchPartialBucketChecksumByBucket, 'bucket' | 'start' | 'end'>) {
108
341
  return {
109
342
  _id: {
110
343
  $gt: {
@@ -113,8 +346,11 @@ function createV3BucketFilter(request: Pick<FetchPartialBucketChecksumV3, 'bucke
113
346
  },
114
347
  $lte: {
115
348
  b: request.bucket,
116
- o: request.end
349
+ o: new bson.MaxKey()
117
350
  }
351
+ },
352
+ min_op: {
353
+ $lte: request.end
118
354
  }
119
355
  };
120
356
  }