@powersync/service-module-mongodb-storage 0.0.0-dev-20260602073133 → 0.0.0-dev-20260803144812

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 (239) hide show
  1. package/CHANGELOG.md +101 -5
  2. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
  3. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
  4. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
  5. package/dist/storage/MongoBucketStorage.d.ts +41 -16
  6. package/dist/storage/MongoBucketStorage.js +226 -71
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +56 -25
  9. package/dist/storage/implementation/MongoBucketBatch.js +110 -62
  10. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  11. package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
  12. package/dist/storage/implementation/MongoChecksums.js +36 -121
  13. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  14. package/dist/storage/implementation/MongoCompactor.d.ts +17 -19
  15. package/dist/storage/implementation/MongoCompactor.js +80 -263
  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 +7 -6
  21. package/dist/storage/implementation/MongoParameterCompactor.js +25 -2
  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 +123 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +39 -4
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +57 -29
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +87 -61
  36. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  38. package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +171 -24
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +8 -0
  44. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  45. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  46. package/dist/storage/implementation/common/PersistedBatch.d.ts +7 -2
  47. package/dist/storage/implementation/common/PersistedBatch.js +12 -0
  48. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  49. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  50. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  51. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  52. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  53. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  54. package/dist/storage/implementation/db.js +17 -15
  55. package/dist/storage/implementation/db.js.map +1 -1
  56. package/dist/storage/implementation/models.d.ts +20 -13
  57. package/dist/storage/implementation/models.js.map +1 -1
  58. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
  59. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +61 -29
  60. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  61. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
  62. package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
  63. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  64. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
  65. package/dist/storage/implementation/v1/MongoCompactorV1.js +251 -2
  66. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  67. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -19
  68. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +82 -58
  69. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  70. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  71. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  72. package/dist/storage/implementation/v1/SingleBucketStoreV1.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 +10 -1
  77. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  78. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  79. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  80. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
  81. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +309 -259
  82. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  83. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
  84. package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
  85. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  86. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +40 -2
  87. package/dist/storage/implementation/v3/MongoCompactorV3.js +647 -23
  88. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  89. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +67 -0
  90. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +351 -0
  91. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  92. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +45 -30
  93. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +272 -140
  94. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  95. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +6 -3
  96. package/dist/storage/implementation/v3/PersistedBatchV3.js +84 -21
  97. package/dist/storage/implementation/v3/PersistedBatchV3.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 +15 -13
  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 +13 -0
  105. package/dist/storage/implementation/v3/bucket-format.js +77 -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 +30 -0
  109. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  110. package/dist/storage/implementation/v3/models.d.ts +101 -21
  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/object-storage/BucketDataObjectStorage.d.ts +21 -0
  114. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
  115. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  116. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -0
  117. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  118. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  119. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +70 -0
  120. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
  121. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  122. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
  123. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
  124. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  125. package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
  126. package/dist/storage/implementation/v3/source-table-utils.js +214 -0
  127. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  128. package/dist/storage/storage-index.d.ts +4 -4
  129. package/dist/storage/storage-index.js +4 -4
  130. package/dist/storage/storage-index.js.map +1 -1
  131. package/dist/types/types.d.ts +49 -3
  132. package/dist/types/types.js +48 -1
  133. package/dist/types/types.js.map +1 -1
  134. package/dist/utils/test-utils.d.ts +1 -2
  135. package/dist/utils/test-utils.js +7 -1
  136. package/dist/utils/test-utils.js.map +1 -1
  137. package/dist/utils/util.d.ts +9 -2
  138. package/dist/utils/util.js +128 -4
  139. package/dist/utils/util.js.map +1 -1
  140. package/package.json +12 -19
  141. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  142. package/src/storage/MongoBucketStorage.ts +347 -88
  143. package/src/storage/implementation/MongoBucketBatch.ts +147 -82
  144. package/src/storage/implementation/MongoChecksums.ts +93 -153
  145. package/src/storage/implementation/MongoCompactor.ts +102 -316
  146. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  147. package/src/storage/implementation/MongoParameterCompactor.ts +25 -6
  148. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
  149. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  150. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +154 -0
  151. package/src/storage/implementation/MongoStorageProvider.ts +46 -4
  152. package/src/storage/implementation/MongoSyncBucketStorage.ts +121 -87
  153. package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
  154. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +202 -28
  155. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +9 -0
  156. package/src/storage/implementation/common/PersistedBatch.ts +19 -3
  157. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  158. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  159. package/src/storage/implementation/db.ts +19 -20
  160. package/src/storage/implementation/models.ts +20 -14
  161. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +75 -37
  162. package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
  163. package/src/storage/implementation/v1/MongoCompactorV1.ts +294 -9
  164. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +142 -105
  165. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  166. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  167. package/src/storage/implementation/v1/models.ts +11 -1
  168. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  169. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +382 -321
  170. package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
  171. package/src/storage/implementation/v3/MongoCompactorV3.ts +847 -32
  172. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +482 -0
  173. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +359 -187
  174. package/src/storage/implementation/v3/PersistedBatchV3.ts +118 -37
  175. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  176. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +46 -36
  177. package/src/storage/implementation/v3/bucket-format.ts +97 -0
  178. package/src/storage/implementation/v3/chunking.ts +40 -0
  179. package/src/storage/implementation/v3/models.ts +132 -55
  180. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
  181. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
  182. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
  183. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
  184. package/src/storage/implementation/v3/source-table-utils.ts +392 -0
  185. package/src/storage/storage-index.ts +12 -4
  186. package/src/types/types.ts +58 -1
  187. package/src/utils/test-utils.ts +8 -3
  188. package/src/utils/util.ts +185 -6
  189. package/test/src/__snapshots__/storage.test.ts.snap +0 -75
  190. package/test/src/__snapshots__/storage_sync.test.ts.snap +35 -617
  191. package/test/src/cleanup-stopped-sync-configs.test.ts +671 -0
  192. package/test/src/helpers/MemoryObjectStorage.ts +53 -0
  193. package/test/src/helpers/s3TestFactory.ts +69 -0
  194. package/test/src/object_storage_lifecycle.test.ts +59 -0
  195. package/test/src/setup.ts +6 -1
  196. package/test/src/storage.test.ts +227 -13
  197. package/test/src/storage_compacting.test.ts +1951 -12
  198. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  199. package/test/src/storage_s3_checksums.test.ts +191 -0
  200. package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
  201. package/test/src/storage_s3_reading.test.ts +526 -0
  202. package/test/src/storage_s3_writing.test.ts +135 -0
  203. package/test/src/storage_sync.test.ts +1180 -73
  204. package/test/src/storeCurrentData.test.ts +227 -0
  205. package/test/src/util.ts +2 -1
  206. package/tsconfig.json +1 -4
  207. package/tsconfig.tsbuildinfo +1 -1
  208. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  209. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  210. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  211. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  212. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  213. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  214. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  215. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  216. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  217. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  218. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  219. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  220. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  221. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  222. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  223. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
  224. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
  225. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
  226. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  227. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  228. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  229. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  230. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
  231. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  232. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  233. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  234. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  235. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  236. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  237. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
  238. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
  239. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
