@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
@@ -0,0 +1,152 @@
1
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
+ import { SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
3
+ import * as bson from 'bson';
4
+ import {
5
+ ReplicationStreamDocumentV3,
6
+ SyncConfigDefinition,
7
+ SyncRuleConfigStateV3,
8
+ SyncRuleDocumentV1
9
+ } from '../storage-index.js';
10
+ import { PowerSyncMongo } from './db.js';
11
+ import { getMongoStorageConfig } from './models.js';
12
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
13
+
14
+ export abstract class MongoPersistedSyncConfigContentBase extends storage.PersistedSyncConfigContent {
15
+ public readonly mapping: SingleSyncConfigBucketDefinitionMapping;
16
+ public readonly syncConfigObjectId: bson.ObjectId | null;
17
+
18
+ protected constructor(
19
+ protected readonly db: PowerSyncMongo,
20
+ options: Omit<storage.PersistedSyncConfigContentData, 'syncConfigId'> & {
21
+ mapping: SingleSyncConfigBucketDefinitionMapping;
22
+ syncConfigId: bson.ObjectId | null;
23
+ }
24
+ ) {
25
+ const { mapping, syncConfigId, ...base } = options;
26
+ super({
27
+ ...base,
28
+ syncConfigId: syncConfigId?.toHexString() ?? null
29
+ });
30
+ this.mapping = mapping;
31
+ this.syncConfigObjectId = syncConfigId;
32
+ }
33
+
34
+ getStorageConfig() {
35
+ return getMongoStorageConfig(this.storageVersion);
36
+ }
37
+
38
+ parsed(options: storage.ParseSyncConfigOptions): MongoParsedSyncConfigSet {
39
+ const parsed = super.parsed(options);
40
+ const storageConfig = this.getStorageConfig();
41
+ const [syncConfig] = parsed.syncConfigs;
42
+ if (syncConfig == null) {
43
+ throw new ServiceAssertionError(`Expected one parsed sync config`);
44
+ }
45
+
46
+ if (storageConfig.incrementalReprocessing) {
47
+ const syncConfigId = this.syncConfigId;
48
+ if (syncConfigId == null) {
49
+ throw new ServiceAssertionError(`syncConfigId is required for v3 sync config content`);
50
+ }
51
+
52
+ return new MongoParsedSyncConfigSet(parsed.replicationStreamId, storageConfig, parsed.replicationStreamName, [
53
+ { syncConfigId, syncConfig, mapping: this.mapping }
54
+ ]);
55
+ }
56
+
57
+ return new MongoParsedSyncConfigSet(parsed.replicationStreamId, storageConfig, parsed.replicationStreamName, [
58
+ { syncConfig, mapping: null }
59
+ ]);
60
+ }
61
+ }
62
+ export class MongoPersistedSyncConfigContentV1 extends MongoPersistedSyncConfigContentBase {
63
+ constructor(db: PowerSyncMongo, doc: SyncRuleDocumentV1) {
64
+ super(db, {
65
+ replicationStreamId: doc._id,
66
+ sync_rules_content: doc.content,
67
+ compiled_plan: doc.serialized_plan ?? null,
68
+ // Handle legacy values
69
+ replicationStreamName: doc.slot_name ?? `powersync_${doc._id}`,
70
+ storageVersion: doc.storage_version ?? storage.LEGACY_STORAGE_VERSION,
71
+ mapping: new SingleSyncConfigBucketDefinitionMapping(),
72
+ syncConfigId: null
73
+ });
74
+ }
75
+
76
+ async getSyncConfigStatus(): Promise<storage.PersistedSyncConfigStatus | null> {
77
+ const doc = await this.db.sync_rules.findOne<SyncRuleDocumentV1>({ _id: this.replicationStreamId });
78
+ if (doc == null) {
79
+ return null;
80
+ }
81
+
82
+ return syncConfigStatusFromV1(doc);
83
+ }
84
+ }
85
+ export class MongoPersistedSyncConfigContentV3 extends MongoPersistedSyncConfigContentBase {
86
+ declare public readonly syncConfigObjectId: bson.ObjectId;
87
+
88
+ constructor(db: PowerSyncMongo, doc: ReplicationStreamDocumentV3, config: SyncConfigDefinition) {
89
+ const state = doc.sync_configs.find((c) => c._id.equals(config._id));
90
+ if (state == null) {
91
+ throw new ServiceAssertionError(`Cannot find sync config ${config._id} in replication stream ${doc._id}`);
92
+ }
93
+ super(db, {
94
+ replicationStreamId: doc._id,
95
+ sync_rules_content: config.content,
96
+ compiled_plan: config.serialized_plan ?? null,
97
+
98
+ replicationStreamName: doc.slot_name ?? `powersync_${doc._id}`,
99
+ storageVersion: doc.storage_version,
100
+ mapping: SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(config.rule_mapping),
101
+ syncConfigId: config._id
102
+ });
103
+ }
104
+
105
+ async getSyncConfigStatus(): Promise<storage.PersistedSyncConfigStatus | null> {
106
+ const doc = await this.db.sync_rules.findOne<ReplicationStreamDocumentV3>({
107
+ _id: this.replicationStreamId,
108
+ 'sync_configs._id': this.syncConfigObjectId
109
+ });
110
+ if (doc == null) {
111
+ return null;
112
+ }
113
+
114
+ const state = doc.sync_configs.find((c) => c._id.equals(this.syncConfigObjectId));
115
+ if (state == null) {
116
+ return null;
117
+ }
118
+
119
+ return syncConfigStatusFromV3(doc, state);
120
+ }
121
+ }
122
+
123
+ function syncConfigStatusFromV1(doc: SyncRuleDocumentV1): storage.PersistedSyncConfigStatus {
124
+ return {
125
+ id: String(doc._id),
126
+ replicationStreamId: doc._id,
127
+ state: doc.state,
128
+ snapshot_done: doc.snapshot_done,
129
+ last_checkpoint_lsn: doc.last_checkpoint_lsn,
130
+ last_fatal_error: doc.last_fatal_error,
131
+ last_fatal_error_ts: doc.last_fatal_error_ts,
132
+ last_keepalive_ts: doc.last_keepalive_ts,
133
+ last_checkpoint_ts: doc.last_checkpoint_ts
134
+ };
135
+ }
136
+
137
+ function syncConfigStatusFromV3(
138
+ doc: ReplicationStreamDocumentV3,
139
+ state: SyncRuleConfigStateV3
140
+ ): storage.PersistedSyncConfigStatus {
141
+ return {
142
+ id: state._id.toHexString(),
143
+ replicationStreamId: doc._id,
144
+ state: state.state,
145
+ snapshot_done: state.snapshot_done,
146
+ last_checkpoint_lsn: state.last_checkpoint_lsn,
147
+ last_fatal_error: doc.last_fatal_error,
148
+ last_fatal_error_ts: doc.last_fatal_error_ts,
149
+ last_keepalive_ts: doc.last_keepalive_ts,
150
+ last_checkpoint_ts: doc.last_checkpoint_ts
151
+ };
152
+ }
@@ -39,8 +39,11 @@ export class MongoStorageProvider implements storage.StorageProvider {
39
39
 
40
40
  const database = new PowerSyncMongo(client, { database: resolvedConfig.storage.database });
41
41
  const syncStorageFactory = new MongoBucketStorage(database, {
42
- // TODO currently need the entire resolved config due to this
43
- slot_name_prefix: resolvedConfig.slot_name_prefix
42
+ replicationStreamNamePrefix: resolvedConfig.slot_name_prefix,
43
+ // Right now, only MongoDB source databases supports incremental reprocessing.
44
+ // Remove this filter when we support it for other source databases.
45
+ // This assumes a single source connection - revisit if we ever support multiple connections.
46
+ supportsMultipleSyncConfigs: resolvedConfig.connections?.[0]?.type == lib_mongo.MONGO_CONNECTION_TYPE
44
47
  });
45
48
 
46
49
  // Storage factory for reports
@@ -13,33 +13,33 @@ import {
13
13
  CheckpointChanges,
14
14
  GetCheckpointChangesOptions,
15
15
  InternalOpId,
16
- maxLsn,
17
16
  mergeAsyncIterables,
18
17
  PopulateChecksumCacheOptions,
19
18
  PopulateChecksumCacheResults,
20
19
  ReplicationCheckpoint,
20
+ ReplicationStreamStorageIds,
21
21
  storage,
22
22
  utils,
23
23
  WatchWriteCheckpointOptions
24
24
  } from '@powersync/service-core';
25
- import { HydratedSyncRules, ParameterLookupRows, ScopedParameterLookup } from '@powersync/service-sync-rules';
25
+ import { HydratedSyncConfig, ParameterLookupRows, ScopedParameterLookup } from '@powersync/service-sync-rules';
26
26
  import * as bson from 'bson';
27
27
  import { LRUCache } from 'lru-cache';
28
28
  import * as timers from 'timers/promises';
29
29
  import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
30
30
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
31
- import { MongoSyncBucketStorageContext } from './common/MongoSyncBucketStorageContext.js';
32
31
  import type { VersionedPowerSyncMongo } from './db.js';
33
- import { CommonSourceTableDocument, StorageConfig } from './models.js';
32
+ import { StorageConfig } from './models.js';
34
33
  import { MongoBucketBatchOptions } from './MongoBucketBatch.js';
35
34
  import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
36
35
  import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
37
36
  import { MongoParameterCompactor } from './MongoParameterCompactor.js';
38
- import { MongoPersistedSyncRulesContent } from './MongoPersistedSyncRulesContent.js';
37
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
38
+ import { MongoPersistedReplicationStream } from './MongoPersistedReplicationStream.js';
39
39
  import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
40
40
 
41
41
  export interface MongoSyncBucketStorageOptions {
42
- checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
42
+ checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
43
43
  storageConfig: StorageConfig;
44
44
  }
45
45
 
@@ -64,32 +64,42 @@ export abstract class MongoSyncBucketStorage
64
64
  implements storage.SyncRulesBucketStorage
65
65
  {
66
66
  readonly db: VersionedPowerSyncMongo;
67
+
67
68
  [DO_NOT_LOG] = true;
68
69
 
69
70
  readonly checksums: MongoChecksums;
70
71
 
71
- private parsedSyncRulesCache: { parsed: HydratedSyncRules; options: storage.ParseSyncRulesOptions } | undefined;
72
+ /**
73
+ * Canonical parsed sync config sets, keyed by defaultSchema.
74
+ *
75
+ * Entries are never evicted: each parse options value maps to exactly one parsed set for
76
+ * the lifetime of this storage instance, so parsed source objects and mappings always
77
+ * stay associated.
78
+ */
79
+ private readonly parsedSyncConfigSets = new Map<string, MongoParsedSyncConfigSet>();
72
80
  private writeCheckpointAPI: MongoWriteCheckpointAPI;
73
81
  public readonly logger: Logger;
82
+ public readonly storageConfig: StorageConfig;
74
83
  #storageInitialized = false;
75
84
 
76
85
  constructor(
77
86
  public readonly factory: MongoBucketStorage,
78
- public readonly group_id: number,
79
- protected readonly sync_rules: MongoPersistedSyncRulesContent,
80
- public readonly slot_name: string,
87
+ public readonly replicationStreamId: number,
88
+ public readonly replicationStream: MongoPersistedReplicationStream,
89
+ public readonly replicationStreamName: string,
81
90
  writeCheckpointMode: storage.WriteCheckpointMode | undefined,
82
91
  options: MongoSyncBucketStorageOptions
83
92
  ) {
84
93
  super();
85
- this.db = factory.db.versioned(sync_rules.getStorageConfig());
94
+ this.storageConfig = options.storageConfig;
95
+ this.db = factory.db.versioned(this.storageConfig);
86
96
  this.checksums = this.createMongoChecksums(options);
87
97
  this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
88
98
  db: this.db,
89
99
  mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
90
- sync_rules_id: group_id
100
+ sync_rules_id: replicationStreamId
91
101
  });
92
- this.logger = sync_rules.logger;
102
+ this.logger = replicationStream.logger;
93
103
  }
94
104
 
95
105
  /**
@@ -109,16 +119,11 @@ export abstract class MongoSyncBucketStorage
109
119
  return this.writeCheckpointAPI.writeCheckpointMode;
110
120
  }
111
121
 
112
- get mapping() {
113
- return this.sync_rules.mapping;
114
- }
115
-
116
- protected get versionContext(): MongoSyncBucketStorageContext {
117
- return {
118
- db: this.db,
119
- group_id: this.group_id,
120
- mapping: this.mapping
121
- };
122
+ /**
123
+ * Persisted storage ids of all sync configs in this replication stream. Parse-free.
124
+ */
125
+ get storageIds(): ReplicationStreamStorageIds {
126
+ return this.replicationStream.storageIds;
122
127
  }
123
128
 
124
129
  setWriteCheckpointMode(mode: storage.WriteCheckpointMode): void {
@@ -132,33 +137,35 @@ export abstract class MongoSyncBucketStorage
132
137
  lastWriteCheckpoint(filters: storage.SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
133
138
  return this.writeCheckpointAPI.lastWriteCheckpoint({
134
139
  ...filters,
135
- sync_rules_id: this.group_id
140
+ sync_rules_id: this.replicationStreamId
136
141
  });
137
142
  }
138
143
 
139
- getParsedSyncRules(options: storage.ParseSyncRulesOptions): HydratedSyncRules {
140
- const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
141
- if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
142
- this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncRules(), options };
144
+ getParsedSyncConfigSet(options: storage.ParseSyncConfigOptions): MongoParsedSyncConfigSet {
145
+ let parsed = this.parsedSyncConfigSets.get(options.defaultSchema);
146
+ if (parsed == null) {
147
+ parsed = this.replicationStream.parsed(options);
148
+ this.parsedSyncConfigSets.set(options.defaultSchema, parsed);
143
149
  }
150
+ return parsed;
151
+ }
144
152
 
145
- return this.parsedSyncRulesCache!.parsed;
153
+ getParsedSyncRules(options: storage.ParseSyncConfigOptions): HydratedSyncConfig {
154
+ return this.getParsedSyncConfigSet(options).hydratedSyncConfig;
146
155
  }
147
156
 
148
157
  async getCheckpoint(): Promise<storage.ReplicationCheckpoint> {
149
158
  return (await this.getCheckpointInternal()) ?? new EmptyReplicationCheckpoint();
150
159
  }
151
160
 
161
+ protected abstract fetchCheckpointState(
162
+ session: mongo.ClientSession
163
+ ): Promise<{ checkpoint: bigint; lsn: string | null } | null>;
164
+
152
165
  async getCheckpointInternal(): Promise<storage.ReplicationCheckpoint | null> {
153
166
  return await this.db.client.withSession({ snapshot: true }, async (session) => {
154
- const doc = await this.db.sync_rules.findOne(
155
- { _id: this.group_id },
156
- {
157
- session,
158
- projection: { _id: 1, state: 1, last_checkpoint: 1, last_checkpoint_lsn: 1, snapshot_done: 1 }
159
- }
160
- );
161
- if (!doc?.snapshot_done || !['ACTIVE', 'ERRORED'].includes(doc.state)) {
167
+ const state = await this.fetchCheckpointState(session);
168
+ if (state == null) {
162
169
  return null;
163
170
  }
164
171
 
@@ -166,12 +173,7 @@ export abstract class MongoSyncBucketStorage
166
173
  if (snapshotTime == null) {
167
174
  throw new ServiceAssertionError('Missing snapshotTime in getCheckpoint()');
168
175
  }
169
- return new MongoReplicationCheckpoint(
170
- this,
171
- doc.last_checkpoint ?? 0n,
172
- doc.last_checkpoint_lsn ?? null,
173
- snapshotTime
174
- );
176
+ return new MongoReplicationCheckpoint(this, state.checkpoint, state.lsn, snapshotTime);
175
177
  });
176
178
  }
177
179
 
@@ -182,40 +184,40 @@ export abstract class MongoSyncBucketStorage
182
184
  return;
183
185
  }
184
186
 
185
- await this.db.initializeStreamStorage(this.group_id);
187
+ await this.db.initializeStreamStorage(this.replicationStreamId);
186
188
  await this.initializeVersionStorage();
187
189
  this.#storageInitialized = true;
188
190
  }
189
191
 
190
- protected abstract createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch;
191
-
192
- async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
193
- await this.initializeStorage();
194
-
195
- const doc = await this.db.sync_rules.findOne(
196
- {
197
- _id: this.group_id
198
- },
199
- { projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1, keepalive_op: 1, snapshot_lsn: 1 } }
200
- );
201
- const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
192
+ /**
193
+ * Create the version-specific writer. Implementations fetch their own resume state
194
+ * (e.g. resume LSN, v1 keepalive op) and construct the batch from
195
+ * {@link writerBatchOptions} plus the version-specific fields.
196
+ */
197
+ protected abstract createWriterImpl(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch>;
202
198
 
203
- const batchOptions: MongoBucketBatchOptions = {
199
+ /**
200
+ * The version-independent part of the batch options.
201
+ */
202
+ protected writerBatchOptions(options: storage.CreateWriterOptions): Omit<MongoBucketBatchOptions, 'resumeFromLsn'> {
203
+ return {
204
204
  logger: options.logger ?? this.logger,
205
205
  db: this.db,
206
- syncRules: this.sync_rules.parsed(options).hydratedSyncRules(),
207
- mapping: this.sync_rules.mapping,
208
- groupId: this.group_id,
209
- slotName: this.slot_name,
210
- lastCheckpointLsn: checkpoint_lsn,
211
- resumeFromLsn: maxLsn(checkpoint_lsn, doc?.snapshot_lsn),
212
- keepaliveOp: doc?.keepalive_op ? BigInt(doc.keepalive_op) : null,
206
+ parsedSyncConfig: this.getParsedSyncConfigSet(options),
207
+ replicationStreamId: this.replicationStreamId,
208
+ replicationStreamName: this.replicationStreamName,
213
209
  storeCurrentData: options.storeCurrentData,
214
210
  skipExistingRows: options.skipExistingRows ?? false,
215
211
  markRecordUnavailable: options.markRecordUnavailable,
212
+ hooks: options.hooks,
216
213
  tracer: options.tracer
217
214
  };
218
- const writer = this.createWriterImpl(batchOptions);
215
+ }
216
+
217
+ async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
218
+ await this.initializeStorage();
219
+
220
+ const writer = await this.createWriterImpl(options);
219
221
  this.iterateListeners((cb) => cb.batchStarted?.(writer));
220
222
  return writer;
221
223
  }
@@ -230,134 +232,6 @@ export abstract class MongoSyncBucketStorage
230
232
  return writer.last_flushed_op != null ? { flushed_op: writer.last_flushed_op } : null;
231
233
  }
