@powersync/service-module-mongodb 0.21.2 → 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 (90) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/benchmarks/env.ts +11 -0
  3. package/benchmarks/replication_branch_throughput.benchmark.ts +240 -0
  4. package/dist/api/MongoRouteAPIAdapter.d.ts +6 -0
  5. package/dist/api/MongoRouteAPIAdapter.js +21 -2
  6. package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/module/MongoModule.d.ts +1 -1
  11. package/dist/module/MongoModule.js +1 -0
  12. package/dist/module/MongoModule.js.map +1 -1
  13. package/dist/replication/ChangeStream.d.ts +13 -3
  14. package/dist/replication/ChangeStream.js +130 -196
  15. package/dist/replication/ChangeStream.js.map +1 -1
  16. package/dist/replication/ChangeStreamReplicationJob.d.ts +4 -1
  17. package/dist/replication/ChangeStreamReplicationJob.js +19 -8
  18. package/dist/replication/ChangeStreamReplicationJob.js.map +1 -1
  19. package/dist/replication/ChangeStreamReplicator.d.ts +4 -0
  20. package/dist/replication/ChangeStreamReplicator.js +6 -0
  21. package/dist/replication/ChangeStreamReplicator.js.map +1 -1
  22. package/dist/replication/MongoReplicationQueryProvider.d.ts +85 -0
  23. package/dist/replication/MongoReplicationQueryProvider.js +23 -0
  24. package/dist/replication/MongoReplicationQueryProvider.js.map +1 -0
  25. package/dist/replication/MongoReplicationStream.d.ts +74 -0
  26. package/dist/replication/MongoReplicationStream.js +228 -0
  27. package/dist/replication/MongoReplicationStream.js.map +1 -0
  28. package/dist/replication/MongoReplicationStreamTypes.d.ts +157 -0
  29. package/dist/replication/MongoReplicationStreamTypes.js +19 -0
  30. package/dist/replication/MongoReplicationStreamTypes.js.map +1 -0
  31. package/dist/replication/MongoSnapshotQuery.d.ts +2 -0
  32. package/dist/replication/MongoSnapshotQuery.js +12 -2
  33. package/dist/replication/MongoSnapshotQuery.js.map +1 -1
  34. package/dist/replication/MongoSnapshotter.d.ts +7 -2
  35. package/dist/replication/MongoSnapshotter.js +90 -88
  36. package/dist/replication/MongoSnapshotter.js.map +1 -1
  37. package/dist/replication/RawChangeStream.d.ts +1 -1
  38. package/dist/replication/RawChangeStream.js +1 -1
  39. package/dist/replication/RawChangeStream.js.map +1 -1
  40. package/dist/replication/estimateFilteredSnapshotCount.d.ts +12 -0
  41. package/dist/replication/estimateFilteredSnapshotCount.js +79 -0
  42. package/dist/replication/estimateFilteredSnapshotCount.js.map +1 -0
  43. package/dist/replication/replication-index.d.ts +7 -0
  44. package/dist/replication/replication-index.js +7 -0
  45. package/dist/replication/replication-index.js.map +1 -1
  46. package/dist/test-utils/ChangeStreamTestContext.d.ts +131 -0
  47. package/dist/test-utils/ChangeStreamTestContext.js +390 -0
  48. package/dist/test-utils/ChangeStreamTestContext.js.map +1 -0
  49. package/dist/test-utils/test-utils-index.d.ts +1 -0
  50. package/dist/test-utils/test-utils-index.js +2 -0
  51. package/dist/test-utils/test-utils-index.js.map +1 -0
  52. package/package.json +11 -10
  53. package/src/api/MongoRouteAPIAdapter.ts +26 -2
  54. package/src/index.ts +1 -0
  55. package/src/module/MongoModule.ts +2 -1
  56. package/src/replication/ChangeStream.ts +143 -226
  57. package/src/replication/ChangeStreamReplicationJob.ts +21 -9
  58. package/src/replication/ChangeStreamReplicator.ts +9 -0
  59. package/src/replication/MongoReplicationQueryProvider.ts +106 -0
  60. package/src/replication/MongoReplicationStream.ts +314 -0
  61. package/src/replication/MongoReplicationStreamTypes.ts +169 -0
  62. package/src/replication/MongoSnapshotQuery.ts +19 -3
  63. package/src/replication/MongoSnapshotter.ts +101 -92
  64. package/src/replication/RawChangeStream.ts +2 -2
  65. package/src/replication/estimateFilteredSnapshotCount.ts +93 -0
  66. package/src/replication/replication-index.ts +7 -0
  67. package/src/test-utils/ChangeStreamTestContext.ts +520 -0
  68. package/src/test-utils/test-utils-index.ts +1 -0
  69. package/test/src/change_event_memory.test.ts +33 -0
  70. package/test/src/change_stream.test.ts +3 -3
  71. package/test/src/change_stream_abort.test.ts +110 -0
  72. package/test/src/change_stream_test_setup.ts +38 -0
  73. package/test/src/chunked_snapshot.test.ts +3 -3
  74. package/test/src/documentdb_mode.test.ts +3 -3
  75. package/test/src/filtering_example.test.ts +65 -0
  76. package/test/src/filtering_lifecycle.test.ts +101 -0
  77. package/test/src/query_provider_availability.test.ts +77 -0
  78. package/test/src/query_provider_diagnostics.test.ts +55 -0
  79. package/test/src/replication_stream.test.ts +688 -0
  80. package/test/src/resume.test.ts +10 -4
  81. package/test/src/resuming_snapshots.test.ts +3 -3
  82. package/test/src/slow_tests.test.ts +3 -3
  83. package/test/src/snapshot_count.test.ts +146 -0
  84. package/test/src/snapshot_count_integration.test.ts +70 -0
  85. package/test/src/snapshot_query_provider.test.ts +122 -0
  86. package/test/src/stream_progress.test.ts +334 -0
  87. package/test/tsconfig.json +2 -2
  88. package/tsconfig.tsbuildinfo +1 -1
  89. package/vitest.benchmark.config.ts +15 -0
  90. package/test/src/change_stream_utils.ts +0 -375
