@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,5 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
- import { HydratedSyncRules, SqlEventDescriptor, SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
2
+ import { HydratedSyncConfig, SqlEventDescriptor, SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
3
3
  import * as bson from 'bson';
4
4
 
5
5
  import {
@@ -12,26 +12,24 @@ import {
12
12
  ServiceError
13
13
  } from '@powersync/lib-services-framework';
14
14
  import {
15
+ BucketDefinitionMapping,
15
16
  BucketStorageMarkRecordUnavailable,
16
- CheckpointResult,
17
17
  deserializeBson,
18
18
  InternalOpId,
19
19
  isCompleteRow,
20
20
  PerformanceTracer,
21
21
  SaveOperationTag,
22
22
  storage,
23
- SyncRuleState,
24
23
  utils
25
24
  } from '@powersync/service-core';
26
25
  import * as timers from 'node:timers/promises';
27
26
  import { mongoTableId } from '../../utils/util.js';
28
- import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
29
27
  import { PersistedBatch } from './common/PersistedBatch.js';
30
28
  import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
31
29
  import type { VersionedPowerSyncMongo } from './db.js';
32
- import { SyncRuleDocument } from './models.js';
33
30
  import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
34
31
  import { MongoIdSequence } from './MongoIdSequence.js';
32
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
35
33
  import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
36
34
  import { OperationBatch, RecordOperation } from './OperationBatch.js';
37
35
 
@@ -44,20 +42,32 @@ const replicationMutex = new utils.Mutex();
44
42
 
45
43
  export interface MongoBucketBatchOptions {
46
44
  db: VersionedPowerSyncMongo;
47
- syncRules: HydratedSyncRules;
48
- groupId: number;
49
- slotName: string;
50
- lastCheckpointLsn: string | null;
51
- keepaliveOp: InternalOpId | null;
45
+ /**
46
+ * The parsed sync config set for this batch.
47
+ *
48
+ * The batch derives both the hydrated sync rules and the bucket definition mapping from
49
+ * this single set, so they always come from the same parse. Do not add separate
50
+ * syncRules/mapping options - pairing values from different parses is exactly the bug
51
+ * this shape prevents.
52
+ */
53
+ parsedSyncConfig: MongoParsedSyncConfigSet;
54
+ replicationStreamId: number;
55
+ replicationStreamName: string;
56
+ syncConfigIds?: bson.ObjectId[];
57
+ /**
58
+ * Seeds the in-memory persisted-op tracking for v1 storage. Not used by v3 storage, which
59
+ * tracks the persisted-op head durably on the replication stream document instead.
60
+ */
61
+ keepaliveOp?: InternalOpId | null;
52
62
  resumeFromLsn: string | null;
53
63
  storeCurrentData: boolean;
54
- mapping: BucketDefinitionMapping;
55
64
  /**
56
65
  * Set to true for initial replication.
57
66
  */
58
67
  skipExistingRows: boolean;
59
68
 
60
69
  markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
70
+ hooks: storage.StorageHooks | undefined;
61
71
 
62
72
  logger: Logger;
63
73
  tracer?: PerformanceTracer<'storage' | 'evaluate'>;
@@ -67,47 +77,47 @@ export abstract class MongoBucketBatch
67
77
  extends BaseObserver<storage.BucketBatchStorageListener>
68
78
  implements storage.BucketStorageBatch
69
79
  {
80
+ protected readonly options: MongoBucketBatchOptions;
70
81
  protected logger: Logger;
71
82
 
72
83
  private readonly client: mongo.MongoClient;
73
- public readonly db: VersionedPowerSyncMongo;
84
+ readonly db: VersionedPowerSyncMongo;
74
85
  public readonly session: mongo.ClientSession;
75
- private readonly sync_rules: HydratedSyncRules;
86
+ protected readonly sync_rules: HydratedSyncConfig;
76
87
 
77
- protected readonly group_id: number;
88
+ protected readonly replicationStreamId: number;
78
89
 
79
- private readonly slot_name: string;
90
+ private readonly replicationStreamName: string;
91
+ /**
92
+ * Source-level setting for whether raw row data should be stored in current_data.
93
+ *
94
+ * Some sources always send complete rows (MongoDB, MySQL with binlog_row_image=full),
95
+ * in which case this is false for the whole batch. For sources where it depends on the
96
+ * table (Postgres REPLICA IDENTITY), this is true and the decision is refined per-table
97
+ * via SourceTable.storeCurrentData. The effective per-record value is the conjunction of
98
+ * the two.
99
+ */
80
100
  private readonly storeCurrentData: boolean;
81
- private readonly skipExistingRows: boolean;
101
+ public readonly skipExistingRows: boolean;
82
102
  protected readonly mapping: BucketDefinitionMapping;
83
103
 
84
104
  private batch: OperationBatch | null = null;
85
105
  private write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
86
106
  private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
107
+ private hooks: storage.StorageHooks | undefined;
87
108
  private clearedError = false;
88
109
 
89
110
  private tracer: PerformanceTracer<'storage' | 'evaluate'>;
90
111
 
91
- /**
92
- * Last LSN received associated with a checkpoint.
93
- *
94
- * This could be either:
95
- * 1. A commit LSN.
96
- * 2. A keepalive message LSN.
97
- */
98
- private last_checkpoint_lsn: string | null = null;
99
-
100
- private persisted_op: InternalOpId | null = null;
101
-
102
112
  /**
103
113
  * Last written op, if any. This may not reflect a consistent checkpoint.
104
114
  */
105
115
  public last_flushed_op: InternalOpId | null = null;
106
116
 
107
117
  /**
108
- * lastCheckpointLsn is the last consistent commit.
118
+ * LSN to resume replication from.
109
119
  *
110
- * While that is generally a "safe" point to resume from, there are cases where we may want to resume from a different point:
120
+ * This is typically the last commit LSN, but there are cases where it differs:
111
121
  * 1. After an initial snapshot, we don't have a consistent commit yet, but need to resume from the snapshot LSN.
112
122
  * 2. If "no_checkpoint_before_lsn" is set far in advance, it may take a while to reach that point. We
113
123
  * may want to resume at incremental points before that.
@@ -116,39 +126,35 @@ export abstract class MongoBucketBatch
116
126
  */
117
127
  public resumeFromLsn: string | null = null;
118
128
 
119
- private needsActivation = true;
120
-
121
129
  constructor(options: MongoBucketBatchOptions) {
122
130
  super();
123
131
  this.logger = options.logger;
132
+ this.options = options;
124
133
  this.client = options.db.client;
125
134
  this.db = options.db;
126
- this.group_id = options.groupId;
127
- this.last_checkpoint_lsn = options.lastCheckpointLsn;
135
+ this.replicationStreamId = options.replicationStreamId;
128
136
  this.resumeFromLsn = options.resumeFromLsn;
129
137
  this.session = this.client.startSession();
130
- this.slot_name = options.slotName;
131
- this.sync_rules = options.syncRules;
138
+ this.replicationStreamName = options.replicationStreamName;
139
+ this.sync_rules = options.parsedSyncConfig.hydratedSyncConfig;
132
140
  this.storeCurrentData = options.storeCurrentData;
133
- this.mapping = options.mapping;
141
+ this.mapping = options.parsedSyncConfig.mapping;
134
142
  this.skipExistingRows = options.skipExistingRows;
135
143
  this.markRecordUnavailable = options.markRecordUnavailable;
144
+ this.hooks = options.hooks;
136
145
  this.batch = new OperationBatch();
137
146
 
138
- this.persisted_op = options.keepaliveOp ?? null;
139
147
  this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
140
148
  }
141
149
 
142
150
  addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void {
143
151
  this.write_checkpoint_batch.push({
144
152
  ...checkpoint,
145
- sync_rules_id: this.group_id
153
+ sync_rules_id: this.replicationStreamId
146
154
  });
147
155
  }
148
156
 
149
- get lastCheckpointLsn() {
150
- return this.last_checkpoint_lsn;
151
- }
157
+ abstract resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
152
158
 
153
159
  protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
154
160
 
@@ -156,6 +162,25 @@ export abstract class MongoBucketBatch
156
162
 
157
163
  protected abstract cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]): Promise<void>;
158
164
 
165
+ abstract commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult>;
166
+
167
+ abstract keepalive(lsn: string): Promise<storage.CheckpointResult>;
168
+
169
+ abstract setResumeLsn(lsn: string): Promise<void>;
170
+
171
+ abstract getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null>;
172
+
173
+ abstract markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void>;
174
+
175
+ abstract markSnapshotDone(no_checkpoint_before_lsn: string, options?: { throwOnConflict?: boolean }): Promise<void>;
176
+
177
+ abstract markTableSnapshotRequired(table: storage.SourceTable): Promise<void>;
178
+
179
+ abstract markTableSnapshotDone(
180
+ tables: storage.SourceTable[],
181
+ no_checkpoint_before_lsn?: string
182
+ ): Promise<storage.SourceTable[]>;
183
+
159
184
  async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
160
185
  let result: storage.FlushedResult | null = null;
161
186
  // One flush may be split over multiple transactions.
@@ -176,6 +201,8 @@ export abstract class MongoBucketBatch
176
201
 
177
202
  using _ = this.tracer.span('storage', 'flush');
178
203
 
204
+ await this.hooks?.beforeBatchFlush?.(this);
205
+
179
206
  await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
180
207
  if (batch != null) {
181
208
  resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
@@ -197,8 +224,9 @@ export abstract class MongoBucketBatch
197
224
  throw new ReplicationAssertionError('Unexpected last_op == null');
198
225
  }
199
226
 
200
- this.persisted_op = last_op;
227
+ this.recordPersistedOp(last_op);
201
228
  this.last_flushed_op = last_op;
229
+ await this.hooks?.afterBatchFlush?.(this);
202
230
  return { flushed_op: last_op };
203
231
  }
204
232
 
@@ -210,8 +238,12 @@ export abstract class MongoBucketBatch
210
238
  ): Promise<OperationBatch | null> {
211
239
  let sizes: Map<string, number> | undefined = undefined;
212
240
  using _ = this.tracer.span('storage', 'replicate_batch');
213
- if (this.storeCurrentData && !this.skipExistingRows) {
214
- // We skip this step if we don't store current_data, since the sizes will
241
+ // Only look up current_data sizes if the batch stores current_data and at least one
242
+ // table in it does too (per-table can disable it, e.g. Postgres REPLICA IDENTITY FULL).
243
+ const anyTableStoresCurrentData =
244
+ this.storeCurrentData && batch.batch.some((r) => r.record.sourceTable.storeCurrentData);
245
+ if (anyTableStoresCurrentData && !this.skipExistingRows) {
246
+ // We skip this step if no tables store current_data, since the sizes will
215
247
  // always be small in that case.
216
248
 
217
249
  // With skipExistingRows, we don't load the full documents into memory,
@@ -224,10 +256,14 @@ export abstract class MongoBucketBatch
224
256
  // (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
225
257
  // the order of processing, which then becomes really tricky to manage.
226
258
  // This now takes 2+ queries, but doesn't have any issues with order of operations.
227
- const sizeLookups = batch.batch.map((r) => ({
228
- sourceTableId: mongoTableId(r.record.sourceTable.id),
229
- replicaId: r.beforeId
230
- }));
259
+ // Within this branch this.storeCurrentData is true, so the per-table flag is the
260
+ // effective value - only look up sizes for tables that actually store current_data.
261
+ const sizeLookups = batch.batch
262
+ .filter((r) => r.record.sourceTable.storeCurrentData)
263
+ .map((r) => ({
264
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
265
+ replicaId: r.beforeId
266
+ }));
231
267
 
232
268
  sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
233
269
  }
@@ -323,6 +359,9 @@ export abstract class MongoBucketBatch
323
359
  const afterId = operation.afterId;
324
360
  let after = record.after;
325
361
  const sourceTable = record.sourceTable;
362
+ // Effective per-record flag: store current_data only if both the batch (source-level,
363
+ // e.g. Postgres) and the table (e.g. non-FULL replica identity) require it.
364
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
326
365
 
327
366
  let existing_buckets: LoadedSourceRecord['buckets'] = [];
328
367
  let new_buckets: LoadedSourceRecord['buckets'] = [];
@@ -351,7 +390,7 @@ export abstract class MongoBucketBatch
351
390
  // Not an error if we re-apply a transaction
352
391
  existing_buckets = [];
353
392
  existing_lookups = [];
354
- if (!isCompleteRow(this.storeCurrentData, after!)) {
393
+ if (!isCompleteRow(storeCurrentData, after!)) {
355
394
  if (this.markRecordUnavailable != null) {
356
395
  // This will trigger a "resnapshot" of the record.
357
396
  // This is not relevant if storeCurrentData is false, since we'll get the full row
@@ -367,7 +406,7 @@ export abstract class MongoBucketBatch
367
406
  } else {
368
407
  existing_buckets = result.buckets;
369
408
  existing_lookups = result.lookups;
370
- if (this.storeCurrentData && result.data != null) {
409
+ if (storeCurrentData && result.data != null) {
371
410
  const data = deserializeBson(result.data.buffer) as SqliteRow;
372
411
  after = storage.mergeToast<SqliteValue>(after!, data);
373
412
  }
@@ -378,7 +417,9 @@ export abstract class MongoBucketBatch
378
417
  // Not an error if we re-apply a transaction
379
418
  existing_buckets = [];
380
419
  existing_lookups = [];
381
- // Log to help with debugging if there was a consistency issue
420
+ // Log to help with debugging if there was a consistency issue.
421
+ // Gate on the batch-level flag: FULL tables (per-record flag false) still get a
422
+ // current_data entry, so a missing record on DELETE is meaningful for them too.
382
423
  if (this.storeCurrentData && this.markRecordUnavailable == null) {
383
424
  this.logger.warn(
384
425
  `Cannot find previous record for delete on ${record.sourceTable.qualifiedName}: ${beforeId} / ${record.before?.id}`
@@ -391,7 +432,7 @@ export abstract class MongoBucketBatch
391
432
  }
392
433
 
393
434
  let afterData: bson.Binary | null = null;
394
- if (afterId != null && !this.storeCurrentData) {
435
+ if (afterId != null && !storeCurrentData) {
395
436
  afterData = null;
396
437
  } else if (afterId != null) {
397
438
  try {
@@ -415,7 +456,7 @@ export abstract class MongoBucketBatch
415
456
  {
416
457
  level: errors.ErrorSeverity.WARNING,
417
458
  metadata: {
418
- replication_slot: this.slot_name,
459
+ replication_slot: this.replicationStreamName,
419
460
  table: record.sourceTable.qualifiedName
420
461
  }
421
462
  }
@@ -458,13 +499,15 @@ export abstract class MongoBucketBatch
458
499
  // However, it will be valid by the end of the transaction.
459
500
  //
460
501
  // In this case, we don't save the op, but we do save the current data.
461
- if (afterId && after && utils.isCompleteRow(this.storeCurrentData, after)) {
502
+ if (afterId && after && utils.isCompleteRow(storeCurrentData, after)) {
462
503
  // Insert or update
463
504
  if (sourceTable.syncData) {
464
- const { results: evaluated, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
505
+ const { results, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
465
506
  record: after,
466
- sourceTable
507
+ sourceTable: sourceTable.ref,
508
+ bucketDataSources: sourceTable.bucketDataSources
467
509
  });
510
+ const evaluated = results;
468
511
 
469
512
  for (let error of syncErrors) {
470
513
  container.reporter.captureMessage(
@@ -472,7 +515,7 @@ export abstract class MongoBucketBatch
472
515
  {
473
516
  level: errors.ErrorSeverity.WARNING,
474
517
  metadata: {
475
- replication_slot: this.slot_name,
518
+ replication_slot: this.replicationStreamName,
476
519
  table: record.sourceTable.qualifiedName
477
520
  }
478
521
  }
@@ -496,8 +539,9 @@ export abstract class MongoBucketBatch
496
539
  if (sourceTable.syncParameters) {
497
540
  // Parameters
498
541
  const { results: paramEvaluated, errors: paramErrors } = this.sync_rules.evaluateParameterRowWithErrors(
499
- sourceTable,
500
- after
542
+ sourceTable.ref,
543
+ after,
544
+ { parameterLookupSources: sourceTable.parameterLookupSources }
501
545
  );
502
546
 
503
547
  for (let error of paramErrors) {
@@ -506,7 +550,7 @@ export abstract class MongoBucketBatch
506
550
  {
507
551
  level: errors.ErrorSeverity.WARNING,
508
552
  metadata: {
509
- replication_slot: this.slot_name,
553
+ replication_slot: this.replicationStreamName,
510
554
  table: record.sourceTable.qualifiedName
511
555
  }
512
556
  }
@@ -559,7 +603,7 @@ export abstract class MongoBucketBatch
559
603
  return result;
560
604
  }
561
605
 
562
- private async withTransaction(cb: () => Promise<void>) {
606
+ protected async withTransaction(cb: () => Promise<void>) {
563
607
  using lockSpan = this.tracer.span('storage', 'internal_lock');
564
608
  await replicationMutex.exclusiveLock(async () => {
565
609
  lockSpan.end();
@@ -641,7 +685,7 @@ export abstract class MongoBucketBatch
641
685
 
642
686
  await this.db.sync_rules.updateOne(
643
687
  {
644
- _id: this.group_id
688
+ _id: this.replicationStreamId
645
689
  },
646
690
  {
647
691
  $set: {
@@ -650,12 +694,41 @@ export abstract class MongoBucketBatch
650
694
  },
651
695
  { session }
652
696
  );
697
+
698
+ // Allow subclasses to persist additional flush-time state in the same transaction
699
+ // (e.g. v3 advances the stream-level last_persisted_op).
700
+ await this.onReplicationTransactionFlush(session, opSeq.last());
653
701
  // We don't notify checkpoint here - we don't make any checkpoint updates directly
654
702
  });
655
703
  }
656
704
 
705
+ /**
706
+ * Hook called inside the replication flush transaction, after ops have been persisted.
707
+ *
708
+ * The base implementation does nothing; v3 storage overrides this to `$max` the stream-level
709
+ * `last_persisted_op` durably within the same transaction.
710
+ */
711
+ protected async onReplicationTransactionFlush(_session: mongo.ClientSession, _lastOp: InternalOpId): Promise<void> {
712
+ // No-op by default (v1 behaviour unchanged).
713
+ }
714
+
715
+ /**
716
+ * Called after a replication transaction has successfully committed, with the last persisted op id.
717
+ *
718
+ * v1 storage tracks this in memory to fold into the next checkpoint. v3 storage does not need it:
719
+ * the stream-level `last_persisted_op` is already advanced durably by
720
+ * {@link onReplicationTransactionFlush} within the same transaction, and checkpoints read it
721
+ * from the document.
722
+ *
723
+ * Keep calls to this adjacent to {@link withReplicationTransaction} usage - both must observe
724
+ * every path that persists ops.
725
+ */
726
+ protected recordPersistedOp(_lastOp: InternalOpId): void {
727
+ // No-op by default.
728
+ }
729
+
657
730
  async [Symbol.asyncDispose]() {
658
- if (this.batch != null || this.write_checkpoint_batch.length > 0) {
731
+ if (this.batch?.hasData() || this.write_checkpoint_batch.length > 0) {
659
732
  // We don't error here, since:
660
733
  // 1. In error states, this is expected (we can't distinguish between disposing after success or error).
661
734
  // 2. SuppressedError is messy to deal with.
@@ -669,272 +742,27 @@ export abstract class MongoBucketBatch
669
742
  await this[Symbol.asyncDispose]();
670
743
  }
671
744
 
672
- private lastWaitingLogThottled = 0;
673
-
674
- async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<CheckpointResult> {
675
- const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
676
-
677
- await this.flush(options);
678
-
679
- const now = new Date();
680
-
681
- // Mark relevant write checkpoints as "processed".
682
- // This makes it easier to identify write checkpoints that are "valid" in order.
683
- await this.db.write_checkpoints.updateMany(
684
- {
685
- processed_at_lsn: null,
686
- 'lsns.1': { $lte: lsn }
687
- },
688
- {
689
- $set: {
690
- processed_at_lsn: lsn
691
- }
692
- },
693
- {
694
- session: this.session
695
- }
696
- );
697
-
698
- const can_checkpoint = {
699
- $and: [
700
- { $eq: ['$snapshot_done', true] },
701
- {
702
- $or: [{ $eq: ['$last_checkpoint_lsn', null] }, { $lte: ['$last_checkpoint_lsn', { $literal: lsn }] }]
703
- },
704
- {
705
- $or: [{ $eq: ['$no_checkpoint_before', null] }, { $lte: ['$no_checkpoint_before', { $literal: lsn }] }]
706
- }
707
- ]
708
- };
709
-
710
- const new_keepalive_op = {
711
- $cond: [
712
- can_checkpoint,
713
- { $literal: null },
714
- {
715
- $toString: {
716
- $max: [{ $toLong: '$keepalive_op' }, { $literal: this.persisted_op }, 0n]
717
- }
718
- }
719
- ]
720
- };
721
-
722
- const new_last_checkpoint = {
723
- $cond: [
724
- can_checkpoint,
725
- {
726
- $max: ['$last_checkpoint', { $literal: this.persisted_op }, { $toLong: '$keepalive_op' }, 0n]
727
- },
728
- '$last_checkpoint'
729
- ]
730
- };
731
-
732
- // For this query, we need to handle multiple cases, depending on the state:
733
- // 1. Normal commit - advance last_checkpoint to this.persisted_op.
734
- // 2. Commit delayed by no_checkpoint_before due to snapshot. In this case we only advance keepalive_op.
735
- // 3. Commit with no new data - here may may set last_checkpoint = keepalive_op, if a delayed commit is relevant.
736
- // We want to do as much as possible in a single atomic database operation, which makes this somewhat complex.
737
- let preUpdateDocument = await this.db.sync_rules.findOneAndUpdate(
738
- { _id: this.group_id },
739
- [
740
- {
741
- $set: {
742
- _can_checkpoint: can_checkpoint,
743
- _not_empty: createEmptyCheckpoints
744
- ? true
745
- : {
746
- $or: [
747
- { $literal: createEmptyCheckpoints },
748
- { $ne: ['$keepalive_op', new_keepalive_op] },
749
- { $ne: ['$last_checkpoint', new_last_checkpoint] }
750
- ]
751
- }
752
- }
753
- },
754
- {
755
- $set: {
756
- last_checkpoint_lsn: {
757
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: lsn }, '$last_checkpoint_lsn']
758
- },
759
- last_checkpoint_ts: {
760
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: now }, '$last_checkpoint_ts']
745
+ async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
746
+ const { after, before, sourceTable, tag } = record;
747
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
748
+ // syncEvent is the per-table designation from resolveTables. With v3 storage, multiple
749
+ // SourceTables can exist for the same ref, with a row change saved once per table -
750
+ // only the designated event carrier may fire events, so each event fires once per row.
751
+ if (sourceTable.syncEvent) {
752
+ for (const event of this.getTableEvents(sourceTable)) {
753
+ this.iterateListeners((cb) =>
754
+ cb.replicationEvent?.({
755
+ batch: this,
756
+ table: sourceTable,
757
+ data: {
758
+ op: tag,
759
+ after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
760
+ before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
761
761
  },
762
- last_keepalive_ts: { $literal: now },
763
- last_fatal_error: { $literal: null },
764
- last_fatal_error_ts: { $literal: null },
765
- keepalive_op: new_keepalive_op,
766
- last_checkpoint: new_last_checkpoint,
767
- // Unset snapshot_lsn on checkpoint
768
- snapshot_lsn: {
769
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: null }, '$snapshot_lsn']
770
- }
771
- }
772
- },
773
- {
774
- $unset: ['_can_checkpoint', '_not_empty']
775
- }
776
- ],
777
- {
778
- session: this.session,
779
- // We return the before document, so that we can check the previous state to determine if a checkpoint was actually created or if we were blocked by snapshot/no_checkpoint_before.
780
- returnDocument: 'before',
781
- projection: {
782
- snapshot_done: 1,
783
- last_checkpoint_lsn: 1,
784
- no_checkpoint_before: 1,
785
- keepalive_op: 1,
786
- last_checkpoint: 1
787
- }
788
- }
789
- );
790
-
791
- if (preUpdateDocument == null) {
792
- throw new ReplicationAssertionError(
793
- 'Failed to update checkpoint - no matching sync_rules document for _id: ' + this.group_id
794
- );
795
- }
796
-
797
- // This re-implements the same logic as in the pipeline, to determine what was actually updated.
798
- // Unfortunately we cannot return these from the pipeline directly, so we need to re-implement the logic.
799
- const canCheckpoint =
800
- preUpdateDocument.snapshot_done === true &&
801
- (preUpdateDocument.last_checkpoint_lsn == null || preUpdateDocument.last_checkpoint_lsn <= lsn) &&
802
- (preUpdateDocument.no_checkpoint_before == null || preUpdateDocument.no_checkpoint_before <= lsn);
803
-
804
- const keepaliveOp = preUpdateDocument.keepalive_op == null ? null : BigInt(preUpdateDocument.keepalive_op);
805
- const maxKeepalive = [keepaliveOp ?? 0n, this.persisted_op ?? 0n, 0n].reduce((a, b) => (a > b ? a : b));
806
- const newKeepaliveOp = canCheckpoint ? null : maxKeepalive.toString();
807
- const newLastCheckpoint = canCheckpoint
808
- ? [preUpdateDocument.last_checkpoint ?? 0n, this.persisted_op ?? 0n, keepaliveOp ?? 0n, 0n].reduce((a, b) =>
809
- a > b ? a : b
810
- )
811
- : preUpdateDocument.last_checkpoint;
812
- const notEmpty =
813
- createEmptyCheckpoints ||
814
- preUpdateDocument.keepalive_op !== newKeepaliveOp ||
815
- preUpdateDocument.last_checkpoint !== newLastCheckpoint;
816
- const checkpointCreated = canCheckpoint && notEmpty;
817
-
818
- const checkpointBlocked = !canCheckpoint;
819
-
820
- if (checkpointBlocked) {
821
- // Failed on snapshot_done or no_checkpoint_before.
822
- if (Date.now() - this.lastWaitingLogThottled > 5_000) {
823
- this.logger.info(
824
- `Waiting before creating checkpoint, currently at ${lsn} / ${preUpdateDocument.keepalive_op}. Current state: ${JSON.stringify(
825
- {
826
- snapshot_done: preUpdateDocument.snapshot_done,
827
- last_checkpoint_lsn: preUpdateDocument.last_checkpoint_lsn,
828
- no_checkpoint_before: preUpdateDocument.no_checkpoint_before
829
- }
830
- )}`
831
- );
832
- this.lastWaitingLogThottled = Date.now();
833
- }
834
- } else {
835
- if (checkpointCreated) {
836
- this.logger.debug(`Created checkpoint at ${lsn} / ${newLastCheckpoint}`);
837
- }
838
- await this.autoActivate(lsn);
839
- await this.db.notifyCheckpoint();
840
- this.persisted_op = null;
841
- this.last_checkpoint_lsn = lsn;
842
- if (newLastCheckpoint != null) {
843
- await this.sourceRecordStore.postCommitCleanup(newLastCheckpoint, this.logger);
844
- }
845
- }
846
- return { checkpointBlocked, checkpointCreated };
847
- }
848
-
849
- /**
850
- * Switch from processing -> active if relevant.
851
- *
852
- * Called on new commits.
853
- */
854
- private async autoActivate(lsn: string) {
855
- if (!this.needsActivation) {
856
- return;
857
- }
858
-
859
- // Activate the batch, so it can start processing.
860
- // This is done automatically when the first save() is called.
861
-
862
- const session = this.session;
863
- let activated = false;
864
- await session.withTransaction(async () => {
865
- const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
866
- if (doc && doc.state == SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
867
- await this.db.sync_rules.updateOne(
868
- {
869
- _id: this.group_id
870
- },
871
- {
872
- $set: {
873
- state: storage.SyncRuleState.ACTIVE
874
- }
875
- },
876
- { session }
877
- );
878
-
879
- await this.db.sync_rules.updateMany(
880
- {
881
- _id: { $ne: this.group_id },
882
- state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
883
- },
884
- {
885
- $set: {
886
- state: storage.SyncRuleState.STOP
887
- }
888
- },
889
- { session }
762
+ event
763
+ })
890
764
  );
891
- activated = true;
892
- } else if (doc?.state != SyncRuleState.PROCESSING) {
893
- this.needsActivation = false;
894
765
  }
895
- });
896
- if (activated) {
897
- this.logger.info(`Activated new replication stream at ${lsn}`);
898
- await this.db.notifyCheckpoint();
899
- this.needsActivation = false;
900
- }
901
- }
902
-
903
- async keepalive(lsn: string): Promise<CheckpointResult> {
904
- return await this.commit(lsn, { createEmptyCheckpoints: true });
905
- }
906
-
907
- async setResumeLsn(lsn: string): Promise<void> {
908
- const update: Partial<SyncRuleDocument> = {
909
- snapshot_lsn: lsn
910
- };
911
-
912
- await this.db.sync_rules.updateOne(
913
- {
914
- _id: this.group_id
915
- },
916
- {
917
- $set: update
918
- },
919
- { session: this.session }
920
- );
921
- }
922
-
923
- async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
924
- const { after, before, sourceTable, tag } = record;
925
- for (const event of this.getTableEvents(sourceTable)) {
926
- this.iterateListeners((cb) =>
927
- cb.replicationEvent?.({
928
- batch: this,
929
- table: sourceTable,
930
- data: {
931
- op: tag,
932
- after: after && utils.isCompleteRow(this.storeCurrentData, after) ? after : undefined,
933
- before: before && utils.isCompleteRow(this.storeCurrentData, before) ? before : undefined
934
- },
935
- event
936
- })
937
- );
938
766
  }
939
767
 
940
768
  /**
@@ -967,7 +795,7 @@ export abstract class MongoBucketBatch
967
795
 
968
796
  await this.withTransaction(async () => {
969
797
  for (let table of sourceTables) {
970
- await this.db.commonSourceTables(this.group_id).deleteOne({ _id: mongoTableId(table.id) });
798
+ await this.db.commonSourceTables(this.replicationStreamId).deleteOne({ _id: mongoTableId(table.id) });
971
799
  }
972
800
  });
973
801
 
@@ -984,7 +812,7 @@ export abstract class MongoBucketBatch
984
812
  }
985
813
 
986
814
  if (last_op) {
987
- this.persisted_op = last_op;
815
+ this.recordPersistedOp(last_op);
988
816
  return {
989
817
  flushed_op: last_op
990
818
  };
@@ -1053,7 +881,7 @@ export abstract class MongoBucketBatch
1053
881
  copy.snapshotStatus = snapshotStatus;
1054
882
 
1055
883
  await this.withTransaction(async () => {
1056
- await this.db.commonSourceTables(this.group_id).updateOne(
884
+ await this.db.commonSourceTables(this.replicationStreamId).updateOne(
1057
885
  { _id: mongoTableId(table.id) },
1058
886
  {
1059
887
  $set: {
@@ -1071,80 +899,6 @@ export abstract class MongoBucketBatch
1071
899
  return copy;
1072
900
  }
1073
901
 
1074
- async markAllSnapshotDone(no_checkpoint_before_lsn: string) {
1075
- await this.db.sync_rules.updateOne(
1076
- {
1077
- _id: this.group_id
1078
- },
1079
- {
1080
- $set: {
1081
- snapshot_done: true,
1082
- last_keepalive_ts: new Date()
1083
- },
1084
- $max: {
1085
- no_checkpoint_before: no_checkpoint_before_lsn
1086
- }
1087
- },
1088
- { session: this.session }
1089
- );
1090
- }
1091
-
1092
- async markTableSnapshotRequired(table: storage.SourceTable): Promise<void> {
1093
- await this.db.sync_rules.updateOne(
1094
- {
1095
- _id: this.group_id
1096
- },
1097
- {
1098
- $set: {
1099
- snapshot_done: false
1100
- }
1101
- },
1102
- { session: this.session }
1103
- );
1104
- }
1105
-
1106
- async markTableSnapshotDone(tables: storage.SourceTable[], no_checkpoint_before_lsn?: string) {
1107
- const session = this.session;
1108
- const ids = tables.map((table) => mongoTableId(table.id));
1109
-
1110
- await this.withTransaction(async () => {
1111
- await this.db.commonSourceTables(this.group_id).updateMany(
1112
- { _id: { $in: ids } },
1113
- {
1114
- $set: {
1115
- snapshot_done: true
1116
- },
1117
- $unset: {
1118
- snapshot_status: 1
1119
- }
1120
- },
1121
- { session }
1122
- );
1123
-
1124
- if (no_checkpoint_before_lsn != null) {
1125
- await this.db.sync_rules.updateOne(
1126
- {
1127
- _id: this.group_id
1128
- },
1129
- {
1130
- $set: {
1131
- last_keepalive_ts: new Date()
1132
- },
1133
- $max: {
1134
- no_checkpoint_before: no_checkpoint_before_lsn
1135
- }
1136
- },
1137
- { session: this.session }
1138
- );
1139
- }
1140
- });
1141
- return tables.map((table) => {
1142
- const copy = table.clone();
1143
- copy.snapshotComplete = true;
1144
- return copy;
1145
- });
1146
- }
1147
-
1148
902
  protected async clearError(): Promise<void> {
1149
903
  // No need to clear an error more than once per batch, since an error would always result in restarting the batch.
1150
904
  if (this.clearedError) {
@@ -1153,7 +907,7 @@ export abstract class MongoBucketBatch
1153
907
 
1154
908
  await this.db.sync_rules.updateOne(
1155
909
  {
1156
- _id: this.group_id
910
+ _id: this.replicationStreamId
1157
911
  },
1158
912
  {
1159
913
  $set: {
@@ -1170,7 +924,7 @@ export abstract class MongoBucketBatch
1170
924
  */
1171
925
  protected getTableEvents(table: storage.SourceTable): SqlEventDescriptor[] {
1172
926
  return this.sync_rules.eventDescriptors.filter((evt) =>
1173
- [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table))
927
+ [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table.ref))
1174
928
  );
1175
929
  }
1176
930
  }