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

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 (213) hide show
  1. package/CHANGELOG.md +129 -7
  2. package/dist/storage/MongoBucketStorage.d.ts +42 -5
  3. package/dist/storage/MongoBucketStorage.js +404 -215
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +39 -14
  6. package/dist/storage/implementation/MongoBucketBatch.js +169 -98
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +21 -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/MongoIdSequence.d.ts +10 -1
  12. package/dist/storage/implementation/MongoIdSequence.js +14 -1
  13. package/dist/storage/implementation/MongoIdSequence.js.map +1 -1
  14. package/dist/storage/implementation/MongoOpIdAllocator.d.ts +27 -0
  15. package/dist/storage/implementation/MongoOpIdAllocator.js +97 -0
  16. package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -0
  17. package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
  18. package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
  19. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  20. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
  21. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +35 -2
  22. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  23. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +2 -1
  24. package/dist/storage/implementation/MongoPersistedReplicationStream.js +7 -4
  25. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -1
  26. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +3 -3
  27. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +10 -8
  28. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  29. package/dist/storage/implementation/MongoStorageProvider.js +8 -4
  30. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  31. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +32 -11
  32. package/dist/storage/implementation/MongoSyncBucketStorage.js +95 -31
  33. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +26 -2
  35. package/dist/storage/implementation/MongoSyncRulesLock.js +59 -10
  36. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  37. package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
  38. package/dist/storage/implementation/MongoWriteBatch.js +149 -0
  39. package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  44. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  45. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  46. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
  47. package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
  48. package/dist/storage/implementation/common/PersistedBatch.js +57 -14
  49. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  50. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
  51. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
  52. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  53. package/dist/storage/implementation/db.d.ts +9 -1
  54. package/dist/storage/implementation/db.js +28 -1
  55. package/dist/storage/implementation/db.js.map +1 -1
  56. package/dist/storage/implementation/models.d.ts +35 -0
  57. package/dist/storage/implementation/models.js.map +1 -1
  58. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +11 -0
  59. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +227 -221
  60. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  61. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
  62. package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
  63. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  64. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  65. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  66. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  67. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
  68. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +66 -17
  69. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  70. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
  71. package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
  72. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  73. package/dist/storage/implementation/v1/models.js +1 -0
  74. package/dist/storage/implementation/v1/models.js.map +1 -1
  75. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  76. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  77. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  78. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +14 -2
  79. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +261 -194
  80. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  81. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +83 -25
  82. package/dist/storage/implementation/v3/MongoCompactorV3.js +702 -159
  83. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  84. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
  85. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
  86. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  87. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
  88. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
  89. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  90. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
  91. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +92 -32
  92. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  93. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  94. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  95. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  96. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
  97. package/dist/storage/implementation/v3/PersistedBatchV3.js +77 -45
  98. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  99. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
  100. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
  101. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  102. package/dist/storage/implementation/v3/bucket-format.js +2 -0
  103. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  104. package/dist/storage/implementation/v3/compact-utils.d.ts +108 -0
  105. package/dist/storage/implementation/v3/compact-utils.js +223 -0
  106. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  107. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  108. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  109. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  110. package/dist/storage/implementation/v3/models.d.ts +90 -6
  111. package/dist/storage/implementation/v3/models.js.map +1 -1
  112. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
  113. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
  114. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  115. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
  116. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
  117. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
  118. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  119. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
  120. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
  121. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  122. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
  123. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
  124. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  125. package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
  126. package/dist/storage/implementation/v3/source-table-utils.js +49 -57
  127. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  128. package/dist/storage/storage-index.d.ts +2 -1
  129. package/dist/storage/storage-index.js +1 -0
  130. package/dist/storage/storage-index.js.map +1 -1
  131. package/dist/types/types.d.ts +28 -1
  132. package/dist/types/types.js +28 -7
  133. package/dist/types/types.js.map +1 -1
  134. package/dist/utils/test-utils.d.ts +4 -2
  135. package/dist/utils/test-utils.js +7 -4
  136. package/dist/utils/test-utils.js.map +1 -1
  137. package/dist/utils/util.js +3 -1
  138. package/dist/utils/util.js.map +1 -1
  139. package/package.json +10 -9
  140. package/src/storage/MongoBucketStorage.ts +545 -277
  141. package/src/storage/implementation/MongoBucketBatch.ts +258 -124
  142. package/src/storage/implementation/MongoCompactor.ts +37 -346
  143. package/src/storage/implementation/MongoIdSequence.ts +21 -1
  144. package/src/storage/implementation/MongoOpIdAllocator.ts +105 -0
  145. package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
  146. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +46 -1
  147. package/src/storage/implementation/MongoPersistedReplicationStream.ts +12 -4
  148. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +48 -29
  149. package/src/storage/implementation/MongoStorageProvider.ts +16 -4
  150. package/src/storage/implementation/MongoSyncBucketStorage.ts +133 -40
  151. package/src/storage/implementation/MongoSyncRulesLock.ts +78 -12
  152. package/src/storage/implementation/MongoWriteBatch.ts +172 -0
  153. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
  154. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  155. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
  156. package/src/storage/implementation/common/PersistedBatch.ts +76 -20
  157. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
  158. package/src/storage/implementation/db.ts +36 -2
  159. package/src/storage/implementation/models.ts +39 -0
  160. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +272 -254
  161. package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
  162. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  163. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +88 -28
  164. package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
  165. package/src/storage/implementation/v1/models.ts +1 -0
  166. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  167. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +339 -247
  168. package/src/storage/implementation/v3/MongoCompactorV3.ts +955 -225
  169. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
  170. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
  171. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +112 -42
  172. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  173. package/src/storage/implementation/v3/PersistedBatchV3.ts +94 -51
  174. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
  175. package/src/storage/implementation/v3/bucket-format.ts +2 -0
  176. package/src/storage/implementation/v3/compact-utils.ts +329 -0
  177. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  178. package/src/storage/implementation/v3/models.ts +97 -4
  179. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
  180. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
  181. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
  182. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
  183. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
  184. package/src/storage/implementation/v3/source-table-utils.ts +82 -76
  185. package/src/storage/storage-index.ts +2 -0
  186. package/src/types/types.ts +42 -7
  187. package/src/utils/test-utils.ts +9 -5
  188. package/src/utils/util.ts +3 -1
  189. package/test/src/__snapshots__/storage.test.ts.snap +25 -0
  190. package/test/src/__snapshots__/storage_sync.test.ts.snap +794 -205
  191. package/test/src/chunk_compaction_config.test.ts +38 -0
  192. package/test/src/cleanup-stopped-sync-configs.test.ts +59 -26
  193. package/test/src/compact-utils.test.ts +251 -0
  194. package/test/src/concurrent_writers.test.ts +599 -0
  195. package/test/src/connection_config.test.ts +159 -0
  196. package/test/src/helpers/MemoryObjectStorage.ts +14 -6
  197. package/test/src/helpers/concurrentWriter.mjs +29 -0
  198. package/test/src/helpers/s3TestFactory.ts +12 -12
  199. package/test/src/object_storage_usage.test.ts +268 -0
  200. package/test/src/parameter_compacting_v1.test.ts +120 -0
  201. package/test/src/parameter_compacting_v3.test.ts +192 -0
  202. package/test/src/parameter_compaction_fence.test.ts +276 -0
  203. package/test/src/storage.test.ts +280 -24
  204. package/test/src/storage_compacting.test.ts +1097 -253
  205. package/test/src/storage_object_storage_inline_threshold.test.ts +3 -3
  206. package/test/src/storage_s3_checksums.test.ts +6 -7
  207. package/test/src/storage_s3_compaction_lifecycle.test.ts +143 -17
  208. package/test/src/storage_s3_reading.test.ts +294 -6
  209. package/test/src/storage_s3_writing.test.ts +4 -3
  210. package/test/src/storage_sync.test.ts +412 -80
  211. package/test/src/storeCurrentData.test.ts +5 -5
  212. package/test/src/util.ts +1 -1
  213. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,223 @@