@@ -0,0 +1,79 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ const COUNT_TIMEOUT_MS = 10_000;
3
+ /**
4
+ * Keep below 5% for MongoDB's random-cursor optimization on large collections.
5
+ * https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/
6
+ */
7
+ const SAMPLE_FRACTION = 0.01;
8
+ const MAX_SAMPLE_SIZE = 10_000;
9
+ /**
10
+ * Try an exact filtered count, then estimate selectivity from a bounded sample.
11
+ * Each attempt has a separate 10-second budget. Failures leave progress unknown (-1).
12
+ */
13
+ export async function estimateFilteredSnapshotCount(options) {
14
+ const { collection, filter, signal, logger } = options;
15
+ signal.throwIfAborted();
16
+ try {
17
+ return await collection.countDocuments(filter, {
18
+ collation: { locale: 'simple' },
19
+ readConcern: 'majority',
20
+ timeoutMS: COUNT_TIMEOUT_MS,
21
+ signal
22
+ });
23
+ }
24
+ catch (error) {
25
+ signal.throwIfAborted();
26
+ logger.debug(`Filtered snapshot count unavailable for ${collection.namespace}; trying a sample strategy`, error);
27
+ }
28
+ const sampleStartedAt = performance.now();
29
+ try {
30
+ const metadataOptions = { timeoutMS: COUNT_TIMEOUT_MS, signal };
31
+ const totalCount = await collection.estimatedDocumentCount(metadataOptions);
32
+ signal.throwIfAborted();
33
+ if (totalCount <= 0) {
34
+ // Metadata alone cannot establish that a filtered result is empty after the count failed.
35
+ return -1;
36
+ }
37
+ const remainingTimeMS = Math.floor(COUNT_TIMEOUT_MS - (performance.now() - sampleStartedAt));
38
+ if (remainingTimeMS <= 0) {
39
+ return -1;
40
+ }
41
+ // Keep $sample first and below 5% for MongoDB's random-cursor optimization on large collections.
42
+ // https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/
43
+ const sampleSize = Math.min(MAX_SAMPLE_SIZE, Math.max(1, Math.floor(totalCount * SAMPLE_FRACTION)));
44
+ const cursor = collection.aggregate([
45
+ { $sample: { size: sampleSize } },
46
+ {
47
+ $facet: {
48
+ sampled: [{ $count: 'count' }],
49
+ matches: [{ $match: filter }, { $count: 'count' }]
50
+ }
51
+ }
52
+ ], {
53
+ collation: { locale: 'simple' },
54
+ readConcern: 'majority',
55
+ timeoutMS: remainingTimeMS,
56
+ signal,
57
+ allowDiskUse: false
58
+ });
59
+ try {
60
+ const sample = await cursor.next();
61
+ const sampledCount = sample?.sampled[0]?.count ?? 0;
62
+ const matchedCount = sample?.matches[0]?.count ?? 0;
63
+ if (sampledCount == 0 || matchedCount == 0) {
64
+ // A rare predicate can miss the whole sample; that does not establish an empty result.
65
+ return -1;
66
+ }
67
+ return Math.round((matchedCount / sampledCount) * totalCount);
68
+ }
69
+ finally {
70
+ await cursor.close();
71
+ }
72
+ }
73
+ catch (error) {
74
+ signal.throwIfAborted();
75
+ logger.debug(`Filtered snapshot sample unavailable for ${collection.namespace}; total remains unknown`, error);
76
+ return -1;
77
+ }
78
+ }
79
+ //# sourceMappingURL=estimateFilteredSnapshotCount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimateFilteredSnapshotCount.js","sourceRoot":"","sources":["../../src/replication/estimateFilteredSnapshotCount.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,14 @@
1
1
  export * from './ChangeStream.js';
