@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # @powersync/service-module-mongodb-storage
2
2
 
3
+ ## 0.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a91a08f: [Experimental] Enable incremental reprocessing for MongoDB source + MongoDB storage. This includes significant changes to the v3 storage format.
8
+
9
+ ### Patch Changes
10
+
11
+ - 101ea67: Include table names in "Cannot mark snapshot done while source tables still require snapshotting" error.
12
+ - Updated dependencies [a91a08f]
13
+ - Updated dependencies [184c39f]
14
+ - Updated dependencies [c3f75df]
15
+ - Updated dependencies [4bd35ea]
16
+ - @powersync/service-core@1.23.0
17
+ - @powersync/service-types@0.16.0
18
+ - @powersync/service-sync-rules@0.38.0
19
+ - @powersync/lib-services-framework@0.9.6
20
+ - @powersync/lib-service-mongodb@0.6.27
21
+
22
+ ## 0.17.0
23
+
24
+ ### Minor Changes
25
+
26
+ - e2bf1ad: [Internal] rework resolveTables to handle multiple SourceTables.
27
+ - cd5a222: [MongoDB V3 Storage] Split storage for "sync config definition" versus "replication stream status".
28
+ - 15e2466: [MongoDB] Support snapshotting concurrently with streaming in storage v3+.
29
+ - ebeaa3b: [Postgres] Skip storing a redundant copy of replicated rows in `current_data` for tables with `REPLICA IDENTITY FULL`.
30
+
31
+ ### Patch Changes
32
+
33
+ - 6e2a57e: Refactor HydratedSyncConfig to support multiple SyncConfigs.
34
+ - 92cc83b: Add the experimental `unstable_sqlite_expression_engine` sync config option to evaluate Sync Streams with SQLite.
35
+ - Updated dependencies [17fd96b]
36
+ - Updated dependencies [6e2a57e]
37
+ - Updated dependencies [ec6df9f]
38
+ - Updated dependencies [99d33d5]
39
+ - Updated dependencies [cae92ce]
40
+ - Updated dependencies [5ac5345]
41
+ - Updated dependencies [15cb880]
42
+ - Updated dependencies [f2f5086]
43
+ - Updated dependencies [5b1b215]
44
+ - Updated dependencies [e2bf1ad]
45
+ - Updated dependencies [92cc83b]
46
+ - Updated dependencies [0aab0f9]
47
+ - Updated dependencies [15e2466]
48
+ - Updated dependencies [ebeaa3b]
49
+ - Updated dependencies [b116857]
50
+ - Updated dependencies [a94b6c3]
51
+ - @powersync/service-core@1.22.0
52
+ - @powersync/service-sync-rules@0.37.0
53
+ - @powersync/lib-services-framework@0.9.5
54
+ - @powersync/lib-service-mongodb@0.6.26
55
+
3
56
  ## 0.16.0
4
57
 
5
58
  ### Minor Changes
@@ -3,31 +3,51 @@ import { DO_NOT_LOG } from '@powersync/lib-services-framework';
3
3
  import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
4
4
  import { PowerSyncMongo } from './implementation/db.js';
5
5
  import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
6
- import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
6
+ import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
7
7
  export interface MongoBucketStorageOptions {
8
- checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
8
+ /**
9
+ * Prefix for replication stream name and Postgres logical replication slot name.
10
+ */
11
+ replicationStreamNamePrefix: string;
12
+ checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
13
+ /**
14
+ * Reuse a compatible active replication stream by appending a new sync config.
15
+ *
16
+ * This currently requires source replication support. MongoDB sources can process multiple
17
+ * sync configs in one replication stream, but other source connectors still expect a single
18
+ * sync config per stream.
19
+ */
20
+ supportsMultipleSyncConfigs?: boolean;
9
21
  }