1
+ import { ReplicationAssertionError } from '@powersync/lib-services-framework';
2
+ import { addChecksums } from '@powersync/service-core';
3
+ import { AVAILABLE_LEASE_EXPR } from './CompactionLease.js';
4
+ /**
5
+ * Perform chunk compaction if at least this many chunks have been added since
6
+ * the latest compaction.
7
+ *
8
+ * A lower value increases compaction frequency and can increase chunk rewrites
9
+ * and object-storage operations. A higher value leaves more small chunks for
10
+ * longer, which can hurt sync performance.
11
+ */
12
+ const MERGE_CHUNKS_THRESHOLD = 8;
13
+ export const FULL_COMPACT_RESCHEDULE_MARGIN_MS = 60 * 1000;
14
+ export var CompactionKind;
15
+ (function (CompactionKind) {
16
+ CompactionKind["Full"] = "full";
17
+ CompactionKind["Chunks"] = "chunks";
18
+ })(CompactionKind || (CompactionKind = {}));
19
+ export class CompactionContext {
20
+ lease;
21
+ kind;
22
+ decision;
23
+ rescheduleNotBefore;
24
+ targetOp;
25
+ constructor(lease, kind, decision, rescheduleNotBefore, targetOp) {
26
+ this.lease = lease;
27
+ this.kind = kind;
28
+ this.decision = decision;
29
+ this.rescheduleNotBefore = rescheduleNotBefore;
30
+ this.targetOp = targetOp;
31
+ }
32
+ get state() {
33
+ return this.lease.state;
34
+ }
35
+ get startedAt() {
36
+ return this.lease.startedAt;
37
+ }
38
+ get lastOp() {
39
+ return this.lease.lastOp;
40
+ }
41
+ }
42
+ export function bucketStats(state) {
43
+ return {
44
+ count: state.bucket_stats.count,
45
+ bytes: state.bucket_stats.bytes,
46
+ chunks: state.bucket_stats.chunks
47
+ };
48
+ }
49
+ export function emptyBucketStats() {
50
+ return { count: 0, bytes: 0n, chunks: 0, checksum: 0 };
51
+ }
52
+ export function statsForDocument(document) {
53
+ return {
54
+ count: document.count,
55
+ bytes: BigInt(document.size),
56
+ chunks: 1,
57
+ checksum: addChecksums(0, Number(document.checksum))
58
+ };
59
+ }
60
+ export function statsForDocuments(documents) {
61
+ let result = emptyBucketStats();
62
+ for (const document of documents) {
63
+ result = combineAdjacentStats(result, statsForDocument(document));
64
+ }
65
+ return result;
66
+ }
67
+ /** A scheduled bucket always has writes awaiting a full compact. */
68
+ export function firstUncompactedWrite(state) {
69
+ if (state.first_uncompacted_write == null) {
70
+ throw new ReplicationAssertionError(`Scheduled V3 bucket ${state._id.b} has no first uncompacted write`);
71
+ }
72
+ return state.first_uncompacted_write;
73
+ }
74
+ export function combineChunkStats(previous, compactedTail, overlappingCompactedChunk) {
75
+ return {
76
+ count: previous.count - overlappingCompactedChunk.count + compactedTail.count,
77
+ bytes: previous.bytes - overlappingCompactedChunk.bytes + compactedTail.bytes,
78
+ chunks: previous.chunks - 1 + compactedTail.chunks,
79
+ checksum: addChecksums(addChecksums(Number(previous.checksum), -overlappingCompactedChunk.checksum), compactedTail.checksum)
80
+ };
81
+ }
82
+ export function combineAdjacentStats(first, second) {
83
+ return {
84
+ count: first.count + second.count,
85
+ bytes: first.bytes + second.bytes,
86
+ chunks: first.chunks + second.chunks,
87
+ checksum: addChecksums(first.checksum, second.checksum)
88
+ };
89
+ }
90
+ export function applyCompactionDelta(total, before, after) {
91
+ return {
92
+ count: total.count - before.count + after.count,
93
+ bytes: total.bytes - before.bytes + after.bytes,
94
+ chunks: total.chunks - before.chunks + after.chunks
95
+ };
96
+ }
97
+ /** Replace one stored range in an accumulated statistics snapshot. */
98
+ export function applyStatsReplacement(total, before, after) {
99
+ return {
100
+ ...applyCompactionDelta(total, before, after),
101
+ checksum: addChecksums(addChecksums(total.checksum, -before.checksum), after.checksum)
102
+ };
103
+ }
104
+ /**
105
+ * This checks that the bucket state hasn't changed by a concurrent write since we checked.
106
+ *
107
+ * If it has changed, we'll re-check in the next batch.
108
+ */
109
+ export function unclaimedSnapshotFilter(state) {
110
+ return {
111
+ _id: state._id,
112
+ last_op: state.last_op,
113
+ next_compact_check: state.next_compact_check,
114
+ first_uncompacted_write: state.first_uncompacted_write ?? { $exists: false },
115
+ bucket_stats: state.bucket_stats,
116
+ compacted_state: state.compacted_state ?? { $exists: false },
117
+ last_full_compact: state.last_full_compact ?? { $exists: false },
118
+ ...AVAILABLE_LEASE_EXPR
119
+ };
120
+ }
121
+ /**
122
+ * Calculate the earliest full compaction time from the first uncompacted
123
+ * write, bounded by the maximum retention interval.
124
+ */
125
+ export function fullCompactionCheckAt(state, config) {
126
+ const firstWrite = firstUncompactedWrite(state);
127
+ const stats = bucketStats(state);
128
+ const lastFull = state.last_full_compact;
129
+ // The number of operations since the last full compact.
130
+ // We may make this more specific in the future, to track new updates and deletes only, ignoring
131
+ // full new inserts, but that requires more granular tracking when replicating.
132
+ const uncompactedCount = lastFull == null ? stats.count : Math.max(0, stats.count - lastFull.count);
133
+ const compactedRows = lastFull?.puts ?? 0;
134
+ // If no full compact has ever been performed: ratio = 1, compact after minCompactFullIntervalMs.
135
+ // If every row has been updated or deleted exactly once since the last full compact: ratio = 0.5, compact after minCompactFullIntervalMs * 2.
136
+ // If 10% of rows has been updated since the last full compact, compact after minCompactFullIntervalMs * 11.
137
+ // If every row has been updated multiple times, the ratio tends closer to 1 again.
138
+ const ratio = uncompactedCount == 0 ? 0 : uncompactedCount / (compactedRows + uncompactedCount);
139
+ const fullIntervalMs = ratio == 0 ? config.maxCompactFullIntervalMs : config.minCompactFullIntervalMs / ratio;
140
+ return new Date(firstWrite.getTime() + Math.min(fullIntervalMs, config.maxCompactFullIntervalMs));
141
+ }
142
+ export function chooseCompactionKind(state, now, config) {
143
+ // For chunk compaction, we consider the number of chunks added.
144
+ // Right now, we trigger a compact if the interval has passed and at least a threshold of chunks were added.
145
+ // A future policy could also use bytes per chunk or records per chunk to
146
+ // decide when to compact.
147
+ const fullCheckAt = fullCompactionCheckAt(state, config);
148
+ // Schedule a little late so a worker using a slightly earlier clock does
149
+ // not wake before the exact full-compaction condition is true.
150
+ const fullCheckWithMargin = new Date(fullCheckAt.getTime() + FULL_COMPACT_RESCHEDULE_MARGIN_MS);
151
+ const compacted = state.compacted_state;
152
+ const shouldCompactChunks = hasEnoughChunksToMerge(state);
153
+ const canCheckChunks = compacted == null || now.getTime() - compacted.at.getTime() >= config.minCompactChunkIntervalMs;
154
+ // Too few new chunks cannot make chunk compaction eligible. Do not poll this
155
+ // bucket at the chunk-compaction interval; only wake it for its full-compact check.
156
+ const nextCompactCheck = !shouldCompactChunks
157
+ ? fullCheckWithMargin
158
+ : {
159
+ $min: [
160
+ fullCheckWithMargin,
161
+ { $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: config.minCompactChunkIntervalMs } }
162
+ ]
163
+ };
164
+ let kind = null;
165
+ if (now >= fullCheckAt) {
166
+ kind = CompactionKind.Full;
167
+ }
168
+ else if (canCheckChunks && shouldCompactChunks) {
169
+ kind = CompactionKind.Chunks;
170
+ }
171
+ return {
172
+ kind,
173
+ nextCompactCheck
174
+ };
175
+ }
176
+ export function chooseRequestedCompactionKind(state, requestedKind, config) {
177
+ if (requestedKind == null) {
178
+ return null;
179
+ }
180
+ // Initial replication bypasses the interval and never runs full compaction,
181
+ // but small buckets should still use the normal bulk-rescheduling path.
182
+ if (requestedKind == CompactionKind.Chunks && !config.forceChunkCompaction && !hasEnoughChunksToMerge(state)) {
183
+ return null;
184
+ }
185
+ const maxOpId = config.maxOpIdCap == null || state.last_op < config.maxOpIdCap ? state.last_op : config.maxOpIdCap;
186
+ return state.compacted_state == null || state.compacted_state.op_id < maxOpId ? requestedKind : null;
187
+ }
188
+ function hasEnoughChunksToMerge(state) {
189
+ return state.bucket_stats.chunks - (state.compacted_state?.chunks ?? 0) >= MERGE_CHUNKS_THRESHOLD;
190
+ }
191
+ /**
192
+ * Read one bounded prefix from a compaction cursor.
193
+ *
194
+ * The document that would cross the byte limit is deliberately not returned:
195
+ * pagination resumes past the last returned `_id`, so that document remains
196
+ * eligible for the next query. The first document is always accepted to
197
+ * ensure progress when a single document exceeds the configured byte limit.
198
+ *
199
+ * `hasMore` is conservative when the document limit is reached. An extra empty
200
+ * query is preferable to exhausting the cursor just to determine whether the
201
+ * limited MongoDB query contained another document.
202
+ */
203
+ export async function readCompactionBatch(cursor, options) {
204
+ const documents = [];
205
+ let cumulativeBytes = 0;
206
+ try {
207
+ for await (const document of cursor) {
208
+ if (documents.length > 0 && cumulativeBytes + document.size > options.byteLimit) {
209
+ return { documents, hasMore: true };
210
+ }
211
+ documents.push(document);
212
+ cumulativeBytes += document.size;
213
+ if (documents.length >= options.documentLimit) {
214
+ return { documents, hasMore: true };
215
+ }
216
+ }
217
+ return { documents, hasMore: false };
218
+ }
219
+ finally {
220
+ await cursor.close();
221
+ }
222
+ }
223
+ //# sourceMappingURL=compact-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-utils.js","sourceRoot":"","sources":["../../../../src/storage/implementation/v3/compact-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Writers use this delay before scheduling their first chunk compaction.
3
+ * Initial replication includes the same delay when it picks up that work.
4
+ */
5
+ export declare const DEFAULT_MIN_COMPACT_CHUNK_INTERVAL_MS: number;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Writers use this delay before scheduling their first chunk compaction.
3
+ * Initial replication includes the same delay when it picks up that work.
4
+ */
5
+ export const DEFAULT_MIN_COMPACT_CHUNK_INTERVAL_MS = 5 * 60 * 1000;
6
+ //# sourceMappingURL=compaction-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-constants.js","sourceRoot":"","sources":["../../../../src/storage/implementation/v3/compaction-constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC"}
@@ -1,7 +1,7 @@
1
- import { InternalOpId, PersistedDefinitionMapping, SerializedSyncPlan, SyncRuleState } from '@powersync/service-core';
2
- import { BucketDefinitionId, ParameterIndexId, ScopedParameterLookup, SqliteJsonValue } from '@powersync/service-sync-rules';
1
+ import { InternalOpId, JsonValue, PersistedDefinitionMapping, SerializedSyncPlan, SyncRuleState } from '@powersync/service-core';
2
+ import { BucketDefinitionId, EventDefinitionId, ParameterIndexId, ScopedParameterLookup, SqliteJsonValue } from '@powersync/service-sync-rules';
3
3
  import * as bson from 'bson';