@@ -0,0 +1,392 @@
1
+ import { ReplicationAssertionError } from '@powersync/lib-services-framework';
2
+ import { BucketDefinitionMapping, ColumnDescriptor, storage } from '@powersync/service-core';
3
+ import {
4
+ BucketDataSource,
5
+ BucketDefinitionId,
6
+ HydratedSyncConfig,
7
+ MatchingSources,
8
+ ParameterIndexId,
9
+ ParameterIndexLookupCreator,
10
+ SourceTableRef
11
+ } from '@powersync/service-sync-rules';
12
+ import * as bson from 'bson';
13
+ import { ReplicaIdColumn, SourceTableDocumentV3 } from './models.js';
14
+
15
+ export interface SourceTableIdentity {
16
+ schema: string;
17
+ name: string;
18
+ objectId: number | string | undefined;
19
+ replicaIdColumns: ReplicaIdColumn[];
20
+ }
21
+
22
+ export interface SourceTableMembershipIds {
23
+ bucketDataSourceIds: BucketDefinitionId[];
24
+ parameterLookupSourceIds: ParameterIndexId[];
25
+ }
26
+
27
+ export interface SourceTableDesiredResolution {
28
+ bucketSourceById: Map<BucketDefinitionId, BucketDataSource>;
29
+ parameterLookupSourceById: Map<ParameterIndexId, ParameterIndexLookupCreator>;
30
+ triggersEvent: boolean;
31
+ }
32
+
33
+ export interface SourceTableReconciliationContext {
34
+ connectionId: number;
35
+ connectionTag: string;
36
+ identity: SourceTableIdentity;
37
+ storeCurrentData: boolean;
38
+ syncConfig: HydratedSyncConfig;
39
+ mapping: BucketDefinitionMapping;
40
+ desired: SourceTableDesiredResolution;
41
+ }
42
+
43
+ export interface SourceTableReconciliationPlan {
44
+ /** Retained source tables, with memberships narrowed to the desired set. */
45
+ tables: storage.SourceTable[];
46
+ /** Membership narrowing to persist for retained snapshot-incomplete docs. */
47
+ narrowingUpdates: SourceTableMembershipUpdate[];
48
+ /**
49
+ * Memberships for a new source-table doc covering desired ids no existing doc covers,
50
+ * or null if no new doc is needed. Empty memberships indicate an event-only table.
51
+ */
52
+ newTableMemberships: SourceTableMembershipIds | null;
53
+ /** Identity-overlapping docs that conflict with the current identity and must be dropped. */
54
+ dropDocs: SourceTableDocumentV3[];
55
+ }
56
+
57
+ export interface SourceTableMembershipUpdate {
58
+ id: bson.ObjectId;
59
+ memberships: SourceTableMembershipIds;
60
+ }
61
+
62
+ export interface NewSourceTable {
63
+ doc: SourceTableDocumentV3;
64
+ table: storage.SourceTable;
65
+ }
66
+
67
+ export function sourceTableDesiredResolution(
68
+ syncConfig: HydratedSyncConfig,
69
+ ref: SourceTableRef,
70
+ mapping: BucketDefinitionMapping
71
+ ): SourceTableDesiredResolution {
72
+ const matchingSources = syncConfig.getMatchingSources(ref);
73
+ return {
74
+ bucketSourceById: new Map(
75
+ matchingSources.bucketDataSources.map((source) => [mapping.bucketSourceId(source), source] as const)
76
+ ),
77
+ parameterLookupSourceById: new Map(
78
+ matchingSources.parameterLookupSources.map((source) => [mapping.parameterLookupId(source), source] as const)
79
+ ),
80
+ triggersEvent: syncConfig.tableTriggersEvent(ref)
81
+ };
82
+ }
83
+
84
+ export function planSourceTableReconciliation(
85
+ candidateDocs: SourceTableDocumentV3[],
86
+ context: SourceTableReconciliationContext
87
+ ): SourceTableReconciliationPlan {
88
+ return new SourceTableReconciliationPlanner(context).plan(candidateDocs);
89
+ }
90
+
91
+ function sameStringArray(left: string[], right: string[]) {
92
+ return left.length == right.length && left.every((value, index) => value == right[index]);
93
+ }
94
+
95
+ function matchingSourcesFor(
96
+ desired: SourceTableDesiredResolution,
97
+ memberships: SourceTableMembershipIds
98
+ ): MatchingSources {
99
+ return {
100
+ bucketDataSources: memberships.bucketDataSourceIds.map((id) => desired.bucketSourceById.get(id)!),
101
+ parameterLookupSources: memberships.parameterLookupSourceIds.map((id) => desired.parameterLookupSourceById.get(id)!)
102
+ };
103
+ }
104
+
105
+ function intersectMembershipIds(
106
+ doc: SourceTableDocumentV3,
107
+ desired: SourceTableDesiredResolution
108
+ ): SourceTableMembershipIds {
109
+ return {
110
+ bucketDataSourceIds: doc.bucket_data_source_ids.filter((id) => desired.bucketSourceById.has(id)),
111
+ parameterLookupSourceIds: doc.parameter_lookup_source_ids.filter((id) => desired.parameterLookupSourceById.has(id))
112
+ };
113
+ }
114
+
115
+ function hasMembershipIds(memberships: SourceTableMembershipIds) {
116
+ return memberships.bucketDataSourceIds.length > 0 || memberships.parameterLookupSourceIds.length > 0;
117
+ }
118
+
119
+ function sameMembershipIds(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
120
+ return (
121
+ sameStringArray(doc.bucket_data_source_ids, memberships.bucketDataSourceIds) &&
122
+ sameStringArray(doc.parameter_lookup_source_ids, memberships.parameterLookupSourceIds)
123
+ );
124
+ }
125
+
126
+ class SourceTableReconciliationPlanner {
127
+ private readonly coveredBucketDataSourceIds = new Set<string>();
128
+ private readonly coveredParameterLookupSourceIds = new Set<string>();
129
+ private readonly retainedDocIds = new Set<string>();
130
+ private readonly tables: storage.SourceTable[] = [];
131
+ private readonly narrowingUpdates: SourceTableMembershipUpdate[] = [];
132
+ private retainedEventOnlyTable = false;
133
+
134
+ constructor(private readonly context: SourceTableReconciliationContext) {}
135
+
136
+ plan(candidateDocs: SourceTableDocumentV3[]): SourceTableReconciliationPlan {
137
+ const { identity } = this.context;
138
+ for (const doc of candidateDocs) {
139
+ if (matchingSourceTableIdentity(doc, identity)) {
140
+ this.retainDoc(doc);
141
+ }
142
+ }
143
+
144
+ return {
145
+ tables: this.tables,
146
+ narrowingUpdates: this.narrowingUpdates,
147
+ newTableMemberships: this.newTableMemberships(),
148
+ // Non-retained overlapping docs represent renames / relation-id changes /
149
+ // replica-identity changes. Same-identity stale docs are retained in production.
150
+ dropDocs: candidateDocs.filter(
151
+ (doc) => !this.retainedDocIds.has(doc._id.toHexString()) && !matchingSourceTableIdentity(doc, identity)
152
+ )
153
+ };
154
+ }
155
+
156
+ private retainDoc(doc: SourceTableDocumentV3) {
157
+ const memberships = intersectMembershipIds(doc, this.context.desired);
158
+ const coversDesiredMembership = hasMembershipIds(memberships);
159
+ const coversEventOnlyTable =
160
+ !this.desiredHasMembership() && this.context.desired.triggersEvent && !this.retainedEventOnlyTable;
161
+
162
+ this.recordCoverage(doc, memberships);
163
+ this.planNarrowingUpdate(doc, memberships, coversDesiredMembership, coversEventOnlyTable);
164
+
165
+ if (coversDesiredMembership || coversEventOnlyTable) {
166
+ this.retainedEventOnlyTable ||= coversEventOnlyTable;
167
+ this.retainedDocIds.add(doc._id.toHexString());
168
+ this.tables.push(this.sourceTableFor(doc, memberships));
169
+ }
170
+ }
171
+
172
+ private desiredHasMembership() {
173
+ const { desired } = this.context;
174
+ return desired.bucketSourceById.size > 0 || desired.parameterLookupSourceById.size > 0;
175
+ }
176
+
177
+ private recordCoverage(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
178
+ this.addCoverage(doc, 'bucket data source', this.coveredBucketDataSourceIds, memberships.bucketDataSourceIds);
179
+ this.addCoverage(
180
+ doc,
181
+ 'parameter lookup source',
182
+ this.coveredParameterLookupSourceIds,
183
+ memberships.parameterLookupSourceIds
184
+ );
185
+ }
186
+
187
+ // Membership sets must be pairwise disjoint across the docs of one physical table:
188
+ // each desired id is covered by exactly one doc, so each definition receives each
189
+ // row exactly once. The algorithm maintains this (new docs only get uncovered ids,
190
+ // narrowing only removes ids) - overlap means the persisted state is corrupt.
191
+ private addCoverage(doc: SourceTableDocumentV3, kind: string, covered: Set<string>, ids: string[]) {
192
+ const { schema, name } = this.context.identity;
193
+ for (const id of ids) {
194
+ if (covered.has(id)) {
195
+ throw new ReplicationAssertionError(
196
+ `Source table ${doc._id} duplicates coverage of ${kind} ${id} for ${schema}.${name}`
197
+ );
198
+ }
199
+ covered.add(id);
200
+ }
201
+ }
202
+
203
+ private planNarrowingUpdate(
204
+ doc: SourceTableDocumentV3,
205
+ memberships: SourceTableMembershipIds,
206
+ coversDesiredMembership: boolean,
207
+ coversEventOnlyTable: boolean
208
+ ) {
209
+ const shouldNarrow =
210
+ (coversDesiredMembership || coversEventOnlyTable) && !doc.snapshot_done && !sameMembershipIds(doc, memberships);
211
+
212
+ if (!shouldNarrow) {
213
+ return;
214
+ }
215
+
216
+ this.narrowingUpdates.push({
217
+ id: doc._id,
218
+ memberships
219
+ });
220
+ }
221
+
222
+ private newTableMemberships(): SourceTableMembershipIds | null {
223
+ const { desired } = this.context;
224
+ const uncovered: SourceTableMembershipIds = {
225
+ bucketDataSourceIds: [...desired.bucketSourceById.keys()].filter(
226
+ (id) => !this.coveredBucketDataSourceIds.has(id)
227
+ ),
228
+ parameterLookupSourceIds: [...desired.parameterLookupSourceById.keys()].filter(
229
+ (id) => !this.coveredParameterLookupSourceIds.has(id)
230
+ )
231
+ };
232
+ if (hasMembershipIds(uncovered) || (desired.triggersEvent && this.tables.length == 0)) {
233
+ return uncovered;
234
+ }
235
+ return null;
236
+ }
237
+
238
+ private sourceTableFor(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds): storage.SourceTable {
239
+ const { connectionTag, syncConfig, mapping, desired, storeCurrentData } = this.context;
240
+ const table = sourceTableFromDocument(
241
+ doc,
242
+ connectionTag,
243
+ syncConfig,
244
+ mapping,
245
+ matchingSourcesFor(desired, memberships),
246
+ memberships
247
+ );
248
+ table.storeCurrentData = storeCurrentData;
249
+ return table;
250
+ }
251
+ }
252
+
253
+ function sameReplicaIdColumns(left: ReplicaIdColumn[] | undefined, right: ReplicaIdColumn[]) {
254
+ return (
255
+ left != null &&
256
+ left.length == right.length &&
257
+ left.every(
258
+ (column, index) =>
259
+ column.name == right[index].name && column.type == right[index].type && column.type_oid == right[index].type_oid
260
+ )
261
+ );
262
+ }
263
+
264
+ function matchingSourceTableIdentity(doc: SourceTableDocumentV3, identity: SourceTableIdentity) {
265
+ return (
266
+ doc.schema_name == identity.schema &&
267
+ doc.table_name == identity.name &&
268
+ (identity.objectId == null || doc.relation_id == identity.objectId) &&
269
+ sameReplicaIdColumns(doc.replica_id_columns, identity.replicaIdColumns)
270
+ );
271
+ }
272
+
273
+ export function overlappingSourceTableFilter(
274
+ connectionId: number,
275
+ identity: Pick<SourceTableIdentity, 'schema' | 'name' | 'objectId'>
276
+ ): Record<string, unknown> {
277
+ const clauses = [{ schema_name: identity.schema, table_name: identity.name }] as Record<string, unknown>[];
278
+ if (identity.objectId != null) {
279
+ clauses.push({ relation_id: identity.objectId });
280
+ }
281
+
282
+ return {
283
+ connection_id: connectionId,
284
+ $or: clauses
285
+ };
286
+ }
287
+
288
+ export function createNewSourceTable(
289
+ id: bson.ObjectId,
290
+ memberships: SourceTableMembershipIds,
291
+ context: SourceTableReconciliationContext
292
+ ): NewSourceTable {
293
+ const { connectionId, connectionTag, identity, syncConfig, mapping, desired, storeCurrentData } = context;
294
+ const doc: SourceTableDocumentV3 = {
295
+ _id: id,
296
+ connection_id: connectionId,
297
+ relation_id: identity.objectId,
298
+ schema_name: identity.schema,
299
+ table_name: identity.name,
300
+ replica_id_columns: identity.replicaIdColumns,
301
+ snapshot_done: false,
302
+ snapshot_status: undefined,
303
+ bucket_data_source_ids: memberships.bucketDataSourceIds,
304
+ parameter_lookup_source_ids: memberships.parameterLookupSourceIds
305
+ };
306
+ const table = sourceTableFromDocument(
307
+ doc,
308
+ connectionTag,
309
+ syncConfig,
310
+ mapping,
311
+ matchingSourcesFor(desired, memberships),
312
+ memberships
313
+ );
314
+ table.storeCurrentData = storeCurrentData;
315
+
316
+ return { doc, table };
317
+ }
318
+
319
+ export function designateEventCarrier(tables: storage.SourceTable[], triggersEvent: boolean) {
320
+ if (!triggersEvent) {
321
+ return;
322
+ }
323
+
324
+ const eventCarrier = tables.find((table) => table.snapshotComplete) ?? tables[0];
325
+ for (const table of tables) {
326
+ table.syncEvent = table === eventCarrier;
327
+ }
328
+ }
329
+
330
+ function sourceTableMembershipsFromDocument(
331
+ doc: SourceTableDocumentV3,
332
+ syncConfig: HydratedSyncConfig,
333
+ mapping: BucketDefinitionMapping
334
+ ): MatchingSources {
335
+ const bucketDataSourceIds = new Set(doc.bucket_data_source_ids);
336
+ const parameterLookupSourceIds = new Set(doc.parameter_lookup_source_ids);
337
+
338
+ return {
339
+ bucketDataSources: syncConfig.bucketDataSources.filter((source) =>
340
+ bucketDataSourceIds.has(mapping.bucketSourceId(source))
341
+ ),
342
+ parameterLookupSources: syncConfig.bucketParameterLookupSources.filter((source) =>
343
+ parameterLookupSourceIds.has(mapping.parameterLookupId(source))
344
+ )
345
+ };
346
+ }
347
+
348
+ export function sourceTableFromDocument(
349
+ doc: SourceTableDocumentV3,
350
+ connectionTag: string,
351
+ syncConfig: HydratedSyncConfig,
352
+ mapping: BucketDefinitionMapping,
353
+ memberships?: MatchingSources,
354
+ membershipIds?: SourceTableMembershipIds
355
+ ): storage.SourceTable {
356
+ const resolvedMemberships = memberships ?? sourceTableMembershipsFromDocument(doc, syncConfig, mapping);
357
+ const resolvedMembershipIds = membershipIds ?? {
358
+ bucketDataSourceIds: resolvedMemberships.bucketDataSources.map((source) => mapping.bucketSourceId(source)),
359
+ parameterLookupSourceIds: resolvedMemberships.parameterLookupSources.map((source) =>
360
+ mapping.parameterLookupId(source)
361
+ )
362
+ };
363
+ const table = new storage.SourceTable({
364
+ id: doc._id,
365
+ ref: {
366
+ connectionTag,
367
+ schema: doc.schema_name,
368
+ name: doc.table_name
369
+ },
370
+ objectId: doc.relation_id,
371
+ replicaIdColumns: doc.replica_id_columns!.map(
372
+ (c) => ({ name: c.name, typeId: c.type_oid, type: c.type }) satisfies ColumnDescriptor
373
+ ),
374
+ snapshotComplete: doc.snapshot_done,
375
+ bucketDataSources: resolvedMemberships.bucketDataSources,
376
+ parameterLookupSources: resolvedMemberships.parameterLookupSources,
377
+ bucketDataSourceIds: new Set(resolvedMembershipIds.bucketDataSourceIds),
378
+ parameterLookupSourceIds: new Set(resolvedMembershipIds.parameterLookupSourceIds)
379
+ });
380
+ table.syncData = table.bucketDataSources.length > 0;
381
+ table.syncParameters = table.parameterLookupSources.length > 0;
382
+ table.syncEvent = syncConfig.tableTriggersEvent(table.ref);
383
+ table.snapshotStatus =
384
+ doc.snapshot_status == null
385
+ ? undefined
386
+ : {
387
+ lastKey: doc.snapshot_status.last_key?.buffer ?? null,
388
+ totalEstimatedCount: doc.snapshot_status.total_estimated_count,
389
+ replicatedCount: doc.snapshot_status.replicated_count
390
+ };
391
+ return table;
392
+ }
@@ -1,17 +1,25 @@
1
1
  export * as test_utils from '../utils/test-utils.js';
