@powersync/service-module-mongodb-storage 0.19.0 → 0.21.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 (176) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +31 -3
  3. package/dist/storage/MongoBucketStorage.js +365 -206
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +15 -4
  6. package/dist/storage/implementation/MongoBucketBatch.js +35 -9
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
  9. package/dist/storage/implementation/MongoCompactor.js +27 -238
  10. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  11. package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
  12. package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
  13. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
  15. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
  16. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  17. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
  18. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  19. package/dist/storage/implementation/MongoStorageProvider.js +3 -1
  20. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  21. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
  22. package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
  23. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  24. package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
  25. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  26. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
  27. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +7 -28
  28. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  29. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  30. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  31. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  32. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
  33. package/dist/storage/implementation/common/PersistedBatch.d.ts +27 -0
  34. package/dist/storage/implementation/common/PersistedBatch.js +43 -2
  35. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  36. package/dist/storage/implementation/db.d.ts +4 -1
  37. package/dist/storage/implementation/db.js +14 -1
  38. package/dist/storage/implementation/db.js.map +1 -1
  39. package/dist/storage/implementation/models.d.ts +35 -0
  40. package/dist/storage/implementation/models.js.map +1 -1
  41. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +6 -0
  42. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +80 -84
  43. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  44. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +36 -5
  45. package/dist/storage/implementation/v1/MongoCompactorV1.js +229 -8
  46. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  47. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  48. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  49. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  50. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
  51. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
  52. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  53. package/dist/storage/implementation/v1/models.js +1 -0
  54. package/dist/storage/implementation/v1/models.js.map +1 -1
  55. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  56. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  57. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  58. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -1
  59. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +120 -39
  60. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  61. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +77 -25
  62. package/dist/storage/implementation/v3/MongoCompactorV3.js +585 -141
  63. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  64. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
  65. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
  66. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  67. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
  68. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
  69. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
  71. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
  72. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  74. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  75. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  76. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -0
  77. package/dist/storage/implementation/v3/PersistedBatchV3.js +52 -11
  78. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  79. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
  80. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
  81. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/bucket-format.js +2 -0
  83. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  84. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  85. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  86. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  87. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  88. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  89. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  90. package/dist/storage/implementation/v3/models.d.ts +89 -5
  91. package/dist/storage/implementation/v3/models.js.map +1 -1
  92. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
  93. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +8 -4
  94. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  95. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
  96. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +3 -5
  97. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +10 -2
  98. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  99. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
  100. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
  101. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  102. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
  103. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +177 -23
  104. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  105. package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
  106. package/dist/storage/implementation/v3/source-table-utils.js +49 -57
  107. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  108. package/dist/storage/storage-index.d.ts +2 -1
  109. package/dist/storage/storage-index.js +1 -0
  110. package/dist/storage/storage-index.js.map +1 -1
  111. package/dist/types/types.d.ts +23 -1
  112. package/dist/types/types.js +16 -7
  113. package/dist/types/types.js.map +1 -1
  114. package/dist/utils/util.js +3 -1
  115. package/dist/utils/util.js.map +1 -1
  116. package/package.json +10 -9
  117. package/src/storage/MongoBucketStorage.ts +495 -268
  118. package/src/storage/implementation/MongoBucketBatch.ts +54 -14
  119. package/src/storage/implementation/MongoCompactor.ts +35 -346
  120. package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
  121. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
  122. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
  123. package/src/storage/implementation/MongoStorageProvider.ts +3 -1
  124. package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
  125. package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
  126. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +12 -44
  127. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  128. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
  129. package/src/storage/implementation/common/PersistedBatch.ts +54 -2
  130. package/src/storage/implementation/db.ts +21 -2
  131. package/src/storage/implementation/models.ts +38 -0
  132. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +107 -96
  133. package/src/storage/implementation/v1/MongoCompactorV1.ts +303 -19
  134. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  135. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
  136. package/src/storage/implementation/v1/models.ts +1 -0
  137. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  138. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +159 -42
  139. package/src/storage/implementation/v3/MongoCompactorV3.ts +744 -190
  140. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
  141. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
  142. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
  143. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  144. package/src/storage/implementation/v3/PersistedBatchV3.ts +57 -12
  145. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
  146. package/src/storage/implementation/v3/bucket-format.ts +2 -0
  147. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  148. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  149. package/src/storage/implementation/v3/models.ts +96 -3
  150. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +16 -8
  151. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
  152. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +15 -4
  153. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
  154. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +257 -27
  155. package/src/storage/implementation/v3/source-table-utils.ts +82 -76
  156. package/src/storage/storage-index.ts +2 -0
  157. package/src/types/types.ts +24 -7
  158. package/src/utils/util.ts +3 -1
  159. package/test/src/__snapshots__/storage.test.ts.snap +25 -0
  160. package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
  161. package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
  162. package/test/src/compact-utils.test.ts +226 -0
  163. package/test/src/helpers/MemoryObjectStorage.ts +14 -6
  164. package/test/src/object_storage_usage.test.ts +255 -0
  165. package/test/src/parameter_compacting_v1.test.ts +120 -0
  166. package/test/src/parameter_compacting_v3.test.ts +192 -0
  167. package/test/src/parameter_compaction_fence.test.ts +276 -0
  168. package/test/src/storage.test.ts +273 -22
  169. package/test/src/storage_compacting.test.ts +714 -244
  170. package/test/src/storage_s3_checksums.test.ts +3 -4
  171. package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
  172. package/test/src/storage_s3_reading.test.ts +243 -1
  173. package/test/src/storage_s3_writing.test.ts +1 -0
  174. package/test/src/storage_sync.test.ts +365 -41
  175. package/test/src/util.ts +1 -1
  176. package/tsconfig.tsbuildinfo +1 -1
