@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.
- package/CHANGELOG.md +48 -7
- package/dist/api/MongoRouteAPIAdapter.js +2 -2
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/replication/ChangeStream.js +13 -8
- package/dist/replication/ChangeStream.js.map +1 -1
- package/package.json +9 -9
- package/src/api/MongoRouteAPIAdapter.ts +2 -2
- package/src/replication/ChangeStream.ts +14 -8
- package/test/src/change_stream_utils.ts +36 -27
- package/test/src/env.ts +1 -1
- package/test/src/resume.test.ts +3 -2
- package/test/src/util.ts +6 -6
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
-
|
|
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.
|
|
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!.
|
|
157
|
-
|
|
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
|
-
|
|
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
|
|
191
|
-
|
|
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
|
|
209
|
+
const syncRules = this.getSyncRulesContent();
|
|
202
210
|
const checkpoint = await this.getCheckpoint(options);
|
|
203
|
-
|
|
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
|
|
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
|
|
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:
|
|
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'),
|
package/test/src/resume.test.ts
CHANGED
|
@@ -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
|
|
64
|
+
const result = await context2.startStreaming();
|
|
65
65
|
// The ChangeStreamReplicationJob will detect this and throw a ChangeStreamInvalidatedError
|
|
66
|
-
expect(
|
|
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
|
-
|
|
9
|
-
|
|
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.
|
|
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 =
|
|
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,
|
|
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
|
});
|