@powersync/service-module-mongodb-storage 0.15.4 → 0.17.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 (202) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
  3. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
  4. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +2 -2
  5. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  6. package/dist/storage/MongoBucketStorage.d.ts +8 -6
  7. package/dist/storage/MongoBucketStorage.js +153 -66
  8. package/dist/storage/MongoBucketStorage.js.map +1 -1
  9. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +15 -0
  10. package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
  11. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  12. package/dist/storage/implementation/CheckpointState.d.ts +20 -0
  13. package/dist/storage/implementation/CheckpointState.js +31 -0
  14. package/dist/storage/implementation/CheckpointState.js.map +1 -0
  15. package/dist/storage/implementation/MongoBucketBatch.d.ts +48 -35
  16. package/dist/storage/implementation/MongoBucketBatch.js +118 -379
  17. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  18. package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
  19. package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
  20. package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
  21. package/dist/storage/implementation/MongoChecksums.d.ts +29 -17
  22. package/dist/storage/implementation/MongoChecksums.js +13 -72
  23. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  24. package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
  25. package/dist/storage/implementation/MongoCompactor.js +229 -296
  26. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  27. package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
  28. package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
  29. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  30. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
  31. package/dist/storage/implementation/MongoPersistedSyncRules.js +67 -0
  32. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  33. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +22 -5
  34. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +56 -13
  35. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  36. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +61 -32
  37. package/dist/storage/implementation/MongoSyncBucketStorage.js +85 -523
  38. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  39. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +10 -4
  40. package/dist/storage/implementation/MongoSyncRulesLock.js +19 -13
  41. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
  43. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  44. package/dist/storage/implementation/OperationBatch.js +1 -1
  45. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
  46. package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
  47. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
  48. package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
  49. package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
  50. package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
  51. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
  52. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
  53. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
  54. package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
  55. package/dist/storage/implementation/common/PersistedBatch.js +237 -0
  56. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
  57. package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
  58. package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
  59. package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
  60. package/dist/storage/implementation/common/SourceRecordStore.d.ts +35 -0
  61. package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
  62. package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
  63. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
  64. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
  65. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
  66. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
  67. package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
  68. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
  69. package/dist/storage/implementation/db.d.ts +41 -36
  70. package/dist/storage/implementation/db.js +77 -99
  71. package/dist/storage/implementation/db.js.map +1 -1
  72. package/dist/storage/implementation/models.d.ts +79 -66
  73. package/dist/storage/implementation/models.js +20 -1
  74. package/dist/storage/implementation/models.js.map +1 -1
  75. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +27 -0
  76. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +407 -0
  77. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
  78. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
  79. package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
  80. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
  81. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
  82. package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
  83. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
  84. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
  85. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
  86. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
  87. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +50 -0
  88. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +354 -0
  89. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
  90. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +25 -0
  91. package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
  92. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
  93. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
  94. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
  95. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
  96. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
  97. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
  98. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
  99. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
  100. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
  101. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
  102. package/dist/storage/implementation/v1/models.d.ts +45 -0
  103. package/dist/storage/implementation/v1/models.js +37 -0
  104. package/dist/storage/implementation/v1/models.js.map +1 -0
  105. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +30 -0
  106. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +463 -0
  107. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
  108. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
  109. package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
  110. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
  111. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
  112. package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
  113. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
  114. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
  115. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
  116. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  117. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +4 -0
  118. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
  119. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
  120. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +63 -0
  121. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +508 -0
  122. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
  123. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +28 -0
  124. package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
  125. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
  126. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
  127. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
  128. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
  129. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
  130. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
  131. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
  132. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +22 -0
  133. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +74 -0
  134. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
  135. package/dist/storage/implementation/v3/models.d.ts +101 -0
  136. package/dist/storage/implementation/v3/models.js +34 -0
  137. package/dist/storage/implementation/v3/models.js.map +1 -0
  138. package/dist/storage/storage-index.d.ts +6 -3
  139. package/dist/storage/storage-index.js +6 -3
  140. package/dist/storage/storage-index.js.map +1 -1
  141. package/dist/utils/util.d.ts +10 -3
  142. package/dist/utils/util.js +24 -3
  143. package/dist/utils/util.js.map +1 -1
  144. package/package.json +9 -9
  145. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
  146. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
  147. package/src/storage/MongoBucketStorage.ts +254 -99
  148. package/src/storage/implementation/BucketDefinitionMapping.ts +75 -0
  149. package/src/storage/implementation/CheckpointState.ts +59 -0
  150. package/src/storage/implementation/MongoBucketBatch.ts +182 -490
  151. package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
  152. package/src/storage/implementation/MongoChecksums.ts +53 -75
  153. package/src/storage/implementation/MongoCompactor.ts +374 -404
  154. package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
  155. package/src/storage/implementation/MongoPersistedSyncRules.ts +82 -0
  156. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +78 -16
  157. package/src/storage/implementation/MongoSyncBucketStorage.ts +179 -628
  158. package/src/storage/implementation/MongoSyncRulesLock.ts +20 -16
  159. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +3 -1
  160. package/src/storage/implementation/OperationBatch.ts +1 -1
  161. package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
  162. package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
  163. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
  164. package/src/storage/implementation/common/PersistedBatch.ts +364 -0
  165. package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
  166. package/src/storage/implementation/common/SourceRecordStore.ts +48 -0
  167. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
  168. package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
  169. package/src/storage/implementation/db.ts +110 -131
  170. package/src/storage/implementation/models.ts +102 -79
  171. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +509 -0
  172. package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
  173. package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
  174. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
  175. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +543 -0
  176. package/src/storage/implementation/v1/PersistedBatchV1.ts +229 -0
  177. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
  178. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
  179. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
  180. package/src/storage/implementation/v1/models.ts +99 -0
  181. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +607 -0
  182. package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
  183. package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
  184. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
  185. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +11 -0
  186. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +678 -0
  187. package/src/storage/implementation/v3/PersistedBatchV3.ts +317 -0
  188. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
  189. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
  190. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +117 -0
  191. package/src/storage/implementation/v3/models.ts +164 -0
  192. package/src/storage/storage-index.ts +6 -3
  193. package/src/utils/util.ts +34 -5
  194. package/test/src/storage_compacting.test.ts +57 -29
  195. package/test/src/storage_sync.test.ts +767 -5
  196. package/test/src/storeCurrentData.test.ts +211 -0
  197. package/test/tsconfig.json +0 -1
  198. package/tsconfig.tsbuildinfo +1 -1
  199. package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
  200. package/dist/storage/implementation/PersistedBatch.js +0 -354
  201. package/dist/storage/implementation/PersistedBatch.js.map +0 -1
  202. package/src/storage/implementation/PersistedBatch.ts +0 -432