2
2
  export * from '../utils/util.js';
3
- export * from './implementation/BucketDefinitionMapping.js';
4
3
  export * from './implementation/common/PersistedBatch.js';
5
4
  export * from './implementation/createMongoSyncBucketStorage.js';
6
5
  export * from './implementation/db.js';
7
6
  export * from './implementation/models.js';
8
7
  export * from './implementation/MongoIdSequence.js';
9
- export * from './implementation/MongoPersistedSyncRules.js';
10
- export * from './implementation/MongoPersistedSyncRulesContent.js';
8
+ export * from './implementation/MongoParsedSyncConfigSet.js';
9
+ export * from './implementation/MongoPersistedReplicationStream.js';
11
10
  export * from './implementation/MongoStorageProvider.js';
12
11
  export * from './implementation/MongoSyncRulesLock.js';
13
12
  export * from './implementation/OperationBatch.js';
14
13
  export * from './implementation/v1/models.js';
15
- export * from './implementation/v3/models.js';
14
+ export { loadBucketDataDocument, serializeBucketData } from './implementation/v3/bucket-format.js';
15
+ export {
16
+ BucketDataDocumentV3,
17
+ BucketOperation,
18
+ RecordedLookupV3,
19
+ ReplicationStreamDocumentV3,
20
+ SyncConfigDefinition,
21
+ SyncRuleConfigStateV3,
22
+ taggedBucketParameterDocumentToTagged
23
+ } from './implementation/v3/models.js';
16
24
  export * from './MongoBucketStorage.js';