4
- import { BucketDataKey, BucketParameterDocumentBase, BucketStateDocumentBase, CurrentBucket, OpType, ReplicaId, SourceTableDocumentSnapshotStatus, SourceTableKey, SyncRuleCheckpointFields, SyncRuleDocumentBase, TaggedBucketParameterDocument } from '../models.js';
4
+ import { BucketDataKey, BucketParameterDocumentBase, CurrentBucket, OpType, ReplicaId, SourceTableDocumentSnapshotStatus, SourceTableKey, SyncRuleCheckpointFields, SyncRuleDocumentBase, TaggedBucketParameterDocument } from '../models.js';
5
5
  /**
6
6
  * Embedded in sync_rules.sync_configs.
7
7
  */
@@ -63,7 +63,7 @@ export interface SyncConfigDefinition {
63
63
  created_at: Date;
64
64
  storage_version: number;
65
65
  /**
66
- * The related SyncRuleDocumentV3.
66
+ * The related ReplicationStreamDocumentV3.
67
67
  *
68
68
  * Note that a specific sync config definition never moves between replication streams. Instead, we can create a new copy for the new replication stream.
69
69
  *
@@ -71,6 +71,8 @@ export interface SyncConfigDefinition {
71
71
  */
72
72
  replication_stream_id: number;
73
73
  content: string;
74
+ /** Optional operator-supplied label identifying this sync config version. */
75
+ version_label?: string;
74
76
  serialized_plan?: SerializedSyncPlan | null;
75
77
  rule_mapping: PersistedDefinitionMapping;
76
78
  }