2
2
  export * from './ChangeStreamReplicationJob.js';
3
3
  export * from './ChangeStreamReplicator.js';
4
+ export * from './ConnectionManagerFactory.js';
4
5
  export * from './internal-mongodb-utils.js';
6
+ export * from './MongoErrorRateLimiter.js';
5
7
  export * from './MongoRelation.js';
8
+ export * from './MongoReplicationQueryProvider.js';
9
+ export * from './MongoReplicationStream.js';
10
+ export * from './MongoReplicationStreamTypes.js';
11
+ export * from './MongoSnapshotter.js';
6
12
  export * from './RawChangeStream.js';
13
+ export { CHECKPOINTS_COLLECTION } from './replication-utils.js';
7
14
  export * from './SourceRowConverter.js';
@@ -1,8 +1,15 @@
1
1
  export * from './ChangeStream.js';
2
2
  export * from './ChangeStreamReplicationJob.js';
3
3
  export * from './ChangeStreamReplicator.js';
4
+ export * from './ConnectionManagerFactory.js';
4
5
  export * from './internal-mongodb-utils.js';
6
+ export * from './MongoErrorRateLimiter.js';
5
7
  export * from './MongoRelation.js';
8
+ export * from './MongoReplicationQueryProvider.js';
9
+ export * from './MongoReplicationStream.js';
10
+ export * from './MongoReplicationStreamTypes.js';
11
+ export * from './MongoSnapshotter.js';
6
12
  export * from './RawChangeStream.js';
13
+ export { CHECKPOINTS_COLLECTION } from './replication-utils.js';
7
14
  export * from './SourceRowConverter.js';
8
15
  //# sourceMappingURL=replication-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"replication-index.js","sourceRoot":"","sources":["../../src/replication/replication-index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"replication-index.js","sourceRoot":"","sources":["../../src/replication/replication-index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { BucketStorageFactory, InternalOpId, OplogEntry, ProtocolOpId, ReplicationCheckpoint, storage, SyncRulesBucketStorage, TestStorageFactory, utils } from '@powersync/service-core';
