@powersync/service-module-mongodb-storage 0.20.0 → 0.22.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 (192) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +36 -3
  3. package/dist/storage/MongoBucketStorage.js +372 -207
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
  6. package/dist/storage/implementation/MongoBucketBatch.js +44 -17
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
  9. package/dist/storage/implementation/MongoCompactor.js +27 -238
  10. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  11. package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
  12. package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
  13. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
  15. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
  16. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  17. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
  18. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  19. package/dist/storage/implementation/MongoStorageProvider.js +7 -3
  20. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  21. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
  22. package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
  23. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  24. package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
  25. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  26. package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
  27. package/dist/storage/implementation/MongoWriteBatch.js +149 -0
  28. package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
  29. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
  30. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
  31. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  32. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  33. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  34. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  35. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
  36. package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
  37. package/dist/storage/implementation/common/PersistedBatch.js +49 -6
  38. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  39. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
  40. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
  41. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  42. package/dist/storage/implementation/db.d.ts +9 -1
  43. package/dist/storage/implementation/db.js +27 -1
  44. package/dist/storage/implementation/db.js.map +1 -1
  45. package/dist/storage/implementation/models.d.ts +31 -0
  46. package/dist/storage/implementation/models.js.map +1 -1
  47. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
  48. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
  49. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  50. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
  51. package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
  52. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  53. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  54. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  55. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  56. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
  57. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
  58. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  59. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
  60. package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
  61. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  62. package/dist/storage/implementation/v1/models.js +1 -0
  63. package/dist/storage/implementation/v1/models.js.map +1 -1
  64. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  65. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  66. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  67. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +10 -2
  68. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
  69. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
  71. package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
  72. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
  74. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
  75. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  76. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
  77. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
  78. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  79. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
  80. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
  81. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  83. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  84. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  85. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
  86. package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
  87. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  88. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
  89. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
  90. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  91. package/dist/storage/implementation/v3/bucket-format.js +2 -0
  92. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  93. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  94. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  95. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  96. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  97. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  98. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  99. package/dist/storage/implementation/v3/models.d.ts +84 -4
  100. package/dist/storage/implementation/v3/models.js.map +1 -1
  101. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
  102. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
  103. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  104. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
  105. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
  106. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
  107. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  108. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
  109. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
  110. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  111. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
  112. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
  113. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  114. package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
  115. package/dist/storage/implementation/v3/source-table-utils.js +30 -37
  116. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  117. package/dist/storage/storage-index.d.ts +2 -1
  118. package/dist/storage/storage-index.js +1 -0
  119. package/dist/storage/storage-index.js.map +1 -1
  120. package/dist/types/types.d.ts +28 -1
  121. package/dist/types/types.js +28 -7
  122. package/dist/types/types.js.map +1 -1
  123. package/dist/utils/test-utils.js +1 -0
  124. package/dist/utils/test-utils.js.map +1 -1
  125. package/dist/utils/util.js +3 -1
  126. package/dist/utils/util.js.map +1 -1
  127. package/package.json +10 -9
  128. package/src/storage/MongoBucketStorage.ts +507 -269
  129. package/src/storage/implementation/MongoBucketBatch.ts +67 -24
  130. package/src/storage/implementation/MongoCompactor.ts +35 -346
  131. package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
  132. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
  133. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
  134. package/src/storage/implementation/MongoStorageProvider.ts +15 -3
  135. package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
  136. package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
  137. package/src/storage/implementation/MongoWriteBatch.ts +172 -0
  138. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
  139. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  140. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
  141. package/src/storage/implementation/common/PersistedBatch.ts +66 -10
  142. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
  143. package/src/storage/implementation/db.ts +35 -2
  144. package/src/storage/implementation/models.ts +34 -0
  145. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
  146. package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
  147. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  148. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
  149. package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
  150. package/src/storage/implementation/v1/models.ts +1 -0
  151. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  152. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
  153. package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
  154. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
  155. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
  156. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
  157. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  158. package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
  159. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
  160. package/src/storage/implementation/v3/bucket-format.ts +2 -0
  161. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  162. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  163. package/src/storage/implementation/v3/models.ts +91 -3
  164. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
  165. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
  166. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
  167. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
  168. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
  169. package/src/storage/implementation/v3/source-table-utils.ts +39 -41
  170. package/src/storage/storage-index.ts +2 -0
  171. package/src/types/types.ts +42 -7
  172. package/src/utils/test-utils.ts +1 -0
  173. package/src/utils/util.ts +3 -1
  174. package/test/src/__snapshots__/storage.test.ts.snap +25 -0
  175. package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
  176. package/test/src/chunk_compaction_config.test.ts +38 -0
  177. package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
  178. package/test/src/compact-utils.test.ts +226 -0
  179. package/test/src/helpers/MemoryObjectStorage.ts +14 -6
  180. package/test/src/object_storage_usage.test.ts +267 -0
  181. package/test/src/parameter_compacting_v1.test.ts +120 -0
  182. package/test/src/parameter_compacting_v3.test.ts +192 -0
  183. package/test/src/parameter_compaction_fence.test.ts +276 -0
  184. package/test/src/storage.test.ts +273 -22
  185. package/test/src/storage_compacting.test.ts +968 -246
  186. package/test/src/storage_s3_checksums.test.ts +3 -4
  187. package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
  188. package/test/src/storage_s3_reading.test.ts +290 -2
  189. package/test/src/storage_s3_writing.test.ts +1 -0
  190. package/test/src/storage_sync.test.ts +336 -41
  191. package/test/src/util.ts +1 -1
  192. package/tsconfig.tsbuildinfo +1 -1
