@powersync/service-core 0.12.2 → 0.14.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 (198) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/auth/KeySpec.d.ts +1 -0
  3. package/dist/auth/KeySpec.js +5 -2
  4. package/dist/auth/KeySpec.js.map +1 -1
  5. package/dist/auth/RemoteJWKSCollector.js +1 -1
  6. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  7. package/dist/entry/commands/compact-action.js +14 -14
  8. package/dist/entry/commands/compact-action.js.map +1 -1
  9. package/dist/entry/commands/migrate-action.js +15 -4
  10. package/dist/entry/commands/migrate-action.js.map +1 -1
  11. package/dist/index.d.ts +1 -3
  12. package/dist/index.js +1 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/migrations/PowerSyncMigrationManager.d.ts +17 -0
  15. package/dist/migrations/PowerSyncMigrationManager.js +21 -0
  16. package/dist/migrations/PowerSyncMigrationManager.js.map +1 -0
  17. package/dist/migrations/ensure-automatic-migrations.d.ts +4 -0
  18. package/dist/migrations/ensure-automatic-migrations.js +14 -0
  19. package/dist/migrations/ensure-automatic-migrations.js.map +1 -0
  20. package/dist/migrations/migrations-index.d.ts +2 -3
  21. package/dist/migrations/migrations-index.js +2 -3
  22. package/dist/migrations/migrations-index.js.map +1 -1
  23. package/dist/routes/configure-fastify.d.ts +12 -12
  24. package/dist/routes/endpoints/admin.d.ts +24 -24
  25. package/dist/routes/endpoints/probes.d.ts +1 -1
  26. package/dist/routes/endpoints/probes.js +5 -5
  27. package/dist/routes/endpoints/probes.js.map +1 -1
  28. package/dist/storage/BucketStorage.d.ts +49 -1
  29. package/dist/storage/BucketStorage.js +26 -0
  30. package/dist/storage/BucketStorage.js.map +1 -1
  31. package/dist/storage/SourceTable.d.ts +4 -0
  32. package/dist/storage/SourceTable.js +4 -0
  33. package/dist/storage/SourceTable.js.map +1 -1
  34. package/dist/storage/bson.d.ts +24 -0
  35. package/dist/storage/bson.js +73 -0
  36. package/dist/storage/bson.js.map +1 -0
  37. package/dist/storage/storage-index.d.ts +3 -14
  38. package/dist/storage/storage-index.js +3 -14
  39. package/dist/storage/storage-index.js.map +1 -1
  40. package/dist/sync/sync.js +3 -1
  41. package/dist/sync/sync.js.map +1 -1
  42. package/dist/system/ServiceContext.d.ts +3 -0
  43. package/dist/system/ServiceContext.js +11 -3
  44. package/dist/system/ServiceContext.js.map +1 -1
  45. package/dist/util/config/types.d.ts +2 -2
  46. package/dist/util/utils.d.ts +13 -1
  47. package/dist/util/utils.js +20 -1
  48. package/dist/util/utils.js.map +1 -1
  49. package/package.json +7 -8
  50. package/src/auth/KeySpec.ts +5 -3
  51. package/src/auth/RemoteJWKSCollector.ts +1 -1
  52. package/src/entry/commands/compact-action.ts +19 -14
  53. package/src/entry/commands/migrate-action.ts +17 -4
  54. package/src/index.ts +1 -4
  55. package/src/migrations/PowerSyncMigrationManager.ts +42 -0
  56. package/src/migrations/ensure-automatic-migrations.ts +15 -0
  57. package/src/migrations/migrations-index.ts +2 -3
  58. package/src/routes/endpoints/probes.ts +6 -6
  59. package/src/storage/BucketStorage.ts +53 -1
  60. package/src/storage/SourceTable.ts +4 -0
  61. package/src/storage/bson.ts +78 -0
  62. package/src/storage/storage-index.ts +3 -15
  63. package/src/sync/sync.ts +3 -1
  64. package/src/system/ServiceContext.ts +17 -4
  65. package/src/util/config/types.ts +2 -2
  66. package/src/util/utils.ts +21 -1
  67. package/test/src/auth.test.ts +33 -0
  68. package/test/src/env.ts +0 -1
  69. package/test/src/routes/probes.test.ts +6 -1
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/dist/db/db-index.d.ts +0 -1
  72. package/dist/db/db-index.js +0 -2
  73. package/dist/db/db-index.js.map +0 -1
  74. package/dist/db/mongo.d.ts +0 -35
  75. package/dist/db/mongo.js +0 -73
  76. package/dist/db/mongo.js.map +0 -1
  77. package/dist/locks/LockManager.d.ts +0 -10
  78. package/dist/locks/LockManager.js +0 -7
  79. package/dist/locks/LockManager.js.map +0 -1
  80. package/dist/locks/MongoLocks.d.ts +0 -36
  81. package/dist/locks/MongoLocks.js +0 -81
  82. package/dist/locks/MongoLocks.js.map +0 -1
  83. package/dist/locks/locks-index.d.ts +0 -2
  84. package/dist/locks/locks-index.js +0 -3
  85. package/dist/locks/locks-index.js.map +0 -1
  86. package/dist/migrations/db/migrations/1684951997326-init.d.ts +0 -3
  87. package/dist/migrations/db/migrations/1684951997326-init.js +0 -33
  88. package/dist/migrations/db/migrations/1684951997326-init.js.map +0 -1
  89. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +0 -2
  90. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +0 -5
  91. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +0 -1
  92. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +0 -3
  93. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +0 -56
  94. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +0 -1
  95. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +0 -3
  96. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +0 -29
  97. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +0 -1
  98. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +0 -3
  99. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +0 -31
  100. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +0 -1
  101. package/dist/migrations/definitions.d.ts +0 -18
  102. package/dist/migrations/definitions.js +0 -6
  103. package/dist/migrations/definitions.js.map +0 -1
  104. package/dist/migrations/executor.d.ts +0 -16
  105. package/dist/migrations/executor.js +0 -64
  106. package/dist/migrations/executor.js.map +0 -1
  107. package/dist/migrations/migrations.d.ts +0 -18
  108. package/dist/migrations/migrations.js +0 -110
  109. package/dist/migrations/migrations.js.map +0 -1
  110. package/dist/migrations/store/migration-store.d.ts +0 -11
  111. package/dist/migrations/store/migration-store.js +0 -46
  112. package/dist/migrations/store/migration-store.js.map +0 -1
  113. package/dist/storage/MongoBucketStorage.d.ts +0 -48
  114. package/dist/storage/MongoBucketStorage.js +0 -427
  115. package/dist/storage/MongoBucketStorage.js.map +0 -1
  116. package/dist/storage/mongo/MongoBucketBatch.d.ts +0 -74
  117. package/dist/storage/mongo/MongoBucketBatch.js +0 -683
  118. package/dist/storage/mongo/MongoBucketBatch.js.map +0 -1
  119. package/dist/storage/mongo/MongoCompactor.d.ts +0 -40
  120. package/dist/storage/mongo/MongoCompactor.js +0 -310
  121. package/dist/storage/mongo/MongoCompactor.js.map +0 -1
  122. package/dist/storage/mongo/MongoIdSequence.d.ts +0 -12
  123. package/dist/storage/mongo/MongoIdSequence.js +0 -21
  124. package/dist/storage/mongo/MongoIdSequence.js.map +0 -1
  125. package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +0 -9
  126. package/dist/storage/mongo/MongoPersistedSyncRules.js +0 -9
  127. package/dist/storage/mongo/MongoPersistedSyncRules.js.map +0 -1
  128. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +0 -20
  129. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +0 -26
  130. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +0 -1
  131. package/dist/storage/mongo/MongoStorageProvider.d.ts +0 -5
  132. package/dist/storage/mongo/MongoStorageProvider.js +0 -26
  133. package/dist/storage/mongo/MongoStorageProvider.js.map +0 -1
  134. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +0 -38
  135. package/dist/storage/mongo/MongoSyncBucketStorage.js +0 -534
  136. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +0 -1
  137. package/dist/storage/mongo/MongoSyncRulesLock.d.ts +0 -16
  138. package/dist/storage/mongo/MongoSyncRulesLock.js +0 -65
  139. package/dist/storage/mongo/MongoSyncRulesLock.js.map +0 -1
  140. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +0 -20
  141. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +0 -104
  142. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +0 -1
  143. package/dist/storage/mongo/OperationBatch.d.ts +0 -35
  144. package/dist/storage/mongo/OperationBatch.js +0 -119
  145. package/dist/storage/mongo/OperationBatch.js.map +0 -1
  146. package/dist/storage/mongo/PersistedBatch.d.ts +0 -46
  147. package/dist/storage/mongo/PersistedBatch.js +0 -223
  148. package/dist/storage/mongo/PersistedBatch.js.map +0 -1
  149. package/dist/storage/mongo/config.d.ts +0 -19
  150. package/dist/storage/mongo/config.js +0 -26
  151. package/dist/storage/mongo/config.js.map +0 -1
  152. package/dist/storage/mongo/db.d.ts +0 -36
  153. package/dist/storage/mongo/db.js +0 -47
  154. package/dist/storage/mongo/db.js.map +0 -1
  155. package/dist/storage/mongo/models.d.ts +0 -163
  156. package/dist/storage/mongo/models.js +0 -27
  157. package/dist/storage/mongo/models.js.map +0 -1
  158. package/dist/storage/mongo/util.d.ts +0 -54
  159. package/dist/storage/mongo/util.js +0 -190
  160. package/dist/storage/mongo/util.js.map +0 -1
  161. package/src/db/db-index.ts +0 -1
  162. package/src/db/mongo.ts +0 -81
  163. package/src/locks/LockManager.ts +0 -16
  164. package/src/locks/MongoLocks.ts +0 -142
  165. package/src/locks/locks-index.ts +0 -2
  166. package/src/migrations/db/migrations/1684951997326-init.ts +0 -38
  167. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +0 -5
  168. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +0 -102
  169. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +0 -34
  170. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +0 -37
  171. package/src/migrations/definitions.ts +0 -21
  172. package/src/migrations/executor.ts +0 -87
  173. package/src/migrations/migrations.ts +0 -142
  174. package/src/migrations/store/migration-store.ts +0 -63
  175. package/src/storage/MongoBucketStorage.ts +0 -541
  176. package/src/storage/mongo/MongoBucketBatch.ts +0 -900
  177. package/src/storage/mongo/MongoCompactor.ts +0 -393
  178. package/src/storage/mongo/MongoIdSequence.ts +0 -24
  179. package/src/storage/mongo/MongoPersistedSyncRules.ts +0 -16
  180. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +0 -50
  181. package/src/storage/mongo/MongoStorageProvider.ts +0 -31
  182. package/src/storage/mongo/MongoSyncBucketStorage.ts +0 -640
  183. package/src/storage/mongo/MongoSyncRulesLock.ts +0 -85
  184. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +0 -154
  185. package/src/storage/mongo/OperationBatch.ts +0 -131
  186. package/src/storage/mongo/PersistedBatch.ts +0 -285
  187. package/src/storage/mongo/config.ts +0 -40
  188. package/src/storage/mongo/db.ts +0 -88
  189. package/src/storage/mongo/models.ts +0 -187
  190. package/src/storage/mongo/util.ts +0 -203
  191. package/test/src/__snapshots__/sync.test.ts.snap +0 -332
  192. package/test/src/bucket_validation.test.ts +0 -143
  193. package/test/src/bucket_validation.ts +0 -60
  194. package/test/src/compacting.test.ts +0 -295
  195. package/test/src/data_storage.test.ts +0 -1569
  196. package/test/src/stream_utils.ts +0 -42
  197. package/test/src/sync.test.ts +0 -511
  198. package/test/src/util.ts +0 -150