232
234
 
233
- protected abstract sourceTableBaseId(): Partial<CommonSourceTableDocument>;
234
-
235
- protected abstract augmentCreatedSourceTableDocument(
236
- createDoc: CommonSourceTableDocument,
237
- options: storage.ResolveTableOptions,
238
- candidateSourceTable: storage.SourceTable
239
- ): void;
240
-
241
- protected abstract initializeResolvedSourceRecords(sourceTableId: bson.ObjectId): Promise<void>;
242
-
243
- async resolveTable(options: storage.ResolveTableOptions): Promise<storage.ResolveTableResult> {
244
- const { group_id, connection_id, connection_tag, entity_descriptor } = options;
245
-
246
- const { schema, name, objectId, replicaIdColumns } = entity_descriptor;
247
-
248
- const normalizedReplicaIdColumns = replicaIdColumns.map((column) => ({
249
- name: column.name,
250
- type: column.type,
251
- type_oid: column.typeId
252
- }));
253
- let result: storage.ResolveTableResult | null = null;
254
- let initializeSourceRecordsFor: bson.ObjectId | null = null;
255
-
256
- const baseId = this.sourceTableBaseId();
257
- await this.db.client.withSession(async (session) => {
258
- const col = this.db.commonSourceTables(group_id);
259
- let filter: Partial<CommonSourceTableDocument> = {
260
- ...baseId,
261
- connection_id: connection_id,
262
- schema_name: schema,
263
- table_name: name,
264
- replica_id_columns2: normalizedReplicaIdColumns
265
- };
266
-
267
- if (objectId != null) {
268
- filter.relation_id = objectId;
269
- }
270
- let doc = await col.findOne(filter, { session });
271
- if (doc == null) {
272
- const candidateSourceTable = new storage.SourceTable({
273
- id: new bson.ObjectId(),
274
- connectionTag: connection_tag,
275
- objectId: objectId,
276
- schema: schema,
277
- name: name,
278
- replicaIdColumns: replicaIdColumns,
279
- snapshotComplete: false
280
- });
281
- const createDoc: CommonSourceTableDocument = {
282
- _id: candidateSourceTable.id as bson.ObjectId,
283
- ...(baseId as any),
284
- connection_id: connection_id,
285
- relation_id: objectId,
286
- schema_name: schema,
287
- table_name: name,
288
- replica_id_columns: null,
289
- replica_id_columns2: normalizedReplicaIdColumns,
290
- snapshot_done: false,
291
- snapshot_status: undefined
292
- };
293
- this.augmentCreatedSourceTableDocument(createDoc, options, candidateSourceTable);
294
- doc = createDoc;
295
-
296
- await col.insertOne(doc, { session });
297
- initializeSourceRecordsFor = doc._id;
298
- }
299
- const sourceTable = new storage.SourceTable({
300
- id: doc._id,
301
- connectionTag: connection_tag,
302
- objectId: objectId,
303
- schema: schema,
304
- name: name,
305
- replicaIdColumns: replicaIdColumns,
306
- snapshotComplete: doc.snapshot_done ?? true
307
- });
308
- sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
309
- sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
310
- sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
311
- sourceTable.snapshotStatus =
312
- doc.snapshot_status == null
313
- ? undefined
314
- : {
315
- lastKey: doc.snapshot_status.last_key?.buffer ?? null,
316
- totalEstimatedCount: doc.snapshot_status.total_estimated_count,
317
- replicatedCount: doc.snapshot_status.replicated_count
318
- };
319
-
320
- let dropTables: storage.SourceTable[] = [];
321
- let truncateFilter = [{ schema_name: schema, table_name: name }] as any[];
322
- if (objectId != null) {
323
- truncateFilter.push({ relation_id: objectId });
324
- }
325
- const truncate = await col
326
- .find(
327
- {
328
- ...baseId,
329
- connection_id: connection_id,
330
- _id: { $ne: doc._id },
331
- $or: truncateFilter
332
- },
333
- { session }
334
- )
335
- .toArray();
336
- dropTables = truncate.map(
337
- (doc) =>
338
- new storage.SourceTable({
339
- id: doc._id,
340
- connectionTag: connection_tag,
341
- objectId: doc.relation_id,
342
- schema: doc.schema_name,
343
- name: doc.table_name,
344
- replicaIdColumns:
345
- doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid, type: c.type })) ?? [],
346
- snapshotComplete: doc.snapshot_done ?? true
347
- })
348
- );
349
-
350
- result = {
351
- table: sourceTable,
352
- dropTables: dropTables
353
- };
354
- });
355
- if (initializeSourceRecordsFor != null) {
356
- await this.initializeResolvedSourceRecords(initializeSourceRecordsFor);
357
- }
358
- return result!;
359
- }
360
-
361
235
  protected abstract getParameterSetsImpl(
362
236
  checkpoint: MongoReplicationCheckpoint,
363
237
  lookups: ScopedParameterLookup[],
@@ -397,49 +271,20 @@ export abstract class MongoSyncBucketStorage
397
271
  this.checksums.clearCache();
398
272
  }