10
22
  export declare class MongoBucketStorage extends storage.BucketStorageFactory {
11
- private internalOptions?;
23
+ private options;
12
24
  [DO_NOT_LOG]: boolean;
13
25
  private readonly client;
14
26
  private readonly session;
15
- readonly slot_name_prefix: string;
27
+ readonly replicationStreamNamePrefix: string;
16
28
  private activeStorageCache;
17
29
  readonly db: PowerSyncMongo;
18
- constructor(db: PowerSyncMongo, options: {
19
- slot_name_prefix: string;
20
- }, internalOptions?: MongoBucketStorageOptions | undefined);
30
+ constructor(db: PowerSyncMongo, options: MongoBucketStorageOptions);
21
31
  [Symbol.asyncDispose](): Promise<void>;
22
- getInstance(syncRules: storage.PersistedSyncRulesContent, options?: GetIntanceOptions): MongoSyncBucketStorage;
32
+ getInstance(replicationStream: storage.PersistedReplicationStream, options?: GetIntanceOptions): MongoSyncBucketStorage;
23
33
  getSystemIdentifier(): Promise<storage.BucketStorageSystemIdentifier>;
24
- restartReplication(sync_rules_group_id: number): Promise<void>;
25
- updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedSyncRulesContent>;
26
- getActiveSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null>;
27
- getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null>;
28
- getReplicatingSyncRules(): Promise<storage.PersistedSyncRulesContent[]>;
29
- getStoppedSyncRules(): Promise<storage.PersistedSyncRulesContent[]>;
30
- getActiveStorage(): Promise<MongoSyncBucketStorage | null>;
34
+ restartReplication(replicationStreamId: number): Promise<void>;
35
+ private updateSyncRulesV3;
36
+ private loadSyncConfigDefinitions;
37
+ /**
38
+ * Load _all_ definition mappings for a replication stream - used as a base to generate new ids.
39
+ */
40
+ private loadHistoricalSyncConfigRuleMappings;
41
+ private stopEmbeddedDeployingConfigs;
42
+ private logIncrementalDefinitionChanges;
43
+ private appendSyncConfigToStream;
44
+ updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedReplicationStream>;
45
+ getActiveSyncConfig(): Promise<storage.ResolvedSyncConfig | null>;
46
+ private replicationStreamFromDoc;
47
+ getDeployingSyncConfig(): Promise<storage.ResolvedSyncConfig | null>;
48
+ getReplicatingReplicationStreams(): Promise<storage.PersistedReplicationStream[]>;
49
+ getStoppedReplicationStreams(): Promise<storage.PersistedReplicationStream[]>;
50
+ private resolvedSyncConfigFromDoc;
31
51
  getStorageMetrics(): Promise<storage.StorageMetrics>;
32
52
  getPowerSyncInstanceId(): Promise<string>;
33
53
  }
