@powersync/service-module-mongodb-storage 0.17.0 → 0.18.1

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 (185) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +34 -16
  3. package/dist/storage/MongoBucketStorage.js +219 -70
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +42 -23
  6. package/dist/storage/implementation/MongoBucketBatch.js +61 -44
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoChecksums.d.ts +10 -9
  9. package/dist/storage/implementation/MongoChecksums.js +3 -111
  10. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  11. package/dist/storage/implementation/MongoCompactor.d.ts +2 -0
  12. package/dist/storage/implementation/MongoCompactor.js +18 -9
  13. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
  15. package/dist/storage/implementation/MongoHydrationState.js +49 -0
  16. package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
  17. package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
  18. package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
  19. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  20. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
  21. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
  22. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  23. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  24. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  25. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  27. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +123 -0
  28. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  29. package/dist/storage/implementation/MongoStorageProvider.js +5 -2
  30. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  31. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +41 -23
  32. package/dist/storage/implementation/MongoSyncBucketStorage.js +51 -43
  33. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  35. package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
  36. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  37. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
  38. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  39. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  40. package/dist/storage/implementation/common/PersistedBatch.d.ts +1 -2
  41. package/dist/storage/implementation/common/PersistedBatch.js +5 -0
  42. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  43. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  44. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  45. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  46. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  47. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  48. package/dist/storage/implementation/db.js +9 -14
  49. package/dist/storage/implementation/db.js.map +1 -1
  50. package/dist/storage/implementation/models.d.ts +4 -13
  51. package/dist/storage/implementation/models.js.map +1 -1
  52. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
  53. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +42 -26
  54. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  55. package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
  56. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  57. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +15 -17
  58. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +47 -45
  59. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  60. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  61. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  62. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  63. package/dist/storage/implementation/v1/models.d.ts +10 -1
  64. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
  65. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +286 -259
  66. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  67. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
  68. package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
  69. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +19 -2
  71. package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
  72. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
  74. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
  75. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  76. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +40 -28
  77. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +232 -116
  78. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  79. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -1
  80. package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
  81. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
  83. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
  84. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  85. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  86. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  87. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +13 -12
  88. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +29 -27
  89. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  90. package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
  91. package/dist/storage/implementation/v3/bucket-format.js +58 -0
  92. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  93. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  94. package/dist/storage/implementation/v3/chunking.js +28 -0
  95. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  96. package/dist/storage/implementation/v3/models.d.ts +64 -21
  97. package/dist/storage/implementation/v3/models.js +11 -28
  98. package/dist/storage/implementation/v3/models.js.map +1 -1
  99. package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
  100. package/dist/storage/implementation/v3/source-table-utils.js +214 -0
  101. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  102. package/dist/storage/storage-index.d.ts +4 -4
  103. package/dist/storage/storage-index.js +4 -4
  104. package/dist/storage/storage-index.js.map +1 -1
  105. package/dist/types/types.d.ts +2 -2
  106. package/dist/utils/test-utils.d.ts +1 -2
  107. package/dist/utils/test-utils.js +5 -1
  108. package/dist/utils/test-utils.js.map +1 -1
  109. package/dist/utils/util.d.ts +1 -1
  110. package/dist/utils/util.js +3 -2
  111. package/dist/utils/util.js.map +1 -1
  112. package/package.json +9 -19
  113. package/src/storage/MongoBucketStorage.ts +334 -87
  114. package/src/storage/implementation/MongoBucketBatch.ts +86 -58
  115. package/src/storage/implementation/MongoChecksums.ts +6 -138
  116. package/src/storage/implementation/MongoCompactor.ts +19 -9
  117. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  118. package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
  119. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
  120. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  121. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +154 -0
  122. package/src/storage/implementation/MongoStorageProvider.ts +5 -2
  123. package/src/storage/implementation/MongoSyncBucketStorage.ts +70 -56
  124. package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
  125. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
  126. package/src/storage/implementation/common/PersistedBatch.ts +7 -3
  127. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  128. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  129. package/src/storage/implementation/db.ts +11 -19
  130. package/src/storage/implementation/models.ts +4 -14
  131. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +55 -34
  132. package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
  133. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +70 -64
  134. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  135. package/src/storage/implementation/v1/models.ts +11 -1
  136. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +358 -314
  137. package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
  138. package/src/storage/implementation/v3/MongoCompactorV3.ts +646 -29
  139. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
  140. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +315 -157
  141. package/src/storage/implementation/v3/PersistedBatchV3.ts +62 -36
  142. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
  143. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  144. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +41 -36
  145. package/src/storage/implementation/v3/bucket-format.ts +70 -0
  146. package/src/storage/implementation/v3/chunking.ts +38 -0
  147. package/src/storage/implementation/v3/models.ts +93 -55
  148. package/src/storage/implementation/v3/source-table-utils.ts +392 -0
  149. package/src/storage/storage-index.ts +12 -4
  150. package/src/utils/test-utils.ts +6 -3
  151. package/src/utils/util.ts +3 -2
  152. package/test/src/__snapshots__/storage.test.ts.snap +0 -75
  153. package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
  154. package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
  155. package/test/src/storage.test.ts +11 -11
  156. package/test/src/storage_compacting.test.ts +1956 -6
  157. package/test/src/storage_sync.test.ts +1077 -68
  158. package/test/src/storeCurrentData.test.ts +17 -7
  159. package/test/src/util.ts +2 -1
  160. package/tsconfig.json +1 -4
  161. package/tsconfig.tsbuildinfo +1 -1
  162. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  163. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  164. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  165. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  166. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  167. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  168. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  169. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  170. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  171. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  172. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  173. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  174. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
  175. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
  176. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
  177. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  178. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  179. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  180. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  181. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  182. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  183. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  184. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
  185. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