@@ -3,6 +3,8 @@ import { BucketDefinitionMapping, ColumnDescriptor, JsonValue, storage } from '@
3
3
  import {
4
4
  BucketDataSource,
5
5
  BucketDefinitionId,
6
+ EventDefinitionId,
7
+ HydratedEventDescriptor,
6
8
  HydratedSyncConfig,
7
9
  MatchingSources,
8
10
  ParameterIndexId,
@@ -22,12 +24,13 @@ export interface SourceTableIdentity {
22
24
  export interface SourceTableMembershipIds {
23
25
  bucketDataSourceIds: BucketDefinitionId[];
24
26
  parameterLookupSourceIds: ParameterIndexId[];
27
+ eventDefinitionIds: EventDefinitionId[];
25
28
  }
26
29
 
27
30
  export interface SourceTableDesiredResolution {
28
31
  bucketSourceById: Map<BucketDefinitionId, BucketDataSource>;
29
32
  parameterLookupSourceById: Map<ParameterIndexId, ParameterIndexLookupCreator>;
30
- triggersEvent: boolean;
33
+ eventDefinitionById: Map<EventDefinitionId, HydratedEventDescriptor>;
31
34
  }
32
35
 
33
36
  export interface SourceTableReconciliationContext {
@@ -55,7 +58,7 @@ export interface SourceTableReconciliationPlan {
55
58
  narrowingUpdates: SourceTableMembershipUpdate[];
56
59
  /**
57
60
  * Memberships for a new source-table doc covering desired ids no existing doc covers,
58
- * or null if no new doc is needed. Empty memberships indicate an event-only table.
61
+ * or null if no new doc is needed.
59
62
  */
60
63
  newTableMemberships: SourceTableMembershipIds | null;
61
64
  /** Identity-overlapping docs that conflict with the current identity and must be dropped. */
@@ -75,7 +78,8 @@ export interface NewSourceTable {
75
78
  export function sourceTableDesiredResolution(
76
79
  syncConfig: HydratedSyncConfig,
77
80
  ref: SourceTableRef,
78
- mapping: BucketDefinitionMapping
81
+ mapping: BucketDefinitionMapping,
82
+ eventById: ReadonlyMap<EventDefinitionId, HydratedEventDescriptor>
79
83
  ): SourceTableDesiredResolution {
80
84
  const matchingSources = syncConfig.getMatchingSources(ref);
81
85
  return {
@@ -85,7 +89,7 @@ export function sourceTableDesiredResolution(
85
89
  parameterLookupSourceById: new Map(
86
90
  matchingSources.parameterLookupSources.map((source) => [mapping.parameterLookupId(source), source] as const)
87
91
  ),
88
- triggersEvent: syncConfig.tableTriggersEvent(ref)
92
+ eventDefinitionById: new Map([...eventById].filter(([, event]) => event.tableTriggersEvent(ref)))
89
93
  };
90
94
  }
91
95
 
@@ -116,28 +120,33 @@ function intersectMembershipIds(
116
120
  ): SourceTableMembershipIds {
117
121
  return {
118
122
  bucketDataSourceIds: doc.bucket_data_source_ids.filter((id) => desired.bucketSourceById.has(id)),
119
- parameterLookupSourceIds: doc.parameter_lookup_source_ids.filter((id) => desired.parameterLookupSourceById.has(id))
123
+ parameterLookupSourceIds: doc.parameter_lookup_source_ids.filter((id) => desired.parameterLookupSourceById.has(id)),
124
+ eventDefinitionIds: doc.event_definition_ids.filter((id) => desired.eventDefinitionById.has(id))
120
125
  };
121
126
  }
122
127
 
123
128
  function hasMembershipIds(memberships: SourceTableMembershipIds) {
124
- return memberships.bucketDataSourceIds.length > 0 || memberships.parameterLookupSourceIds.length > 0;
129
+ return (
130
+ memberships.bucketDataSourceIds.length > 0 ||
131
+ memberships.parameterLookupSourceIds.length > 0 ||
132
+ memberships.eventDefinitionIds.length > 0
133
+ );
125
134
  }
126
135
 
127
136
  function sameMembershipIds(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
128
137
  return (
129
138
  sameStringArray(doc.bucket_data_source_ids, memberships.bucketDataSourceIds) &&
130
- sameStringArray(doc.parameter_lookup_source_ids, memberships.parameterLookupSourceIds)
139
+ sameStringArray(doc.parameter_lookup_source_ids, memberships.parameterLookupSourceIds) &&
140
+ sameStringArray(doc.event_definition_ids, memberships.eventDefinitionIds)
131
141
  );
132
142
  }
133
143
 
134
144
  class SourceTableReconciliationPlanner {
135
145
  private readonly coveredBucketDataSourceIds = new Set<string>();
136
146
  private readonly coveredParameterLookupSourceIds = new Set<string>();
137
- private readonly retainedDocIds = new Set<string>();
147
+ private readonly coveredEventDefinitionIds = new Set<string>();
138
148
  private readonly tables: storage.SourceTable[] = [];
139
149
  private readonly narrowingUpdates: SourceTableMembershipUpdate[] = [];
140
- private retainedEventOnlyTable = false;
141
150
 
142
151
  constructor(private readonly context: SourceTableReconciliationContext) {}
143
152
 
@@ -168,24 +177,15 @@ class SourceTableReconciliationPlanner {
168
177
  private retainDoc(doc: SourceTableDocumentV3) {
169
178
  const memberships = intersectMembershipIds(doc, this.context.desired);
170
179
  const coversDesiredMembership = hasMembershipIds(memberships);
171
- const coversEventOnlyTable =
172
- !this.desiredHasMembership() && this.context.desired.triggersEvent && !this.retainedEventOnlyTable;
173
180
 
174
181
  this.recordCoverage(doc, memberships);
175
- this.planNarrowingUpdate(doc, memberships, coversDesiredMembership, coversEventOnlyTable);
182
+ this.planNarrowingUpdate(doc, memberships, coversDesiredMembership);
176
183
 
177
- if (coversDesiredMembership || coversEventOnlyTable) {
178
- this.retainedEventOnlyTable ||= coversEventOnlyTable;
179
- this.retainedDocIds.add(doc._id.toHexString());
184
+ if (coversDesiredMembership) {
180
185
  this.tables.push(this.sourceTableFor(doc, memberships));
181
186
  }
182
187
  }
183
188
 
184
- private desiredHasMembership() {
185
- const { desired } = this.context;
186
- return desired.bucketSourceById.size > 0 || desired.parameterLookupSourceById.size > 0;
187
- }
188
-
189
189
  private recordCoverage(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
190
190
  this.addCoverage(doc, 'bucket data source', this.coveredBucketDataSourceIds, memberships.bucketDataSourceIds);
191
191
  this.addCoverage(
@@ -194,12 +194,14 @@ class SourceTableReconciliationPlanner {
194
194
  this.coveredParameterLookupSourceIds,
195
195
  memberships.parameterLookupSourceIds
196
196
  );
197
+ this.addCoverage(doc, 'event definition', this.coveredEventDefinitionIds, memberships.eventDefinitionIds);
197
198
  }
198
199
 
199
200
  // Membership sets must be pairwise disjoint across the docs of one physical table:
200
- // each desired id is covered by exactly one doc, so each definition receives each
201
- // row exactly once. The algorithm maintains this (new docs only get uncovered ids,
202
- // narrowing only removes ids) - overlap means the persisted state is corrupt.
201
+ // each desired id is covered by exactly one doc, so each definition is evaluated through
202
+ // only one SourceTable for a physical row. Different docs may own different event ids.
203
+ // The algorithm maintains this (new docs only get uncovered ids, narrowing only removes ids) -
204
+ // overlap means the persisted state is corrupt.
203
205
  private addCoverage(doc: SourceTableDocumentV3, kind: string, covered: Set<string>, ids: string[]) {
204
206
  const { schema, name } = this.context.identity;
205
207
  for (const id of ids) {
@@ -215,11 +217,9 @@ class SourceTableReconciliationPlanner {
215
217
  private planNarrowingUpdate(
216
218
  doc: SourceTableDocumentV3,
217
219
  memberships: SourceTableMembershipIds,
218
- coversDesiredMembership: boolean,
219
- coversEventOnlyTable: boolean
220
+ coversDesiredMembership: boolean
220
221
  ) {
221
- const shouldNarrow =
222
- (coversDesiredMembership || coversEventOnlyTable) && !doc.snapshot_done && !sameMembershipIds(doc, memberships);
222
+ const shouldNarrow = coversDesiredMembership && !doc.snapshot_done && !sameMembershipIds(doc, memberships);
223
223
 
224
224
  if (!shouldNarrow) {
225
225
  return;
@@ -239,9 +239,12 @@ class SourceTableReconciliationPlanner {
239
239
  ),
240
240
  parameterLookupSourceIds: [...desired.parameterLookupSourceById.keys()].filter(
241
241
  (id) => !this.coveredParameterLookupSourceIds.has(id)
242
+ ),
243
+ eventDefinitionIds: [...desired.eventDefinitionById.keys()].filter(
244
+ (id) => !this.coveredEventDefinitionIds.has(id)
242
245
  )
243
246
  };
244
- if (hasMembershipIds(uncovered) || (desired.triggersEvent && this.tables.length == 0)) {
247
+ if (hasMembershipIds(uncovered)) {
245
248
  return uncovered;
246
249
  }
247
250
  return null;
@@ -254,6 +257,7 @@ class SourceTableReconciliationPlanner {
254
257
  connectionTag,
255
258
  syncConfig,
256
259
  mapping,
260
+ desired.eventDefinitionById,
257
261
  matchingSourcesFor(desired, memberships),
258
262
  memberships
259
263
  );
@@ -306,6 +310,7 @@ export function createNewSourceTable(
306
310
  snapshot_status: undefined,
307
311
  bucket_data_source_ids: memberships.bucketDataSourceIds,
308
312
  parameter_lookup_source_ids: memberships.parameterLookupSourceIds,
313
+ event_definition_ids: memberships.eventDefinitionIds,
309
314
  // Records created together share the same source metadata.
310
315
  source_metadata: newTableSourceMetadata
311
316
  };
@@ -314,6 +319,7 @@ export function createNewSourceTable(
314
319
  connectionTag,
315
320
  syncConfig,
316
321
  mapping,
322
+ desired.eventDefinitionById,
317
323
  matchingSourcesFor(desired, memberships),
318
324
  memberships
319
325
  );
@@ -322,17 +328,6 @@ export function createNewSourceTable(
322
328
  return { doc, table };
323
329
  }
324
330
 
325
- export function designateEventCarrier(tables: storage.SourceTable[], triggersEvent: boolean) {
326
- if (!triggersEvent) {
327
- return;
328
- }
329
-
330
- const eventCarrier = tables.find((table) => table.snapshotComplete) ?? tables[0];
331
- for (const table of tables) {
332
- table.syncEvent = table === eventCarrier;
333
- }
334
- }
335
-
336
331
  function sourceTableMembershipsFromDocument(
337
332
  doc: SourceTableDocumentV3,
338
333
  syncConfig: HydratedSyncConfig,
@@ -356,6 +351,7 @@ export function sourceTableFromDocument(
356
351
  connectionTag: string,
357
352
  syncConfig: HydratedSyncConfig,
358
353
  mapping: BucketDefinitionMapping,
354
+ eventById: ReadonlyMap<EventDefinitionId, HydratedEventDescriptor>,
359
355
  memberships?: MatchingSources,
360
356
  membershipIds?: SourceTableMembershipIds
361
357
  ): storage.SourceTable {
@@ -364,7 +360,8 @@ export function sourceTableFromDocument(
364
360
  bucketDataSourceIds: resolvedMemberships.bucketDataSources.map((source) => mapping.bucketSourceId(source)),
365
361
  parameterLookupSourceIds: resolvedMemberships.parameterLookupSources.map((source) =>
366
362
  mapping.parameterLookupId(source)
367
- )
363
+ ),
364
+ eventDefinitionIds: doc.event_definition_ids.filter((id) => eventById.has(id))
368
365
  };
369
366
  const table = new storage.SourceTable({
370
367
  id: doc._id,
@@ -383,11 +380,12 @@ export function sourceTableFromDocument(
383
380
  parameterLookupSources: resolvedMemberships.parameterLookupSources,
384
381
  bucketDataSourceIds: new Set(resolvedMembershipIds.bucketDataSourceIds),
385
382
  parameterLookupSourceIds: new Set(resolvedMembershipIds.parameterLookupSourceIds),
383
+ eventDefinitionIds: new Set(resolvedMembershipIds.eventDefinitionIds),
386
384
  sourceMetadata: doc.source_metadata ?? null
387
385
  });
388
386
  table.syncData = table.bucketDataSources.length > 0;
389
387
  table.syncParameters = table.parameterLookupSources.length > 0;
390
- table.syncEvent = syncConfig.tableTriggersEvent(table.ref);
388
+ table.syncEvent = table.eventDefinitionIds!.size > 0;
391
389
  table.snapshotStatus =
392
390
  doc.snapshot_status == null
393
391
  ? undefined
@@ -15,11 +15,13 @@ export { loadBucketDataDocument, serializeBucketData } from './implementation/v3
15
15
  export {
16
16
  BucketDataDocumentV3,
17
17
  BucketOperation,
18
+ ObjectStorageUsageDocument,
18
19
  RecordedLookupV3,
19
20
  ReplicationStreamDocumentV3,
20
21
  SyncConfigDefinition,
21
22
  SyncRuleConfigStateV3,
22
23
  taggedBucketParameterDocumentToTagged
23
24
  } from './implementation/v3/models.js';
25
+ export * from './implementation/v3/object-storage/ObjectStorageUsage.js';
24
26
  export * from './MongoBucketStorage.js';
25
27
  export * from './MongoReportStorage.js';
@@ -3,6 +3,20 @@ import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
3
3
  import * as service_types from '@powersync/service-types';
4
4
  import * as t from 'ts-codec';
5
5
 
6
+ /**
7
+ * AWS defaults mode, as used by the AWS SDK.
8
+ *
9
+ * This is the baseline for the object storage request timeouts, so that they do not each need to be
10
+ * configured individually.
11
+ *
12
+ * The SDK's `legacy` and `auto` modes are not selectable here: `legacy` defines no timeouts at all,
13
+ * and `auto` makes the timeouts depend on where the process happens to be running. Both are still
14
+ * handled when they come from the AWS environment, and are then treated as `standard`.
15
+ */
16
+ export const S3DefaultsMode = service_types.enumLiteral('standard', 'in-region', 'cross-region', 'mobile');
17
+
18
+ export type S3DefaultsMode = t.Encoded<typeof S3DefaultsMode>;
19
+
6
20
  const S3ObjectStorageConfig = t.object({
7
21
  type: t.literal('s3'),
8
22
  bucket: t.string,
@@ -13,21 +27,24 @@ const S3ObjectStorageConfig = t.object({
13
27
  access_key_id: t.string.optional(),
14
28
  secret_access_key: t.string.optional(),
15
29
  concurrency_limit: t.number.optional(),
30
+ // Defaults to the AWS_DEFAULTS_MODE environment variable.
31
+ defaults_mode: S3DefaultsMode.optional(),
16
32
  // Chunks whose BSON-serialized size falls below this byte threshold
17
33
  // stay inline in MongoDB instead of being offloaded to S3. Default 1024.
18
34
  inline_threshold_bytes: t.number.optional()
19
35
  });
20
36
 
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'));
37
+ export const MongoStorageReadPreference = service_types.enumLiteral(
38
+ 'primary',
39
+ 'primaryPreferred',
40
+ 'secondary',
41
+ 'secondaryPreferred',
42
+ 'nearest'
43
+ );
27
44
 
28
45
  export type MongoStorageReadPreference = t.Encoded<typeof MongoStorageReadPreference>;
29
46
 
30
- export const MongoStorageConfig = lib_mongo.BaseMongoConfig.and(
47
+ export const MongoStorageConfig = service_types.configFile.BaseStorageConfig.and(lib_mongo.BaseMongoConfig).and(
31
48
  t.object({
32
49
  /**
33
50
  * Read preference for bulk checksum and bucket data reads.
@@ -48,6 +65,9 @@ export const MongoStorageConfig = lib_mongo.BaseMongoConfig.and(
48
65
  */
49
66
  clear_batch_throttle_rate: t.number.optional(),
50
67
 
68
+ /** Maximum concurrent buckets across V3 chunk-compaction jobs. Range: 1–64. Default: 4 with object storage, otherwise 2. */
69
+ chunk_compaction_concurrency: t.number.optional(),
70
+
51
71
  object_storage: S3ObjectStorageConfig.optional()
52
72
  })
53
73
  );
@@ -56,6 +76,21 @@ export type MongoStorageConfig = t.Encoded<typeof MongoStorageConfig>;
56
76
  export type MongoStorageConfigDecoded = t.Decoded<typeof MongoStorageConfig>;
57
77
 
58
78
  export const DEFAULT_CLEAR_BATCH_THROTTLE_RATE = 0.2;
79
+ export const DEFAULT_CHUNK_COMPACTION_CONCURRENCY = 2;
80
+ export const DEFAULT_OBJECT_STORAGE_CHUNK_COMPACTION_CONCURRENCY = 4;
81
+
82
+ export function normalizeChunkCompactionConcurrency(value: number | undefined, hasObjectStorage = false): number {
83
+ const concurrency =
84
+ value ??
85
+ (hasObjectStorage ? DEFAULT_OBJECT_STORAGE_CHUNK_COMPACTION_CONCURRENCY : DEFAULT_CHUNK_COMPACTION_CONCURRENCY);
86
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 64) {
87
+ throw new ServiceError(
88
+ ErrorCode.PSYNC_S3201,
89
+ 'storage.chunk_compaction_concurrency must be an integer between 1 and 64'
90
+ );
91
+ }
92
+ return concurrency;
93
+ }
59
94
 
60
95
  export function normalizeClearBatchThrottleRate(value: number | undefined): number {
61
96
  const rate = value ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE;
@@ -31,6 +31,7 @@ export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorag
31
31
  checksumOptions: factoryOptions.checksumOptions,
32
32
  supportsMultipleSyncConfigs: factoryOptions.supportsMultipleSyncConfigs,
33
33
  objectStorage: factoryOptions.objectStorage,
34
+ chunkCompactionConcurrency: factoryOptions.chunkCompactionConcurrency,
34
35
  inlineThresholdBytes: factoryOptions.inlineThresholdBytes
35
36
  });
36
37
  },
package/src/utils/util.ts CHANGED
@@ -121,7 +121,9 @@ export function mapOpEntry(row: BucketDataDoc): utils.OplogEntry {
121
121
  object_type: row.table,
122
122
  object_id: row.row_id,
123
123
  checksum: Number(row.checksum),
124
- subkey: replicaIdToSubkey(row.source_table!, row.source_key!),
124
+ // Storage V3 always persists this during replication. The fallback serves V1 data
125
+ // written before subkey persistence was introduced.
126
+ subkey: row.subkey ?? replicaIdToSubkey(row.source_table!, row.source_key!),
125
127
  data: row.data
126
128
  };
127
129
  } else {
@@ -75,6 +75,31 @@ exports[`Mongo Sync Bucket Storage - Data - v3 > (insert, delete, insert), (dele
75
75
  ]
76
76
  `;
77
77
 
78
+ exports[`Mongo Sync Bucket Storage - Data - v4 > (insert, delete, insert), (delete) 1`] = `
79
+ [
80
+ {
81
+ "checksum": 2871785649,
82
+ "object_id": "test1",
83
+ "op": "PUT",
84
+ },
85
+ {
86
+ "checksum": 2872534815,
87
+ "object_id": "test1",
88
+ "op": "REMOVE",
89
+ },
90
+ {
91
+ "checksum": 2871785649,
92
+ "object_id": "test1",
93
+ "op": "PUT",
94
+ },
95
+ {
96
+ "checksum": 2872534815,
97
+ "object_id": "test1",
98
+ "op": "REMOVE",
99
+ },
100
+ ]
101
+ `;
102
+
78
103
  exports[`Mongo Sync Bucket Storage - split buckets > (insert, delete, insert), (delete) 1`] = `
79
104
  [
80
105
  {