17
25
  export * from './MongoReportStorage.js';
@@ -1,16 +1,73 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
+ import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
2
3
  import * as service_types from '@powersync/service-types';
3
4
  import * as t from 'ts-codec';
4
5
 
6
+ const S3ObjectStorageConfig = t.object({
7
+ type: t.literal('s3'),
8
+ bucket: t.string,
9
+ region: t.string.optional(),
10
+ prefix: t.string.optional(),
11
+ endpoint: t.string.optional(),
12
+ force_path_style: t.boolean.optional(),
13
+ access_key_id: t.string.optional(),
14
+ secret_access_key: t.string.optional(),
15
+ concurrency_limit: t.number.optional(),
16
+ // Chunks whose BSON-serialized size falls below this byte threshold
17
+ // stay inline in MongoDB instead of being offloaded to S3. Default 1024.
18
+ inline_threshold_bytes: t.number.optional()
19
+ });
20
+
21
+ export const MongoStorageReadPreference = t
22
+ .literal('primary')
23
+ .or(t.literal('primaryPreferred'))
24
+ .or(t.literal('secondary'))
25
+ .or(t.literal('secondaryPreferred'))
26
+ .or(t.literal('nearest'));
27
+
28
+ export type MongoStorageReadPreference = t.Encoded<typeof MongoStorageReadPreference>;
29
+
5
30
  export const MongoStorageConfig = lib_mongo.BaseMongoConfig.and(
6
31
  t.object({
7
- // Add any mongo specific storage settings here in future
32
+ /**
33
+ * Read preference for bulk checksum and bucket data reads.
34
+ *
35
+ * If unset, MongoDB driver defaults are used for backwards compatibility.
36
+ *
37
+ * This is an experimental option, and may be removed in a future release.
38
+ */
39
+ bulk_read_preference: MongoStorageReadPreference.optional(),
40
+ /**
41
+ * Throttle clear operations after sync config deploys, by pausing between batches.
42
+ * The rate produces a pause proportional to the previous batch duration.
43
+ *
44
+ * Increase this to reduce impact of clear operations on the storage cluster.
45
+ * Use 0 to clear as fast as possible.
46
+ *
47
+ * Defaults to 0.2.
48
+ */
49
+ clear_batch_throttle_rate: t.number.optional(),
50
+
51
+ object_storage: S3ObjectStorageConfig.optional()
8
52
  })
9
53
  );