@@ -1,53 +1,57 @@
1
- import { storage } from '@powersync/service-core';
2
- import { DO_NOT_LOG, ErrorCode, ServiceError } from '@powersync/lib-services-framework';
1
+ import { LEGACY_STORAGE_VERSION, SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
2
+ import { DO_NOT_LOG, ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
3
3
  import { v4 as uuid } from 'uuid';
4
4
  import * as lib_mongo from '@powersync/lib-service-mongodb';
5
- import { generateSlotName } from '../utils/util.js';
6
- import { BucketDefinitionMapping } from './implementation/BucketDefinitionMapping.js';
5
+ import { describeIncrementalSyncConfigUpdate, formatIncrementalSyncConfigUpdateLog, isCompatible } from '@powersync/service-core';
6
+ import { ObjectId } from 'bson';
7
+ import { generateReplicationStreamName } from '../utils/util.js';
7
8
  import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
8
9
  import { getMongoStorageConfig } from './implementation/models.js';
9
- import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
10
+ import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
11
+ import { syncRuleStateUpdatePipeline } from './implementation/SyncRuleStateUpdate.js';
10
12
  export class MongoBucketStorage extends storage.BucketStorageFactory {
11
- internalOptions;
13
+ options;
12
14
  [DO_NOT_LOG] = true;
13
15
  client;
14
16
  session;
15
- // TODO: This is still Postgres specific and needs to be reworked
16
- slot_name_prefix;
17
+ replicationStreamNamePrefix;
17
18
  activeStorageCache;
18
19
  db;
19
- constructor(db, options, internalOptions) {
20
+ constructor(db, options) {
20
21
  super();
21
- this.internalOptions = internalOptions;
22
+ this.options = options;
22
23
  this.client = db.client;
23
24
  this.db = db;
24
25
  this.session = this.client.startSession();
25
- this.slot_name_prefix = options.slot_name_prefix;
26
+ this.replicationStreamNamePrefix = options.replicationStreamNamePrefix;
26
27
  }
27
28
  async [Symbol.asyncDispose]() {
28
29
  // No-op
29
30
  }
30
- getInstance(syncRules, options) {
31
- let { id, slot_name } = syncRules;
32
- if (typeof id == 'bigint') {
33
- id = Number(id);
31
+ getInstance(replicationStream, options) {
32
+ if (!(replicationStream instanceof MongoPersistedReplicationStream)) {
33
+ throw new Error(`Expected MongoPersistedReplicationStream`);
34
34
  }
35
- const storageConfig = syncRules.getStorageConfig();
36
- const storage = createMongoSyncBucketStorage(this, id, syncRules, slot_name, undefined, {
37
- ...this.internalOptions,
35
+ let { replicationStreamId, replicationStreamName } = replicationStream;
36
+ if (typeof replicationStreamId == 'bigint') {
37
+ replicationStreamId = Number(replicationStreamId);
38
+ }
39
+ const storageConfig = replicationStream.getStorageConfig();
40
+ const syncRuleStorage = createMongoSyncBucketStorage(this, replicationStreamId, replicationStream, replicationStreamName, undefined, {
41
+ checksumOptions: this.options.checksumOptions,
38
42
  storageConfig
39
43
  });
40
44
  if (!options?.skipLifecycleHooks) {
41
- this.iterateListeners((cb) => cb.syncStorageCreated?.(storage));
45
+ this.iterateListeners((cb) => cb.syncStorageCreated?.(syncRuleStorage));
42
46
  }
43
- storage.registerListener({
47
+ syncRuleStorage.registerListener({
44
48
  batchStarted: (batch) => {
45
49
  batch.registerListener({
46
50
  replicationEvent: (payload) => this.iterateListeners((cb) => cb.replicationEvent?.(payload))
47
51
  });
48
52
  }
49
53
  });
50
- return storage;
54
+ return syncRuleStorage;
51
55
  }
52
56
  async getSystemIdentifier() {
53
57
  const { setName: id } = await this.db.db.command({
@@ -61,61 +65,250 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
61
65
  type: lib_mongo.MONGO_CONNECTION_TYPE
62
66
  };
63
67
  }
64
- async restartReplication(sync_rules_group_id) {
65
- const next = await this.getNextSyncRulesContent();
66
- const active = await this.getActiveSyncRulesContent();
67
- if (next != null && next.id == sync_rules_group_id) {
68
+ async restartReplication(replicationStreamId) {
69
+ const next = await this.getDeployingSyncConfig();
70
+ const active = await this.getActiveSyncConfig();
71
+ if (next != null && next.content.replicationStreamId == replicationStreamId) {
68
72
  // We need to redo the "next" replication stream
69
- await this.updateSyncRules(next.asUpdateOptions());
73
+ await this.updateSyncRules(next.content.asUpdateOptions());
70
74
  // Pro-actively stop replicating
71
75
  await this.db.sync_rules.updateOne({
72
- _id: next.id,
76
+ _id: next.content.replicationStreamId,
73
77
  state: storage.SyncRuleState.PROCESSING
74
- }, {
75
- $set: {
76
- state: storage.SyncRuleState.STOP
77
- }
78
- });
78
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP));
79
79
  await this.db.notifyCheckpoint();
80
80
  }
81
- else if (next == null && active?.id == sync_rules_group_id) {
81
+ else if (next == null && active?.content.replicationStreamId == replicationStreamId) {
82
82
  // Slot removed for "active" replication stream, while there is no "next" one.
83
- await this.updateSyncRules(active.asUpdateOptions());
83
+ await this.updateSyncRules(active.content.asUpdateOptions());
84
84
  // In this case we keep the old one as active for clients, so that that existing clients
85
85
  // can still get the latest data while we replicate the new ones.
86
86
  // It will however not replicate anymore.
87
87
  await this.db.sync_rules.updateOne({
88
- _id: active.id,
88
+ _id: active.content.replicationStreamId,
89
89
  state: storage.SyncRuleState.ACTIVE
90
- }, {
91
- $set: {
92
- state: storage.SyncRuleState.ERRORED
93
- }
94
- });
90
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED));
95
91
  await this.db.notifyCheckpoint();
96
92
  }
97
- else if (next != null && active?.id == sync_rules_group_id) {
93
+ else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
98
94
  // Already have next replication stream, but need to stop replicating the active one.
99
95
  await this.db.sync_rules.updateOne({
100
- _id: active.id,
96
+ _id: active.content.replicationStreamId,
101
97
  state: storage.SyncRuleState.ACTIVE
98
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED));
99
+ await this.db.notifyCheckpoint();
100
+ }
101
+ }
102
+ async updateSyncRulesV3(options, storageVersion, storageConfig) {
103
+ let rules = undefined;
104
+ const versioned = this.db.versioned(storageConfig);
105
+ const session = this.session;
106
+ await session.withTransaction(async () => {
107
+ const active = await this.db.sync_rules.findOne({
108
+ state: storage.SyncRuleState.ACTIVE,
109
+ storage_version: storageVersion
110
+ }, { session, sort: { _id: -1 }, limit: 1 });
111
+ if (active != null) {
112
+ const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
113
+ if (this.options.supportsMultipleSyncConfigs &&
114
+ isCompatible(existingConfigDocs.map((d) => d.serialized_plan ?? null), options.config, logger)) {
115
+ logger.info(`Using incremental reprocessing`);
116
+ await this.db.sync_rules.updateMany({
117
+ state: storage.SyncRuleState.PROCESSING
118
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP), { session });
119
+ await this.stopEmbeddedDeployingConfigs(active, session);
120
+ rules = await this.appendSyncConfigToStream({
121
+ versioned,
122
+ existing: active,
123
+ existingConfigDocs,
124
+ options,
125
+ storageVersion,
126
+ session
127
+ });
128
+ return;
129
+ }
130
+ await this.stopEmbeddedDeployingConfigs(active, session);
131
+ }
132
+ // Only have a single replication stream with PROCESSING.
133
+ await this.db.sync_rules.updateMany({
134
+ state: storage.SyncRuleState.PROCESSING
135
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP), { session });
136
+ const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
137
+ _id: 'sync_rules'
102
138
  }, {
103
- $set: {
104
- state: storage.SyncRuleState.ERRORED
139
+ $inc: {
140
+ op_id: 1n
105
141
  }
142
+ }, {
143
+ upsert: true,
144
+ returnDocument: 'after',
145
+ session
106
146
  });
107
- await this.db.notifyCheckpoint();
147
+ const id = Number(id_doc.op_id);
148
+ const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
149
+ const mapping = options.config.plan == null
150
+ ? // For legacy sync rules and streams, use the parsed config directly to create a mapping
151
+ SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
152
+ : // For new sync streams, always use the serialized version
153
+ SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans([], options.config.plan.plan, []);
154
+ const syncConfigDoc = {
155
+ _id: new ObjectId(),
156
+ replication_stream_id: id,
157
+ created_at: new Date(),
158
+ storage_version: storageVersion,
159
+ content: options.config.yaml,
160
+ serialized_plan: options.config.plan,
161
+ rule_mapping: mapping.serialize()
162
+ };
163
+ await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
164
+ const doc = {
165
+ _id: id,
166
+ storage_version: storageVersion,
167
+ sync_configs: [
168
+ {
169
+ _id: syncConfigDoc._id,
170
+ state: storage.SyncRuleState.PROCESSING,
171
+ last_checkpoint: null,
172
+ last_checkpoint_lsn: null,
173
+ no_checkpoint_before: null,
174
+ snapshot_done: false
175
+ }
176
+ ],
177
+ snapshot_lsn: undefined,
178
+ state: storage.SyncRuleState.PROCESSING,
179
+ slot_name: replicationStreamName,
180
+ last_checkpoint_ts: null,
181
+ last_fatal_error: null,
182
+ last_fatal_error_ts: null,
183
+ last_keepalive_ts: null
184
+ };
185
+ await this.db.sync_rules.insertOne(doc, { session });
186
+ rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
187
+ if (options.lock) {
188
+ // The lock is persisted on rules.current_lock
189
+ await rules.lock(session);
190
+ }
191
+ });
192
+ // Notify only after the transaction has committed, so listeners cannot observe pre-commit state.
193
+ await this.db.notifyCheckpoint();
194
+ return rules;
195
+ }
196
+ async loadSyncConfigDefinitions(versioned, existing, session) {
197
+ const activeConfigIds = existing.sync_configs
198
+ .filter((config) => config.state == storage.SyncRuleState.ACTIVE)
199
+ .map((config) => config._id);
200
+ return versioned.syncConfigDefinitions
201
+ .find({
202
+ _id: { $in: activeConfigIds }
203
+ }, { session })
204
+ .toArray();
205
+ }
206
+ /**
207
+ * Load _all_ definition mappings for a replication stream - used as a base to generate new ids.
208
+ */
209
+ async loadHistoricalSyncConfigRuleMappings(versioned, replicationStreamId, session) {
210
+ return versioned.syncConfigDefinitions
211
+ .find({
212
+ replication_stream_id: replicationStreamId
213
+ }, {
214
+ session,
215
+ projection: {
216
+ rule_mapping: 1
217
+ }
218
+ })
219
+ .toArray();
220
+ }
221
+ async stopEmbeddedDeployingConfigs(existing, session) {
222
+ const deployingConfigs = existing.sync_configs
223
+ .filter((config) => config.state == storage.SyncRuleState.PROCESSING)
224
+ .map((config) => config._id);
225
+ if (deployingConfigs.length == 0) {
226
+ return;
227
+ }
228
+ await this.db.sync_rules.updateOne({
229
+ _id: existing._id,
230
+ 'sync_configs._id': { $in: deployingConfigs }
231
+ }, {
232
+ $set: {
233
+ 'sync_configs.$[config].state': storage.SyncRuleState.STOP
234
+ }
235
+ }, {
236
+ session,
237
+ arrayFilters: [{ 'config._id': { $in: deployingConfigs } }]
238
+ });
239
+ }
240
+ logIncrementalDefinitionChanges(changes) {
241
+ logger.info(`Incremental reprocessing sync config update:\n${formatIncrementalSyncConfigUpdateLog(changes)}`);
242
+ }
243
+ async appendSyncConfigToStream(options) {
244
+ const { versioned, existing, existingConfigDocs, options: updateOptions, storageVersion, session } = options;
245
+ const compatibleConfigs = existingConfigDocs.map((doc) => ({
246
+ plan: doc.serialized_plan.plan,
247
+ mapping: SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)
248
+ }));
249
+ const historicalRuleMappings = await this.loadHistoricalSyncConfigRuleMappings(versioned, existing._id, session);
250
+ const reservedMappings = historicalRuleMappings.map((doc) => SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping));
251
+ const mappingResult = SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingWithChanges(compatibleConfigs, updateOptions.config.plan.plan, reservedMappings);
252
+ const mapping = mappingResult.mapping;
253
+ this.logIncrementalDefinitionChanges(describeIncrementalSyncConfigUpdate({
254
+ activeMappings: existingConfigDocs.map((doc) => SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)),
255
+ newMapping: mapping,
256
+ newSyncConfig: updateOptions.config.parsed,
257
+ mappingChanges: mappingResult.changes
258
+ }));
259
+ const syncConfigDoc = {
260
+ _id: new ObjectId(),
261
+ replication_stream_id: existing._id,
262
+ created_at: new Date(),
263
+ storage_version: storageVersion,
264
+ content: updateOptions.config.yaml,
265
+ serialized_plan: updateOptions.config.plan,
266
+ rule_mapping: mapping.serialize()
267
+ };
268
+ await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
269
+ const syncConfigState = {
270
+ _id: syncConfigDoc._id,
271
+ state: storage.SyncRuleState.PROCESSING,
272
+ last_checkpoint: null,
273
+ last_checkpoint_lsn: null,
274
+ no_checkpoint_before: null,
275
+ snapshot_done: false
276
+ };
277
+ await this.db.sync_rules.updateOne({ _id: existing._id }, {
278
+ $push: {
279
+ sync_configs: syncConfigState
280
+ },
281
+ $set: {
282
+ last_fatal_error: null,
283
+ last_fatal_error_ts: null
284
+ }
285
+ }, { session });
286
+ const syncConfigStates = [
287
+ ...existing.sync_configs.filter((config) => config.state == storage.SyncRuleState.ACTIVE),
288
+ syncConfigState
289
+ ];
290
+ const stream = new MongoPersistedReplicationStream(this.db, {
291
+ ...existing,
292
+ sync_configs: syncConfigStates
293
+ }, [...existingConfigDocs, syncConfigDoc]);
294
+ if (updateOptions.lock) {
295
+ await stream.lock(session);
108
296
  }
297
+ return stream;
109
298
  }
110
299
  async updateSyncRules(options) {
111
300
  const storageVersion = options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
112
301
  const storageConfig = getMongoStorageConfig(storageVersion);
302
+ if (storageConfig.incrementalReprocessing) {
303
+ return this.updateSyncRulesV3(options, storageVersion, storageConfig);
304
+ }
113
305
  let rules = undefined;
114
- await this.session.withTransaction(async () => {
306
+ const session = this.session;
307
+ await session.withTransaction(async () => {
115
308
  // Only have a single replication stream with PROCESSING.
116
309
  await this.db.sync_rules.updateMany({
117
310
  state: storage.SyncRuleState.PROCESSING
118
- }, { $set: { state: storage.SyncRuleState.STOP } });
311
+ }, syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP), { session });
119
312
  const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
120
313
  _id: 'sync_rules'
121
314
  }, {
@@ -124,10 +317,11 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
124
317
  }
125
318
  }, {
126
319
  upsert: true,
127
- returnDocument: 'after'
320
+ returnDocument: 'after',
321
+ session
128
322
  });
129
323
  const id = Number(id_doc.op_id);
130
- const slot_name = generateSlotName(this.slot_name_prefix, id);
324
+ const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
131
325
  const doc = {
132
326
  _id: id,
133
327
  storage_version: storageVersion,
@@ -146,73 +340,102 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
146
340
  last_fatal_error_ts: null,
147
341
  last_keepalive_ts: null
148
342
  };
149
- if (storageConfig.incrementalReprocessing) {
150
- const parsed = options.config.parsed;
151
- doc.rule_mapping = BucketDefinitionMapping.fromParsedSyncRules(parsed).serialize();
152
- }
153
- await this.db.sync_rules.insertOne(doc);
343
+ await this.db.sync_rules.insertOne(doc, { session });
154
344
  await this.db.notifyCheckpoint();
155
- rules = new MongoPersistedSyncRulesContent(this.db, doc);
345
+ rules = new MongoPersistedReplicationStream(this.db, doc);
156
346
  if (options.lock) {
157
- const lock = await rules.lock();
347
+ // The lock is persisted on rules.current_lock
348
+ await rules.lock(session);
158
349
  }
159
350
  });
160
351
  return rules;
161
352
  }
162
- async getActiveSyncRulesContent() {
353
+ async getActiveSyncConfig() {
163
354
  const doc = await this.db.sync_rules.findOne({
164
355
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
165
356
  }, { sort: { _id: -1 }, limit: 1 });
357
+ return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED], {
358
+ cacheActiveStorage: true
359
+ });
360
+ }
361
+ async replicationStreamFromDoc(doc, stateFilter) {
166
362
  if (doc == null) {
167
363
  return null;
168
364
  }
169
- return new MongoPersistedSyncRulesContent(this.db, doc);
365
+ const storageConfig = getMongoStorageConfig(doc.storage_version ?? LEGACY_STORAGE_VERSION);
366
+ if (storageConfig.incrementalReprocessing) {
367
+ const v3 = doc;
368
+ const matching = v3.sync_configs.filter((c) => stateFilter.includes(c.state));
369
+ if (matching.length == 0) {
370
+ return null;
371
+ }
372
+ // TODO: cache the config. It could specifically help for the main replication loop
373
+ // that checks for active replication streams.
374
+ // It is not a major bottleneck though, since it only runs once every couple of seconds at most.
375
+ const db = this.db.versioned(storageConfig);
376
+ const syncConfigDocs = await db.syncConfigDefinitions
377
+ .find({
378
+ _id: { $in: matching.map((config) => config._id) }
379
+ })
380
+ .toArray();
381
+ if (syncConfigDocs.length == 0) {
382
+ return null;
383
+ }
384
+ return new MongoPersistedReplicationStream(this.db, v3, syncConfigDocs);
385
+ }
386
+ return new MongoPersistedReplicationStream(this.db, doc);
170
387
  }
171
- async getNextSyncRulesContent() {
388
+ async getDeployingSyncConfig() {
172
389
  const doc = await this.db.sync_rules.findOne({
173
- state: storage.SyncRuleState.PROCESSING
390
+ $or: [{ state: storage.SyncRuleState.PROCESSING }, { 'sync_configs.state': storage.SyncRuleState.PROCESSING }]
174
391
  }, { sort: { _id: -1 }, limit: 1 });
175
- if (doc == null) {
176
- return null;
177
- }
178
- return new MongoPersistedSyncRulesContent(this.db, doc);
392
+ return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING]);
179
393
  }