399
273
 
274
+ protected abstract terminateSyncRuleState(): Promise<void>;
275
+
400
276
  async terminate(options?: storage.TerminateOptions) {
401
277
  if (!options || options?.clearStorage) {
402
278
  await this.clear(options);
403
279
  }
404
- await this.db.sync_rules.updateOne(
405
- {
406
- _id: this.group_id
407
- },
408
- {
409
- $set: {
410
- state: storage.SyncRuleState.TERMINATED,
411
- persisted_lsn: null,
412
- snapshot_done: false
413
- }
414
- }
415
- );
280
+ await this.terminateSyncRuleState();
416
281
  await this.db.notifyCheckpoint();
417
282
  }
418
283
 
419
- async getStatus(): Promise<storage.SyncRuleStatus> {
420
- const doc = await this.db.sync_rules.findOne(
421
- {
422
- _id: this.group_id
423
- },
424
- {
425
- projection: {
426
- snapshot_done: 1,
427
- last_checkpoint_lsn: 1,
428
- state: 1,
429
- snapshot_lsn: 1
430
- }
431
- }
432
- );
433
- if (doc == null) {
434
- throw new ServiceAssertionError('Cannot find replication stream status');
435
- }
284
+ protected abstract getStatusImpl(): Promise<storage.ReplicationStreamStatus>;
436
285
 
437
- return {
438
- snapshot_done: doc.snapshot_done,
439
- snapshot_lsn: doc.snapshot_lsn ?? null,
440
- active: doc.state == 'ACTIVE',
441
- checkpoint_lsn: doc.last_checkpoint_lsn
442
- };
286
+ async getStatus(): Promise<storage.ReplicationStreamStatus> {
287
+ return this.getStatusImpl();
443
288
  }
444
289
 
445
290
  protected abstract clearBucketData(signal?: AbortSignal): Promise<void>;
@@ -451,6 +296,7 @@ export abstract class MongoSyncBucketStorage
451
296
  protected abstract clearBucketState(signal?: AbortSignal): Promise<void>;
452
297
 
453
298
  protected abstract clearSourceTables(signal?: AbortSignal): Promise<void>;
299
+ protected abstract clearSyncRuleState(): Promise<void>;
454
300
 
455
301
  async clear(options?: storage.ClearStorageOptions): Promise<void> {
456
302
  const signal = options?.signal;
@@ -459,24 +305,7 @@ export abstract class MongoSyncBucketStorage
459
305
  throw new ReplicationAbortedError('Aborted clearing data', signal.reason);
460
306
  }
461
307
 
462
- await this.db.sync_rules.updateOne(
463
- {
464
- _id: this.group_id
465
- },
466
- {
467
- $set: {
468
- snapshot_done: false,
469
- persisted_lsn: null,
470
- last_checkpoint_lsn: null,
471
- last_checkpoint: null,
472
- no_checkpoint_before: null
473
- },
474
- $unset: {
475
- snapshot_lsn: 1
476
- }
477
- },
478
- { maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
479
- );
308
+ await this.clearSyncRuleState();
480
309
 
481
310
  await this.clearBucketData(signal);
482
311
  await this.clearParameterIndexes(signal);
@@ -508,7 +337,7 @@ export abstract class MongoSyncBucketStorage
508
337
  const message = String(e.message ?? 'Replication failure');
509
338
  await this.db.sync_rules.updateOne(
510
339
  {
511
- _id: this.group_id
340
+ _id: this.replicationStreamId
512
341
  },
513
342
  {
514
343
  $set: {
@@ -532,11 +361,21 @@ export abstract class MongoSyncBucketStorage
532
361
  }
533
362
  }
534
363
 
364
+ /**
365
+ * The highest op id persisted for this stream, whether or not covered by a checkpoint.
366
+ *
367
+ * Used as the default `maxOpId` for {@link populatePersistentChecksumCache}, which runs after
368
+ * initial replication but before the first checkpoint exists.
369
+ */
370
+ protected abstract fetchPersistedOpHead(): Promise<InternalOpId | null>;
371
+
535
372
  async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
536
373
  this.logger.info(`Populating persistent checksum cache...`);
537
374
  const start = Date.now();
375
+ const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
538
376
  const compactor = this.createMongoCompactor({
539
377
  ...options,
378
+ maxOpId,
540
379
  memoryLimitMB: 0,
541
380
  logger: this.logger
542
381
  });
@@ -588,7 +427,7 @@ export abstract class MongoSyncBucketStorage
588
427
  for await (const nextCheckpoint of iter) {
589
428
  if (nextCheckpoint.lsn != null && !queriedInitialWriteCheckpoint) {
590
429
  writeCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
591
- sync_rules_id: this.group_id,
430
+ sync_rules_id: this.replicationStreamId,
592
431
  user_id: options.user_id,
593
432
  heads: {
594
433
  '1': nextCheckpoint.lsn
@@ -621,7 +460,7 @@ export abstract class MongoSyncBucketStorage
621
460
  let updatedWriteCheckpoint = updates.updatedWriteCheckpoints.get(options.user_id) ?? null;
622
461
  if (updates.invalidateWriteCheckpoints) {
623
462
  updatedWriteCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
624
- sync_rules_id: this.group_id,
463
+ sync_rules_id: this.replicationStreamId,
625
464
  user_id: options.user_id,
626
465
  heads: {
627
466
  '1': nextCheckpoint.lsn!
@@ -792,7 +631,7 @@ class EmptyReplicationCheckpoint implements ReplicationCheckpoint {
792
631
  readonly checkpoint: InternalOpId = 0n;
793
632
  readonly lsn: string | null = null;
794
633
 
795
- async getParameterSets(_lookups: ScopedParameterLookup[]): Promise<ParameterLookupRows[]> {
634
+ async getParameterSets(_lookups: ScopedParameterLookup[], _limit: number): Promise<ParameterLookupRows[]> {
796
635
  return [];
797
636
  }
798
637
  }