@powersync/service-module-mongodb-storage 0.16.0 → 0.18.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 (206) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +35 -15
  3. package/dist/storage/MongoBucketStorage.js +309 -86
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/CheckpointState.d.ts +20 -0
  6. package/dist/storage/implementation/CheckpointState.js +31 -0
  7. package/dist/storage/implementation/CheckpointState.js.map +1 -0
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +69 -39
  9. package/dist/storage/implementation/MongoBucketBatch.js +104 -313
  10. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  11. package/dist/storage/implementation/MongoChecksums.d.ts +11 -9
  12. package/dist/storage/implementation/MongoChecksums.js +3 -111
  13. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  14. package/dist/storage/implementation/MongoCompactor.d.ts +3 -1
  15. package/dist/storage/implementation/MongoCompactor.js +18 -9
  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 +6 -5
  21. package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
  22. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  23. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
  24. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
  25. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  27. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  28. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  29. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  30. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +121 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +5 -2
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +50 -21
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +62 -199
  36. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
  38. package/dist/storage/implementation/MongoSyncRulesLock.js +9 -5
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
  41. package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
  42. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
  43. package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
  44. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
  45. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  46. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  47. package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -3
  48. package/dist/storage/implementation/common/PersistedBatch.js +5 -0
  49. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  50. package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
  51. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +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 +10 -2
  58. package/dist/storage/implementation/db.js +9 -14
  59. package/dist/storage/implementation/db.js.map +1 -1
  60. package/dist/storage/implementation/models.d.ts +25 -50
  61. package/dist/storage/implementation/models.js.map +1 -1
  62. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +23 -1
  63. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +403 -2
  64. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  65. package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
  66. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  67. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
  68. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +22 -15
  69. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +100 -27
  70. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  71. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
  72. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  73. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  74. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  75. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  76. package/dist/storage/implementation/v1/models.d.ts +21 -1
  77. package/dist/storage/implementation/v1/models.js.map +1 -1
  78. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +21 -0
  79. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +459 -3
  80. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  81. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
  82. package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
  83. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  84. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +20 -3
  85. package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
  86. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  87. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
  88. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
  89. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  90. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +50 -16
  91. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +292 -75
  92. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  93. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -3
  94. package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
  95. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  96. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
  97. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
  98. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  99. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  100. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  101. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +16 -14
  102. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
  103. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  104. package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
  105. package/dist/storage/implementation/v3/bucket-format.js +58 -0
  106. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  107. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  108. package/dist/storage/implementation/v3/chunking.js +28 -0
  109. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  110. package/dist/storage/implementation/v3/models.d.ts +111 -10
  111. package/dist/storage/implementation/v3/models.js +11 -28
  112. package/dist/storage/implementation/v3/models.js.map +1 -1
  113. package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
  114. package/dist/storage/implementation/v3/source-table-utils.js +214 -0
  115. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  116. package/dist/storage/storage-index.d.ts +4 -4
  117. package/dist/storage/storage-index.js +4 -4
  118. package/dist/storage/storage-index.js.map +1 -1
  119. package/dist/types/types.d.ts +2 -2
  120. package/dist/utils/test-utils.d.ts +1 -2
  121. package/dist/utils/test-utils.js +5 -1
  122. package/dist/utils/test-utils.js.map +1 -1
  123. package/dist/utils/util.d.ts +1 -1
  124. package/dist/utils/util.js +3 -2
  125. package/dist/utils/util.js.map +1 -1
  126. package/package.json +9 -19
  127. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
  128. package/src/storage/MongoBucketStorage.ts +464 -95
  129. package/src/storage/implementation/CheckpointState.ts +59 -0
  130. package/src/storage/implementation/MongoBucketBatch.ts +159 -405
  131. package/src/storage/implementation/MongoChecksums.ts +7 -138
  132. package/src/storage/implementation/MongoCompactor.ts +20 -10
  133. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  134. package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
  135. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
  136. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  137. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +152 -0
  138. package/src/storage/implementation/MongoStorageProvider.ts +5 -2
  139. package/src/storage/implementation/MongoSyncBucketStorage.ts +89 -250
  140. package/src/storage/implementation/MongoSyncRulesLock.ts +14 -5
  141. package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
  142. package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
  143. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
  144. package/src/storage/implementation/common/PersistedBatch.ts +8 -4
  145. package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
  146. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  147. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  148. package/src/storage/implementation/db.ts +16 -21
  149. package/src/storage/implementation/models.ts +28 -61
  150. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +503 -5
  151. package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
  152. package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
  153. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +150 -49
  154. package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
  155. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  156. package/src/storage/implementation/v1/models.ts +25 -0
  157. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +611 -4
  158. package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
  159. package/src/storage/implementation/v3/MongoCompactorV3.ts +647 -30
  160. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
  161. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +397 -111
  162. package/src/storage/implementation/v3/PersistedBatchV3.ts +63 -38
  163. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
  164. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  165. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +48 -38
  166. package/src/storage/implementation/v3/bucket-format.ts +70 -0
  167. package/src/storage/implementation/v3/chunking.ts +38 -0
  168. package/src/storage/implementation/v3/models.ts +150 -44
  169. package/src/storage/implementation/v3/source-table-utils.ts +392 -0
  170. package/src/storage/storage-index.ts +12 -4
  171. package/src/utils/test-utils.ts +6 -3
  172. package/src/utils/util.ts +3 -2
  173. package/test/src/__snapshots__/storage.test.ts.snap +0 -75
  174. package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
  175. package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
  176. package/test/src/storage.test.ts +11 -11
  177. package/test/src/storage_compacting.test.ts +1956 -6
  178. package/test/src/storage_sync.test.ts +1429 -42
  179. package/test/src/storeCurrentData.test.ts +221 -0
  180. package/test/src/util.ts +2 -1
  181. package/tsconfig.json +1 -4
  182. package/tsconfig.tsbuildinfo +1 -1
  183. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -17
  184. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  185. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  186. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  187. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -64
  188. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  189. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -15
  190. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -42
  191. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  192. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  193. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  194. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  195. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
  196. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
  197. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
  198. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -5
  199. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  200. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  201. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -72
  202. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -76
  203. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -56
  204. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  205. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
  206. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -12