10
54
 
11
55
  export type MongoStorageConfig = t.Encoded<typeof MongoStorageConfig>;
12
56
  export type MongoStorageConfigDecoded = t.Decoded<typeof MongoStorageConfig>;
13
57
 
58
+ export const DEFAULT_CLEAR_BATCH_THROTTLE_RATE = 0.2;
59
+
60
+ export function normalizeClearBatchThrottleRate(value: number | undefined): number {
61
+ const rate = value ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE;
62
+ if (!Number.isFinite(rate) || rate < 0 || rate > 20) {
63
+ throw new ServiceError(
64
+ ErrorCode.PSYNC_S3201,
65
+ 'storage.clear_batch_throttle_rate must be a finite number between 0 and 20'
66
+ );
67
+ }
68
+ return rate;
69
+ }
70
+
14
71
  export function isMongoStorageConfig(
15
72
  config: service_types.configFile.GenericStorageConfig
16
73
  ): config is MongoStorageConfig {
@@ -7,8 +7,7 @@ import { PowerSyncMongo } from '../storage/implementation/db.js';
7
7
  export type MongoTestStorageOptions = {
8
8
  url: string;
9
9
  isCI: boolean;
10
- internalOptions?: MongoBucketStorageOptions;
11
- };
10
+ } & Omit<MongoBucketStorageOptions, 'replicationStreamNamePrefix'>;
12
11
 
13
12
  export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorageOptions) {
14
13
  return {
@@ -27,7 +26,13 @@ export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorag
27
26
  await db.clear();
28
27
  }
29
28
 
30
- return new MongoBucketStorage(db, { slot_name_prefix: 'test_' }, factoryOptions.internalOptions);
29
+ return new MongoBucketStorage(db, {
30
+ replicationStreamNamePrefix: 'test_',
31
+ checksumOptions: factoryOptions.checksumOptions,
32
+ supportsMultipleSyncConfigs: factoryOptions.supportsMultipleSyncConfigs,
33
+ objectStorage: factoryOptions.objectStorage,
34
+ inlineThresholdBytes: factoryOptions.inlineThresholdBytes
35
+ });
31
36
  },
32
37
  tableIdStrings: false
33
38
  };