180
- async getReplicatingSyncRules() {
394
+ async getReplicatingReplicationStreams() {
181
395
  const docs = await this.db.sync_rules
182
396
  .find({
183
397
  state: { $in: [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE] }
184
398
  })
185
399
  .toArray();
186
- return docs.map((doc) => {
187
- return new MongoPersistedSyncRulesContent(this.db, doc);
188
- });
400
+ return (await Promise.all(docs.map((doc) => {
401
+ return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE]);
402
+ }))).filter((r) => r != null);
189
403
  }
190
- async getStoppedSyncRules() {
404
+ async getStoppedReplicationStreams() {
191
405
  const docs = await this.db.sync_rules
192
406
  .find({
193
407
  state: storage.SyncRuleState.STOP
194
408
  })
195
409
  .toArray();
196
- return docs.map((doc) => {
197
- return new MongoPersistedSyncRulesContent(this.db, doc);
198
- });
410
+ return (await Promise.all(docs.map((doc) => {
411
+ return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.STOP]);
412
+ }))).filter((d) => d != null);
199
413
  }
200
- async getActiveStorage() {
201
- const content = await this.getActiveSyncRulesContent();
202
- if (content == null) {
414
+ async resolvedSyncConfigFromDoc(doc, stateFilter, options = {}) {
415
+ const stream = await this.replicationStreamFromDoc(doc, stateFilter);
416
+ if (stream == null) {
203
417
  return null;
204
418
  }
205
- // It is important that this instance is cached.
206
- // Not for the instance construction itself, but to ensure that internal caches on the instance
207
- // are re-used properly.
208
- if (this.activeStorageCache?.group_id == content.id) {
209
- return this.activeStorageCache;
210
- }
211
- else {
212
- const instance = this.getInstance(content);
213
- this.activeStorageCache = instance;
214
- return instance;
215
- }
419
+ const content = stream.syncConfigContent[0];
420
+ const thisFactory = this;
421
+ return {
422
+ content,
423
+ replicationStream: stream,
424
+ get storage() {
425
+ // It is important that this instance is cached.
426
+ // Not for the instance construction itself, but to ensure that internal caches on the instance
427
+ // are re-used properly.
428
+ if (options.cacheActiveStorage &&
429
+ thisFactory.activeStorageCache?.replicationStream.replicationJobId == stream.replicationJobId) {
430
+ return thisFactory.activeStorageCache;
431
+ }
432
+ const instance = thisFactory.getInstance(stream);
433
+ if (options.cacheActiveStorage) {
434
+ thisFactory.activeStorageCache = instance;
435
+ }
436
+ return instance;
437
+ }
438
+ };
216
439
  }
217
440
  async getStorageMetrics() {
218
441
  const ignoreNotExisting = (e) => {