@@ -24,8 +24,9 @@ import {
24
24
  CurrentDataDocument
25
25
  } from './v1/models.js';
26
26
  import { VersionedPowerSyncMongoV1 } from './v1/VersionedPowerSyncMongoV1.js';
27
- import { BucketDataDocumentV3 } from './v3/models.js';
28
- import { VersionedPowerSyncMongoV3 } from './v3/VersionedPowerSyncMongoV3.js';
27
+ import { BucketDataDocumentV3, CustomCheckpointRequestDocumentV3 } from './v3/models.js';
28
+ import type { CustomCheckpointRequestCollectionSpecifier } from './v3/VersionedPowerSyncMongoV3.js';
29
+ import { OBJECT_STORAGE_USAGE_COLLECTION, VersionedPowerSyncMongoV3 } from './v3/VersionedPowerSyncMongoV3.js';
29
30
 
30
31
  export interface PowerSyncMongoOptions {
31
32
  /**
@@ -137,6 +138,20 @@ export class PowerSyncMongo {
137
138
  return `source_table_${replicationStreamId}`;
138
139
  }
139
140
 
141
+ customCheckpointRequestCollectionName(options: CustomCheckpointRequestCollectionSpecifier) {
142
+ return `custom_checkpoint_requests_${options.replicationStreamId}_${options.eventId}`;
143
+ }
144
+
145
+ async listCustomCheckpointRequestCollections(
146
+ replicationStreamId?: number
147
+ ): Promise<mongo.Collection<CustomCheckpointRequestDocumentV3>[]> {
148
+ const prefix =
149
+ replicationStreamId == null
150
+ ? 'custom_checkpoint_requests_'
151
+ : `custom_checkpoint_requests_${replicationStreamId}_`;
152
+ return this.collectionsByPrefix(prefix);
153
+ }
154
+
140
155
  async listSourceTableCollections(
141
156
  replicationStreamId?: number
142
157
  ): Promise<mongo.Collection<CommonSourceTableDocument>[]> {
@@ -178,6 +193,10 @@ export class PowerSyncMongo {
178
193
  await this.locks.deleteMany({});
179
194
  await this.bucket_state.deleteMany({});
180
195
  await this.custom_write_checkpoints.deleteMany({});
196
+ await this.db.collection(OBJECT_STORAGE_USAGE_COLLECTION).deleteMany({});
197
+ for (const collection of await this.listCustomCheckpointRequestCollections()) {
198
+ await collection.drop();
199
+ }
181
200
  }
182
201
 
183
202
  /**
@@ -74,6 +74,12 @@ export interface BucketDataProperties {
74
74
  op: OpType;
75
75
  source_table?: bson.ObjectId;
76
76
  source_key?: ReplicaId;
77
+ /**
78
+ * Stable identifier for the source record in the client protocol.
79
+ *
80
+ * This is derived from (source_table, source_key), so persistence is optional but preferred.
81
+ */
82
+ subkey?: string;
77
83
  table?: string;
78
84
  row_id?: string;
79
85
  checksum: bigint;
@@ -110,6 +116,10 @@ export interface SourceTableDocument {
110
116
  replica_id_columns2: { name: string; type_oid?: number; type?: string }[] | undefined;
111
117
  snapshot_done: boolean | undefined;
112
118
  snapshot_status: SourceTableDocumentSnapshotStatus | undefined;
119
+ /**
120
+ * Source-specific metadata. Absent for legacy records.
121
+ */
122
+ source_metadata?: storage.JsonValue;
113
123
  }
114
124
 
115
125
  export interface SourceTableDocumentSnapshotStatus {
@@ -211,6 +221,34 @@ export interface SyncRuleDocumentBase {
211
221
  } | null;
212
222
 
213
223
  storage_version?: number;
224
+
225
+ /** Optional operator-supplied label identifying the sync config version. */
226
+ version_label?: string | null;
227
+
228
+ /**
229
+ * Incremental parameter compaction state for the replication stream.
230
+ *
231
+ * Operation ids are allocated across all parameter indexes of a stream, so this is
232
+ * stream-level state, shared by all sync configs of the stream.
233
+ */
234
+ parameter_compaction?: {
235
+ /**
236
+ * The exclusive operation-id boundary through which every parameter index in this stream has
237
+ * been compacted.
238
+ */
239
+ compacted_before: InternalOpId;
240
+
241
+ /**
242
+ * Parameter entries below this boundary may no longer be available for checkpoint change
243
+ * detection, since compaction may have deleted them.
244
+ *
245
+ * This is advanced before the first delete of a compaction pass, while
246
+ * {@link compacted_before} is only advanced after every delete of the pass completed.
247
+ * A checkpoint transition starting below this boundary must conservatively invalidate all
248
+ * parameter buckets, since the individual changes may no longer be available.
249
+ */
250
+ checkpoint_changes_invalid_before?: InternalOpId;
251
+ };
214
252
  }
215
253
 
216
254
  export interface SyncRuleCheckpointFields {
@@ -1,5 +1,7 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
1
2
  import { ReplicationAssertionError } from '@powersync/lib-services-framework';
2
3
  import { ColumnDescriptor, InternalOpId, SourceTable, storage } from '@powersync/service-core';
4
+ import { HydratedSyncConfig } from '@powersync/service-sync-rules';
3
5
  import * as bson from 'bson';
4
6
  import { mongoTableId } from '../../../utils/util.js';
5
7
  import { calculateCheckpointState } from '../CheckpointState.js';
@@ -9,7 +11,7 @@ import { SourceRecordStore } from '../common/SourceRecordStore.js';
9
11
  import { PersistedBatchV1 } from './PersistedBatchV1.js';
10
12
  import { SourceRecordStoreV1 } from './SourceRecordStoreV1.js';
11
13
  import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
12
- import { SyncRuleDocumentV1 } from './models.js';
14
+ import { SourceTableDocumentV1, SyncRuleDocumentV1 } from './models.js';
13
15
 
14
16
  export class MongoBucketBatchV1 extends MongoBucketBatch {
15
17
  declare readonly db: VersionedPowerSyncMongoV1;
@@ -49,8 +51,39 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
49
51
  // No-op for V1: source records live in a shared collection.
50
52
  }
51
53
 
54
+ protected override async batchCreateCustomWriteCheckpoints(
55
+ session: mongo.ClientSession,
56
+ opId: InternalOpId
57
+ ): Promise<void> {
58
+ await this.db.custom_write_checkpoints.bulkWrite(
59
+ this.write_checkpoint_batch.map((checkpointOptions) => {
60
+ const set: Record<string, unknown> = {
61
+ checkpoint: checkpointOptions.checkpoint,
62
+ sync_rules_id: checkpointOptions.sync_rules_id,
63
+ op_id: opId
64
+ };
65
+ if (checkpointOptions.checkpoint_requested_at != null) {
66
+ set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
67
+ }
68
+
69
+ return {
70
+ updateOne: {
71
+ filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
72
+ update: {
73
+ $set: set,
74
+ ...(checkpointOptions.checkpoint_requested_at == null ? { $unset: { checkpoint_requested_at: 1 } } : {})
75
+ },
76
+ upsert: true
77
+ }
78
+ };
79
+ }),
80
+ { session }
81
+ );
82
+ }
83
+
52
84
  async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
53
85
  const syncRules = options.parsedSyncConfig?.hydratedSyncConfig ?? this.sync_rules;
86
+ const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
54
87
  const { connection_id, source } = options;
55
88
  const { schema, name, objectId, replicaIdColumns, connectionTag, sendsCompleteRows } = source;
56
89
 
@@ -63,20 +96,38 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
63
96
  let result: storage.ResolveTablesResult | null = null;
64
97
  await this.db.client.withSession(async (session) => {
65
98
  const col = this.db.sourceTablesV1(this.replicationStreamId);
66
- const filter: any = {
67
- group_id: this.replicationStreamId,
68
- connection_id,
69
- schema_name: schema,
70
- table_name: name,
71
- replica_id_columns2: normalizedReplicaIdColumns
72
- };
99
+
100
+ // Find records that overlap by name or relation id.
101
+ const orClauses: Record<string, unknown>[] = [{ schema_name: schema, table_name: name }];
73
102
  if (objectId != null) {
74
- filter.relation_id = objectId;
103
+ orClauses.push({ relation_id: objectId });
75
104
  }
105
+ const candidateDocs = await col
106
+ .find({ group_id: this.replicationStreamId, connection_id, $or: orClauses }, { session })
107
+ .toArray();
76
108
 
77
- let doc = await col.findOne(filter, { session });
78
- if (doc == null) {
79
- doc = {
109
+ const candidateTables = candidateDocs.map((doc) =>
110
+ this.hydrateSourceTable(doc, connectionTag, syncRules, sendsCompleteRows)
111
+ );
112
+ const candidates = candidateTables.map((table) => table.clone());
113
+ const resolution = await reconcile({ source, candidates });
114
+ storage.validateSourceTableCandidateResolution(candidates, resolution);
115
+
116
+ for (const { id, sourceMetadata } of storage.diffSourceTableUpdates(candidateTables, resolution)) {
117
+ await col.updateOne({ _id: mongoTableId(id) }, { $set: { source_metadata: sourceMetadata } }, { session });
118
+ }
119
+
120
+ const materializedResolution = storage.materializeSourceTableResolution(candidateTables, resolution);
121
+ const compatibleTables = materializedResolution.compatibleTables;
122
+
123
+ // V1 keeps one record, preferring one that has already been snapshotted.
124
+ const reuse = compatibleTables.find((table) => table.snapshotComplete) ?? compatibleTables[0] ?? null;
125
+
126
+ let sourceTable: storage.SourceTable;
127
+ if (reuse != null) {
128
+ sourceTable = reuse;
129
+ } else {
130
+ const doc: SourceTableDocumentV1 = {
80
131
  _id: options.idGenerator ? (options.idGenerator() as bson.ObjectId) : new bson.ObjectId(),
81
132
  group_id: this.replicationStreamId,
82
133
  connection_id,
@@ -86,69 +137,18 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
86
137
  replica_id_columns: null,
87
138
  replica_id_columns2: normalizedReplicaIdColumns,
88
139
  snapshot_done: false,
89
- snapshot_status: undefined
140
+ snapshot_status: undefined,
141
+ source_metadata: resolution.newTableValues.sourceMetadata
90
142
  };
91
143
  await col.insertOne(doc, { session });
144
+ sourceTable = this.hydrateSourceTable(doc, connectionTag, syncRules, sendsCompleteRows);
92
145
  }
93
146
 
94
- const sourceTable = new storage.SourceTable({
95
- id: doc._id,
96
- ref: source,
97
- objectId,
98
- replicaIdColumns,
99
- snapshotComplete: doc.snapshot_done ?? true,
100
- ...syncRules.getMatchingSources(source)
101
- });
102
- sourceTable.syncEvent = syncRules.tableTriggersEvent(source);
103
- sourceTable.syncData = sourceTable.bucketDataSources.length > 0;
104
- sourceTable.syncParameters = sourceTable.parameterLookupSources.length > 0;
105
- sourceTable.storeCurrentData = sendsCompleteRows !== true;
106
- sourceTable.snapshotStatus =
107
- doc.snapshot_status == null
108
- ? undefined
109
- : {
110
- lastKey: doc.snapshot_status.last_key?.buffer ?? null,
111
- totalEstimatedCount: doc.snapshot_status.total_estimated_count,
112
- replicatedCount: doc.snapshot_status.replicated_count
113
- };
114
-
115
- const truncateFilter = [{ schema_name: schema, table_name: name }] as any[];
116
- if (objectId != null) {
117
- truncateFilter.push({ relation_id: objectId });
118
- }
119
- const truncate = await col
120
- .find(
121
- {
122
- group_id: this.replicationStreamId,
123
- connection_id,
124
- _id: { $ne: doc._id },
125
- $or: truncateFilter
126
- },
127
- { session }
128
- )
129
- .toArray();
130
- const dropTables = truncate.map((dropDoc) => {
131
- const ref = {
132
- connectionTag,
133
- schema: dropDoc.schema_name,
134
- name: dropDoc.table_name
135
- };
136
- const table = new storage.SourceTable({
137
- id: dropDoc._id,
138
- ref,
139
- objectId: dropDoc.relation_id,
140
- replicaIdColumns:
141
- dropDoc.replica_id_columns2?.map(
142
- (c) => ({ name: c.name, typeId: c.type_oid, type: c.type }) satisfies ColumnDescriptor
143
- ) ?? [],
144
- snapshotComplete: dropDoc.snapshot_done ?? true,
145
- ...syncRules.getMatchingSources(ref)
146
- });
147
- table.syncEvent = syncRules.tableTriggersEvent(ref);
148
- table.syncData = table.bucketDataSources.length > 0;
149
- table.syncParameters = table.parameterLookupSources.length > 0;
150
- return table;
151
- });
147
+ const dropTables = [
148
+ ...materializedResolution.incompatibleTables,
149
+ // V1 only keeps one SourceTable per physical table.
150
+ ...compatibleTables.filter((table) => !storage.sourceTableIdEquals(table.id, sourceTable.id))
151
+ ];
152
152
 
153
153
  result = { tables: [sourceTable], dropTables };
154
154
  });
@@ -156,38 +156,33 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
156
156
  return result!;
157
157
  }
158
158
 
159
- async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
160
- const doc = await this.db.sourceTablesV1(this.replicationStreamId).findOne(
161
- {
162
- group_id: this.replicationStreamId,
163
- _id: mongoTableId(table.id)
164
- },
165
- { session: this.session }
166
- );
167
- if (doc == null) {
168
- return null;
169
- }
170
-
171
- const ref = {
172
- connectionTag: table.ref.connectionTag,
173
- schema: doc.schema_name,
174
- name: doc.table_name
175
- };
176
- const sourceTable = new storage.SourceTable({
159
+ /**
160
+ * Build a SourceTable from its persisted v1 record.
161
+ */
162
+ private hydrateSourceTable(
163
+ doc: SourceTableDocumentV1,
164
+ connectionTag: string,
165
+ syncRules: HydratedSyncConfig,
166
+ sendsCompleteRows: boolean | undefined
167
+ ): storage.SourceTable {
168
+ const ref = { connectionTag, schema: doc.schema_name, name: doc.table_name };
169
+ const table = new storage.SourceTable({
177
170
  id: doc._id,
178
171
  ref,
179
172
  objectId: doc.relation_id,
180
173
  replicaIdColumns:
181
174
  doc.replica_id_columns2?.map(
182
- (c) => ({ name: c.name, typeId: c.type_oid, type: c.type }) satisfies ColumnDescriptor
175
+ (column) => ({ name: column.name, typeId: column.type_oid, type: column.type }) satisfies ColumnDescriptor
183
176
  ) ?? [],
184
177
  snapshotComplete: doc.snapshot_done ?? true,
185
- ...this.sync_rules.getMatchingSources(ref)
178
+ sourceMetadata: doc.source_metadata ?? null,
179
+ ...syncRules.getMatchingSources(ref)
186
180
  });
187
- sourceTable.syncEvent = this.sync_rules.tableTriggersEvent(ref);
188
- sourceTable.syncData = sourceTable.bucketDataSources.length > 0;
189
- sourceTable.syncParameters = sourceTable.parameterLookupSources.length > 0;
190
- sourceTable.snapshotStatus =
181
+ table.syncEvent = syncRules.tableTriggersEvent(ref);
182
+ table.syncData = table.bucketDataSources.length > 0;
183
+ table.syncParameters = table.parameterLookupSources.length > 0;
184
+ table.storeCurrentData = sendsCompleteRows !== true;
185
+ table.snapshotStatus =
191
186
  doc.snapshot_status == null
192
187
  ? undefined
193
188
  : {
@@ -195,7 +190,23 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
195
190
  totalEstimatedCount: doc.snapshot_status.total_estimated_count,
196
191
  replicatedCount: doc.snapshot_status.replicated_count
197
192
  };
198
- return sourceTable;
193
+ return table;
194
+ }
195
+
196
+ async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
197
+ const doc = await this.db.sourceTablesV1(this.replicationStreamId).findOne(
198
+ {
199
+ group_id: this.replicationStreamId,
200
+ _id: mongoTableId(table.id)
201
+ },
202
+ { session: this.session }
203
+ );
204
+ if (doc == null) {
205
+ return null;
206
+ }
207
+
208
+ // Use the default storeCurrentData value when reading outside resolution.
209
+ return this.hydrateSourceTable(doc, table.ref.connectionTag, this.sync_rules, undefined);
199
210
  }
200
211
 
201
212
  async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult> {