@powersync/service-module-mongodb 0.0.0-dev-20260225093637 → 0.0.0-dev-20260313100403

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.
@@ -8,13 +8,14 @@ import {
8
8
  OplogEntry,
9
9
  ProtocolOpId,
10
10
  ReplicationCheckpoint,
11
+ storage,
11
12
  STORAGE_VERSION_CONFIG,
12
13
  SyncRulesBucketStorage,
13
14
  TestStorageOptions,
14
15
  updateSyncRulesFromYaml,
15
16
  utils
16
17
  } from '@powersync/service-core';
17
- import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
18
+ import { bucketRequest, METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
18
19
 
19
20
  import { ChangeStream, ChangeStreamOptions } from '@module/replication/ChangeStream.js';
20
21
  import { MongoManager } from '@module/replication/MongoManager.js';
@@ -26,8 +27,9 @@ import { clearTestDb, TEST_CONNECTION_OPTIONS } from './util.js';
26
27
  export class ChangeStreamTestContext {
27
28
  private _walStream?: ChangeStream;
28
29
  private abortController = new AbortController();
29
- private streamPromise?: Promise<void>;
30
+ private streamPromise?: Promise<PromiseSettledResult<void>>;
30
31
  private syncRulesId?: number;
32
+ private syncRulesContent?: storage.PersistedSyncRulesContent;
31
33
  public storage?: SyncRulesBucketStorage;
32
34
 
33
35
  /**
@@ -104,6 +106,7 @@ export class ChangeStreamTestContext {
104
106
  updateSyncRulesFromYaml(content, { validate: true, storageVersion: this.storageVersion })
105
107
  );
106
108
  this.syncRulesId = syncRules.id;
109
+ this.syncRulesContent = syncRules;
107
110
  this.storage = this.factory.getInstance(syncRules);
108
111
  return this.storage!;
109
112
  }
@@ -115,11 +118,19 @@ export class ChangeStreamTestContext {
115
118
  }
116
119
 
117
120
  this.syncRulesId = syncRules.id;
121
+ this.syncRulesContent = syncRules;
118
122
  this.storage = this.factory.getInstance(syncRules);
119
123
  return this.storage!;
120
124
  }
121
125
 
122
- get walStream() {
126
+ private getSyncRulesContent(): storage.PersistedSyncRulesContent {
127
+ if (this.syncRulesContent == null) {
128
+ throw new Error('Sync rules not configured - call updateSyncRules() first');
129
+ }
130
+ return this.syncRulesContent;
131
+ }
132
+
133
+ get streamer() {
123
134
  if (this.storage == null) {
124
135
  throw new Error('updateSyncRules() first');
125
136
  }
@@ -141,7 +152,7 @@ export class ChangeStreamTestContext {
141
152
  }
142
153
 
143
154
  async replicateSnapshot() {
144
- await this.walStream.initReplication();
155
+ await this.streamer.initReplication();
145
156
  }
146
157
 
147
158
  /**
@@ -153,19 +164,27 @@ export class ChangeStreamTestContext {
153
164
  async markSnapshotConsistent() {
154
165
  const checkpoint = await createCheckpoint(this.client, this.db, STANDALONE_CHECKPOINT_ID);
155
166
 
156
- await this.storage!.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
157
- await batch.keepalive(checkpoint);
158
- });
167
+ await using writer = await this.storage!.createWriter(test_utils.BATCH_OPTIONS);
168
+ await writer.keepalive(checkpoint);
169
+ await writer.flush();
159
170
  }
160
171
 
161
172
  startStreaming() {
162
- return (this.streamPromise = this.walStream.streamChanges());
173
+ this.streamPromise = this.streamer
174
+ .streamChanges()
175
+ .then(() => ({ status: 'fulfilled', value: undefined }) satisfies PromiseFulfilledResult<void>)
176
+ .catch((reason) => ({ status: 'rejected', reason }) satisfies PromiseRejectedResult);
177
+ return this.streamPromise;
163
178
  }
164
179
 
165
180
  async getCheckpoint(options?: { timeout?: number }) {
166
181
  let checkpoint = await Promise.race([
167
182
  getClientCheckpoint(this.client, this.db, this.factory, { timeout: options?.timeout ?? 15_000 }),
168
- this.streamPromise
183
+ this.streamPromise?.then((e) => {
184
+ if (e.status == 'rejected') {
185
+ throw e.reason;
186
+ }
187
+ })
169
188
  ]);
170
189
  if (checkpoint == null) {
171
190
  // This indicates an issue with the test setup - streamingPromise completed instead
@@ -175,21 +194,10 @@ export class ChangeStreamTestContext {
175
194
  return checkpoint;
176
195
  }
177
196
 
178
- private resolveBucketName(bucket: string) {
179
- if (!this.versionedBuckets || /^\d+#/.test(bucket)) {
180
- return bucket;
181
- }
182
- if (this.syncRulesId == null) {
183
- throw new Error('Sync rules not configured - call updateSyncRules() first');
184
- }
185
- return `${this.syncRulesId}#${bucket}`;
186
- }
187
-
188
197
  async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
189
198
  let checkpoint = await this.getCheckpoint(options);
190
- const map = new Map<string, InternalOpId>(
191
- Object.entries(buckets).map(([bucket, opId]) => [this.resolveBucketName(bucket), opId])
192
- );
199
+ const syncRules = this.getSyncRulesContent();
200
+ const map = Object.entries(buckets).map(([bucket, start]) => bucketRequest(syncRules, bucket, start));
193
201
  return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
194
202
  }
195
203
 
@@ -198,9 +206,9 @@ export class ChangeStreamTestContext {
198
206
  if (typeof start == 'string') {
199
207
  start = BigInt(start);
200
208
  }
201
- const resolvedBucket = this.resolveBucketName(bucket);
209
+ const syncRules = this.getSyncRulesContent();
202
210
  const checkpoint = await this.getCheckpoint(options);
203
- const map = new Map<string, InternalOpId>([[resolvedBucket, start]]);
211
+ let map = [bucketRequest(syncRules, bucket, start)];
204
212
  let data: OplogEntry[] = [];
205
213
  while (true) {
206
214
  const batch = this.storage!.getBucketDataBatch(checkpoint, map);
@@ -210,19 +218,20 @@ export class ChangeStreamTestContext {
210
218
  if (batches.length == 0 || !batches[0]!.chunkData.has_more) {
211
219
  break;
212
220
  }
213
- map.set(resolvedBucket, BigInt(batches[0]!.chunkData.next_after));
221
+ map = [bucketRequest(syncRules, bucket, BigInt(batches[0]!.chunkData.next_after))];
214
222
  }
215
223
  return data;
216
224
  }
217
225
 
218
226
  async getChecksums(buckets: string[], options?: { timeout?: number }): Promise<utils.ChecksumMap> {
219
227
  let checkpoint = await this.getCheckpoint(options);
220
- const versionedBuckets = buckets.map((bucket) => this.resolveBucketName(bucket));
228
+ const syncRules = this.getSyncRulesContent();
229
+ const versionedBuckets = buckets.map((bucket) => bucketRequest(syncRules, bucket, 0n));
221
230
  const checksums = await this.storage!.getChecksums(checkpoint, versionedBuckets);
222
231
 
223
232
  const unversioned: utils.ChecksumMap = new Map();
224
233
  for (let i = 0; i < buckets.length; i++) {
225
- unversioned.set(buckets[i], checksums.get(versionedBuckets[i])!);
234
+ unversioned.set(buckets[i], checksums.get(versionedBuckets[i].bucket)!);
226
235
  }
227
236
  return unversioned;
228
237
  }
package/test/src/env.ts CHANGED
@@ -3,7 +3,7 @@ import { utils } from '@powersync/lib-services-framework';
3
3
  export const env = utils.collectEnvironmentVariables({
4
4
  MONGO_TEST_URL: utils.type.string.default('mongodb://localhost:27017/powersync_test'),
5
5
  MONGO_TEST_DATA_URL: utils.type.string.default('mongodb://localhost:27017/powersync_test_data'),
6
- PG_STORAGE_TEST_URL: utils.type.string.default('postgres://postgres:postgres@localhost:5431/powersync_storage_test'),
6
+ PG_STORAGE_TEST_URL: utils.type.string.default('postgres://postgres:postgres@localhost:5432/powersync_storage_test'),
7
7
  CI: utils.type.boolean.default('false'),
8
8
  SLOW_TESTS: utils.type.boolean.default('false'),
9
9
  TEST_MONGO_STORAGE: utils.type.boolean.default('true'),
@@ -61,8 +61,9 @@ function defineResumeTest({ factory: factoryGenerator, storageVersion }: Storage
61
61
  context2.storage = factory.getInstance(activeContent!);
62
62
 
63
63
  // If this test times out, it likely didn't throw the expected error here.
64
- const error = await context2.startStreaming().catch((ex) => ex);
64
+ const result = await context2.startStreaming();
65
65
  // The ChangeStreamReplicationJob will detect this and throw a ChangeStreamInvalidatedError
66
- expect(error).toBeInstanceOf(ChangeStreamInvalidatedError);
66
+ expect(result.status).toEqual('rejected');
67
+ expect((result as PromiseRejectedResult).reason).toBeInstanceOf(ChangeStreamInvalidatedError);
67
68
  });
68
69
  }
package/test/src/util.ts CHANGED
@@ -5,8 +5,8 @@ import * as postgres_storage from '@powersync/service-module-postgres-storage';
5
5
  import * as types from '@module/types/types.js';
6
6
  import {
7
7
  BSON_DESERIALIZE_DATA_OPTIONS,
8
- CURRENT_STORAGE_VERSION,
9
- LEGACY_STORAGE_VERSION,
8
+ SUPPORTED_STORAGE_VERSIONS,
9
+ TestStorageConfig,
10
10
  TestStorageFactory
11
11
  } from '@powersync/service-core';
12
12
  import { describe, TestOptions } from 'vitest';
@@ -24,11 +24,11 @@ export const INITIALIZED_MONGO_STORAGE_FACTORY = mongo_storage.test_utils.mongoT
24
24
  isCI: env.CI
25
25
  });
26
26
 
27
- export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.test_utils.postgresTestStorageFactoryGenerator({
27
+ export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.test_utils.postgresTestSetup({
28
28
  url: env.PG_STORAGE_TEST_URL
29
29
  });
30
30
 
31
- const TEST_STORAGE_VERSIONS = [LEGACY_STORAGE_VERSION, CURRENT_STORAGE_VERSION];
31
+ export const TEST_STORAGE_VERSIONS = SUPPORTED_STORAGE_VERSIONS;
32
32
 
33
33
  export interface StorageVersionTestContext {
34
34
  factory: TestStorageFactory;
@@ -36,12 +36,12 @@ export interface StorageVersionTestContext {
36
36
  }
37
37
 
38
38
  export function describeWithStorage(options: TestOptions, fn: (context: StorageVersionTestContext) => void) {
39
- const describeFactory = (storageName: string, factory: TestStorageFactory) => {
39
+ const describeFactory = (storageName: string, config: TestStorageConfig) => {
40
40
  describe(`${storageName} storage`, options, function () {
41
41
  for (const storageVersion of TEST_STORAGE_VERSIONS) {
42
42
  describe(`storage v${storageVersion}`, function () {
43
43
  fn({
44
- factory,
44
+ factory: config.factory,
45
45
  storageVersion
46
46
  });
47
47
  });