@@ -1,5 +1,6 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
2
  import { mongo } from '@powersync/lib-service-mongodb';
3
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
3
4
  import {
4
5
  CheckpointChanges,
5
6
  GetCheckpointChangesOptions,
@@ -7,6 +8,7 @@ import {
7
8
  internalToExternalOpId,
8
9
  ParameterSetLimitExceededError,
9
10
  ProtocolOpId,
11
+ SingleSyncConfigBucketDefinitionMapping,
10
12
  storage,
11
13
  utils
12
14
  } from '@powersync/service-core';
@@ -15,56 +17,145 @@ import { ParameterLookupRows, ScopedParameterLookup, SqliteJsonRow } from '@powe
15
17
  import * as bson from 'bson';
16
18
  import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
17
19
  import { MongoBucketStorage } from '../../MongoBucketStorage.js';
18
- import {
19
- MongoSyncBucketStorageCheckpoint,
20
- MongoSyncBucketStorageContext
21
- } from '../common/MongoSyncBucketStorageContext.js';
22
- import { CommonSourceTableDocument } from '../models.js';
23
- import { MongoBucketBatchOptions } from '../MongoBucketBatch.js';
20
+ import { BucketDataDoc } from '../common/BucketDataDoc.js';
21
+ import { MongoSyncBucketStorageCheckpoint } from '../common/MongoSyncBucketStorageCheckpoint.js';
24
22
  import { MongoChecksums } from '../MongoChecksums.js';
25
23
  import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
26
24
  import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
27
- import { MongoPersistedSyncRulesContent } from '../MongoPersistedSyncRulesContent.js';
25
+ import { MongoPersistedReplicationStream } from '../MongoPersistedReplicationStream.js';
28
26
  import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from '../MongoSyncBucketStorage.js';
29
- import { BucketDataDocumentV3, BucketParameterDocumentV3, loadBucketDataDocumentV3 } from './models.js';
27
+ import { loadBucketDataDocument } from './bucket-format.js';
28
+ import {
29
+ BucketDataDocumentV3,
30
+ BucketParameterDocumentV3,
31
+ deserializeParameterLookup,
32
+ ReplicationStreamDocumentV3,
33
+ serializeParameterLookup,
34
+ SyncRuleConfigStateV3
35
+ } from './models.js';
30
36
  import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
31
37
  import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
32
38
  import { MongoCompactorV3 } from './MongoCompactorV3.js';
33
- import { MongoParameterCompactorV3 } from './MongoParameterCompactorV3.js';
34
- import { deserializeParameterLookupV3, serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
39
+ import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
35
40
  import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
36
41
 
42
+ export interface MongoSyncBucketStorageContextV3 {
43
+ db: VersionedPowerSyncMongoV3;
44
+ replicationStreamId: number;
45
+ /**
46
+ * Persisted mapping of the single sync config that read operations are served from.
47
+ *
48
+ * Implemented as a lazy getter: accessing it on a storage instance with multiple sync
49
+ * configs throws, but operations that don't use it remain unaffected.
50
+ */
51
+ readonly mapping: SingleSyncConfigBucketDefinitionMapping;
52
+ }
53
+
54
+ function* walkDocumentOps(
55
+ data: BucketDataDoc[],
56
+ documentOpCounts: number[],
57
+ documentSizes: number[]
58
+ ): Generator<{ row: BucketDataDoc; docIndex: number; isLastOpInDocument: boolean }> {
59
+ let opIndex = 0;
60
+ for (const [docIndex, opCount] of documentOpCounts.entries()) {
61
+ for (let i = 0; i < opCount; i++) {
62
+ yield { row: data[opIndex++], docIndex, isLastOpInDocument: i === opCount - 1 };
63
+ }
64
+ }
65
+ }
66
+
67
+ function extractRowsFromDocument(
68
+ doc: BucketDataDocumentV3,
69
+ context: { replicationStreamId: number; definitionId: string },
70
+ bucketMap: Map<string, InternalOpId>,
71
+ endOpId: InternalOpId,
72
+ remainingLimit: number
73
+ ): { rows: BucketDataDoc[]; remainingLimit: number; limitReached: boolean } {
74
+ const rows: BucketDataDoc[] = [];
75
+ for (const row of loadBucketDataDocument(context, doc)) {
76
+ const bucket = row.bucketKey.bucket;
77
+ const bucketStart = bucketMap.get(bucket);
78
+ if (bucketStart == null) {
79
+ throw new Error(`data for unexpected bucket: ${bucket}`);
80
+ }
81
+ if (row.o <= bucketStart) {
82
+ continue;
83
+ }
84
+ if (row.o > endOpId) {
85
+ continue;
86
+ }
87
+
88
+ rows.push(row);
89
+ remainingLimit--;
90
+ if (remainingLimit <= 0) {
91
+ return { rows, remainingLimit, limitReached: true };
92
+ }
93
+ }
94
+ return { rows, remainingLimit, limitReached: false };
95
+ }
96
+
37
97
  export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
38
- // Declare types to be more specific
39
98
  declare readonly db: VersionedPowerSyncMongoV3;
40
99
  declare readonly checksums: MongoChecksumsV3;
41
100
 
42
101
  constructor(
43
102
  factory: MongoBucketStorage,
44
- group_id: number,
45
- sync_rules: MongoPersistedSyncRulesContent,
46
- slot_name: string,
103
+ replicationStreamId: number,
104
+ replicationStream: MongoPersistedReplicationStream,
105
+ replicationStreamName: string,
47
106
  writeCheckpointMode: storage.WriteCheckpointMode | undefined,
48
107
  options: MongoSyncBucketStorageOptions
49
108
  ) {
50
- super(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options);
109
+ super(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options);
110
+ if (replicationStream.syncConfigIds.length == 0) {
111
+ throw new ServiceAssertionError('Missing sync config id for storage v3');
112
+ }
113
+ }
114
+
115
+ private get syncConfigIds(): bson.ObjectId[] {
116
+ return this.replicationStream.syncConfigIds;
117
+ }
118
+
119
+ private get syncRulesCollection(): mongo.Collection<ReplicationStreamDocumentV3> {
120
+ return this.db.sync_rules as unknown as mongo.Collection<ReplicationStreamDocumentV3>;
121
+ }
122
+
123
+ private syncConfigMatch(extra: mongo.Document = {}): mongo.Filter<ReplicationStreamDocumentV3> {
124
+ return {
125
+ _id: this.replicationStreamId,
126
+ sync_configs: {
127
+ $elemMatch: {
128
+ _id: { $in: this.syncConfigIds },
129
+ ...extra
130
+ }
131
+ }
132
+ };
133
+ }
134
+
135
+ private syncConfigProjection(extra: mongo.Document = {}): mongo.Document {
136
+ return {
137
+ ...extra,
138
+ sync_configs: 1
139
+ };
140
+ }
141
+
142
+ private selectedSyncConfigs(doc: Pick<ReplicationStreamDocumentV3, 'sync_configs'> | null): SyncRuleConfigStateV3[] {
143
+ return doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
51
144
  }
52
145
 
53
146
  protected async initializeVersionStorage(): Promise<void> {
54
- const mapping = this.mapping;
55
- for (let source of mapping.allBucketDefinitionIds()) {
56
- const collection = this.db.bucketDataV3(this.group_id, source).collectionName;
57
- await this.db.db
58
- .createCollection(collection, { clusteredIndex: { name: '_id', unique: true, key: { _id: 1 } } })
59
- .catch((error) => {
60
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
61
- return;
62
- }
63
- throw error;
64
- });
147
+ const storageIds = this.storageIds;
148
+ for (const source of storageIds.bucketDefinitionIds) {
149
+ const collection = this.db.bucketData(this.replicationStreamId, source).collectionName;
150
+ await this.db.db.createCollection(collection, {}).catch((error) => {
151
+ if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
152
+ return;
153
+ }
154
+ throw error;
155
+ });
65
156
  }
66
- for (let indexId of mapping.allParameterIndexIds()) {
67
- await this.db.parameterIndexV3(this.group_id, indexId).createIndex(
157
+ for (const indexId of storageIds.parameterIndexIds) {
158
+ await this.db.parameterIndex(this.replicationStreamId, indexId).createIndex(
68
159
  {
69
160
  lookup: 1,
70
161
  key: 1,
@@ -78,10 +169,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
78
169
  }
79
170
 
80
171
  protected createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums {
81
- return new MongoChecksumsV3(this.db, this.group_id, {
172
+ return new MongoChecksumsV3(this.db, this.replicationStreamId, {
82
173
  ...options.checksumOptions,
83
174
  storageConfig: options?.storageConfig,
84
- mapping: this.sync_rules.mapping
175
+ syncConfigMapping: () => this.singleSyncConfigMapping()
85
176
  });
86
177
  }
87
178
 
@@ -93,44 +184,161 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
93
184
  checkpoint: InternalOpId,
94
185
  options: storage.CompactOptions
95
186
  ): MongoParameterCompactor {
96
- return new MongoParameterCompactorV3(this.db, this.group_id, checkpoint, options);
187
+ return new MongoParameterCompactor(this.db, this.replicationStreamId, checkpoint, options, () =>
188
+ this.db
189
+ .listParameterIndexCollections(this.replicationStreamId)
190
+ .then((collections) =>
191
+ collections.map((c) => c.collection as unknown as lib_mongo.mongo.Collection<lib_mongo.mongo.Document>)
192
+ )
193
+ );
97
194
  }
98
195
 
99
- protected createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch {
100
- return new MongoBucketBatchV3(batchOptions);
196
+ protected async fetchPersistedOpHead(): Promise<InternalOpId | null> {
197
+ const doc = await this.syncRulesCollection.findOne(
198
+ { _id: this.replicationStreamId },
199
+ { projection: { last_persisted_op: 1 } }
200
+ );
201
+ return doc?.last_persisted_op == null ? null : BigInt(doc.last_persisted_op);
101
202
  }
102
203
 
103
- protected sourceTableBaseId(): Partial<CommonSourceTableDocument> {
104
- return {};
204
+ protected async createWriterImpl(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
205
+ const doc = await this.syncRulesCollection.findOne(
206
+ { _id: this.replicationStreamId },
207
+ { projection: { resume_lsn: 1 } }
208
+ );
209
+
210
+ return new MongoBucketBatchV3({
211
+ ...this.writerBatchOptions(options),
212
+ // The stream-level replication position - per-config checkpoint LSNs are consistency
213
+ // markers and do not affect where replication resumes.
214
+ resumeFromLsn: doc?.resume_lsn ?? null,
215
+ syncConfigIds: this.syncConfigIds
216
+ });
105
217
  }
106
218
 
107
- protected augmentCreatedSourceTableDocument(
108
- createDoc: CommonSourceTableDocument,
109
- options: storage.ResolveTableOptions,
110
- candidateSourceTable: storage.SourceTable
111
- ): void {
112
- const bucketDataSourceIds = options.sync_rules.definition.bucketDataSources
113
- .filter((source) => source.tableSyncsData(candidateSourceTable))
114
- .map((source) => this.mapping.bucketSourceId(source));
115
- const parameterLookupSourceIds = options.sync_rules.definition.bucketParameterLookupSources
116
- .filter((source) => source.tableSyncsParameters(candidateSourceTable))
117
- .map((source) => this.mapping.parameterLookupId(source));
219
+ protected async fetchCheckpointState(
220
+ session: mongo.ClientSession
221
+ ): Promise<{ checkpoint: bigint; lsn: string | null } | null> {
222
+ const doc = await this.syncRulesCollection.findOne(
223
+ this.syncConfigMatch({
224
+ state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
225
+ }),
226
+ {
227
+ session,
228
+ projection: this.syncConfigProjection()
229
+ }
230
+ );
231
+ // Checkpoints are served from the single active config. A PROCESSING config in the same
232
+ // stream (incremental reprocessing) does not affect checkpoints until it is activated.
233
+ const syncConfigs = this.selectedSyncConfigs(doc).filter((config) =>
234
+ [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED].includes(config.state)
235
+ );
236
+ if (syncConfigs.length > 1) {
237
+ // Activation atomically replaces the previous active config, so this cannot happen unless
238
+ // the stored state is corrupt.
239
+ throw new ServiceAssertionError(
240
+ `Expected a single active sync config, got ${syncConfigs.map((config) => config._id.toHexString()).join(', ')}`
241
+ );
242
+ }
243
+ const syncConfig = syncConfigs[0];
244
+ if (syncConfig == null || !syncConfig.snapshot_done) {
245
+ return null;
246
+ }
247
+ return {
248
+ checkpoint: syncConfig.last_checkpoint ?? 0n,
249
+ lsn: syncConfig.last_checkpoint_lsn ?? null
250
+ };
251
+ }
118
252
 
119
- Object.assign(createDoc, {
120
- bucket_data_source_ids: bucketDataSourceIds,
121
- parameter_lookup_source_ids: parameterLookupSourceIds
253
+ protected async terminateSyncRuleState(): Promise<void> {
254
+ await this.db.sync_rules.updateOne(
255
+ {
256
+ _id: this.replicationStreamId
257
+ },
258
+ {
259
+ $set: {
260
+ state: storage.SyncRuleState.TERMINATED,
261
+ persisted_lsn: null,
262
+ sync_configs: []
263
+ }
264
+ }
265
+ );
266
+ }
267
+
268
+ protected async getStatusImpl(): Promise<storage.ReplicationStreamStatus> {
269
+ const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
270
+ projection: this.syncConfigProjection({ resume_lsn: 1 })
122
271
  });
272
+ const syncConfigs = this.selectedSyncConfigs(doc);
273
+ if (doc == null || syncConfigs.length == 0) {
274
+ throw new ServiceAssertionError('Cannot find replication stream status');
275
+ }
276
+
277
+ return {
278
+ snapshotDone:
279
+ syncConfigs.every((config) => config.snapshot_done ?? false) &&
280
+ syncConfigs.every((config) => config.last_checkpoint_lsn != null),
281
+ resumeLsn: doc.resume_lsn ?? null
282
+ };
283
+ }
284
+
285
+ protected async clearSyncRuleState(): Promise<void> {
286
+ // Clearing resets the entire replication stream (bucket data and the op sequence), so reset
287
+ // the checkpoint state for _all_ embedded sync configs, not only the ones selected for this
288
+ // storage instance. This maintains the invariant that no config has a last_checkpoint past
289
+ // the stream-level last_persisted_op.
290
+ await this.syncRulesCollection.updateOne(
291
+ { _id: this.replicationStreamId },
292
+ {
293
+ $set: {
294
+ persisted_lsn: null,
295
+ 'sync_configs.$[].snapshot_done': false,
296
+ 'sync_configs.$[].last_checkpoint_lsn': null,
297
+ 'sync_configs.$[].last_checkpoint': null,
298
+ 'sync_configs.$[].no_checkpoint_before': null
299
+ },
300
+ $unset: {
301
+ resume_lsn: 1,
302
+ last_persisted_op: 1
303
+ }
304
+ },
305
+ {
306
+ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
307
+ }
308
+ );
123
309
  }
124
310
 
125
- protected async initializeResolvedSourceRecords(sourceTableId: bson.ObjectId): Promise<void> {
126
- await this.db.initializeSourceRecordsCollection(this.group_id, sourceTableId);
311
+ /**
312
+ * The persisted mapping of the single sync config that read operations are served from.
313
+ *
314
+ * Reads always operate on a single sync config: read-path storage instances are
315
+ * constructed for the active sync config only (see MongoBucketStorage.getActiveSyncConfig),
316
+ * and checkpoints are served from the single active config (see fetchCheckpointState).
317
+ * Within a single sync config, unique names are the persistence key of its rule mapping,
318
+ * so its name-keyed {@link SingleSyncConfigBucketDefinitionMapping} resolves sources from
319
+ * any parse of that config unambiguously - no parsed-set identity is required.
320
+ *
321
+ * Throws on storage instances with multiple sync configs (replication-side instances),
322
+ * which must not serve reads.
323
+ */
324
+ private singleSyncConfigMapping(): SingleSyncConfigBucketDefinitionMapping {
325
+ const content = this.replicationStream.syncConfigContent;
326
+ if (content.length != 1) {
327
+ throw new ServiceAssertionError(
328
+ `Read operations require a storage instance with a single sync config, got ${content.length}`
329
+ );
330
+ }
331
+ return content[0].mapping;
127
332
  }
128
333
 
129
- protected override get versionContext(): MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3> {
334
+ protected get versionContext(): MongoSyncBucketStorageContextV3 {
335
+ const self = this;
130
336
  return {
131
337
  db: this.db,
132
- group_id: this.group_id,
133
- mapping: this.mapping
338
+ replicationStreamId: this.replicationStreamId,
339
+ get mapping() {
340
+ return self.singleSyncConfigMapping();
341
+ }
134
342
  };
135
343
  }
136
344
 
@@ -151,26 +359,26 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
151
359
  }
152
360
 
153
361
  protected async clearBucketData(_signal?: AbortSignal): Promise<void> {
154
- for (const collection of await this.db.listBucketDataCollectionsV3(this.group_id)) {
362
+ for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
155
363
  await collection.drop();
156
364
  }
157
365
  }
158
366
 
159
367
  protected async clearParameterIndexes(_signal?: AbortSignal): Promise<void> {
160
- for (const collection of await this.db.listParameterIndexCollectionsV3(this.group_id)) {
368
+ for (const collection of await this.db.listParameterIndexCollections(this.replicationStreamId)) {
161
369
  await collection.collection.drop();
162
370
  }
163
371
  }
164
372
 
165
373
  protected async clearSourceRecords(_signal?: AbortSignal): Promise<void> {
166
- for (const collection of await this.db.listSourceRecordCollectionsV3(this.group_id)) {
374
+ for (const collection of await this.db.listSourceRecordCollections(this.replicationStreamId)) {
167
375
  await collection.drop();
168
376
  }
169
377
  }
170
378
 
171
379
  protected async clearBucketState(_signal?: AbortSignal): Promise<void> {
172
380
  await this.db
173
- .bucketStateV3(this.group_id)
381
+ .bucketState(this.replicationStreamId)
174
382
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
175
383
  .catch((error) => {
176
384
  if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
@@ -182,7 +390,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
182
390
 
183
391
  protected async clearSourceTables(_signal?: AbortSignal): Promise<void> {
184
392
  await this.db
185
- .sourceTablesV3(this.group_id)
393
+ .sourceTables(this.replicationStreamId)
186
394
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
187
395
  .catch((error) => {
188
396
  if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
@@ -192,6 +400,19 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
192
400
  });
193
401
  }
194
402
 
403
+ async cleanupStoppedSyncConfigs(
404
+ options: storage.CleanupStoppedSyncConfigsOptions
405
+ ): Promise<storage.CleanupStoppedSyncConfigsResult> {
406
+ return new MongoStoppedSyncConfigCleanup({
407
+ db: this.db,
408
+ replicationStreamId: this.replicationStreamId,
409
+ signal: options.signal,
410
+ logger: options.logger ?? this.logger,
411
+ defaultSchema: options.defaultSchema,
412
+ sourceConnectionTag: options.sourceConnectionTag
413
+ }).run();
414
+ }
415
+
195
416
  protected getDataBucketChangesImpl(
196
417
  options: GetCheckpointChangesOptions
197
418
  ): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
@@ -206,7 +427,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
206
427
  }
207
428
 
208
429
  export async function getParameterSetsV3(
209
- ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
430
+ ctx: MongoSyncBucketStorageContextV3,
210
431
  checkpoint: MongoSyncBucketStorageCheckpoint,
211
432
  lookups: ScopedParameterLookup[],
212
433
  limit: number
@@ -222,8 +443,8 @@ export async function getParameterSetsV3(
222
443
  pipeline: mongo.Document[];
223
444
  } => {
224
445
  const indexId = lookup.indexId;
225
- const collection = ctx.db.parameterIndexV3(ctx.group_id, indexId);
226
- const lookupFilter = serializeParameterLookupV3(lookup);
446
+ const collection = ctx.db.parameterIndex(ctx.replicationStreamId, indexId);
447
+ const lookupFilter = serializeParameterLookup(lookup);
227
448
 
228
449
  return {
229
450
  collection,
@@ -306,7 +527,7 @@ export async function getParameterSetsV3(
306
527
  }
307
528
 
308
529
  export async function* getBucketDataBatchV3(
309
- ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
530
+ ctx: MongoSyncBucketStorageContextV3,
310
531
  checkpoint: utils.InternalOpId,
311
532
  dataBuckets: storage.BucketDataRequest[],
312
533
  options?: storage.BucketDataBatchOptions
@@ -333,61 +554,125 @@ export async function* getBucketDataBatchV3(
333
554
  }
334
555
 
335
556
  const definitionGroups = Array.from(requestsByDefinition.entries());
336
- for (let groupIndex = 0; groupIndex < definitionGroups.length && remainingLimit > 0; groupIndex++) {
337
- const [definitionId, requests] = definitionGroups[groupIndex];
557
+ for (const [groupIndex, [definitionId, requests]] of definitionGroups.entries()) {
558
+ if (remainingLimit <= 0) {
559
+ break;
560
+ }
338
561
  const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
339
562
  const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
340
- const filters: mongo.Filter<BucketDataDocumentV3>[] = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
563
+ const filters = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
341
564
  _id: {
342
- $gt: {
343
- b: bucket,
344
- o: start
345
- },
346
- $lte: {
347
- b: bucket,
348
- o: end as any
349
- }
350
- }
351
- }));
352
-
353
- const cursor = ctx.db.bucketDataV3(ctx.group_id, definitionId).find(
354
- {
355
- $or: filters
565
+ $gt: { b: bucket, o: start },
566
+ $lte: { b: bucket, o: new bson.MaxKey() }
356
567
  },
357
- {
358
- session: undefined,
359
- sort: { _id: 1 },
360
- limit: remainingLimit,
361
- batchSize: remainingLimit + 1,
362
- raw: true,
363
- maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
364
- }
365
- ) as unknown as mongo.FindCursor<Buffer>;
366
-
367
- let { data, hasMore: batchHasMore } = await readSingleBatch(cursor).catch((e) => {
568
+ min_op: { $lte: end }
569
+ // MongoDB Filter<T> doesn't accept compound _id ranges or dotted field paths in its type.
570
+ })) as unknown as mongo.Filter<BucketDataDocumentV3>[];
571
+
572
+ const collection = ctx.db.bucketData(ctx.replicationStreamId, definitionId);
573
+ // MongoDB Filter<T> doesn't accept the $or operator in its type.
574
+ const filter = { $or: filters } as unknown as mongo.Filter<BucketDataDocumentV3>;
575
+ const context = { replicationStreamId: ctx.replicationStreamId, definitionId };
576
+ const limit = remainingLimit;
577
+
578
+ const cursorOptions = { limit: remainingLimit, batchSize: remainingLimit + 1 };
579
+
580
+ // raw: true returns Buffers, but the driver typing doesn't reflect that
581
+ // without an explicit cast to FindCursor<Buffer>.
582
+ const cursor = collection.find(filter, {
583
+ session: undefined,
584
+ sort: { _id: 1 },
585
+ raw: true,
586
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS,
587
+ ...cursorOptions
588
+ }) as unknown as mongo.FindCursor<Buffer>;
589
+
590
+ let { data: rawData, hasMore } = await readSingleBatch(cursor).catch((e: unknown) => {
368
591
  throw lib_mongo.mapQueryError(e, 'while reading bucket data');
369
592
  });
370
- if (data.length == remainingLimit) {
371
- batchHasMore = true;
593
+
594
+ if (cursorOptions.limit != null && rawData.length >= cursorOptions.limit) {
595
+ hasMore = true;
596
+ }
597
+
598
+ const data: BucketDataDoc[] = [];
599
+ const documentOpCounts: number[] = [];
600
+ const documentSizes: number[] = [];
601
+ let sharedRemainingLimit = limit;
602
+ let limitReached = false;
603
+ // Buckets whose matched document contributed no rows after filtering.
604
+ const completeEmptyBuckets = new Set<string>();
605
+
606
+ for (const raw of rawData) {
607
+ const doc = bson.deserialize(raw, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3;
608
+ const {
609
+ rows,
610
+ remainingLimit,
611
+ limitReached: docLimitReached
612
+ } = extractRowsFromDocument(doc, context, bucketMap, end, sharedRemainingLimit);
613
+ if (rows.length == 0) {
614
+ // The document straddles the requested (start, end] window: it matched the
615
+ // query, but none of its ops are in range. Since its _id.o (max op) must be
616
+ // > end (any op <= end would have been > start, and thus in range), and
617
+ // document ranges per bucket are disjoint, no later document for this bucket
618
+ // can match either. The bucket is complete through the checkpoint.
619
+ completeEmptyBuckets.add(doc._id.b);
620
+ }
621
+ data.push(...rows);
622
+ documentOpCounts.push(rows.length);
623
+ documentSizes.push(raw.byteLength);
624
+ sharedRemainingLimit = remainingLimit;
625
+ if (docLimitReached) {
626
+ limitReached = true;
627
+ break;
628
+ }
372
629
  }
630
+
631
+ const batchHasMore = hasMore || limitReached;
632
+
633
+ // Empty chunks are not forwarded to clients, but report progress to the caller:
634
+ // the bucket's position advances to the checkpoint, so it is not re-requested.
635
+ // If the batch produced no data at all, the last empty chunk also carries the
636
+ // has_more signal, so the caller re-requests the remaining buckets instead of
637
+ // treating an all-filtered batch as the end of the stream.
638
+ const emptyBuckets = Array.from(completeEmptyBuckets);
639
+ for (const [index, bucket] of emptyBuckets.entries()) {
640
+ const startOpId = bucketMap.get(bucket);
641
+ if (startOpId == null) {
642
+ throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
643
+ }
644
+ const isLastChunkOfBatch = data.length == 0 && index == emptyBuckets.length - 1;
645
+ yield {
646
+ chunkData: {
647
+ bucket,
648
+ after: internalToExternalOpId(startOpId),
649
+ has_more: isLastChunkOfBatch && batchHasMore,
650
+ data: [],
651
+ next_after: internalToExternalOpId(end)
652
+ },
653
+ targetOp: null
654
+ };
655
+ }
656
+
373
657
  if (data.length == 0) {
658
+ if (batchHasMore) {
659
+ // The remaining documents are read in the next round, after the caller has
660
+ // advanced the positions of the empty buckets above.
661
+ return;
662
+ }
374
663
  continue;
375
664
  }
376
665
 
377
666
  remainingLimit -= data.length;
378
667
 
379
- let chunkSizeBytes = 0;
668
+ let currentChunkSizeBytes = 0;
380
669
  let currentChunk: utils.SyncBucketData | null = null;
381
670
  let targetOp: InternalOpId | null = null;
382
671
 
383
- for (let rawData of data) {
384
- const row = loadBucketDataDocumentV3(
385
- { replicationStreamId: ctx.group_id, definitionId },
386
- bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3
387
- );
672
+ for (const { row, docIndex, isLastOpInDocument } of walkDocumentOps(data, documentOpCounts, documentSizes)) {
388
673
  const bucket = row.bucketKey.bucket;
389
674
 
390
- if (currentChunk == null || currentChunk.bucket != bucket || chunkSizeBytes >= chunkSizeLimitBytes) {
675
+ if (currentChunk == null || currentChunk.bucket != bucket || currentChunkSizeBytes >= chunkSizeLimitBytes) {
391
676
  let start: ProtocolOpId | undefined = undefined;
392
677
  if (currentChunk != null) {
393
678
  if (currentChunk.bucket == bucket) {
@@ -397,8 +682,8 @@ export async function* getBucketDataBatchV3(
397
682
 
398
683
  const yieldChunk = currentChunk;
399
684
  currentChunk = null;
400
- chunkSizeBytes = 0;
401
- yield { chunkData: yieldChunk, targetOp: targetOp };
685
+ currentChunkSizeBytes = 0;
686
+ yield { chunkData: yieldChunk, targetOp };
402
687
  targetOp = null;
403
688
  }
404
689
 
@@ -425,13 +710,16 @@ export async function* getBucketDataBatchV3(
425
710
 
426
711
  currentChunk.data.push(entry);
427
712
  currentChunk.next_after = entry.op_id;
428
- chunkSizeBytes += rawData.byteLength;
713
+
714
+ if (isLastOpInDocument) {
715
+ currentChunkSizeBytes += documentSizes[docIndex];
716
+ }
429
717
  }
430
718
 
431
719
  if (currentChunk != null) {
432
720
  const yieldChunk = currentChunk;
433
721
  yieldChunk.has_more = batchHasMore || (remainingLimit <= 0 && hasLaterDefinitionGroups);
434
- yield { chunkData: yieldChunk, targetOp: targetOp };
722
+ yield { chunkData: yieldChunk, targetOp };
435
723
  }
436
724
 
437
725
  if (batchHasMore || remainingLimit <= 0) {
@@ -441,12 +729,12 @@ export async function* getBucketDataBatchV3(
441
729
  }
442
730
 
443
731
  export async function getDataBucketChangesV3(
444
- ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
732
+ ctx: MongoSyncBucketStorageContextV3,
445
733
  options: GetCheckpointChangesOptions
446
734
  ): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
447
735
  const limit = 1000;
448
736
  const bucketStateUpdates = await ctx.db
449
- .bucketStateV3(ctx.group_id)
737
+ .bucketState(ctx.replicationStreamId)
450
738
  .aggregate<{ _id: string; last_op: bigint }>(
451
739
  [
452
740
  {
@@ -483,14 +771,14 @@ export async function getDataBucketChangesV3(
483
771
  }
484
772
 
485
773
  export async function getParameterBucketChangesV3(
486
- ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
774
+ ctx: MongoSyncBucketStorageContextV3,
487
775
  options: GetCheckpointChangesOptions
488
776
  ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
489
777
  const limit = 1000;
490
778
  const indexIds = ctx.mapping.allParameterIndexIds();
491
779
  const collections = indexIds.map((indexId) => ({
492
780
  indexId,
493
- collection: ctx.db.parameterIndexV3(ctx.group_id, indexId)
781
+ collection: ctx.db.parameterIndex(ctx.replicationStreamId, indexId)
494
782
  }));
495
783
  if (collections.length == 0) {
496
784
  return {
@@ -543,8 +831,6 @@ export async function getParameterBucketChangesV3(
543
831
  invalidateParameterBuckets: invalidateParameterUpdates,
544
832
  updatedParameterLookups: invalidateParameterUpdates
545
833
  ? new Set<string>()
546
- : new Set<string>(
547
- parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookupV3(p.lookup, p.indexId)))
548
- )
834
+ : new Set<string>(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup, p.indexId))))
549
835
  };
550
836
  }