3
+ import { ChangeStream, ChangeStreamOptions } from '../replication/ChangeStream.js';
4
+ import { MongoManager } from '../replication/MongoManager.js';
5
+ import { NormalizedMongoConnectionConfig } from '../types/types.js';
6
+ /**
7
+ * Stream settings callers may override while the context owns connections, storage, metrics and cancellation.
8
+ */
9
+ export type TestChangeStreamOptions = Omit<ChangeStreamOptions, 'connections' | 'storage' | 'metrics' | 'abort_signal'>;
10
+ export interface ChangeStreamTestContextOptions {
11
+ factory: TestStorageFactory;
12
+ connectionOptions: NormalizedMongoConnectionConfig;
13
+ metrics: ChangeStreamOptions['metrics'];
14
+ /**
15
+ * Preserve both source data and bucket storage when reopening an interrupted replication attempt.
16
+ */
17
+ doNotClear?: boolean;
18
+ storageVersion?: number;
19
+ streamOptions?: TestChangeStreamOptions;
20
+ /**
21
+ * Defaults to detecting DocumentDB from the source's hello response.
22
+ */
23
+ documentDbMode?: boolean;
24
+ }
25
+ export interface ChangeStreamTestContextResources {
26
+ factory: BucketStorageFactory;
27
+ connectionManager: MongoManager;
28
+ metrics: ChangeStreamOptions['metrics'];
29
+ storageVersion?: number;
30
+ streamOptions?: TestChangeStreamOptions;
31
+ documentDbMode?: boolean;
32
+ }
33
+ /**
34
+ * Owns source/storage connections and real replication. Callers supply their environment and storage implementation.
35
+ */
36
+ export declare class ChangeStreamTestContext implements AsyncDisposable {
37
+ private _walStream?;
38
+ private abortController;
39
+ private settledReplicationPromise?;
40
+ private syncRulesContent?;
41
+ private readonly replicationLeases;
42
+ storage?: SyncRulesBucketStorage;
43
+ /**
44
+ * Open a fresh fixture by default; use doNotClear to resume its persisted source and storage state.
45
+ */
46
+ static open(options: ChangeStreamTestContextOptions): Promise<ChangeStreamTestContext>;
47
+ /**
48
+ * Share resource setup and failure cleanup with subclasses that supply their own context and defaults.
49
+ */
50
+ protected static openWith<T extends ChangeStreamTestContext>({ options, createContext }: {
51
+ options: ChangeStreamTestContextOptions;
52
+ createContext: (resources: ChangeStreamTestContextResources) => T | Promise<T>;
53
+ }): Promise<T>;
54
+ readonly factory: BucketStorageFactory;
55
+ readonly connectionManager: MongoManager;
56
+ protected readonly metrics: ChangeStreamOptions['metrics'];
57
+ protected readonly streamOptions: TestChangeStreamOptions;
58
+ protected readonly storageVersion: number;
59
+ private readonly documentDbMode;
60
+ /**
61
+ * Takes ownership of an already configured storage factory and source connection.
62
+ */
63
+ constructor({ factory, connectionManager, metrics, streamOptions, storageVersion, documentDbMode }: ChangeStreamTestContextResources);
64
+ /**
65
+ * Abort snapshot and/or replication, without actively closing connections.
66
+ */
67
+ abort(cause?: Error): void;
68
+ /**
69
+ * Drain replication before inspecting its final persisted state or closing connections.
70
+ */
71
+ stop(cause?: Error): Promise<void>;
72
+ dispose(): Promise<void>;
73
+ [Symbol.asyncDispose](): Promise<void>;
74
+ get client(): mongo.MongoClient;
75
+ get db(): mongo.Db;
76
+ get connectionTag(): string;
77
+ updateSyncRules(content: string): Promise<storage.SyncRulesBucketStorage>;
78
+ loadNextSyncRules(): Promise<storage.SyncRulesBucketStorage>;
79
+ loadActiveSyncRules(): Promise<storage.SyncRulesBucketStorage>;
80
+ /**
81
+ * Reuse a replication lease per stream until the owning factory is disposed.
82
+ */
83
+ private getWritableStorage;
84
+ getSyncConfigContent(): storage.PersistedSyncConfigContent;
85
+ get streamer(): ChangeStream;
86
+ replicateSnapshot(): Promise<void>;
87
+ /**
88
+ * A snapshot is not consistent until streaming replication has caught up.
89
+ * We simulate that for tests.
90
+ * Do not use if there are any writes performed while doing the snapshot, as that
91
+ * would result in inconsistent data.
92
+ */
93
+ markSnapshotConsistent(): Promise<void>;
94
+ startStreaming(): Promise<PromiseSettledResult<void>>;
95
+ /**
96
+ * Wait for this stream to publish a real source marker; stop polling if replication exits first.
97
+ */
98
+ getCheckpoint(options?: {
99
+ timeout?: number;
100
+ }): Promise<ReplicationCheckpoint>;
101
+ getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: {
102
+ timeout?: number;
103
+ }): Promise<(storage.SyncBucketDataChunk | storage.SyncBucketDataBatchEnd)[]>;
104
+ getBucketData(bucket: string, start?: ProtocolOpId | InternalOpId | undefined, options?: {
105
+ timeout?: number;
106
+ }): Promise<OplogEntry<utils.StoredOplogData>[]>;
107
+ getBucketDataAtLatestCheckpoint(bucket: string, start?: ProtocolOpId | InternalOpId | undefined): Promise<OplogEntry<utils.StoredOplogData>[]>;
108
+ getBucketDataAtCheckpoint(bucket: string, checkpoint: ReplicationCheckpoint, start?: ProtocolOpId | InternalOpId | undefined): Promise<OplogEntry<utils.StoredOplogData>[]>;
109
+ /**
110
+ * Resolve a readable test bucket such as global[] to its persisted, versioned bucket name.
111
+ */
112
+ private bucketRequest;
113
+ getChecksums(buckets: string[], options?: {
114
+ timeout?: number;
115
+ }): Promise<utils.ChecksumMap>;
116
+ getChecksum(bucket: string, options?: {
117
+ timeout?: number;
118
+ }): Promise<utils.BucketChecksum | undefined>;
119
+ }
120
+ export declare function getClientCheckpoint({ client, db, storageFactory, timeout, documentDbMode: forcedDocumentDbMode, storage: expectedStorage, signal }: {
121
+ client: mongo.MongoClient;
122
+ db: mongo.Db;
123
+ storageFactory: BucketStorageFactory;
124
+ timeout?: number;
125
+ documentDbMode?: boolean;
126
+ /**
127
+ * Ignore checkpoints from another active stream while this one is still deploying.
128
+ */
129
+ storage?: SyncRulesBucketStorage;
130
+ signal?: AbortSignal;
131
+ }): Promise<ReplicationCheckpoint>;
@@ -0,0 +1,390 @@
1
+ import { ReplicationAbortedError } from '@powersync/lib-services-framework';
2
+ import { createCoreReplicationMetrics, initializeCoreReplicationMetrics, isBatchEnd, LEGACY_STORAGE_VERSION, settledPromise, unsettledPromise, updateSyncRulesFromConfig } from '@powersync/service-core';
3
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
4
+ import { setTimeout } from 'node:timers/promises';
5
+ import { SentinelLSN } from '../common/SentinelLSN.js';
6
+ import { ChangeStream } from '../replication/ChangeStream.js';
7
+ import { MongoManager } from '../replication/MongoManager.js';
8
+ import { createCheckpoint, createSentinelCheckpointLsn, STANDALONE_CHECKPOINT_ID } from '../replication/MongoRelation.js';
9
+ import { detectDocumentDb } from '../replication/replication-utils.js';
10
+ /**
11
+ * Owns source/storage connections and real replication. Callers supply their environment and storage implementation.
12
+ */
13
+ export class ChangeStreamTestContext {
14
+ _walStream;
15
+ abortController = new AbortController();
16
+ settledReplicationPromise;
17
+ syncRulesContent;
18
+ replicationLeases = new Map();
19
+ storage;
20
+ /**
21
+ * Open a fresh fixture by default; use doNotClear to resume its persisted source and storage state.
22
+ */
23
+ static async open(options) {
24
+ return this.openWith({ options, createContext: (resources) => new ChangeStreamTestContext(resources) });
25
+ }
26
+ /**
27
+ * Share resource setup and failure cleanup with subclasses that supply their own context and defaults.
28
+ */
29
+ static async openWith({ options, createContext }) {
30
+ const factory = await options.factory({ doNotClear: options.doNotClear });
31
+ try {
32
+ const connectionManager = new MongoManager(options.connectionOptions);
33
+ try {
34
+ if (!options.doNotClear) {
35
+ await connectionManager.db.dropDatabase();
36
+ }
37
+ const documentDbMode = options.documentDbMode ?? (await detectDocumentDb(connectionManager.db));
38
+ // Await subclass setup so failures still close the shared connections below.
39
+ return await createContext({ ...options, factory, connectionManager, documentDbMode });
40
+ }
41
+ catch (error) {
42
+ await connectionManager.end();
43
+ throw error;
44
+ }
45
+ }
46
+ catch (error) {
47
+ await factory[Symbol.asyncDispose]();
48
+ throw error;
49
+ }
50
+ }
51
+ factory;
52
+ connectionManager;
53
+ metrics;
54
+ streamOptions;
55
+ storageVersion;
56
+ documentDbMode;
57
+ /**
58
+ * Takes ownership of an already configured storage factory and source connection.
59
+ */
60
+ constructor({ factory, connectionManager, metrics, streamOptions = {}, storageVersion = LEGACY_STORAGE_VERSION, documentDbMode = false }) {
61
+ this.factory = factory;
62
+ this.connectionManager = connectionManager;
63
+ this.metrics = metrics;
64
+ this.streamOptions = streamOptions;
65
+ this.storageVersion = storageVersion;
66
+ this.documentDbMode = documentDbMode;
67
+ createCoreReplicationMetrics(metrics);
68
+ initializeCoreReplicationMetrics(metrics);
69
+ const unregister = factory.registerListener({
70
+ beforeDispose: async () => {
71
+ unregister();
72
+ try {
73
+ for (const lease of this.replicationLeases.values()) {
74
+ await (await lease).release();
75
+ }
76
+ }
77
+ finally {
78
+ this.replicationLeases.clear();
79
+ }
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Abort snapshot and/or replication, without actively closing connections.
85
+ */
86
+ abort(cause) {
87
+ this.abortController.abort(cause);
88
+ }
89
+ /**
90
+ * Drain replication before inspecting its final persisted state or closing connections.
91
+ */
92
+ async stop(cause) {
93
+ this.abort(cause);
94
+ await this.settledReplicationPromise;
95
+ }
96
+ async dispose() {
97
+ await this.stop(new Error('Disposing test context'));
98
+ try {
99
+ await this.factory[Symbol.asyncDispose]();
100
+ }
101
+ finally {
102
+ await this.connectionManager.end();
103
+ }
104
+ }
105
+ async [Symbol.asyncDispose]() {
106
+ await this.dispose();
107
+ }
108
+ get client() {
109
+ return this.connectionManager.client;
110
+ }
111
+ get db() {
112
+ return this.connectionManager.db;
113
+ }
114
+ get connectionTag() {
115
+ return this.connectionManager.connectionTag;
116
+ }
117
+ async updateSyncRules(content) {
118
+ const replicationStream = await this.factory.updateSyncRules(updateSyncRulesFromConfig(SqlSyncRules.fromYaml(content, {
119
+ defaultSchema: this.db.databaseName,
120
+ throwOnError: true
121
+ }), { storageVersion: this.storageVersion, defaultSchema: this.db.databaseName }));
122
+ this.syncRulesContent = replicationStream.syncConfigContent[0];
123
+ this.storage = await this.getWritableStorage(replicationStream);
124
+ return this.storage;
125
+ }
126
+ async loadNextSyncRules() {
127
+ const syncConfig = await this.factory.getDeployingSyncConfig();
128
+ if (syncConfig == null) {
129
+ throw new Error(`Next sync config not available`);
130
+ }
131
+ this.syncRulesContent = syncConfig.content;
132
+ this.storage = await this.getWritableStorage(syncConfig.replicationStream);
133
+ return this.storage;
134
+ }
135
+ async loadActiveSyncRules() {
136
+ const syncConfig = await this.factory.getActiveSyncConfig();
137
+ if (syncConfig == null) {
138
+ throw new Error(`Active sync config not found`);
139
+ }
140
+ this.syncRulesContent = syncConfig.content;
141
+ this.storage = await this.getWritableStorage(syncConfig.replicationStream);
142
+ return this.storage;
143
+ }
144
+ /**
145
+ * Reuse a replication lease per stream until the owning factory is disposed.
146
+ */
147
+ async getWritableStorage(stream) {
148
+ let pending = this.replicationLeases.get(stream.replicationStreamId);
149
+ if (pending == null) {
150
+ pending = stream.lock();
151
+ this.replicationLeases.set(stream.replicationStreamId, pending);
152
+ }
153
+ let replicationLock;
154
+ try {
155
+ replicationLock = await pending;
156
+ }
157
+ catch (error) {
158
+ this.replicationLeases.delete(stream.replicationStreamId);
159
+ throw error;
160
+ }
161
+ return this.factory.getInstance(stream, { replicationLock });
162
+ }
163
+ getSyncConfigContent() {
164
+ if (this.syncRulesContent == null) {
165
+ throw new Error('Sync config not configured - call updateSyncRules() first');
166
+ }
167
+ return this.syncRulesContent;
168
+ }
169
+ get streamer() {
170
+ if (this.storage == null) {
171
+ throw new Error('updateSyncRules() first');
172
+ }
173
+ if (this._walStream) {
174
+ return this._walStream;
175
+ }
176
+ const options = {
177
+ // Forward all supported stream options, including custom query providers and snapshot/storage hooks.
178
+ ...this.streamOptions,
179
+ storage: this.storage,
180
+ metrics: this.metrics,
181
+ connections: this.connectionManager,
182
+ abort_signal: this.abortController.signal,
183
+ // A shorter await also keeps aborts responsive on MongoDB <= 6.0.
184
+ maxAwaitTimeMS: this.streamOptions.maxAwaitTimeMS ?? 200
185
+ };
186
+ this._walStream = new ChangeStream(options);
187
+ return this._walStream;
188
+ }
189
+ async replicateSnapshot() {
190
+ this.settledReplicationPromise ??= settledPromise(this.streamer.replicate());
191
+ try {
192
+ await Promise.race([unsettledPromise(this.settledReplicationPromise), this.streamer.waitForInitialSnapshot()]);
193
+ }
194
+ catch (e) {
195
+ if (e instanceof ReplicationAbortedError && e.cause != null) {
196
+ throw e.cause;
197
+ }
198
+ throw e;
199
+ }
200
+ }
201
+ /**
202
+ * A snapshot is not consistent until streaming replication has caught up.
203
+ * We simulate that for tests.
204
+ * Do not use if there are any writes performed while doing the snapshot, as that
205
+ * would result in inconsistent data.
206
+ */
207
+ async markSnapshotConsistent() {
208
+ let checkpoint;
209
+ if (this.documentDbMode) {
210
+ const sentinelCheckpoint = SentinelLSN.fromSerialized(await createSentinelCheckpointLsn(this.client, this.db));
211
+ const status = await this.storage.getStatus();
212
+ const resumeFrom = status.resumeLsn;
213
+ const resumeToken = resumeFrom ? SentinelLSN.fromSerialized(resumeFrom).resumeToken : null;
214
+ // This helper artificially marks the snapshot as consistent without
215
+ // waiting for the stream to observe the sentinel. Keep the sentinel as the
216
+ // comparable position, but carry forward the existing snapshot resume
217
+ // token so later DocumentDB streaming still resumes from a real token.
218
+ checkpoint = new SentinelLSN({
219
+ sentinel: sentinelCheckpoint.sentinel,
220
+ resume_token: resumeToken
221
+ }).comparable;
222
+ }
223
+ else {
224
+ checkpoint = await createCheckpoint(this.db, STANDALONE_CHECKPOINT_ID);
225
+ }
226
+ await using writer = await this.storage.createWriter({
227
+ zeroLSN: '0000000000000000',
228
+ defaultSchema: this.db.databaseName,
229
+ storeCurrentData: true
230
+ });
231
+ await writer.keepalive(checkpoint);
232
+ await writer.flush();
233
+ }
234
+ startStreaming() {
235
+ this.settledReplicationPromise ??= settledPromise(this.streamer.replicate());
236
+ return this.settledReplicationPromise;
237
+ }
238
+ /**
239
+ * Wait for this stream to publish a real source marker; stop polling if replication exits first.
240
+ */
241
+ async getCheckpoint(options) {
242
+ if (this.settledReplicationPromise == null) {
243
+ throw new Error('Start replication before requesting a checkpoint.');
244
+ }
245
+ const polling = new AbortController();
246
+ const wait = getClientCheckpoint({
247
+ client: this.client,
248
+ db: this.db,
249
+ storageFactory: this.factory,
250
+ storage: this.storage,
251
+ timeout: options?.timeout ?? 15_000,
252
+ documentDbMode: this.documentDbMode,
253
+ signal: polling.signal
254
+ });
255
+ try {
256
+ return await Promise.race([
257
+ wait,
258
+ unsettledPromise(this.settledReplicationPromise).then(() => {
259
+ throw new Error('Replication exited before publishing the requested checkpoint.');
260
+ })
261
+ ]);
262
+ }
263
+ finally {
264
+ polling.abort();
265
+ await wait.catch(() => { });
266
+ }
267
+ }
268
+ async getBucketsDataBatch(buckets, options) {
269
+ let checkpoint = await this.getCheckpoint(options);
270
+ const map = Object.entries(buckets).map(([bucket, start]) => this.bucketRequest(bucket, start));
271
+ const chunks = [];
272
+ for await (const chunk of this.storage.getBucketDataBatch(checkpoint, map)) {
273
+ chunks.push(chunk);
274
+ }
275
+ return chunks;
276
+ }
277
+ async getBucketData(bucket, start, options) {
278
+ const checkpoint = await this.getCheckpoint(options);
279
+ return this.getBucketDataAtCheckpoint(bucket, checkpoint, start);
280
+ }
281
+ async getBucketDataAtLatestCheckpoint(bucket, start) {
282
+ if (this.storage == null) {
283
+ throw new Error('updateSyncRules() first');
284
+ }
285
+ const checkpoint = await this.storage.getCheckpoint();
286
+ return this.getBucketDataAtCheckpoint(bucket, checkpoint, start);
287
+ }
288
+ async getBucketDataAtCheckpoint(bucket, checkpoint, start) {
289
+ start ??= 0n;
290
+ if (typeof start == 'string') {
291
+ start = BigInt(start);
292
+ }
293
+ let map = [this.bucketRequest(bucket, start)];
294
+ let data = [];
295
+ while (true) {
296
+ const batch = this.storage.getBucketDataBatch(checkpoint, map);
297
+ const chunks = [];
298
+ for await (const chunk of batch) {
299
+ chunks.push(chunk);
300
+ }
301
+ if (chunks.length == 0) {
302
+ break;
303
+ }
304
+ for (let chunk of chunks) {
305
+ if (isBatchEnd(chunk)) {
306
+ if (!chunk.hasMore) {
307
+ return data;
308
+ }
309
+ }
310
+ else {
311
+ data = data.concat(chunk.chunkData.data ?? []);
312
+ map = [this.bucketRequest(bucket, BigInt(chunk.chunkData.next_after))];
313
+ if (!chunk.chunkData.has_more) {
314
+ return data;
315
+ }
316
+ }
317
+ }
318
+ }
319
+ return data;
320
+ }
321
+ /**
322
+ * Resolve a readable test bucket such as global[] to its persisted, versioned bucket name.
323
+ */
324
+ bucketRequest(bucket, start) {
325
+ const parsed = this.getSyncConfigContent().parsed({ defaultSchema: this.db.databaseName });
326
+ const parameterStart = bucket.indexOf('[');
327
+ const definitionName = bucket.substring(0, parameterStart);
328
+ const source = parsed.syncConfigs
329
+ .flatMap((config) => config.config.bucketDataSources)
330
+ .find((source) => source.uniqueName == definitionName);
331
+ if (source == null) {
332
+ throw new Error(`Unknown bucket '${bucket}'.`);
333
+ }
334
+ return {
335
+ bucket: parsed.hydrationState.getBucketSourceScope(source).bucketPrefix + bucket.substring(parameterStart),
336
+ source,
337
+ start
338
+ };
339
+ }
340
+ async getChecksums(buckets, options) {
341
+ let checkpoint = await this.getCheckpoint(options);
342
+ const versionedBuckets = buckets.map((bucket) => this.bucketRequest(bucket, 0n));
343
+ const checksums = await this.storage.getChecksums(checkpoint, versionedBuckets);
344
+ const unversioned = new Map();
345
+ for (let i = 0; i < buckets.length; i++) {
346
+ unversioned.set(buckets[i], checksums.get(versionedBuckets[i].bucket));
347
+ }
348
+ return unversioned;
349
+ }
350
+ async getChecksum(bucket, options) {
351
+ const checksums = await this.getChecksums([bucket], options);
352
+ return checksums.get(bucket);
353
+ }
354
+ }
355
+ export async function getClientCheckpoint({ client, db, storageFactory, timeout = 50_000, documentDbMode: forcedDocumentDbMode, storage: expectedStorage, signal }) {
356
+ const start = Date.now();
357
+ const documentDbMode = forcedDocumentDbMode ?? (await detectDocumentDb(db));
358
+ const lsn = documentDbMode
359
+ ? await createSentinelCheckpointLsn(client, db)
360
+ : await createCheckpoint(db, STANDALONE_CHECKPOINT_ID);
361
+ // The marker establishes a source position. Wait until replication publishes a client-visible checkpoint past it.
362
+ // DocumentDB: the streaming loop skips standalone checkpoint events while a
363
+ // batch barrier is pending (see ChangeStream.ts), so a single sentinel bump
364
+ // can be missed on an idle stream with no later event to advance the
365
+ // checkpoint. Periodically re-bump the sentinel so a standalone event
366
+ // eventually commits past `lsn`, mirroring getSnapshotLsn's retry loop.
367
+ const NUDGE_INTERVAL_MS = 1000;
368
+ let lastNudge = Date.now();
369
+ let lastCp = null;
370
+ while (Date.now() - start < timeout) {
371
+ signal?.throwIfAborted();
372
+ const activeStorage = (await storageFactory.getActiveSyncConfig())?.storage;
373
+ const cp = expectedStorage == null || activeStorage?.replicationStreamId == expectedStorage.replicationStreamId
374
+ ? await activeStorage?.getCheckpoint()
375
+ : undefined;
376
+ if (cp != null) {
377
+ lastCp = cp;
378
+ if (cp.lsn && cp.lsn >= lsn) {
379
+ return cp;
380
+ }
381
+ }
382
+ if (documentDbMode && Date.now() - lastNudge >= NUDGE_INTERVAL_MS) {
383
+ await createSentinelCheckpointLsn(client, db);
384
+ lastNudge = Date.now();
385
+ }
386
+ await setTimeout(30, undefined, { signal });
387
+ }
388
+ throw new Error(`Timeout while waiting for checkpoint ${lsn}. Last checkpoint: ${lastCp?.lsn}`);
389
+ }
390
+ //# sourceMappingURL=ChangeStreamTestContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChangeStreamTestContext.js","sourceRoot":"","sources":["../../src/test-utils/ChangeStreamTestContext.ts"],"names":[],"mappings":"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"}