@@ -1,640 +0,0 @@
1
- import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service-sync-rules';
2
- import * as bson from 'bson';
3
- import * as mongo from 'mongodb';
4
-
5
- import { DisposableObserver, logger } from '@powersync/lib-services-framework';
6
- import * as timers from 'timers/promises';
7
- import * as db from '../../db/db-index.js';
8
- import * as util from '../../util/util-index.js';
9
- import {
10
- BucketDataBatchOptions,
11
- BucketStorageBatch,
12
- CompactOptions,
13
- DEFAULT_DOCUMENT_BATCH_LIMIT,
14
- DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
15
- FlushedResult,
16
- ParseSyncRulesOptions,
17
- PersistedSyncRulesContent,
18
- ReplicationCheckpoint,
19
- ResolveTableOptions,
20
- ResolveTableResult,
21
- StartBatchOptions,
22
- SyncBucketDataBatch,
23
- SyncRulesBucketStorage,
24
- SyncRulesBucketStorageListener,
25
- SyncRuleStatus,
26
- TerminateOptions
27
- } from '../BucketStorage.js';
28
- import { ChecksumCache, FetchPartialBucketChecksum, PartialChecksum, PartialChecksumMap } from '../ChecksumCache.js';
29
- import { MongoBucketStorage } from '../MongoBucketStorage.js';
30
- import { SourceTable } from '../SourceTable.js';
31
- import {
32
- BatchedCustomWriteCheckpointOptions,
33
- ManagedWriteCheckpointOptions,
34
- SyncStorageLastWriteCheckpointFilters,
35
- WriteCheckpointAPI,
36
- WriteCheckpointMode
37
- } from '../WriteCheckpointAPI.js';
38
- import { PowerSyncMongo } from './db.js';
39
- import { BucketDataDocument, BucketDataKey, SourceKey, SyncRuleState } from './models.js';
40
- import { MongoBucketBatch } from './MongoBucketBatch.js';
41
- import { MongoCompactor } from './MongoCompactor.js';
42
- import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
43
- import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, mapOpEntry, readSingleBatch, serializeLookup } from './util.js';
44
-
45
- export class MongoSyncBucketStorage
46
- extends DisposableObserver<SyncRulesBucketStorageListener>
47
- implements SyncRulesBucketStorage
48
- {
49
- private readonly db: PowerSyncMongo;
50
- private checksumCache = new ChecksumCache({
51
- fetchChecksums: (batch) => {
52
- return this.getChecksumsInternal(batch);
53
- }
54
- });
55
-
56
- private parsedSyncRulesCache: { parsed: SqlSyncRules; options: ParseSyncRulesOptions } | undefined;
57
- private writeCheckpointAPI: WriteCheckpointAPI;
58
-
59
- constructor(
60
- public readonly factory: MongoBucketStorage,
61
- public readonly group_id: number,
62
- private readonly sync_rules: PersistedSyncRulesContent,
63
- public readonly slot_name: string,
64
- writeCheckpointMode: WriteCheckpointMode = WriteCheckpointMode.MANAGED
65
- ) {
66
- super();
67
- this.db = factory.db;
68
- this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
69
- db: this.db,
70
- mode: writeCheckpointMode
71
- });
72
- }
73
-
74
- get writeCheckpointMode() {
75
- return this.writeCheckpointAPI.writeCheckpointMode;
76
- }
77
-
78
- setWriteCheckpointMode(mode: WriteCheckpointMode): void {
79
- this.writeCheckpointAPI.setWriteCheckpointMode(mode);
80
- }
81
-
82
- batchCreateCustomWriteCheckpoints(checkpoints: BatchedCustomWriteCheckpointOptions[]): Promise<void> {
83
- return this.writeCheckpointAPI.batchCreateCustomWriteCheckpoints(
84
- checkpoints.map((checkpoint) => ({ ...checkpoint, sync_rules_id: this.group_id }))
85
- );
86
- }
87
-
88
- createCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): Promise<bigint> {
89
- return this.writeCheckpointAPI.createCustomWriteCheckpoint({
90
- ...checkpoint,
91
- sync_rules_id: this.group_id
92
- });
93
- }
94
-
95
- createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
96
- return this.writeCheckpointAPI.createManagedWriteCheckpoint(checkpoint);
97
- }
98
-
99
- lastWriteCheckpoint(filters: SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
100
- return this.writeCheckpointAPI.lastWriteCheckpoint({
101
- ...filters,
102
- sync_rules_id: this.group_id
103
- });
104
- }
105
-
106
- getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules {
107
- const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
108
- /**
109
- * Check if the cached sync rules, if present, had the same options.
110
- * Parse sync rules if the options are different or if there is no cached value.
111
- */
112
- if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
113
- this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).sync_rules, options };
114
- }
115
-
116
- return this.parsedSyncRulesCache!.parsed;
117
- }
118
-
119
- async getCheckpoint(): Promise<ReplicationCheckpoint> {
120
- const doc = await this.db.sync_rules.findOne(
121
- { _id: this.group_id },
122
- {
123
- projection: { last_checkpoint: 1, last_checkpoint_lsn: 1 }
124
- }
125
- );
126
- return {
127
- checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
128
- lsn: doc?.last_checkpoint_lsn ?? null
129
- };
130
- }
131
-
132
- async startBatch(
133
- options: StartBatchOptions,
134
- callback: (batch: BucketStorageBatch) => Promise<void>
135
- ): Promise<FlushedResult | null> {
136
- const doc = await this.db.sync_rules.findOne(
137
- {
138
- _id: this.group_id
139
- },
140
- { projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1, keepalive_op: 1 } }
141
- );
142
- const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
143
-
144
- await using batch = new MongoBucketBatch({
145
- db: this.db,
146
- syncRules: this.sync_rules.parsed(options).sync_rules,
147
- groupId: this.group_id,
148
- slotName: this.slot_name,
149
- lastCheckpointLsn: checkpoint_lsn,
150
- noCheckpointBeforeLsn: doc?.no_checkpoint_before ?? options.zeroLSN,
151
- keepaliveOp: doc?.keepalive_op ?? null,
152
- storeCurrentData: options.storeCurrentData,
153
- skipExistingRows: options.skipExistingRows ?? false
154
- });
155
- this.iterateListeners((cb) => cb.batchStarted?.(batch));
156
-
157
- await callback(batch);
158
- await batch.flush();
159
- if (batch.last_flushed_op) {
160
- return { flushed_op: String(batch.last_flushed_op) };
161
- } else {
162
- return null;
163
- }
164
- }
165
-
166
- async resolveTable(options: ResolveTableOptions): Promise<ResolveTableResult> {
167
- const { group_id, connection_id, connection_tag, entity_descriptor } = options;
168
-
169
- const { schema, name: table, objectId, replicationColumns } = entity_descriptor;
170
-
171
- const columns = replicationColumns.map((column) => ({
172
- name: column.name,
173
- type: column.type,
174
- type_oid: column.typeId
175
- }));
176
- let result: ResolveTableResult | null = null;
177
- await this.db.client.withSession(async (session) => {
178
- const col = this.db.source_tables;
179
- let doc = await col.findOne(
180
- {
181
- group_id: group_id,
182
- connection_id: connection_id,
183
- relation_id: objectId,
184
- schema_name: schema,
185
- table_name: table,
186
- replica_id_columns2: columns
187
- },
188
- { session }
189
- );
190
- if (doc == null) {
191
- doc = {
192
- _id: new bson.ObjectId(),
193
- group_id: group_id,
194
- connection_id: connection_id,
195
- relation_id: objectId,
196
- schema_name: schema,
197
- table_name: table,
198
- replica_id_columns: null,
199
- replica_id_columns2: columns,
200
- snapshot_done: false
201
- };
202
-
203
- await col.insertOne(doc, { session });
204
- }
205
- const sourceTable = new SourceTable(
206
- doc._id,
207
- connection_tag,
208
- objectId,
209
- schema,
210
- table,
211
- replicationColumns,
212
- doc.snapshot_done ?? true
213
- );
214
- sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
215
- sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
216
- sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
217
-
218
- const truncate = await col
219
- .find(
220
- {
221
- group_id: group_id,
222
- connection_id: connection_id,
223
- _id: { $ne: doc._id },
224
- $or: [{ relation_id: objectId }, { schema_name: schema, table_name: table }]
225
- },
226
- { session }
227
- )
228
- .toArray();
229
- result = {
230
- table: sourceTable,
231
- dropTables: truncate.map(
232
- (doc) =>
233
- new SourceTable(
234
- doc._id,
235
- connection_tag,
236
- doc.relation_id ?? 0,
237
- doc.schema_name,
238
- doc.table_name,
239
- doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid, type: c.type })) ?? [],
240
- doc.snapshot_done ?? true
241
- )
242
- )
243
- };
244
- });
245
- return result!;
246
- }
247
-
248
- async getParameterSets(checkpoint: util.OpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> {
249
- const lookupFilter = lookups.map((lookup) => {
250
- return serializeLookup(lookup);
251
- });
252
- const rows = await this.db.bucket_parameters
253
- .aggregate([
254
- {
255
- $match: {
256
- 'key.g': this.group_id,
257
- lookup: { $in: lookupFilter },
258
- _id: { $lte: BigInt(checkpoint) }
259
- }
260
- },
261
- {
262
- $sort: {
263
- _id: -1
264
- }
265
- },
266
- {
267
- $group: {
268
- _id: { key: '$key', lookup: '$lookup' },
269
- bucket_parameters: {
270
- $first: '$bucket_parameters'
271
- }
272
- }
273
- }
274
- ])
275
- .toArray();
276
- const groupedParameters = rows.map((row) => {
277
- return row.bucket_parameters;
278
- });
279
- return groupedParameters.flat();
280
- }
281
-
282
- async *getBucketDataBatch(
283
- checkpoint: util.OpId,
284
- dataBuckets: Map<string, string>,
285
- options?: BucketDataBatchOptions
286
- ): AsyncIterable<SyncBucketDataBatch> {
287
- if (dataBuckets.size == 0) {
288
- return;
289
- }
290
- let filters: mongo.Filter<BucketDataDocument>[] = [];
291
-
292
- const end = checkpoint ? BigInt(checkpoint) : new bson.MaxKey();
293
- for (let [name, start] of dataBuckets.entries()) {
294
- filters.push({
295
- _id: {
296
- $gt: {
297
- g: this.group_id,
298
- b: name,
299
- o: BigInt(start)
300
- },
301
- $lte: {
302
- g: this.group_id,
303
- b: name,
304
- o: end as any
305
- }
306
- }
307
- });
308
- }
309
-
310
- const limit = options?.limit ?? DEFAULT_DOCUMENT_BATCH_LIMIT;
311
- const sizeLimit = options?.chunkLimitBytes ?? DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
312
-
313
- const cursor = this.db.bucket_data.find(
314
- {
315
- $or: filters
316
- },
317
- {
318
- session: undefined,
319
- sort: { _id: 1 },
320
- limit: limit,
321
- // Increase batch size above the default 101, so that we can fill an entire batch in
322
- // one go.
323
- batchSize: limit,
324
- // Raw mode is returns an array of Buffer instead of parsed documents.
325
- // We use it so that:
326
- // 1. We can calculate the document size accurately without serializing again.
327
- // 2. We can delay parsing the results until it's needed.
328
- // We manually use bson.deserialize below
329
- raw: true,
330
-
331
- // Since we're using raw: true and parsing ourselves later, we don't need bigint
332
- // support here.
333
- // Disabling due to https://jira.mongodb.org/browse/NODE-6165, and the fact that this
334
- // is one of our most common queries.
335
- useBigInt64: false
336
- }
337
- ) as unknown as mongo.FindCursor<Buffer>;
338
-
339
- // We want to limit results to a single batch to avoid high memory usage.
340
- // This approach uses MongoDB's batch limits to limit the data here, which limits
341
- // to the lower of the batch count and size limits.
342
- // This is similar to using `singleBatch: true` in the find options, but allows
343
- // detecting "hasMore".
344
- let { data, hasMore } = await readSingleBatch(cursor);
345
- if (data.length == limit) {
346
- // Limit reached - could have more data, despite the cursor being drained.
347
- hasMore = true;
348
- }
349
-
350
- let batchSize = 0;
351
- let currentBatch: util.SyncBucketData | null = null;
352
- let targetOp: bigint | null = null;
353
-
354
- // Ordered by _id, meaning buckets are grouped together
355
- for (let rawData of data) {
356
- const row = bson.deserialize(rawData, BSON_DESERIALIZE_OPTIONS) as BucketDataDocument;
357
- const bucket = row._id.b;
358
-
359
- if (currentBatch == null || currentBatch.bucket != bucket || batchSize >= sizeLimit) {
360
- let start: string | undefined = undefined;
361
- if (currentBatch != null) {
362
- if (currentBatch.bucket == bucket) {
363
- currentBatch.has_more = true;
364
- }
365
-
366
- const yieldBatch = currentBatch;
367
- start = currentBatch.after;
368
- currentBatch = null;
369
- batchSize = 0;
370
- yield { batch: yieldBatch, targetOp: targetOp };
371
- targetOp = null;
372
- }
373
-
374
- start ??= dataBuckets.get(bucket);
375
- if (start == null) {
376
- throw new Error(`data for unexpected bucket: ${bucket}`);
377
- }
378
- currentBatch = {
379
- bucket,
380
- after: start,
381
- has_more: hasMore,
382
- data: [],
383
- next_after: start
384
- };
385
- targetOp = null;
386
- }
387
-
388
- const entry = mapOpEntry(row);
389
-
390
- if (row.target_op != null) {
391
- // MOVE, CLEAR
392
- if (targetOp == null || row.target_op > targetOp) {
393
- targetOp = row.target_op;
394
- }
395
- }
396
-
397
- currentBatch.data.push(entry);
398
- currentBatch.next_after = entry.op_id;
399
-
400
- batchSize += rawData.byteLength;
401
- }
402
-
403
- if (currentBatch != null) {
404
- const yieldBatch = currentBatch;
405
- currentBatch = null;
406
- yield { batch: yieldBatch, targetOp: targetOp };
407
- targetOp = null;
408
- }
409
- }
410
-
411
- async getChecksums(checkpoint: util.OpId, buckets: string[]): Promise<util.ChecksumMap> {
412
- return this.checksumCache.getChecksumMap(checkpoint, buckets);
413
- }
414
-
415
- private async getChecksumsInternal(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
416
- if (batch.length == 0) {
417
- return new Map();
418
- }
419
-
420
- const filters: any[] = [];
421
- for (let request of batch) {
422
- filters.push({
423
- _id: {
424
- $gt: {
425
- g: this.group_id,
426
- b: request.bucket,
427
- o: request.start ? BigInt(request.start) : new bson.MinKey()
428
- },
429
- $lte: {
430
- g: this.group_id,
431
- b: request.bucket,
432
- o: BigInt(request.end)
433
- }
434
- }
435
- });
436
- }
437
-
438
- const aggregate = await this.db.bucket_data
439
- .aggregate(
440
- [
441
- {
442
- $match: {
443
- $or: filters
444
- }
445
- },
446
- {
447
- $group: {
448
- _id: '$_id.b',
449
- checksum_total: { $sum: '$checksum' },
450
- count: { $sum: 1 },
451
- has_clear_op: {
452
- $max: {
453
- $cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
454
- }
455
- }
456
- }
457
- }
458
- ],
459
- { session: undefined, readConcern: 'snapshot' }
460
- )
461
- .toArray();
462
-
463
- return new Map<string, PartialChecksum>(
464
- aggregate.map((doc) => {
465
- return [
466
- doc._id,
467
- {
468
- bucket: doc._id,
469
- partialCount: doc.count,
470
- partialChecksum: Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff,
471
- isFullChecksum: doc.has_clear_op == 1
472
- } satisfies PartialChecksum
473
- ];
474
- })
475
- );
476
- }
477
-
478
- async terminate(options?: TerminateOptions) {
479
- // Default is to clear the storage except when explicitly requested not to.
480
- if (!options || options?.clearStorage) {
481
- await this.clear();
482
- }
483
- await this.db.sync_rules.updateOne(
484
- {
485
- _id: this.group_id
486
- },
487
- {
488
- $set: {
489
- state: SyncRuleState.TERMINATED,
490
- persisted_lsn: null,
491
- snapshot_done: false
492
- }
493
- }
494
- );
495
- }
496
-
497
- async getStatus(): Promise<SyncRuleStatus> {
498
- const doc = await this.db.sync_rules.findOne(
499
- {
500
- _id: this.group_id
501
- },
502
- {
503
- projection: {
504
- snapshot_done: 1,
505
- last_checkpoint_lsn: 1,
506
- state: 1
507
- }
508
- }
509
- );
510
- if (doc == null) {
511
- throw new Error('Cannot find sync rules status');
512
- }
513
-
514
- return {
515
- snapshot_done: doc.snapshot_done,
516
- active: doc.state == 'ACTIVE',
517
- checkpoint_lsn: doc.last_checkpoint_lsn
518
- };
519
- }
520
-
521
- async clear(): Promise<void> {
522
- while (true) {
523
- try {
524
- await this.clearIteration();
525
-
526
- logger.info(`${this.slot_name} Done clearing data`);
527
- return;
528
- } catch (e: unknown) {
529
- if (e instanceof mongo.MongoServerError && e.codeName == 'MaxTimeMSExpired') {
530
- logger.info(
531
- `${this.slot_name} Cleared batch of data in ${db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`
532
- );
533
- await timers.setTimeout(db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5);
534
- continue;
535
- } else {
536
- throw e;
537
- }
538
- }
539
- }
540
- }
541
-
542
- private async clearIteration(): Promise<void> {
543
- // Individual operations here may time out with the maxTimeMS option.
544
- // It is expected to still make progress, and continue on the next try.
545
-
546
- await this.db.sync_rules.updateOne(
547
- {
548
- _id: this.group_id
549
- },
550
- {
551
- $set: {
552
- snapshot_done: false,
553
- persisted_lsn: null,
554
- last_checkpoint_lsn: null,
555
- last_checkpoint: null,
556
- no_checkpoint_before: null
557
- }
558
- },
559
- { maxTimeMS: db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
560
- );
561
- await this.db.bucket_data.deleteMany(
562
- {
563
- _id: idPrefixFilter<BucketDataKey>({ g: this.group_id }, ['b', 'o'])
564
- },
565
- { maxTimeMS: db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
566
- );
567
- await this.db.bucket_parameters.deleteMany(
568
- {
569
- key: idPrefixFilter<SourceKey>({ g: this.group_id }, ['t', 'k'])
570
- },
571
- { maxTimeMS: db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
572
- );
573
-
574
- await this.db.current_data.deleteMany(
575
- {
576
- _id: idPrefixFilter<SourceKey>({ g: this.group_id }, ['t', 'k'])
577
- },
578
- { maxTimeMS: db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
579
- );
580
-
581
- await this.db.source_tables.deleteMany(
582
- {
583
- group_id: this.group_id
584
- },
585
- { maxTimeMS: db.mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
586
- );
587
- }
588
-
589
- async autoActivate(): Promise<void> {
590
- await this.db.client.withSession(async (session) => {
591
- await session.withTransaction(async () => {
592
- const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
593
- if (doc && doc.state == 'PROCESSING') {
594
- await this.db.sync_rules.updateOne(
595
- {
596
- _id: this.group_id
597
- },
598
- {
599
- $set: {
600
- state: SyncRuleState.ACTIVE
601
- }
602
- },
603
- { session }
604
- );
605
-
606
- await this.db.sync_rules.updateMany(
607
- {
608
- _id: { $ne: this.group_id },
609
- state: SyncRuleState.ACTIVE
610
- },
611
- {
612
- $set: {
613
- state: SyncRuleState.STOP
614
- }
615
- },
616
- { session }
617
- );
618
- }
619
- });
620
- });
621
- }
622
-
623
- async reportError(e: any): Promise<void> {
624
- const message = String(e.message ?? 'Replication failure');
625
- await this.db.sync_rules.updateOne(
626
- {
627
- _id: this.group_id
628
- },
629
- {
630
- $set: {
631
- last_fatal_error: message
632
- }
633
- }
634
- );
635
- }
636
-
637
- async compact(options?: CompactOptions) {
638
- return new MongoCompactor(this.db, this.group_id, options).compact();
639
- }
640
- }