@@ -17,6 +17,7 @@ import {
17
17
  PopulateChecksumCacheOptions,
18
18
  PopulateChecksumCacheResults,
19
19
  ReplicationCheckpoint,
20
+ ReplicationStreamStorageIds,
20
21
  storage,
21
22
  utils,
22
23
  WatchWriteCheckpointOptions
@@ -27,18 +28,18 @@ import { LRUCache } from 'lru-cache';
27
28
  import * as timers from 'timers/promises';
28
29
  import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
29
30
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
30
- import { MongoSyncBucketStorageContext } from './common/MongoSyncBucketStorageContext.js';
31
31
  import type { VersionedPowerSyncMongo } from './db.js';
32
32
  import { StorageConfig } from './models.js';
33
33
  import { MongoBucketBatchOptions } from './MongoBucketBatch.js';
34
34
  import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
35
35
  import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
36
36
  import { MongoParameterCompactor } from './MongoParameterCompactor.js';
37
- import { MongoPersistedSyncRulesContentV1 } from './MongoPersistedSyncRulesContent.js';
37
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
38
+ import { MongoPersistedReplicationStream } from './MongoPersistedReplicationStream.js';
38
39
  import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
39
40
 
40
41
  export interface MongoSyncBucketStorageOptions {
41
- checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
42
+ checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
42
43
  storageConfig: StorageConfig;
43
44
  }
44
45
 
@@ -47,13 +48,6 @@ interface InternalCheckpointChanges extends CheckpointChanges {
47
48
  invalidateWriteCheckpoints: boolean;
48
49
  }
49
50
 
50
- interface WriterSyncState {
51
- lastCheckpointLsn: string | null;
52
- resumeFromLsn: string | null;
53
- keepaliveOp: InternalOpId | null;
54
- syncConfigId?: bson.ObjectId | null;
55
- }
56
-
57
51
  /**
58
52
  * Only keep checkpoints around for a minute, before fetching a fresh one.
59
53
  *
@@ -70,11 +64,19 @@ export abstract class MongoSyncBucketStorage
70
64
  implements storage.SyncRulesBucketStorage
71
65
  {
72
66
  readonly db: VersionedPowerSyncMongo;
67
+
73
68
  [DO_NOT_LOG] = true;
74
69
 
75
70
  readonly checksums: MongoChecksums;
76
71
 
77
- private parsedSyncRulesCache: { parsed: HydratedSyncConfig; 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>();
78
80
  private writeCheckpointAPI: MongoWriteCheckpointAPI;
79
81
  public readonly logger: Logger;
80
82
  public readonly storageConfig: StorageConfig;
@@ -82,9 +84,9 @@ export abstract class MongoSyncBucketStorage
82
84
 
83
85
  constructor(
84
86
  public readonly factory: MongoBucketStorage,
85
- public readonly group_id: number,
86
- protected readonly sync_rules: MongoPersistedSyncRulesContentV1,
87
- public readonly slot_name: string,
87
+ public readonly replicationStreamId: number,
88
+ public readonly replicationStream: MongoPersistedReplicationStream,
89
+ public readonly replicationStreamName: string,
88
90
  writeCheckpointMode: storage.WriteCheckpointMode | undefined,
89
91
  options: MongoSyncBucketStorageOptions
90
92
  ) {
@@ -95,9 +97,9 @@ export abstract class MongoSyncBucketStorage
95
97
  this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
96
98
  db: this.db,
97
99
  mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
98
- sync_rules_id: group_id
100
+ sync_rules_id: replicationStreamId
99
101
  });
100
- this.logger = sync_rules.logger;
102
+ this.logger = replicationStream.logger;
101
103
  }
102
104
 
103
105
  /**
@@ -117,16 +119,11 @@ export abstract class MongoSyncBucketStorage
117
119
  return this.writeCheckpointAPI.writeCheckpointMode;
118
120
  }
119
121
 
120
- get mapping() {
121
- return this.sync_rules.mapping;
122
- }
123
-
124
- protected get versionContext(): MongoSyncBucketStorageContext {
125
- return {
126
- db: this.db,
127
- group_id: this.group_id,
128
- mapping: this.mapping
129
- };
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;
130
127
  }
131
128
 
132
129
  setWriteCheckpointMode(mode: storage.WriteCheckpointMode): void {
@@ -140,17 +137,21 @@ export abstract class MongoSyncBucketStorage
140
137
  lastWriteCheckpoint(filters: storage.SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
141
138
  return this.writeCheckpointAPI.lastWriteCheckpoint({
142
139
  ...filters,
143
- sync_rules_id: this.group_id
140
+ sync_rules_id: this.replicationStreamId
144
141
  });
145
142
  }
146
143
 
147
- getParsedSyncRules(options: storage.ParseSyncRulesOptions): HydratedSyncConfig {
148
- const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
149
- if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
150
- this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncConfig(), 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);
151
149
  }
150
+ return parsed;
151
+ }
152
152
 
153
- return this.parsedSyncRulesCache!.parsed;
153
+ getParsedSyncRules(options: storage.ParseSyncConfigOptions): HydratedSyncConfig {
154
+ return this.getParsedSyncConfigSet(options).hydratedSyncConfig;
154
155
  }
155
156
 
156
157
  async getCheckpoint(): Promise<storage.ReplicationCheckpoint> {
@@ -183,37 +184,40 @@ export abstract class MongoSyncBucketStorage
183
184
  return;
184
185
  }
185
186
 
186
- await this.db.initializeStreamStorage(this.group_id);
187
+ await this.db.initializeStreamStorage(this.replicationStreamId);
187
188
  await this.initializeVersionStorage();
188
189
  this.#storageInitialized = true;
189
190
  }
190
191
 
191
- protected abstract createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch;
192
- protected abstract getWriterSyncState(): Promise<WriterSyncState>;
193
-
194
- async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
195
- await this.initializeStorage();
196
-
197
- const state = await this.getWriterSyncState();
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>;
198
198
 
199
- 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 {
200
204
  logger: options.logger ?? this.logger,
201
205
  db: this.db,
202
- syncRules: this.sync_rules.parsed(options).hydratedSyncConfig(),
203
- mapping: this.sync_rules.mapping,
204
- groupId: this.group_id,
205
- slotName: this.slot_name,
206
- lastCheckpointLsn: state.lastCheckpointLsn,
207
- resumeFromLsn: state.resumeFromLsn,
208
- keepaliveOp: state.keepaliveOp,
206
+ parsedSyncConfig: this.getParsedSyncConfigSet(options),
207
+ replicationStreamId: this.replicationStreamId,
208
+ replicationStreamName: this.replicationStreamName,
209
209
  storeCurrentData: options.storeCurrentData,
210
210
  skipExistingRows: options.skipExistingRows ?? false,
211
211
  markRecordUnavailable: options.markRecordUnavailable,
212
212
  hooks: options.hooks,
213
- syncConfigId: state.syncConfigId,
214
213
  tracer: options.tracer
215
214
  };
216
- 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);
217
221
  this.iterateListeners((cb) => cb.batchStarted?.(writer));
218
222
  return writer;
219
223
  }
@@ -277,9 +281,9 @@ export abstract class MongoSyncBucketStorage
277
281
  await this.db.notifyCheckpoint();
278
282
  }
279
283
 
280
- protected abstract getStatusImpl(): Promise<storage.SyncRuleStatus>;
284
+ protected abstract getStatusImpl(): Promise<storage.ReplicationStreamStatus>;
281
285
 
282
- async getStatus(): Promise<storage.SyncRuleStatus> {
286
+ async getStatus(): Promise<storage.ReplicationStreamStatus> {
283
287
  return this.getStatusImpl();
284
288
  }
285
289
 
@@ -333,7 +337,7 @@ export abstract class MongoSyncBucketStorage
333
337
  const message = String(e.message ?? 'Replication failure');
334
338
  await this.db.sync_rules.updateOne(
335
339
  {
336
- _id: this.group_id
340
+ _id: this.replicationStreamId
337
341
  },
338
342
  {
339
343
  $set: {
@@ -357,11 +361,21 @@ export abstract class MongoSyncBucketStorage
357
361
  }
358
362
  }
359
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
+
360
372
  async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
361
373
  this.logger.info(`Populating persistent checksum cache...`);
362
374
  const start = Date.now();
375
+ const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
363
376
  const compactor = this.createMongoCompactor({
364
377
  ...options,
378
+ maxOpId,
365
379
  memoryLimitMB: 0,
366
380
  logger: this.logger
367
381
  });
@@ -413,7 +427,7 @@ export abstract class MongoSyncBucketStorage
413
427
  for await (const nextCheckpoint of iter) {
414
428
  if (nextCheckpoint.lsn != null && !queriedInitialWriteCheckpoint) {
415
429
  writeCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
416
- sync_rules_id: this.group_id,
430
+ sync_rules_id: this.replicationStreamId,
417
431
  user_id: options.user_id,
418
432
  heads: {
419
433
  '1': nextCheckpoint.lsn
@@ -446,7 +460,7 @@ export abstract class MongoSyncBucketStorage
446
460
  let updatedWriteCheckpoint = updates.updatedWriteCheckpoints.get(options.user_id) ?? null;
447
461
  if (updates.invalidateWriteCheckpoints) {
448
462
  updatedWriteCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
449
- sync_rules_id: this.group_id,
463
+ sync_rules_id: this.replicationStreamId,
450
464
  user_id: options.user_id,
451
465
  heads: {
452
466
  '1': nextCheckpoint.lsn!
@@ -617,7 +631,7 @@ class EmptyReplicationCheckpoint implements ReplicationCheckpoint {
617
631
  readonly checkpoint: InternalOpId = 0n;
618
632
  readonly lsn: string | null = null;
619
633
 
620
- async getParameterSets(_lookups: ScopedParameterLookup[]): Promise<ParameterLookupRows[]> {
634
+ async getParameterSets(_lookups: ScopedParameterLookup[], _limit: number): Promise<ParameterLookupRows[]> {
621
635
  return [];
622
636
  }
623
637
  }
@@ -17,12 +17,12 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
17
17
  */
18
18
  static async createLock(
19
19
  db: VersionedPowerSyncMongo,
20
- sync_rules: storage.PersistedSyncRulesContent,
20
+ sync_rules: storage.PersistedReplicationStream,
21
21
  session?: mongo.ClientSession
22
22
  ): Promise<MongoSyncRulesLock> {
23
23
  const lockId = crypto.randomBytes(8).toString('hex');
24
24
  const doc = await db.sync_rules.findOneAndUpdate(
25
- { _id: sync_rules.id, $or: [{ lock: null }, { 'lock.expires_at': { $lt: new Date() } }] },
25
+ { _id: sync_rules.replicationStreamId, $or: [{ lock: null }, { 'lock.expires_at': { $lt: new Date() } }] },
26
26
  {
27
27
  $set: {
28
28
  lock: {
@@ -40,7 +40,10 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
40
40
 
41
41
  if (doc == null) {
42
42
  // Query the existing lock to get the expiration time (best effort - it may have been released in the meantime).
43
- const heldLock = await db.sync_rules.findOne({ _id: sync_rules.id }, { projection: { lock: 1 }, session });
43
+ const heldLock = await db.sync_rules.findOne(
44
+ { _id: sync_rules.replicationStreamId },
45
+ { projection: { lock: 1 }, session }
46
+ );
44
47
  if (heldLock?.lock?.expires_at) {
45
48
  throw new ServiceError(
46
49
  ErrorCode.PSYNC_S1003,
@@ -51,7 +54,7 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
51
54
  }
52
55
  }
53
56
  sync_rules.logger.info(`Locked replication stream for processing`);
54
- return new MongoSyncRulesLock(db, sync_rules.id, lockId, sync_rules.logger);
57
+ return new MongoSyncRulesLock(db, sync_rules.replicationStreamId, lockId, sync_rules.logger);
55
58
  }
56
59
 
57
60
  constructor(
@@ -0,0 +1,7 @@
1
+ import { InternalOpId } from '@powersync/service-core';
2
+ import * as bson from 'bson';
3
+
4
+ export interface MongoSyncBucketStorageCheckpoint {
5
+ checkpoint: InternalOpId;
6
+ snapshotTime: bson.Timestamp;
7
+ }
@@ -3,10 +3,9 @@ import { BucketDataSource, BucketDefinitionId, EvaluatedParameters, EvaluatedRow
3
3
  import * as bson from 'bson';
4
4
 
5
5
  import { logger as defaultLogger, Logger } from '@powersync/lib-services-framework';
6
- import { InternalOpId, storage, utils } from '@powersync/service-core';
6
+ import { BucketDefinitionMapping, InternalOpId, storage, utils } from '@powersync/service-core';
7
7
  import { JSONBig } from '@powersync/service-jsonbig';
8
8
  import { mongoTableId, replicaIdToSubkey } from '../../../utils/util.js';
9
- import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
10
9
  import { currentBucketKey, MAX_ROW_SIZE } from '../MongoBucketBatchShared.js';
11
10
  import { MongoIdSequence } from '../MongoIdSequence.js';
12
11
  import type { VersionedPowerSyncMongo } from '../db.js';
@@ -97,7 +96,7 @@ export abstract class PersistedBatch {
97
96
  }
98
97
 
99
98
  saveBucketData(options: SaveBucketDataOptions) {
100
- const remaining_buckets = new Map<string, SaveBucketDataOptions['before_buckets'][number]>();
99
+ const remaining_buckets = new Map<string, SourceRecordBucketState>();
101
100
  for (let bucket of options.before_buckets) {
102
101
  const mapped: SourceRecordBucketState = {
103
102
  bucket: bucket.bucket,
@@ -105,6 +104,11 @@ export abstract class PersistedBatch {
105
104
  id: bucket.id,
106
105
  table: bucket.table
107
106
  };
107
+ if (options.table.bucketDataSourceIds != null && !options.table.bucketDataSourceIds.has(mapped.definitionId!)) {
108
+ // The bucket definition is not active anymore.
109
+ // We don't cleanup these references upfront, but do need to ignore them after the definition is removed.
110
+ continue;
111
+ }
108
112
  remaining_buckets.set(currentBucketKey(mapped), mapped);
109
113
  }
110
114
 
@@ -67,7 +67,7 @@ export abstract class BaseVersionedPowerSyncMongo {
67
67
  }
68
68
 
69
69
  protected async listCollectionsByPrefix<T extends mongo.Document>(prefix: string): Promise<mongo.Collection<T>[]> {
70
- const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
70
+ const collections = await this.db.listCollections({ name: { $regex: `^${prefix}` } }, { nameOnly: true }).toArray();
71
71
 
72
72
  return collections
73
73
  .filter((collection) => collection.name.startsWith(prefix))
@@ -1,6 +1,6 @@
1
1
  import { storage } from '@powersync/service-core';
2
2
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
3
- import { MongoPersistedSyncRulesContentV1 } from './MongoPersistedSyncRulesContent.js';
3
+ import { MongoPersistedReplicationStream } from './MongoPersistedReplicationStream.js';
4
4
  import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from './MongoSyncBucketStorage.js';
5
5
  import { MongoSyncBucketStorageV1 } from './v1/MongoSyncBucketStorageV1.js';
6
6
  import { MongoSyncBucketStorageV3 } from './v3/MongoSyncBucketStorageV3.js';
@@ -11,15 +11,29 @@ export type { MongoSyncBucketStorage };
11
11
 
12
12
  export function createMongoSyncBucketStorage(
13
13
  factory: MongoBucketStorage,
14
- group_id: number,
15
- sync_rules: MongoPersistedSyncRulesContentV1,
16
- slot_name: string,
14
+ replicationStreamId: number,
15
+ replicationStream: MongoPersistedReplicationStream,
16
+ replicationStreamName: string,
17
17
  writeCheckpointMode: storage.WriteCheckpointMode | undefined,
18
18
  options: MongoSyncBucketStorageOptions
19
19
  ): MongoSyncBucketStorage {
20
- if (sync_rules.getStorageConfig().incrementalReprocessing) {
21
- return new MongoSyncBucketStorageV3(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options);
20
+ if (replicationStream.getStorageConfig().incrementalReprocessing) {
21
+ return new MongoSyncBucketStorageV3(
22
+ factory,
23
+ replicationStreamId,
24
+ replicationStream,
25
+ replicationStreamName,
26
+ writeCheckpointMode,
27
+ options
28
+ );
22
29
  }
23
30
 
24
- return new MongoSyncBucketStorageV1(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options);
31
+ return new MongoSyncBucketStorageV1(
32
+ factory,
33
+ replicationStreamId,
34
+ replicationStream,
35
+ replicationStreamName,
36
+ writeCheckpointMode,
37
+ options
38
+ );
25
39
  }
@@ -81,7 +81,7 @@ export class PowerSyncMongo {
81
81
  versioned(storageConfig: StorageConfig & { incrementalReprocessing: false }): VersionedPowerSyncMongoV1;
82
82
  versioned(storageConfig: StorageConfig): VersionedPowerSyncMongo;
83
83
  versioned(storageConfig: StorageConfig): VersionedPowerSyncMongo {
84
- if (storageConfig.incrementalReprocessing) {
84
+ if (storageConfig.version >= storage.STORAGE_VERSION_3) {
85
85
  return new VersionedPowerSyncMongoV3(this, storageConfig);
86
86
  }
87
87
 
@@ -93,22 +93,18 @@ export class PowerSyncMongo {
93
93
  */
94
94
  async listBucketDataCollectionsV3(groupId?: number): Promise<mongo.Collection<BucketDataDocumentV3>[]> {
95
95
  const prefix = groupId == null ? 'bucket_data_' : `bucket_data_${groupId}_`;
96
- const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
97
-
98
- return collections
99
- .filter((collection) => collection.name.startsWith(prefix))
100
- .map((collection) => this.db.collection<BucketDataDocumentV3>(collection.name));
96
+ return this.collectionsByPrefix<BucketDataDocumentV3>(prefix);
101
97
  }
102
98
 
103
99
  /**
104
100
  * Not safe for user-provided prefix - only for hardcoded values.
105
101
  */
106
- private async collectionsByPrefix(prefix: string): Promise<mongo.Collection<never>[]> {
107
- const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
102
+ private async collectionsByPrefix<T extends mongo.Document = never>(prefix: string): Promise<mongo.Collection<T>[]> {
103
+ const collections = await this.db.listCollections({ name: { $regex: `^${prefix}` } }, { nameOnly: true }).toArray();
108
104
 
109
105
  return collections
110
106
  .filter((collection) => collection.name.startsWith(prefix))
111
- .map((collection) => this.db.collection<never>(collection.name));
107
+ .map((collection) => this.db.collection<T>(collection.name));
112
108
  }
113
109
 
114
110
  /**
@@ -144,16 +140,12 @@ export class PowerSyncMongo {
144
140
  async listSourceTableCollections(
145
141
  replicationStreamId?: number
146
142
  ): Promise<mongo.Collection<CommonSourceTableDocument>[]> {
147
- const filter =
148
- replicationStreamId == null
149
- ? { name: new RegExp('^source_table_') }
150
- : { name: this.sourceTableCollectionName(replicationStreamId) };
151
- const prefix = replicationStreamId == null ? 'source_table_' : this.sourceTableCollectionName(replicationStreamId);
152
- const collections = await this.db.listCollections(filter, { nameOnly: true }).toArray();
153
-
154
- return collections
155
- .filter((collection) => collection.name.startsWith(prefix))
156
- .map((collection) => this.db.collection<CommonSourceTableDocument>(collection.name));
143
+ if (replicationStreamId == null) {
144
+ return this.collectionsByPrefix('source_table_');
145
+ }
146
+ const name = this.sourceTableCollectionName(replicationStreamId);
147
+ const collections = await this.db.listCollections({ name }, { nameOnly: true }).toArray();
148
+ return collections.map((c) => this.db.collection<CommonSourceTableDocument>(c.name));
157
149
  }
158
150
 
159
151
  /**
@@ -4,7 +4,7 @@ import { ParameterIndexId, SqliteJsonValue } from '@powersync/service-sync-rules
4
4
  import { event_types } from '@powersync/service-types';
5
5
  import * as bson from 'bson';
6
6
  import type { CurrentDataDocument, SourceTableDocumentV1 } from './v1/models.js';
7
- import type { CurrentBucketV3, CurrentDataDocumentV3, RecordedLookupV3, SourceTableDocumentV3 } from './v3/models.js';
7
+ import type { CurrentDataDocumentV3, RecordedLookupV3 } from './v3/models.js';
8
8
 
9
9
  /**
10
10
  * Replica id uniquely identifying a row on the source database.
@@ -208,7 +208,7 @@ export interface SyncRuleDocumentBase {
208
208
  storage_version?: number;
209
209
  }
210
210
 
211
- export interface SyncRuleCheckpointFields<TKeepaliveOp extends string | bigint | null> {
211
+ export interface SyncRuleCheckpointFields {
212
212
  /**
213
213
  * The last consistent checkpoint.
214
214
  *
@@ -225,16 +225,6 @@ export interface SyncRuleCheckpointFields<TKeepaliveOp extends string | bigint |
225
225
  * If set, no new checkpoints may be created < this value.
226
226
  */
227
227
  no_checkpoint_before: string | null;
228
-
229
- /**
230
- * Goes together with no_checkpoint_before.
231
- *
232
- * If a keepalive is triggered that creates the checkpoint > no_checkpoint_before,
233
- * then the checkpoint must be equal to this keepalive_op.
234
- *
235
- * This is a string in V1, bigint in V3.
236
- */
237
- keepalive_op: TKeepaliveOp;
238
228
  }
239
229
 
240
230
  export interface StorageConfig extends storage.StorageVersionConfig {
@@ -306,6 +296,6 @@ export interface InstanceDocument {
306
296
  export interface ClientConnectionDocument extends event_types.ClientConnection {}
307
297
 
308
298
  export type CurrentDataDocumentId = CurrentDataDocument['_id'] | CurrentDataDocumentV3['_id'];
309
- export type CommonCurrentBucket = CurrentBucket | CurrentBucketV3;
299
+ export type CommonCurrentBucket = CurrentBucket;
310
300
  export type CommonCurrentLookup = bson.Binary | RecordedLookupV3;
311
- export type CommonSourceTableDocument = SourceTableDocumentV1 | SourceTableDocumentV3;
301
+ export type CommonSourceTableDocument = Pick<SourceTableDocumentV1, '_id' | 'snapshot_status'>;