@powersync/service-module-mongodb-storage 0.23.2 → 0.24.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 (99) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -67
  3. package/dist/storage/MongoBucketStorage.d.ts +1 -0
  4. package/dist/storage/MongoBucketStorage.js +3 -0
  5. package/dist/storage/MongoBucketStorage.js.map +1 -1
  6. package/dist/storage/implementation/MongoBucketBatch.d.ts +60 -39
  7. package/dist/storage/implementation/MongoBucketBatch.js +123 -296
  8. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  9. package/dist/storage/implementation/MongoOpIdAllocator.d.ts +6 -1
  10. package/dist/storage/implementation/MongoOpIdAllocator.js +12 -5
  11. package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -1
  12. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +7 -0
  13. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +28 -2
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  15. package/dist/storage/implementation/MongoReplicationOptions.d.ts +27 -0
  16. package/dist/storage/implementation/MongoReplicationOptions.js +26 -0
  17. package/dist/storage/implementation/MongoReplicationOptions.js.map +1 -0
  18. package/dist/storage/implementation/MongoSyncBucketStorage.js +2 -3
  19. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  20. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +15 -2
  21. package/dist/storage/implementation/MongoSyncRulesLock.js +36 -14
  22. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  23. package/dist/storage/implementation/common/BatchOperationBuffer.d.ts +79 -0
  24. package/dist/storage/implementation/common/BatchOperationBuffer.js +286 -0
  25. package/dist/storage/implementation/common/BatchOperationBuffer.js.map +1 -0
  26. package/dist/storage/implementation/common/PersistedBatch.d.ts +10 -103
  27. package/dist/storage/implementation/common/PersistedBatch.js +1 -291
  28. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  29. package/dist/storage/implementation/common/PreparedPublication.d.ts +43 -0
  30. package/dist/storage/implementation/common/PreparedPublication.js +2 -0
  31. package/dist/storage/implementation/common/PreparedPublication.js.map +1 -0
  32. package/dist/storage/implementation/common/SourceRecordStore.d.ts +0 -2
  33. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +17 -13
  34. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +259 -18
  35. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  36. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +21 -15
  37. package/dist/storage/implementation/v1/PersistedBatchV1.js +58 -24
  38. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  39. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +0 -2
  40. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +0 -3
  41. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -1
  42. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +25 -14
  43. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +350 -23
  44. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  45. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +1 -0
  46. package/dist/storage/implementation/v3/MongoCompactorV3.js +10 -4
  47. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  48. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +39 -28
  49. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  50. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +46 -24
  51. package/dist/storage/implementation/v3/PersistedBatchV3.js +159 -83
  52. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  53. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +12 -0
  54. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +41 -13
  55. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  56. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +4 -0
  57. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +4 -0
  58. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  59. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +27 -2
  60. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +61 -5
  61. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  62. package/package.json +9 -10
  63. package/src/storage/MongoBucketStorage.ts +4 -0
  64. package/src/storage/implementation/MongoBucketBatch.ts +185 -392
  65. package/src/storage/implementation/MongoOpIdAllocator.ts +12 -5
  66. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +40 -1
  67. package/src/storage/implementation/MongoReplicationOptions.ts +28 -0
  68. package/src/storage/implementation/MongoSyncBucketStorage.ts +2 -3
  69. package/src/storage/implementation/MongoSyncRulesLock.ts +39 -17
  70. package/src/storage/implementation/common/BatchOperationBuffer.ts +353 -0
  71. package/src/storage/implementation/common/PersistedBatch.ts +12 -390
  72. package/src/storage/implementation/common/PreparedPublication.ts +49 -0
  73. package/src/storage/implementation/common/SourceRecordStore.ts +0 -2
  74. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +339 -27
  75. package/src/storage/implementation/v1/PersistedBatchV1.ts +74 -32
  76. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +0 -5
  77. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +430 -32
  78. package/src/storage/implementation/v3/MongoCompactorV3.ts +13 -4
  79. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +43 -29
  80. package/src/storage/implementation/v3/PersistedBatchV3.ts +209 -108
  81. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +45 -15
  82. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +9 -0
  83. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +79 -5
  84. package/test/src/checkpoint_cleanup.test.ts +75 -0
  85. package/test/src/concurrent_writers.test.ts +127 -53
  86. package/test/src/helpers/PublicationCommandMonitor.ts +119 -0
  87. package/test/src/helpers/concurrentWriter.mjs +2 -2
  88. package/test/src/invalid_sync_config.test.ts +9 -0
  89. package/test/src/object_storage_lifecycle.test.ts +72 -2
  90. package/test/src/prepared_publication.test.ts +388 -0
  91. package/test/src/replication_preparation.test.ts +829 -0
  92. package/test/src/replication_publication_baseline.test.ts +203 -0
  93. package/test/src/replication_publication_batching.test.ts +212 -0
  94. package/test/src/replication_uploads.test.ts +393 -0
  95. package/test/src/source_table_config.test.ts +4 -4
  96. package/test/src/storage_object_storage_inline_threshold.test.ts +1 -1
  97. package/test/src/storage_s3_compaction_lifecycle.test.ts +93 -1
  98. package/test/src/storage_sync.test.ts +83 -2
  99. package/tsconfig.tsbuildinfo +1 -1