@@ -113,18 +115,68 @@ export interface SourceTableDocumentV3 {
113
115
  snapshot_status: SourceTableDocumentSnapshotStatus | undefined;
114
116
  bucket_data_source_ids: BucketDefinitionId[];
115
117
  parameter_lookup_source_ids: ParameterIndexId[];
118
+ /**
119
+ * Stream-local ids of the compiled event definitions evaluated by this source table.
120
+ */
121
+ event_definition_ids: EventDefinitionId[];
116
122
  latest_pending_delete?: InternalOpId | undefined;
123
+ /**
124
+ * Source-specific metadata. Absent for legacy records.
125
+ */
126
+ source_metadata?: JsonValue;
117
127
  }
118
- export interface BucketStateDocumentV3 extends BucketStateDocumentBase {
119
- _id: BucketStateDocumentBase['_id'] & {
128
+ export interface BucketStateDocumentV3 {
129
+ _id: {
130
+ b: string;
120
131
  d: BucketDefinitionId;
121
132
  };
133
+ /** Must always identify an actual operation in this logical stream. */
134
+ last_op: bigint;
135
+ /** The next time a compact worker should inspect this bucket. */
136
+ next_compact_check: Date | undefined;
137
+ /**
138
+ * Scheduling epoch for work not covered by a full compact. Writers set the
139
+ * first actual write time; a partial full compact advances it to completion.
140
+ */
141
+ first_uncompacted_write: Date | undefined;
142
+ /**
143
+ * A checksum cache and the statistics captured by the latest compact (full
144
+ * or chunk). Keeping these separate from bucket_stats lets writers only
145
+ * update one set of counters.
146
+ */
147
+ compacted_state?: {
148
+ op_id: InternalOpId;
149
+ checksum: bigint;
150
+ count: number;
151
+ bytes: bigint;
152
+ at: Date;
153
+ chunks: number;
154
+ };
155
+ /** Statistics for the prefix covered by the most recent full compact. */
156
+ last_full_compact?: {
157
+ op_id: InternalOpId;
158
+ count: number;
159
+ at: Date;
160
+ puts: number;
161
+ };
162
+ /** Current aggregate bucket statistics, maintained by writers and compactors. */
163
+ bucket_stats: {
164
+ count: number;
165
+ bytes: bigint;
166
+ chunks: number;
167
+ };
168
+ /** A short-lived ownership marker used to distribute bucket compaction. */
169
+ compact_lease?: {
170
+ expires_at: Date;
171
+ id: unknown;
172
+ };
122
173
  }
123
174
  export interface BucketOperation {
124
175
  o: bigint;
125
176
  op: OpType;
126
177
  source_table?: bson.ObjectId;
127
178
  source_key?: ReplicaId;
179
+ subkey?: string;
128
180
  table?: string;
129
181
  row_id?: string;
130
182
  checksum: bigint;
@@ -134,6 +186,14 @@ export interface StorageRef {
134
186
  path: string;
135
187
  file_size: number;
136
188
  }
189
+ export interface ObjectStorageUsageDocument {
190
+ _id: {
191
+ g: number;
192
+ w: string;
193
+ };
194
+ updated_at: Date;
195
+ definitions: Record<BucketDefinitionId, bigint>;
196
+ }
137
197
  /** An S3 object that may be deleted once its grace period has elapsed. */
138
198
  export interface ObjectStorageDeletionMarker {
139
199
  _id: bson.ObjectId;
@@ -176,6 +236,30 @@ export interface BucketDataDocumentV3 {
176
236
  */
177
237
  has_clear_op?: true;
178
238
  }
239
+ /**
240
+ * v3 Custom checkpoints (new name for legacy write checkpoints) are related to
241
+ * a replication stream and a specific event (in a sync config) which created them.
242
+ * Unchanged events, between sync configs, can share the documents.
243
+ * For v3, we store each replication stream's custom checkpoint requests in their own collection:
244
+ * `custom_checkpoint_requests_${replicationStreamId}_${eventId}`
245
+ */
246
+ export interface CustomCheckpointRequestDocumentV3 {
247
+ _id: bson.ObjectId;
248
+ user_id: string;
249
+ checkpoint: bigint;
250
+ /**
251
+ * Unlike managed write checkpoints, custom write checkpoints are flushed together with
252
+ * normal ops. This means we can assign an op_id for ordering / correlating with read checkpoints.
253
+ *
254
+ * This is not unique - multiple write checkpoints can have the same op_id.
255
+ */
256
+ op_id?: InternalOpId;
257
+ /**
258
+ * Set when this checkpoint was created from a client-supplied checkpoint
259
+ * request rather than a persistent custom write checkpoint.
260
+ */
261
+ checkpoint_requested_at?: Date | null;
262
+ }
179
263
  export declare function serializeParameterLookup(lookup: ScopedParameterLookup): bson.Binary;
180
264
  export declare function deserializeParameterLookup(lookup: bson.Binary, indexId: ParameterIndexId): SqliteJsonValue[];
181
265
  export declare function taggedBucketParameterDocumentToTagged(document: TaggedBucketParameterDocument): BucketParameterDocumentV3;
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../../src/storage/implementation/v3/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,0BAA0B,IAAI,8BAA8B,EAC7D,MAAM,yBAAyB,CAAC;AAOjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AA2H7B,MAAM,UAAU,iCAAiC,CAAC,QAAuC;IACvF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC5C,OAAO,IAAiC,CAAC;AAC3C,CAAC;AAwFD,MAAM,UAAU,wBAAwB,CAAC,MAA6B;IACpE,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAmB,EAAE,OAAyB;IACvF,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,8BAA8B,CAAC,MAAM,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,qCAAqC,CACnD,QAAuC;IAEvC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC5C,OAAO,IAAiC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../../src/storage/implementation/v3/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,0BAA0B,IAAI,8BAA8B,EAC7D,MAAM,yBAAyB,CAAC;AAQjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AA4H7B,MAAM,UAAU,iCAAiC,CAAC,QAAuC;IACvF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC5C,OAAO,IAAiC,CAAC;AAC3C,CAAC;AAkLD,MAAM,UAAU,wBAAwB,CAAC,MAA6B;IACpE,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAmB,EAAE,OAAyB;IACvF,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,8BAA8B,CAAC,MAAM,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,qCAAqC,CACnD,QAAuC;IAEvC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC5C,OAAO,IAAiC,CAAC;AAC3C,CAAC"}
@@ -1,21 +1,20 @@
1
+ import { PerformanceTracer } from '@powersync/service-core';
1
2
  import { BucketDataDocumentV3, BucketOperation } from '../models.js';
2
- import { ObjectStorage } from './ObjectStorage.js';
3
+ import { ObjectStorage, ObjectStorageOperationOptions } from './ObjectStorage.js';
3
4
  export declare class BucketDataObjectStorage {
4
5
  private readonly storage;
5
6
  constructor(storage: ObjectStorage);
6
- store(path: string, ops: BucketOperation[]): Promise<{
7
+ store(path: string, ops: BucketOperation[], options?: ObjectStorageOperationOptions): Promise<{
7
8
  fileSize: number;
8
9
  }>;
9
- retrieve(path: string, options: {
10
- signal?: AbortSignal;
11
- }): Promise<BucketOperation[]>;
12
- delete(paths: string[]): Promise<void>;
10
+ retrieve(path: string, options: ObjectStorageOperationOptions): Promise<BucketOperation[]>;
11
+ delete(paths: string[], options?: ObjectStorageOperationOptions): Promise<void>;
13
12
  }
14
13
  /**
15
14
  * Load offloaded operations and patch them onto their MongoDB metadata documents.
16
15
  * The object-storage implementation is responsible for limiting request
17
16
  * concurrency across all callers.
18
17
  */
19
- export declare function hydrateBucketDataDocuments(documents: BucketDataDocumentV3[], objectStorage: ObjectStorage | undefined, options: {
20
- signal?: AbortSignal;
18
+ export declare function hydrateBucketDataDocuments(documents: BucketDataDocumentV3[], objectStorage: ObjectStorage | undefined, options: ObjectStorageOperationOptions & {
19
+ tracer?: PerformanceTracer<string>;
21
20
  }): Promise<void>;
@@ -5,9 +5,9 @@ export class BucketDataObjectStorage {
5
5
  constructor(storage) {
6
6
  this.storage = storage;
7
7
  }
8
- async store(path, ops) {
8
+ async store(path, ops, options) {
9
9
  const bsonBuffer = bson.serialize({ ops });
10
- await this.storage.put(path, bsonBuffer, { contentType: 'application/bson', contentEncoding: null });
10
+ await this.storage.put(path, bsonBuffer, { contentType: 'application/bson', contentEncoding: null }, options);
11
11
  return { fileSize: bsonBuffer.byteLength };
12
12
  }
13
13
  async retrieve(path, options) {
@@ -21,8 +21,8 @@ export class BucketDataObjectStorage {
21
21
  const wrapper = bson.deserialize(data, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS);
22
22
  return wrapper.ops;
23
23
  }
24
- async delete(paths) {
25
- return this.storage.delete(paths);
24
+ async delete(paths, options) {
25
+ return this.storage.delete(paths, options);
26
26
  }
27
27
  }
28
28
  /**
@@ -37,8 +37,19 @@ export async function hydrateBucketDataDocuments(documents, objectStorage, optio
37
37
  options.signal?.throwIfAborted();
38
38
  const store = new BucketDataObjectStorage(objectStorage);
39
39
  const storedDocuments = documents.filter((document) => document.storage_ref);
40
- await Promise.all(storedDocuments.map(async (document) => {
40
+ if (storedDocuments.length == 0) {
41
+ return;
42
+ }
43
+ using _ = options?.tracer?.span('s3', 'read');
44
+ const results = await Promise.allSettled(storedDocuments.map(async (document) => {
41
45
  document.ops = await store.retrieve(document.storage_ref.path, { signal: options.signal });
42
46
  }));
47
+ // Drain every download before propagating failure so callers can release their
48
+ // worker slot and payloads without a sibling download hydrating a document later.
49
+ for (const result of results) {
50
+ if (result.status === 'rejected') {
51
+ throw result.reason;
52
+ }
53
+ }
43
54
  }
44
55
  //# sourceMappingURL=BucketDataObjectStorage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BucketDataObjectStorage.js","sourceRoot":"","sources":["../../../../../src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAI7B,MAAM,OAAO,uBAAuB;IACL;IAA7B,YAA6B,OAAsB;QAAtB,YAAO,GAAP,OAAO,CAAe;IAAG,CAAC;IAEvD,KAAK,CAAC,KAAK,CAAC,IAAY,EAAE,GAAsB;QAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC3C,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;QACrG,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAiC;QAC5D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACpF,IAAI,QAAQ,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,QAAQ,CAAC,WAAW,KAAK,kBAAkB,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,iCAAiC,CAAC,CAAC;QAClF,OAAO,OAAO,CAAC,GAAG,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAe;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAAiC,EACjC,aAAwC,EACxC,OAAiC;IAEjC,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,uBAAuB,CAAC,aAAa,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC7E,MAAM,OAAO,CAAC,GAAG,CACf,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QACrC,QAAQ,CAAC,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAY,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9F,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"BucketDataObjectStorage.js","sourceRoot":"","sources":["../../../../../src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts"],"names":[],"mappings":"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"}
@@ -10,21 +10,24 @@ export declare class ObjectStorageError extends Error {
10
10
  });
11
11
  }
12
12
  export declare function isRetryableObjectStorageError(error: unknown): error is ObjectStorageError;
13
+ export interface ObjectStorageOperationOptions {
14
+ /**
15
+ * Cancels the operation, including while it waits for a concurrency slot.
16
+ *
17
+ * Operations are bounded by their own timeouts regardless, but a signal releases the slot as
18
+ * soon as the caller no longer needs the result.
19
+ */
20
+ signal?: AbortSignal;
21
+ }
13
22
  export interface ObjectStorage {
14
- put(path: string, data: Uint8Array, metadata: ObjectStoragePutMetadata): Promise<void>;
15
- get(path: string, options?: {
16
- signal?: AbortSignal;
17
- }): Promise<{
23
+ put(path: string, data: Uint8Array, metadata: ObjectStoragePutMetadata, options?: ObjectStorageOperationOptions): Promise<void>;
24
+ get(path: string, options?: ObjectStorageOperationOptions): Promise<{
18
25
  data: Uint8Array;
19
26
  metadata: ObjectStoragePutMetadata;
20
27
  }>;
21
- list(prefix: string, options?: {
22
- signal?: AbortSignal;
23
- }): AsyncIterable<string>;
24
- delete(paths: string[]): Promise<void>;
25
- deletePrefix(prefix: string, options?: {
26
- signal?: AbortSignal;
27
- }): Promise<{
28
+ list(prefix: string, options?: ObjectStorageOperationOptions): AsyncIterable<string>;
29
+ delete(paths: string[], options?: ObjectStorageOperationOptions): Promise<void>;
30
+ deletePrefix(prefix: string, options?: ObjectStorageOperationOptions): Promise<{
28
31
  objectCount: number;
29
32
  }>;
30
33
  }
@@ -1,9 +1,9 @@
1
- import { mongo } from '@powersync/lib-service-mongodb';
2
1
  import { Logger } from '@powersync/lib-services-framework';
3
2
  import * as bson from 'bson';
3
+ import { MongoWriteBatch } from '../../MongoWriteBatch.js';
4
4
  import { VersionedPowerSyncMongoV3 } from '../VersionedPowerSyncMongoV3.js';
5
5
  import { BucketDataObjectStorage } from './BucketDataObjectStorage.js';
6
- import { ObjectStorage } from './ObjectStorage.js';
6
+ import { ObjectStorage, ObjectStorageOperationOptions } from './ObjectStorage.js';
7
7
  export declare const OBJECT_STORAGE_UPLOAD_LEASE_MS: number;
8
8
  export declare const OBJECT_STORAGE_PUBLICATION_SAFETY_MARGIN_MS: number;
9
9
  export declare const OBJECT_STORAGE_REFERENCE_GRACE_MS: number;
@@ -44,7 +44,7 @@ export declare class ObjectStorageLifecycle {
44
44
  *
45
45
  * Call this in the same transaction as the one creating the references.
46
46
  */
47
- publishUploads(uploads: PreparedObjectStorageUpload[], session: mongo.ClientSession): Promise<void>;
47
+ publishUploads(uploads: PreparedObjectStorageUpload[], writes: MongoWriteBatch): void;
48
48
  /**
49
49
  * Mark old paths for deletion.
50
50
  *
@@ -56,15 +56,13 @@ export declare class ObjectStorageLifecycle {
56
56
  *
57
57
  * Call this in the same transaction as the one removing the references to these files.
58
58
  */
59
- retire(paths: Iterable<string>, session: mongo.ClientSession, now?: Date): Promise<void>;
59
+ retire(paths: Iterable<string>, writes: MongoWriteBatch, now?: Date): void;
60
60
  /**
61
61
  * Discover and delete objects by their storage prefix
62
62
  * without touching the database.
63
63
  */
64
- deletePrefix(prefix: string, options?: {
65
- signal?: AbortSignal;
66
- }): Promise<{
64
+ deletePrefix(prefix: string, options?: ObjectStorageOperationOptions): Promise<{
67
65
  objectCount: number;
68
66
  }>;
69
- cleanup(logger: Logger): Promise<void>;
67
+ cleanup(logger: Logger, options?: ObjectStorageOperationOptions): Promise<void>;
70
68
  }
@@ -72,7 +72,7 @@ export class ObjectStorageLifecycle {
72
72
  *
73
73
  * Call this in the same transaction as the one creating the references.
74
74
  */
75
- async publishUploads(uploads, session) {
75
+ publishUploads(uploads, writes) {
76
76
  for (const upload of uploads) {
77
77
  if (!this.canPublish(upload)) {
78
78
  throw new Error(`Object storage publication lease expired for ${upload.path}`);
@@ -81,12 +81,10 @@ export class ObjectStorageLifecycle {
81
81
  if (uploads.length === 0) {
82
82
  return;
83
83
  }
84
- const result = await this.db
85
- .pendingObjectStorageDeletes(this.replicationStreamId)
86
- .deleteMany({ _id: { $in: uploads.map((upload) => upload.markerId) } }, { session });
87
- if (result.deletedCount !== uploads.length) {
88
- throw new Error(`Missing object storage publication markers`);
89
- }
84
+ writes.deleteMany(this.db.pendingObjectStorageDeletes(this.replicationStreamId), { _id: { $in: uploads.map((upload) => upload.markerId) } }, (deletedCount) => {
85
+ if (deletedCount !== uploads.length)
86
+ throw new Error('Missing object storage publication markers');
87
+ });
90
88
  }
91
89
  /**
92
90
  * Mark old paths for deletion.
@@ -99,14 +97,14 @@ export class ObjectStorageLifecycle {
99
97
  *
100
98
  * Call this in the same transaction as the one removing the references to these files.
101
99
  */
102
- async retire(paths, session, now = new Date()) {
100
+ retire(paths, writes, now = new Date()) {
103
101
  const markers = Array.from(paths, (path) => ({
104
102
  _id: new bson.ObjectId(),
105
103
  path,
106
104
  delete_after: new Date(now.getTime() + OBJECT_STORAGE_REFERENCE_GRACE_MS)
107
105
  }));
108
106
  if (markers.length) {
109
- await this.db.pendingObjectStorageDeletes(this.replicationStreamId).insertMany(markers, { session });
107
+ writes.insertMany(this.db.pendingObjectStorageDeletes(this.replicationStreamId), markers);
110
108
  }
111
109
  }
112
110
  /**
@@ -116,7 +114,8 @@ export class ObjectStorageLifecycle {
116
114
  async deletePrefix(prefix, options) {
117
115
  return this.objectStorage.deletePrefix(prefix, { signal: options?.signal });
118
116
  }
119
- async cleanup(logger) {
117
+ async cleanup(logger, options) {
118
+ const signal = options?.signal;
120
119
  const markers = this.db.pendingObjectStorageDeletes(this.replicationStreamId);
121
120
  const cursor = markers.find({ delete_after: { $lte: new Date() } });
122
121
  let batch = [];
@@ -127,14 +126,21 @@ export class ObjectStorageLifecycle {
127
126
  const deleting = batch;
128
127
  batch = [];
129
128
  try {
130
- await this.bucketData.delete(deleting.map((marker) => marker.path));
129
+ await this.bucketData.delete(deleting.map((marker) => marker.path), { signal });
131
130
  await markers.deleteMany({ _id: { $in: deleting.map((marker) => marker._id) } });
132
131
  }
133
132
  catch (error) {
133
+ if (signal?.aborted) {
134
+ // Shutting down: stop instead of working through the remaining markers. They stay
135
+ // pending, and are cleaned up during the next compaction. Checked on the signal rather
136
+ // than the error, since a caller may abort with any reason.
137
+ throw error;
138
+ }
134
139
  logger.warn('Failed to clean up object storage deletion markers; will retry during the next compaction', error);
135
140
  }
136
141
  };
137
142
  for await (const marker of cursor) {
143
+ signal?.throwIfAborted();
138
144
  batch.push(marker);
139
145
  if (batch.length === 500) {
140
146
  await deleteBatch();