@@ -0,0 +1,678 @@
1
+ import * as lib_mongo from '@powersync/lib-service-mongodb';
2
+ import { mongo } from '@powersync/lib-service-mongodb';
3
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
4
+ import {
5
+ CheckpointChanges,
6
+ GetCheckpointChangesOptions,
7
+ InternalOpId,
8
+ internalToExternalOpId,
9
+ maxLsn,
10
+ ParameterSetLimitExceededError,
11
+ ProtocolOpId,
12
+ storage,
13
+ utils
14
+ } from '@powersync/service-core';
15
+ import { JSONBig } from '@powersync/service-jsonbig';
16
+ import { ParameterLookupRows, ScopedParameterLookup, SqliteJsonRow } from '@powersync/service-sync-rules';
17
+ import * as bson from 'bson';
18
+ import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
19
+ import { MongoBucketStorage } from '../../MongoBucketStorage.js';
20
+ import {
21
+ MongoSyncBucketStorageCheckpoint,
22
+ MongoSyncBucketStorageContext
23
+ } from '../common/MongoSyncBucketStorageContext.js';
24
+ import { MongoBucketBatchOptions } from '../MongoBucketBatch.js';
25
+ import { MongoChecksums } from '../MongoChecksums.js';
26
+ import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
27
+ import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
28
+ import {
29
+ MongoPersistedSyncRulesContentV1,
30
+ MongoPersistedSyncRulesContentV3
31
+ } from '../MongoPersistedSyncRulesContent.js';
32
+ import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from '../MongoSyncBucketStorage.js';
33
+ import {
34
+ BucketDataDocumentV3,
35
+ BucketParameterDocumentV3,
36
+ loadBucketDataDocumentV3,
37
+ ReplicationStreamDocumentV3,
38
+ SyncRuleConfigStateV3
39
+ } from './models.js';
40
+ import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
41
+ import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
42
+ import { MongoCompactorV3 } from './MongoCompactorV3.js';
43
+ import { MongoParameterCompactorV3 } from './MongoParameterCompactorV3.js';
44
+ import { deserializeParameterLookupV3, serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
45
+ import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
46
+
47
+ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
48
+ // Declare types to be more specific
49
+ declare readonly db: VersionedPowerSyncMongoV3;
50
+ declare readonly checksums: MongoChecksumsV3;
51
+
52
+ private readonly syncRulesV3: MongoPersistedSyncRulesContentV3;
53
+
54
+ constructor(
55
+ factory: MongoBucketStorage,
56
+ group_id: number,
57
+ sync_rules: MongoPersistedSyncRulesContentV1,
58
+ slot_name: string,
59
+ writeCheckpointMode: storage.WriteCheckpointMode | undefined,
60
+ options: MongoSyncBucketStorageOptions
61
+ ) {
62
+ super(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options);
63
+ if (!(sync_rules instanceof MongoPersistedSyncRulesContentV3)) {
64
+ throw new ServiceAssertionError('Missing sync config id for storage v3');
65
+ }
66
+ this.syncRulesV3 = sync_rules;
67
+ }
68
+
69
+ private get syncConfigId(): bson.ObjectId {
70
+ return this.syncRulesV3.syncConfigId;
71
+ }
72
+
73
+ private get syncRulesCollection(): mongo.Collection<ReplicationStreamDocumentV3> {
74
+ return this.db.sync_rules as unknown as mongo.Collection<ReplicationStreamDocumentV3>;
75
+ }
76
+
77
+ private syncConfigMatch(extra: mongo.Document = {}): mongo.Filter<ReplicationStreamDocumentV3> {
78
+ return {
79
+ _id: this.group_id,
80
+ sync_configs: {
81
+ $elemMatch: {
82
+ _id: this.syncConfigId,
83
+ ...extra
84
+ }
85
+ }
86
+ };
87
+ }
88
+
89
+ private syncConfigProjection(extra: mongo.Document = {}): mongo.Document {
90
+ return {
91
+ ...extra,
92
+ sync_configs: {
93
+ $elemMatch: {
94
+ _id: this.syncConfigId
95
+ }
96
+ }
97
+ };
98
+ }
99
+
100
+ private syncConfigArrayFilters(): mongo.UpdateOptions['arrayFilters'] {
101
+ return [{ 'config._id': this.syncConfigId }];
102
+ }
103
+
104
+ /**
105
+ * For now, we only support a single sync config per replication stream.
106
+ *
107
+ * In the future we'll add support for multiple.
108
+ */
109
+ private selectedSyncConfig(
110
+ doc: Pick<ReplicationStreamDocumentV3, 'sync_configs'> | null
111
+ ): SyncRuleConfigStateV3 | null {
112
+ return doc?.sync_configs?.[0] ?? null;
113
+ }
114
+
115
+ protected async initializeVersionStorage(): Promise<void> {
116
+ const mapping = this.mapping;
117
+ for (let source of mapping.allBucketDefinitionIds()) {
118
+ const collection = this.db.bucketDataV3(this.group_id, source).collectionName;
119
+ await this.db.db
120
+ .createCollection(collection, { clusteredIndex: { name: '_id', unique: true, key: { _id: 1 } } })
121
+ .catch((error) => {
122
+ if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
123
+ return;
124
+ }
125
+ throw error;
126
+ });
127
+ }
128
+ for (let indexId of mapping.allParameterIndexIds()) {
129
+ await this.db.parameterIndexV3(this.group_id, indexId).createIndex(
130
+ {
131
+ lookup: 1,
132
+ key: 1,
133
+ _id: -1
134
+ },
135
+ {
136
+ name: 'lookup_op_id'
137
+ }
138
+ );
139
+ }
140
+ }
141
+
142
+ protected createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums {
143
+ return new MongoChecksumsV3(this.db, this.group_id, {
144
+ ...options.checksumOptions,
145
+ storageConfig: options?.storageConfig,
146
+ mapping: this.sync_rules.mapping
147
+ });
148
+ }
149
+
150
+ createMongoCompactor(options: MongoCompactOptions): MongoCompactor {
151
+ return new MongoCompactorV3(this, this.db, options);
152
+ }
153
+
154
+ protected createMongoParameterCompactor(
155
+ checkpoint: InternalOpId,
156
+ options: storage.CompactOptions
157
+ ): MongoParameterCompactor {
158
+ return new MongoParameterCompactorV3(this.db, this.group_id, checkpoint, options);
159
+ }
160
+
161
+ protected createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch {
162
+ return new MongoBucketBatchV3(batchOptions);
163
+ }
164
+
165
+ protected async fetchCheckpointState(
166
+ session: mongo.ClientSession
167
+ ): Promise<{ checkpoint: bigint; lsn: string | null } | null> {
168
+ const doc = await this.syncRulesCollection.findOne(
169
+ this.syncConfigMatch({
170
+ state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
171
+ }),
172
+ {
173
+ session,
174
+ projection: this.syncConfigProjection()
175
+ }
176
+ );
177
+ const syncConfig = this.selectedSyncConfig(doc);
178
+ if (!syncConfig?.snapshot_done) {
179
+ return null;
180
+ }
181
+ return {
182
+ checkpoint: syncConfig.last_checkpoint ?? 0n,
183
+ lsn: syncConfig.last_checkpoint_lsn ?? null
184
+ };
185
+ }
186
+
187
+ protected async getWriterSyncState() {
188
+ const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
189
+ projection: this.syncConfigProjection({ snapshot_lsn: 1 })
190
+ });
191
+ const syncConfig = this.selectedSyncConfig(doc);
192
+ const checkpointLsn = syncConfig?.last_checkpoint_lsn ?? null;
193
+ return {
194
+ lastCheckpointLsn: checkpointLsn,
195
+ resumeFromLsn: maxLsn(checkpointLsn, doc?.snapshot_lsn),
196
+ keepaliveOp: syncConfig?.keepalive_op ?? null,
197
+ syncConfigId: this.syncConfigId
198
+ };
199
+ }
200
+
201
+ protected async terminateSyncRuleState(): Promise<void> {
202
+ await this.db.sync_rules.updateOne(
203
+ {
204
+ _id: this.group_id
205
+ },
206
+ {
207
+ $set: {
208
+ state: storage.SyncRuleState.TERMINATED,
209
+ persisted_lsn: null,
210
+ sync_configs: []
211
+ }
212
+ }
213
+ );
214
+ }
215
+
216
+ protected async getStatusImpl(): Promise<storage.SyncRuleStatus> {
217
+ const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
218
+ projection: this.syncConfigProjection({ state: 1, snapshot_lsn: 1, keepalive_op: 1 })
219
+ });
220
+ const syncConfig = this.selectedSyncConfig(doc);
221
+ if (doc == null || syncConfig == null) {
222
+ throw new ServiceAssertionError('Cannot find replication stream status');
223
+ }
224
+
225
+ return {
226
+ snapshot_done: syncConfig.snapshot_done ?? false,
227
+ snapshot_lsn: doc.snapshot_lsn ?? null,
228
+ active: doc.state == storage.SyncRuleState.ACTIVE && syncConfig.state == storage.SyncRuleState.ACTIVE,
229
+ checkpoint_lsn: syncConfig.last_checkpoint_lsn ?? null,
230
+ keepalive_op: syncConfig.keepalive_op ?? null
231
+ };
232
+ }
233
+
234
+ protected async clearSyncRuleState(): Promise<void> {
235
+ await this.syncRulesCollection.updateOne(
236
+ this.syncConfigMatch(),
237
+ {
238
+ $set: {
239
+ persisted_lsn: null,
240
+ 'sync_configs.$[config].snapshot_done': false,
241
+ 'sync_configs.$[config].last_checkpoint_lsn': null,
242
+ 'sync_configs.$[config].last_checkpoint': null,
243
+ 'sync_configs.$[config].no_checkpoint_before': null,
244
+ 'sync_configs.$[config].keepalive_op': null
245
+ },
246
+ $unset: {
247
+ snapshot_lsn: 1
248
+ }
249
+ },
250
+ {
251
+ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS,
252
+ arrayFilters: this.syncConfigArrayFilters()
253
+ }
254
+ );
255
+ }
256
+
257
+ protected override get versionContext(): MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3> {
258
+ return {
259
+ db: this.db,
260
+ group_id: this.group_id,
261
+ mapping: this.mapping
262
+ };
263
+ }
264
+
265
+ protected getParameterSetsImpl(
266
+ checkpoint: MongoSyncBucketStorageCheckpoint,
267
+ lookups: ScopedParameterLookup[],
268
+ limit: number
269
+ ): Promise<ParameterLookupRows[]> {
270
+ return getParameterSetsV3(this.versionContext, checkpoint, lookups, limit);
271
+ }
272
+
273
+ protected getBucketDataBatchImpl(
274
+ checkpoint: utils.InternalOpId,
275
+ dataBuckets: storage.BucketDataRequest[],
276
+ options?: storage.BucketDataBatchOptions
277
+ ): AsyncIterable<storage.SyncBucketDataChunk> {
278
+ return getBucketDataBatchV3(this.versionContext, checkpoint, dataBuckets, options);
279
+ }
280
+
281
+ protected async clearBucketData(_signal?: AbortSignal): Promise<void> {
282
+ for (const collection of await this.db.listBucketDataCollectionsV3(this.group_id)) {
283
+ await collection.drop();
284
+ }
285
+ }
286
+
287
+ protected async clearParameterIndexes(_signal?: AbortSignal): Promise<void> {
288
+ for (const collection of await this.db.listParameterIndexCollectionsV3(this.group_id)) {
289
+ await collection.collection.drop();
290
+ }
291
+ }
292
+
293
+ protected async clearSourceRecords(_signal?: AbortSignal): Promise<void> {
294
+ for (const collection of await this.db.listSourceRecordCollectionsV3(this.group_id)) {
295
+ await collection.drop();
296
+ }
297
+ }
298
+
299
+ protected async clearBucketState(_signal?: AbortSignal): Promise<void> {
300
+ await this.db
301
+ .bucketStateV3(this.group_id)
302
+ .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
303
+ .catch((error) => {
304
+ if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
305
+ return;
306
+ }
307
+ throw error;
308
+ });
309
+ }
310
+
311
+ protected async clearSourceTables(_signal?: AbortSignal): Promise<void> {
312
+ await this.db
313
+ .sourceTablesV3(this.group_id)
314
+ .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
315
+ .catch((error) => {
316
+ if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
317
+ return;
318
+ }
319
+ throw error;
320
+ });
321
+ }
322
+
323
+ protected getDataBucketChangesImpl(
324
+ options: GetCheckpointChangesOptions
325
+ ): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
326
+ return getDataBucketChangesV3(this.versionContext, options);
327
+ }
328
+
329
+ protected getParameterBucketChangesImpl(
330
+ options: GetCheckpointChangesOptions
331
+ ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
332
+ return getParameterBucketChangesV3(this.versionContext, options);
333
+ }
334
+ }
335
+
336
+ export async function getParameterSetsV3(
337
+ ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
338
+ checkpoint: MongoSyncBucketStorageCheckpoint,
339
+ lookups: ScopedParameterLookup[],
340
+ limit: number
341
+ ): Promise<ParameterLookupRows[]> {
342
+ return ctx.db.client.withSession({ snapshot: true }, async (session) => {
343
+ setSessionSnapshotTime(session, checkpoint.snapshotTime);
344
+
345
+ const buildLookupPipeline = (
346
+ lookup: ScopedParameterLookup,
347
+ index: number
348
+ ): {
349
+ collection: mongo.Collection<BucketParameterDocumentV3>;
350
+ pipeline: mongo.Document[];
351
+ } => {
352
+ const indexId = lookup.indexId;
353
+ const collection = ctx.db.parameterIndexV3(ctx.group_id, indexId);
354
+ const lookupFilter = serializeParameterLookupV3(lookup);
355
+
356
+ return {
357
+ collection,
358
+ pipeline: [
359
+ {
360
+ $match: {
361
+ lookup: lookupFilter,
362
+ _id: { $lte: checkpoint.checkpoint }
363
+ }
364
+ },
365
+ {
366
+ $sort: {
367
+ key: 1,
368
+ _id: -1
369
+ }
370
+ },
371
+ {
372
+ $group: {
373
+ _id: {
374
+ key: '$key'
375
+ },
376
+ bucket_parameters: {
377
+ $first: '$bucket_parameters'
378
+ }
379
+ }
380
+ },
381
+ {
382
+ $project: {
383
+ _id: 0,
384
+ bucket_parameters: 1,
385
+ index: { $literal: index }
386
+ }
387
+ }
388
+ ]
389
+ };
390
+ };
391
+
392
+ const [firstLookup, ...remainingLookups] = lookups;
393
+ const firstQuery = firstLookup == null ? null : buildLookupPipeline(firstLookup, 0);
394
+ if (firstQuery == null) {
395
+ return [];
396
+ }
397
+
398
+ const pipeline: mongo.Document[] = [
399
+ ...firstQuery.pipeline,
400
+ ...remainingLookups.map((lookup, indexInRemaining) => {
401
+ const query = buildLookupPipeline(lookup, indexInRemaining + 1);
402
+ return {
403
+ $unionWith: {
404
+ coll: query.collection.collectionName,
405
+ pipeline: query.pipeline
406
+ }
407
+ };
408
+ }),
409
+ { $unwind: '$bucket_parameters' },
410
+ { $limit: limit + 1 }
411
+ ];
412
+
413
+ const rows = await firstQuery.collection
414
+ .aggregate<{ index: number; bucket_parameters: SqliteJsonRow }>(pipeline, {
415
+ session,
416
+ readConcern: 'snapshot',
417
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
418
+ })
419
+ .toArray()
420
+ .catch((e) => {
421
+ throw lib_mongo.mapQueryError(e, 'while evaluating parameter queries');
422
+ });
423
+
424
+ if (rows.length > limit) {
425
+ throw new ParameterSetLimitExceededError(limit);
426
+ }
427
+
428
+ const byLookup = Map.groupBy(rows, (row) => lookups[row.index]);
429
+
430
+ const results: ParameterLookupRows[] = [];
431
+ byLookup.forEach((value, lookup) => results.push({ lookup, rows: value.map((r) => r.bucket_parameters) }));
432
+ return results;
433
+ });
434
+ }
435
+
436
+ export async function* getBucketDataBatchV3(
437
+ ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
438
+ checkpoint: utils.InternalOpId,
439
+ dataBuckets: storage.BucketDataRequest[],
440
+ options?: storage.BucketDataBatchOptions
441
+ ): AsyncIterable<storage.SyncBucketDataChunk> {
442
+ if (dataBuckets.length == 0) {
443
+ return;
444
+ }
445
+
446
+ if (checkpoint == null) {
447
+ throw new Error('checkpoint is null');
448
+ }
449
+
450
+ const batchLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
451
+ const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
452
+ const end = checkpoint;
453
+ let remainingLimit = batchLimit;
454
+
455
+ const requestsByDefinition = new Map<string, storage.BucketDataRequest[]>();
456
+ for (const request of dataBuckets) {
457
+ const definitionId = ctx.mapping.bucketSourceId(request.source);
458
+ const requests = requestsByDefinition.get(definitionId) ?? [];
459
+ requests.push(request);
460
+ requestsByDefinition.set(definitionId, requests);
461
+ }
462
+
463
+ const definitionGroups = Array.from(requestsByDefinition.entries());
464
+ for (let groupIndex = 0; groupIndex < definitionGroups.length && remainingLimit > 0; groupIndex++) {
465
+ const [definitionId, requests] = definitionGroups[groupIndex];
466
+ const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
467
+ const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
468
+ const filters: mongo.Filter<BucketDataDocumentV3>[] = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
469
+ _id: {
470
+ $gt: {
471
+ b: bucket,
472
+ o: start
473
+ },
474
+ $lte: {
475
+ b: bucket,
476
+ o: end as any
477
+ }
478
+ }
479
+ }));
480
+
481
+ const cursor = ctx.db.bucketDataV3(ctx.group_id, definitionId).find(
482
+ {
483
+ $or: filters
484
+ },
485
+ {
486
+ session: undefined,
487
+ sort: { _id: 1 },
488
+ limit: remainingLimit,
489
+ batchSize: remainingLimit + 1,
490
+ raw: true,
491
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
492
+ }
493
+ ) as unknown as mongo.FindCursor<Buffer>;
494
+
495
+ let { data, hasMore: batchHasMore } = await readSingleBatch(cursor).catch((e) => {
496
+ throw lib_mongo.mapQueryError(e, 'while reading bucket data');
497
+ });
498
+ if (data.length == remainingLimit) {
499
+ batchHasMore = true;
500
+ }
501
+ if (data.length == 0) {
502
+ continue;
503
+ }
504
+
505
+ remainingLimit -= data.length;
506
+
507
+ let chunkSizeBytes = 0;
508
+ let currentChunk: utils.SyncBucketData | null = null;
509
+ let targetOp: InternalOpId | null = null;
510
+
511
+ for (let rawData of data) {
512
+ const row = loadBucketDataDocumentV3(
513
+ { replicationStreamId: ctx.group_id, definitionId },
514
+ bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3
515
+ );
516
+ const bucket = row.bucketKey.bucket;
517
+
518
+ if (currentChunk == null || currentChunk.bucket != bucket || chunkSizeBytes >= chunkSizeLimitBytes) {
519
+ let start: ProtocolOpId | undefined = undefined;
520
+ if (currentChunk != null) {
521
+ if (currentChunk.bucket == bucket) {
522
+ currentChunk.has_more = true;
523
+ start = currentChunk.next_after;
524
+ }
525
+
526
+ const yieldChunk = currentChunk;
527
+ currentChunk = null;
528
+ chunkSizeBytes = 0;
529
+ yield { chunkData: yieldChunk, targetOp: targetOp };
530
+ targetOp = null;
531
+ }
532
+
533
+ if (start == null) {
534
+ const startOpId = bucketMap.get(bucket);
535
+ if (startOpId == null) {
536
+ throw new Error(`data for unexpected bucket: ${bucket}`);
537
+ }
538
+ start = internalToExternalOpId(startOpId);
539
+ }
540
+ currentChunk = {
541
+ bucket,
542
+ after: start,
543
+ has_more: false,
544
+ data: [],
545
+ next_after: start
546
+ };
547
+ }
548
+
549
+ const entry = mapOpEntry(row);
550
+ if (row.target_op != null && (targetOp == null || row.target_op > targetOp)) {
551
+ targetOp = row.target_op;
552
+ }
553
+
554
+ currentChunk.data.push(entry);
555
+ currentChunk.next_after = entry.op_id;
556
+ chunkSizeBytes += rawData.byteLength;
557
+ }
558
+
559
+ if (currentChunk != null) {
560
+ const yieldChunk = currentChunk;
561
+ yieldChunk.has_more = batchHasMore || (remainingLimit <= 0 && hasLaterDefinitionGroups);
562
+ yield { chunkData: yieldChunk, targetOp: targetOp };
563
+ }
564
+
565
+ if (batchHasMore || remainingLimit <= 0) {
566
+ return;
567
+ }
568
+ }
569
+ }
570
+
571
+ export async function getDataBucketChangesV3(
572
+ ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
573
+ options: GetCheckpointChangesOptions
574
+ ): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
575
+ const limit = 1000;
576
+ const bucketStateUpdates = await ctx.db
577
+ .bucketStateV3(ctx.group_id)
578
+ .aggregate<{ _id: string; last_op: bigint }>(
579
+ [
580
+ {
581
+ $match: {
582
+ last_op: { $gt: options.lastCheckpoint.checkpoint }
583
+ }
584
+ },
585
+ {
586
+ $group: {
587
+ _id: '$_id.b',
588
+ last_op: { $max: '$last_op' }
589
+ }
590
+ },
591
+ {
592
+ $sort: {
593
+ last_op: 1
594
+ }
595
+ },
596
+ {
597
+ $limit: limit + 1
598
+ }
599
+ ],
600
+ { maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS }
601
+ )
602
+ .toArray();
603
+
604
+ const buckets = bucketStateUpdates.map((doc) => doc._id);
605
+ const invalidateDataBuckets = buckets.length > limit;
606
+
607
+ return {
608
+ invalidateDataBuckets,
609
+ updatedDataBuckets: invalidateDataBuckets ? new Set<string>() : new Set(buckets)
610
+ };
611
+ }
612
+
613
+ export async function getParameterBucketChangesV3(
614
+ ctx: MongoSyncBucketStorageContext<VersionedPowerSyncMongoV3>,
615
+ options: GetCheckpointChangesOptions
616
+ ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
617
+ const limit = 1000;
618
+ const indexIds = ctx.mapping.allParameterIndexIds();
619
+ const collections = indexIds.map((indexId) => ({
620
+ indexId,
621
+ collection: ctx.db.parameterIndexV3(ctx.group_id, indexId)
622
+ }));
623
+ if (collections.length == 0) {
624
+ return {
625
+ invalidateParameterBuckets: false,
626
+ updatedParameterLookups: new Set<string>()
627
+ };
628
+ }
629
+ const checkpointFilter = {
630
+ _id: { $gt: options.lastCheckpoint.checkpoint, $lte: options.nextCheckpoint.checkpoint }
631
+ };
632
+ const pipelineForCollection = (indexId: string) => [
633
+ {
634
+ $match: checkpointFilter
635
+ },
636
+ {
637
+ $project: {
638
+ _id: 0,
639
+ lookup: 1,
640
+ indexId: { $literal: indexId }
641
+ }
642
+ }
643
+ ];
644
+ const [firstCollection, ...remainingCollections] = collections;
645
+ const parameterUpdates = await firstCollection.collection
646
+ .aggregate<{ lookup: bson.Binary; indexId: string }>(
647
+ [
648
+ ...pipelineForCollection(firstCollection.indexId),
649
+ ...remainingCollections.map((collection) => {
650
+ return {
651
+ $unionWith: {
652
+ coll: collection.collection.collectionName,
653
+ pipeline: pipelineForCollection(collection.indexId)
654
+ }
655
+ };
656
+ }),
657
+ {
658
+ $limit: limit + 1
659
+ }
660
+ ],
661
+ {
662
+ batchSize: limit + 2,
663
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
664
+ }
665
+ )
666
+ .toArray();
667
+
668
+ const invalidateParameterUpdates = parameterUpdates.length > limit;
669
+
670
+ return {
671
+ invalidateParameterBuckets: invalidateParameterUpdates,
672
+ updatedParameterLookups: invalidateParameterUpdates
673
+ ? new Set<string>()
674
+ : new Set<string>(
675
+ parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookupV3(p.lookup, p.indexId)))
676
+ )
677
+ };
678
+ }