@@ -4,18 +4,19 @@ import * as bson from 'bson';
4
4
  import { BaseObserver, Logger } from '@powersync/lib-services-framework';
5
5
  import { BucketDefinitionMapping, BucketStorageMarkRecordUnavailable, InternalOpId, PerformanceTracer, storage } from '@powersync/service-core';
6
6
  import { PersistedBatch } from './common/PersistedBatch.js';
7
- import { SourceRecordStore } from './common/SourceRecordStore.js';
7
+ import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
8
8
  import type { VersionedPowerSyncMongo } from './db.js';
9
9
  import { SyncRuleDocumentBase } from './models.js';
10
+ import { MongoIdSequence } from './MongoIdSequence.js';
10
11
  import { MongoOpIdAllocator } from './MongoOpIdAllocator.js';
11
12
  import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
12
13
  import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
13
14
  import { MongoWriteBatch } from './MongoWriteBatch.js';
15
+ import { OperationBatch, RecordOperation } from './OperationBatch.js';
14
16
  import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
15
17
  export interface MongoBucketBatchOptions {
16
18
  replicationLock: MongoSyncRulesLock;
17
19
  opIdAllocator: MongoOpIdAllocator;
18
- db: VersionedPowerSyncMongo;
19
20
  /**
20
21
  * The parsed sync config set for this batch.
21
22
  *
@@ -53,8 +54,8 @@ export interface MongoBucketBatchOptions {
53
54
  export declare abstract class MongoBucketBatch extends BaseObserver<storage.BucketBatchStorageListener> implements storage.BucketStorageBatch {
54
55
  protected readonly options: MongoBucketBatchOptions;
55
56
  protected logger: Logger;
56
- private readonly client;
57
57
  readonly db: VersionedPowerSyncMongo;
58
+ /** Shared by this lease's writers; only use inside a serialized public operation. */
58
59
  readonly session: mongo.ClientSession;
59
60
  protected readonly sync_rules: HydratedSyncConfig;
60
61
  protected readonly replicationStreamId: number;
@@ -68,15 +69,15 @@ export declare abstract class MongoBucketBatch extends BaseObserver<storage.Buck
68
69
  * via SourceTable.storeCurrentData. The effective per-record value is the conjunction of
69
70
  * the two.
70
71
  */
71
- private readonly storeCurrentData;
72
+ protected readonly storeCurrentData: boolean;
72
73
  readonly skipExistingRows: boolean;
73
74
  protected readonly mapping: BucketDefinitionMapping;
74
75
  protected readonly objectStorageUsageWriterId: string;
75
- private batch;
76
+ protected batch: OperationBatch | null;
76
77
  protected write_checkpoint_batch: storage.CustomWriteCheckpointOptions[];
77
- private markRecordUnavailable;
78
- private hooks;
79
- private clearedError;
78
+ protected readonly markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
79
+ protected hooks: storage.StorageHooks | undefined;
80
+ protected clearedError: boolean;
80
81
  protected tracer: PerformanceTracer<'storage' | 'evaluate'>;
81
82
  /**
82
83
  * Last written op, if any. This may not reflect a consistent checkpoint.
@@ -95,56 +96,73 @@ export declare abstract class MongoBucketBatch extends BaseObserver<storage.Buck
95
96
  resumeFromLsn: string | null;
96
97
  constructor(options: MongoBucketBatchOptions);
97
98
  addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void;
98
- abstract resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
99
+ resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
100
+ protected abstract resolveTablesInternal(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
99
101
  protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
100
102
  protected abstract get sourceRecordStore(): SourceRecordStore;
101
103
  protected abstract cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]): Promise<void>;
102
- abstract commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult>;
103
- abstract keepalive(lsn: string): Promise<storage.CheckpointResult>;
104
- abstract setResumeLsn(lsn: string): Promise<void>;
105
- abstract getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null>;
106
- abstract markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void>;
107
- abstract markSnapshotDone(no_checkpoint_before_lsn: string, options?: {
104
+ commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult>;
105
+ protected abstract commitInternal(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult>;
106
+ keepalive(lsn: string): Promise<storage.CheckpointResult>;
107
+ setResumeLsn(lsn: string): Promise<void>;
108
+ protected abstract setResumeLsnInternal(lsn: string): Promise<void>;
109
+ getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null>;
110
+ protected abstract getSourceTableStatusInternal(table: storage.SourceTable): Promise<storage.SourceTable | null>;
111
+ markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void>;
112
+ protected abstract markAllSnapshotDoneInternal(no_checkpoint_before_lsn: string): Promise<void>;
113
+ markSnapshotDone(no_checkpoint_before_lsn: string, options?: {
108
114
  throwOnConflict?: boolean;
109
115
  }): Promise<void>;
110
- abstract markTableSnapshotRequired(table: storage.SourceTable): Promise<void>;
111
- abstract markTableSnapshotDone(tables: storage.SourceTable[], no_checkpoint_before_lsn?: string): Promise<storage.SourceTable[]>;
112
- protected abstract batchCreateCustomWriteCheckpoints(session: mongo.ClientSession, opId: InternalOpId): Promise<void>;
116
+ protected abstract markSnapshotDoneInternal(no_checkpoint_before_lsn: string, options?: {
117
+ throwOnConflict?: boolean;
118
+ }): Promise<void>;
119
+ markTableSnapshotRequired(table: storage.SourceTable): Promise<void>;
120
+ protected abstract markTableSnapshotRequiredInternal(table: storage.SourceTable): Promise<void>;
121
+ markTableSnapshotDone(tables: storage.SourceTable[], no_checkpoint_before_lsn?: string): Promise<storage.SourceTable[]>;
122
+ protected abstract markTableSnapshotDoneInternal(tables: storage.SourceTable[], no_checkpoint_before_lsn?: string): Promise<storage.SourceTable[]>;
113
123
  /** Perform any version-specific setup required before writing custom checkpoints. */
114
124
  protected prepareCustomWriteCheckpoints(): Promise<void>;
115
125
  flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null>;
126
+ /** Internal helpers require the public operation to hold the writer mutex. */
127
+ protected flushInternal(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null>;
116
128
  /** Publish the checkpoint in the final flush transaction, or use the empty-commit path. */
117
- protected flushAndCommit<T>(checkpoint: (stream: SyncRuleDocumentBase, lastOp: InternalOpId) => Promise<T>, commitWithoutFlush: () => Promise<T>, options?: storage.BatchBucketFlushOptions, projection?: mongo.Document): Promise<T>;
118
- private flushInner;
119
- private replicateBatch;
120
- private saveOperation;
129
+ protected flushAndCommit<T>(checkpoint: (stream: SyncRuleDocumentBase, lastOp: InternalOpId, writes?: MongoWriteBatch) => Promise<T>, commitWithoutFlush: () => Promise<T>, options?: storage.BatchBucketFlushOptions, projection?: mongo.Document): Promise<T>;
130
+ protected abstract flushInner(options?: storage.BatchBucketFlushOptions, checkpoint?: (stream: SyncRuleDocumentBase, lastOp: InternalOpId, writes?: MongoWriteBatch) => Promise<void>, projection?: mongo.Document): Promise<storage.FlushedResult | null>;
131
+ /**
132
+ * Evaluate one source operation and append its storage writes to batch.
133
+ *
134
+ * Diagnostics run during evaluation. V1 calls markRecordUnavailable immediately;
135
+ * v3/v4 supply a collector and deliver repair notifications only after a
136
+ * confirmed publication. A failed row attempt may have registered a repair,
137
+ * so its caller must discard that row's collected notification on failure.
138
+ */
139
+ protected saveOperation(batch: PersistedBatch, operation: RecordOperation, sourceRecord: LoadedSourceRecord | null, opSeq: MongoIdSequence, onRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined): LoadedSourceRecord | null;
121
140
  protected fence(session?: mongo.ClientSession, projection?: mongo.Document): Promise<mongo.WithId<SyncRuleDocumentBase>>;
122
141
  protected withTransaction<T>(cb: (stream: SyncRuleDocumentBase) => Promise<T>, session?: mongo.ClientSession): Promise<T>;
123
- /** The first operation must modify the stream document conditional on lease ownership. */
124
- protected withFencedTransaction<S, T>(acquireFence: () => Promise<S>, cb: (stream: S) => Promise<T>, session?: mongo.ClientSession): Promise<T>;
142
+ /**
143
+ * Internal transaction helper: the public writer operation already holds the
144
+ * mutex. The first operation must modify the stream document conditional on
145
+ * lease ownership. This database fence is required on every retry even though
146
+ * local writers are serialized; another process may have taken over the lease.
147
+ */
148
+ protected withFencedTransaction<S, T>(acquireFence: () => Promise<S>, cb: (stream: S) => Promise<T>, session?: mongo.ClientSession, description?: string): Promise<T>;
149
+ /**
150
+ * Public database operations acquire the lease's mutex once, then call only
151
+ * internal helpers until they finish. Those helpers must never call a public
152
+ * database operation or reacquire this mutex. All session use, including reads,
153
+ * belongs inside this boundary so it cannot join another writer's transaction.
154
+ * Transaction helpers still fence database ownership on every attempt.
155
+ */
125
156
  private withWriterLock;
126
- /** Single-document updates enforce ownership atomically, without a separate transaction. */
157
+ /** Internal single-document update; the public operation already holds the mutex. */
127
158
  protected updateStreamMetadata(set: mongo.Document, filter?: mongo.Document, writeConcern?: mongo.WriteConcernSettings): Promise<void>;
128
159
  /** Caller must already be inside a fenced transaction holding the writer mutex. */
129
160
  protected updateStreamMetadataInTransaction(set: mongo.Document, filter?: mongo.Document): Promise<void>;
130
161
  private writeStreamMetadata;
131
- private withReplicationTransaction;
132
162
  /** Highest durably persisted operation, including operations not yet checkpointed. */
133
163
  protected abstract persistedOpHead(stream: SyncRuleDocumentBase): InternalOpId;
134
164
  /** Advance the persisted head in the same transaction as the operations. */
135
165
  protected abstract onReplicationTransactionFlush(writes: MongoWriteBatch, lastOp: InternalOpId): void;
136
- /**
137
- * Called after a replication transaction has successfully committed, with the last persisted op id.
138
- *
139
- * v1 storage tracks this in memory to fold into the next checkpoint. v3 storage does not need it:
140
- * the stream-level `last_persisted_op` is already advanced durably by
141
- * {@link onReplicationTransactionFlush} or the combined checkpoint update within the same
142
- * transaction, and empty commits read it from the document.
143
- *
144
- * Keep calls to this adjacent to {@link withReplicationTransaction} usage - both must observe
145
- * every path that persists ops.
146
- */
147
- protected recordPersistedOp(_lastOp: InternalOpId): void;
148
166
  [Symbol.asyncDispose](): Promise<void>;
149
167
  dispose(): Promise<void>;
150
168
  save(record: storage.SaveOptions): Promise<storage.FlushedResult | null>;
@@ -152,10 +170,13 @@ export declare abstract class MongoBucketBatch extends BaseObserver<storage.Buck
152
170
  * Drop is equivalent to TRUNCATE, plus removing our record of the table.
153
171
  */
154
172
  drop(sourceTables: storage.SourceTable[]): Promise<storage.FlushedResult | null>;
173
+ protected dropInternal(sourceTables: storage.SourceTable[]): Promise<storage.FlushedResult | null>;
155
174
  truncate(sourceTables: storage.SourceTable[]): Promise<storage.FlushedResult | null>;
175
+ protected truncateInternal(sourceTables: storage.SourceTable[]): Promise<storage.FlushedResult | null>;
156
176
  truncateSingle(sourceTable: storage.SourceTable): Promise<InternalOpId>;
177
+ protected abstract truncateSingleInternal(sourceTable: storage.SourceTable): Promise<InternalOpId>;
157
178
  updateTableProgress(table: storage.SourceTable, progress: Partial<storage.TableSnapshotStatus>): Promise<storage.SourceTable>;
158
- protected clearError(session: mongo.ClientSession): Promise<void>;
179
+ private updateTableProgressInternal;
159
180
  /**
160
181
  * Gets relevant {@link HydratedEventDescriptor}s for the given {@link SourceTable}
161
182
  */