@powersync/service-module-mongodb 0.0.0-dev-20241128134723 → 0.0.0-dev-20241219110735

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 (137) hide show
  1. package/CHANGELOG.md +69 -4
  2. package/dist/db/db-index.d.ts +1 -0
  3. package/dist/db/db-index.js +2 -0
  4. package/dist/db/db-index.js.map +1 -0
  5. package/dist/db/mongo.d.ts +35 -0
  6. package/dist/db/mongo.js +73 -0
  7. package/dist/db/mongo.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/locks/MonogLocks.d.ts +36 -0
  12. package/dist/locks/MonogLocks.js +83 -0
  13. package/dist/locks/MonogLocks.js.map +1 -0
  14. package/dist/migrations/MonogMigrationAgent.d.ts +12 -0
  15. package/dist/migrations/MonogMigrationAgent.js +25 -0
  16. package/dist/migrations/MonogMigrationAgent.js.map +1 -0
  17. package/dist/migrations/db/migrations/1684951997326-init.d.ts +3 -0
  18. package/dist/migrations/db/migrations/1684951997326-init.js +30 -0
  19. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -0
  20. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +2 -0
  21. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +5 -0
  22. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -0
  23. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +3 -0
  24. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +54 -0
  25. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -0
  26. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +3 -0
  27. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +26 -0
  28. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -0
  29. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
  30. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +28 -0
  31. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
  32. package/dist/migrations/mongo-migration-store.d.ts +7 -0
  33. package/dist/migrations/mongo-migration-store.js +49 -0
  34. package/dist/migrations/mongo-migration-store.js.map +1 -0
  35. package/dist/module/MongoModule.js +15 -4
  36. package/dist/module/MongoModule.js.map +1 -1
  37. package/dist/replication/MongoManager.d.ts +1 -1
  38. package/dist/replication/MongoManager.js +3 -2
  39. package/dist/replication/MongoManager.js.map +1 -1
  40. package/dist/storage/MongoBucketStorage.d.ts +48 -0
  41. package/dist/storage/MongoBucketStorage.js +425 -0
  42. package/dist/storage/MongoBucketStorage.js.map +1 -0
  43. package/dist/storage/implementation/MongoBucketBatch.d.ts +72 -0
  44. package/dist/storage/implementation/MongoBucketBatch.js +681 -0
  45. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -0
  46. package/dist/storage/implementation/MongoCompactor.d.ts +40 -0
  47. package/dist/storage/implementation/MongoCompactor.js +310 -0
  48. package/dist/storage/implementation/MongoCompactor.js.map +1 -0
  49. package/dist/storage/implementation/MongoIdSequence.d.ts +12 -0
  50. package/dist/storage/implementation/MongoIdSequence.js +21 -0
  51. package/dist/storage/implementation/MongoIdSequence.js.map +1 -0
  52. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +9 -0
  53. package/dist/storage/implementation/MongoPersistedSyncRules.js +9 -0
  54. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  55. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +20 -0
  56. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +26 -0
  57. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -0
  58. package/dist/storage/implementation/MongoStorageProvider.d.ts +6 -0
  59. package/dist/storage/implementation/MongoStorageProvider.js +34 -0
  60. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -0
  61. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +36 -0
  62. package/dist/storage/implementation/MongoSyncBucketStorage.js +529 -0
  63. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -0
  64. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +16 -0
  65. package/dist/storage/implementation/MongoSyncRulesLock.js +65 -0
  66. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -0
  67. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +20 -0
  68. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +104 -0
  69. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -0
  70. package/dist/storage/implementation/OperationBatch.d.ts +34 -0
  71. package/dist/storage/implementation/OperationBatch.js +119 -0
  72. package/dist/storage/implementation/OperationBatch.js.map +1 -0
  73. package/dist/storage/implementation/PersistedBatch.d.ts +46 -0
  74. package/dist/storage/implementation/PersistedBatch.js +223 -0
  75. package/dist/storage/implementation/PersistedBatch.js.map +1 -0
  76. package/dist/storage/implementation/config.d.ts +19 -0
  77. package/dist/storage/implementation/config.js +26 -0
  78. package/dist/storage/implementation/config.js.map +1 -0
  79. package/dist/storage/implementation/db.d.ts +36 -0
  80. package/dist/storage/implementation/db.js +47 -0
  81. package/dist/storage/implementation/db.js.map +1 -0
  82. package/dist/storage/implementation/models.d.ts +139 -0
  83. package/dist/storage/implementation/models.js +2 -0
  84. package/dist/storage/implementation/models.js.map +1 -0
  85. package/dist/storage/implementation/util.d.ts +58 -0
  86. package/dist/storage/implementation/util.js +196 -0
  87. package/dist/storage/implementation/util.js.map +1 -0
  88. package/dist/storage/storage-index.d.ts +14 -0
  89. package/dist/storage/storage-index.js +15 -0
  90. package/dist/storage/storage-index.js.map +1 -0
  91. package/dist/types/types.d.ts +3 -0
  92. package/dist/types/types.js +4 -1
  93. package/dist/types/types.js.map +1 -1
  94. package/package.json +11 -8
  95. package/src/db/db-index.ts +1 -0
  96. package/src/db/mongo.ts +81 -0
  97. package/src/index.ts +4 -0
  98. package/src/locks/MonogLocks.ts +147 -0
  99. package/src/migrations/MonogMigrationAgent.ts +39 -0
  100. package/src/migrations/db/migrations/1684951997326-init.ts +39 -0
  101. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +5 -0
  102. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +105 -0
  103. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +38 -0
  104. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +40 -0
  105. package/src/migrations/mongo-migration-store.ts +62 -0
  106. package/src/module/MongoModule.ts +18 -4
  107. package/src/replication/MongoManager.ts +6 -2
  108. package/src/storage/MongoBucketStorage.ts +530 -0
  109. package/src/storage/implementation/MongoBucketBatch.ts +893 -0
  110. package/src/storage/implementation/MongoCompactor.ts +392 -0
  111. package/src/storage/implementation/MongoIdSequence.ts +24 -0
  112. package/src/storage/implementation/MongoPersistedSyncRules.ts +16 -0
  113. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +49 -0
  114. package/src/storage/implementation/MongoStorageProvider.ts +42 -0
  115. package/src/storage/implementation/MongoSyncBucketStorage.ts +612 -0
  116. package/src/storage/implementation/MongoSyncRulesLock.ts +88 -0
  117. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +146 -0
  118. package/src/storage/implementation/OperationBatch.ts +130 -0
  119. package/src/storage/implementation/PersistedBatch.ts +283 -0
  120. package/src/storage/implementation/config.ts +40 -0
  121. package/src/storage/implementation/db.ts +88 -0
  122. package/src/storage/implementation/models.ts +160 -0
  123. package/src/storage/implementation/util.ts +209 -0
  124. package/src/storage/storage-index.ts +14 -0
  125. package/src/types/types.ts +8 -1
  126. package/test/src/__snapshots__/storage_sync.test.ts.snap +332 -0
  127. package/test/src/change_stream.test.ts +34 -33
  128. package/test/src/change_stream_utils.ts +6 -6
  129. package/test/src/env.ts +1 -0
  130. package/test/src/slow_tests.test.ts +4 -4
  131. package/test/src/storage.test.ts +7 -0
  132. package/test/src/storage_compacting.test.ts +6 -0
  133. package/test/src/storage_sync.test.ts +113 -0
  134. package/test/src/util.ts +20 -7
  135. package/test/tsconfig.json +4 -0
  136. package/tsconfig.tsbuildinfo +1 -1
  137. package/vitest.config.ts +1 -1
@@ -0,0 +1,147 @@
1
+ import * as framework from '@powersync/lib-services-framework';
2
+ import * as bson from 'bson';
3
+ import * as mongo from 'mongodb';
4
+
5
+ /**
6
+ * Lock Document Schema
7
+ */
8
+ export type Lock = {
9
+ name: string;
10
+ active_lock?: {
11
+ lock_id: bson.ObjectId;
12
+ ts: Date;
13
+ };
14
+ };
15
+
16
+ export type Collection = mongo.Collection<Lock>;
17
+
18
+ export type AcquireLockParams = {
19
+ /**
20
+ * Name of the process/user trying to acquire the lock.
21
+ */
22
+ name: string;
23
+ /**
24
+ * The TTL of the lock (ms). Default: 60000 ms (1 min)
25
+ */
26
+ timeout?: number;
27
+ };
28
+
29
+ const DEFAULT_LOCK_TIMEOUT = 60 * 1000; // 1 minute
30
+
31
+ const acquireLock = async (collection: Collection, params: AcquireLockParams) => {
32
+ const now = new Date();
33
+ const lock_timeout = params.timeout ?? DEFAULT_LOCK_TIMEOUT;
34
+ const lock_id = new bson.ObjectId();
35
+
36
+ await collection.updateOne(
37
+ {
38
+ name: params.name
39
+ },
40
+ {
41
+ $setOnInsert: {
42
+ name: params.name
43
+ }
44
+ },
45
+ {
46
+ upsert: true
47
+ }
48
+ );
49
+
50
+ const expired_ts = now.getTime() - lock_timeout;
51
+
52
+ const res = await collection.updateOne(
53
+ {
54
+ $and: [
55
+ { name: params.name },
56
+ {
57
+ $or: [{ active_lock: { $exists: false } }, { 'active_lock.ts': { $lte: new Date(expired_ts) } }]
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ $set: {
63
+ active_lock: {
64
+ lock_id: lock_id,
65
+ ts: now
66
+ }
67
+ }
68
+ }
69
+ );
70
+
71
+ if (res.modifiedCount === 0) {
72
+ return null;
73
+ }
74
+
75
+ return lock_id.toString();
76
+ };
77
+
78
+ const refreshLock = async (collection: Collection, lock_id: string) => {
79
+ const lockId = new bson.ObjectId(lock_id);
80
+ const res = await collection.updateOne(
81
+ {
82
+ 'active_lock.lock_id': lockId
83
+ },
84
+ {
85
+ $set: {
86
+ 'active_lock.ts': new Date()
87
+ }
88
+ }
89
+ );
90
+
91
+ if (res.modifiedCount === 0) {
92
+ throw new Error('Lock not found, could not refresh');
93
+ }
94
+ };
95
+
96
+ export const releaseLock = async (collection: Collection, lock_id: string) => {
97
+ const lockId = new bson.ObjectId(lock_id);
98
+ const res = await collection.updateOne(
99
+ {
100
+ 'active_lock.lock_id': lockId
101
+ },
102
+ {
103
+ $unset: {
104
+ active_lock: true
105
+ }
106
+ }
107
+ );
108
+
109
+ if (res.modifiedCount === 0) {
110
+ throw new Error('Lock not found, could not release');
111
+ }
112
+ };
113
+
114
+ export type CreateLockManagerParams = {
115
+ /**
116
+ * Name of the process/user trying to acquire the lock.
117
+ */
118
+ name: string;
119
+ /**
120
+ * The TTL for each lock (ms). Default: 60000 ms (1 min)
121
+ */
122
+ timeout?: number;
123
+ };
124
+
125
+ export const createMongoLockManager = (
126
+ collection: Collection,
127
+ params: CreateLockManagerParams
128
+ ): framework.locks.LockManager => {
129
+ return {
130
+ acquire: () => acquireLock(collection, params),
131
+ refresh: (lock_id: string) => refreshLock(collection, lock_id),
132
+ release: (lock_id: string) => releaseLock(collection, lock_id),
133
+
134
+ lock: async (handler) => {
135
+ const lock_id = await acquireLock(collection, params);
136
+ if (!lock_id) {
137
+ throw new framework.locks.LockActiveError();
138
+ }
139
+
140
+ try {
141
+ await handler(() => refreshLock(collection, lock_id));
142
+ } finally {
143
+ await releaseLock(collection, lock_id);
144
+ }
145
+ }
146
+ };
147
+ };
@@ -0,0 +1,39 @@
1
+ import * as framework from '@powersync/lib-services-framework';
2
+
3
+ import { migrations } from '@powersync/service-core';
4
+ import { configFile } from '@powersync/service-types';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { createMongoLockManager } from '../locks/MonogLocks.js';
8
+ import { createPowerSyncMongo, PowerSyncMongo } from '../storage/storage-index.js';
9
+ import { createMongoMigrationStore } from './mongo-migration-store.js';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ const MONGO_LOCK_PROCESS = 'migrations';
15
+ const MIGRATIONS_DIR = path.join(__dirname, '/db/migrations');
16
+
17
+ export class MongoMigrationAgent extends migrations.AbstractPowerSyncMigrationAgent {
18
+ store: framework.MigrationStore;
19
+ locks: framework.LockManager;
20
+
21
+ protected client: PowerSyncMongo;
22
+
23
+ constructor(mongoConfig: configFile.MongoStorageConfig) {
24
+ super();
25
+
26
+ this.client = createPowerSyncMongo(mongoConfig);
27
+
28
+ this.store = createMongoMigrationStore(this.client.db);
29
+ this.locks = createMongoLockManager(this.client.locks, { name: MONGO_LOCK_PROCESS });
30
+ }
31
+
32
+ getInternalScriptsDir(): string {
33
+ return MIGRATIONS_DIR;
34
+ }
35
+
36
+ async [Symbol.asyncDispose](): Promise<void> {
37
+ await this.client.client.close();
38
+ }
39
+ }
@@ -0,0 +1,39 @@
1
+ import { migrations } from '@powersync/service-core';
2
+ import { configFile } from '@powersync/service-types';
3
+ import * as mongo from '../../../db/mongo.js';
4
+ import * as storage from '../../../storage/storage-index.js';
5
+
6
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
7
+ const {
8
+ service_context: { configuration }
9
+ } = context;
10
+ const database = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
11
+ await mongo.waitForAuth(database.db);
12
+ try {
13
+ await database.bucket_parameters.createIndex(
14
+ {
15
+ 'key.g': 1,
16
+ lookup: 1,
17
+ _id: 1
18
+ },
19
+ { name: 'lookup1' }
20
+ );
21
+ } finally {
22
+ await database.client.close();
23
+ }
24
+ };
25
+
26
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
27
+ const {
28
+ service_context: { configuration }
29
+ } = context;
30
+
31
+ const database = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
32
+ try {
33
+ if (await database.bucket_parameters.indexExists('lookup')) {
34
+ await database.bucket_parameters.dropIndex('lookup1');
35
+ }
36
+ } finally {
37
+ await database.client.close();
38
+ }
39
+ };
@@ -0,0 +1,5 @@
1
+ export const up = async () => {
2
+ // No-op - we don't auto-create sync rules anymore
3
+ };
4
+
5
+ export const down = async () => {};
@@ -0,0 +1,105 @@
1
+ import { storage as core_storage, migrations } from '@powersync/service-core';
2
+ import { configFile } from '@powersync/service-types';
3
+ import * as mongo from '../../../db/mongo.js';
4
+ import * as storage from '../../../storage/storage-index.js';
5
+
6
+ interface LegacySyncRulesDocument extends storage.SyncRuleDocument {
7
+ /**
8
+ * True if this is the active sync rules.
9
+ * requires `snapshot_done == true` and `replicating == true`.
10
+ */
11
+ active?: boolean;
12
+
13
+ /**
14
+ * True if this sync rules should be used for replication.
15
+ *
16
+ * During reprocessing, there is one sync rules with `replicating = true, active = true`,
17
+ * and one with `replicating = true, active = false, auto_activate = true`.
18
+ */
19
+ replicating?: boolean;
20
+
21
+ /**
22
+ * True if the sync rules should set `active = true` when `snapshot_done` = true.
23
+ */
24
+ auto_activate?: boolean;
25
+ }
26
+
27
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
28
+ const {
29
+ service_context: { configuration }
30
+ } = context;
31
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
32
+
33
+ await mongo.waitForAuth(db.db);
34
+ try {
35
+ // We keep the old flags for existing deployments still shutting down.
36
+
37
+ // 1. New sync rules: `active = false, snapshot_done = false, replicating = true, auto_activate = true`
38
+ await db.sync_rules.updateMany(
39
+ {
40
+ active: { $ne: true },
41
+ replicating: true,
42
+ auto_activate: true
43
+ },
44
+ { $set: { state: core_storage.SyncRuleState.PROCESSING } }
45
+ );
46
+
47
+ // 2. Snapshot done: `active = true, snapshot_done = true, replicating = true, auto_activate = false`
48
+ await db.sync_rules.updateMany(
49
+ {
50
+ active: true
51
+ },
52
+ { $set: { state: core_storage.SyncRuleState.ACTIVE } }
53
+ );
54
+
55
+ // 3. Stopped: `active = false, snapshot_done = true, replicating = false, auto_activate = false`.
56
+ await db.sync_rules.updateMany(
57
+ {
58
+ active: { $ne: true },
59
+ replicating: { $ne: true },
60
+ auto_activate: { $ne: true }
61
+ },
62
+ { $set: { state: core_storage.SyncRuleState.STOP } }
63
+ );
64
+
65
+ const remaining = await db.sync_rules.find({ state: null as any }).toArray();
66
+ if (remaining.length > 0) {
67
+ const slots = remaining.map((doc) => doc.slot_name).join(', ');
68
+ throw new Error(`Invalid state for sync rules: ${slots}`);
69
+ }
70
+ } finally {
71
+ await db.client.close();
72
+ }
73
+ };
74
+
75
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
76
+ const {
77
+ service_context: { configuration }
78
+ } = context;
79
+
80
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
81
+ try {
82
+ await db.sync_rules.updateMany(
83
+ {
84
+ state: core_storage.SyncRuleState.ACTIVE
85
+ },
86
+ { $set: { active: true, replicating: true } }
87
+ );
88
+
89
+ await db.sync_rules.updateMany(
90
+ {
91
+ state: core_storage.SyncRuleState.PROCESSING
92
+ },
93
+ { $set: { active: false, replicating: true, auto_activate: true } }
94
+ );
95
+
96
+ await db.sync_rules.updateMany(
97
+ {
98
+ $or: [{ state: core_storage.SyncRuleState.STOP }, { state: core_storage.SyncRuleState.TERMINATED }]
99
+ },
100
+ { $set: { active: false, replicating: false, auto_activate: false } }
101
+ );
102
+ } finally {
103
+ await db.client.close();
104
+ }
105
+ };
@@ -0,0 +1,38 @@
1
+ import { migrations } from '@powersync/service-core';
2
+ import { configFile } from '@powersync/service-types';
3
+
4
+ import * as storage from '../../../storage/storage-index.js';
5
+
6
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
7
+ const {
8
+ service_context: { configuration }
9
+ } = context;
10
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
11
+
12
+ try {
13
+ await db.write_checkpoints.createIndex(
14
+ {
15
+ user_id: 1
16
+ },
17
+ { name: 'user_id' }
18
+ );
19
+ } finally {
20
+ await db.client.close();
21
+ }
22
+ };
23
+
24
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
25
+ const {
26
+ service_context: { configuration }
27
+ } = context;
28
+
29
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
30
+
31
+ try {
32
+ if (await db.write_checkpoints.indexExists('user_id')) {
33
+ await db.write_checkpoints.dropIndex('user_id');
34
+ }
35
+ } finally {
36
+ await db.client.close();
37
+ }
38
+ };
@@ -0,0 +1,40 @@
1
+ import { migrations } from '@powersync/service-core';
2
+ import { configFile } from '@powersync/service-types';
3
+ import * as storage from '../../../storage/storage-index.js';
4
+
5
+ const INDEX_NAME = 'user_sync_rule_unique';
6
+
7
+ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
8
+ const {
9
+ service_context: { configuration }
10
+ } = context;
11
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
12
+
13
+ try {
14
+ await db.custom_write_checkpoints.createIndex(
15
+ {
16
+ user_id: 1,
17
+ sync_rules_id: 1
18
+ },
19
+ { name: INDEX_NAME, unique: true }
20
+ );
21
+ } finally {
22
+ await db.client.close();
23
+ }
24
+ };
25
+
26
+ export const down: migrations.PowerSyncMigrationFunction = async (context) => {
27
+ const {
28
+ service_context: { configuration }
29
+ } = context;
30
+
31
+ const db = storage.createPowerSyncMongo(configuration.storage as configFile.MongoStorageConfig);
32
+
33
+ try {
34
+ if (await db.custom_write_checkpoints.indexExists(INDEX_NAME)) {
35
+ await db.custom_write_checkpoints.dropIndex(INDEX_NAME);
36
+ }
37
+ } finally {
38
+ await db.client.close();
39
+ }
40
+ };
@@ -0,0 +1,62 @@
1
+ import { migrations } from '@powersync/lib-services-framework';
2
+ import { Db } from 'mongodb';
3
+ import * as path from 'path';
4
+
5
+ /**
6
+ * A custom store for node-migrate which is used to save and load migrations that have
7
+ * been operated on to mongo.
8
+ */
9
+ export const createMongoMigrationStore = (db: Db): migrations.MigrationStore => {
10
+ const collection = db.collection<migrations.MigrationState>('migrations');
11
+
12
+ return {
13
+ load: async () => {
14
+ const state_entry = await collection.findOne();
15
+ if (!state_entry) {
16
+ return;
17
+ }
18
+
19
+ const { _id, ...state } = state_entry;
20
+
21
+ /**
22
+ * This is for backwards compatibility. A previous version of the migration tool used to save
23
+ * state as `lastRun`.
24
+ */
25
+ let last_run = state.last_run;
26
+ if ('lastRun' in state) {
27
+ last_run = (state as any).lastRun;
28
+ }
29
+
30
+ /**
31
+ * This is for backwards compatibility. A previous version of the migration tool used to include the
32
+ * file extension in migration names. This strips that extension off if it exists
33
+ */
34
+ const extension = path.extname(last_run);
35
+ if (extension) {
36
+ last_run = last_run.replace(extension, '');
37
+ }
38
+
39
+ return {
40
+ last_run,
41
+ log: state.log || []
42
+ };
43
+ },
44
+
45
+ clear: async () => {
46
+ await collection.deleteMany({});
47
+ },
48
+
49
+ save: async (state: migrations.MigrationState) => {
50
+ await collection.replaceOne(
51
+ {},
52
+ {
53
+ last_run: state.last_run,
54
+ log: state.log
55
+ },
56
+ {
57
+ upsert: true
58
+ }
59
+ );
60
+ }
61
+ };
62
+ };
@@ -1,11 +1,13 @@
1
1
  import { api, ConfigurationFileSyncRulesProvider, replication, system, TearDownOptions } from '@powersync/service-core';
2
2
  import { MongoRouteAPIAdapter } from '../api/MongoRouteAPIAdapter.js';
3
+ import { MongoMigrationAgent } from '../migrations/MonogMigrationAgent.js';
4
+ import { ChangeStreamReplicator } from '../replication/ChangeStreamReplicator.js';
3
5
  import { ConnectionManagerFactory } from '../replication/ConnectionManagerFactory.js';
4
6
  import { MongoErrorRateLimiter } from '../replication/MongoErrorRateLimiter.js';
5
- import { ChangeStreamReplicator } from '../replication/ChangeStreamReplicator.js';
6
- import * as types from '../types/types.js';
7
7
  import { MongoManager } from '../replication/MongoManager.js';
8
8
  import { checkSourceConfiguration } from '../replication/replication-utils.js';
9
+ import { MongoStorageProvider } from '../storage/storage-index.js';
10
+ import * as types from '../types/types.js';
9
11
 
10
12
  export class MongoModule extends replication.ReplicationModule<types.MongoConnectionConfig> {
11
13
  constructor() {
@@ -18,6 +20,13 @@ export class MongoModule extends replication.ReplicationModule<types.MongoConnec
18
20
 
19
21
  async initialize(context: system.ServiceContextContainer): Promise<void> {
20
22
  await super.initialize(context);
23
+ context.storageEngine.registerProvider(new MongoStorageProvider());
24
+
25
+ if (types.isMongoStorageConfig(context.configuration.storage)) {
26
+ context.migrations.registerMigrationAgent(
27
+ new MongoMigrationAgent(this.resolveConfig(context.configuration.storage))
28
+ );
29
+ }
21
30
  }
22
31
 
23
32
  protected createRouteAPIAdapter(): api.RouteAPI {
@@ -55,9 +64,14 @@ export class MongoModule extends replication.ReplicationModule<types.MongoConnec
55
64
  async testConnection(config: types.MongoConnectionConfig): Promise<void> {
56
65
  this.decodeConfig(config);
57
66
  const normalisedConfig = this.resolveConfig(this.decodedConfig!);
58
- const connectionManager = new MongoManager(normalisedConfig);
67
+ const connectionManager = new MongoManager(normalisedConfig, {
68
+ // Use short timeouts for testing connections.
69
+ // Must be < 30s, to ensure we get a proper timeout error.
70
+ socketTimeoutMS: 5_000,
71
+ serverSelectionTimeoutMS: 5_000
72
+ });
59
73
  try {
60
- return checkSourceConfiguration(connectionManager);
74
+ return await checkSourceConfiguration(connectionManager);
61
75
  } finally {
62
76
  await connectionManager.end();
63
77
  }
@@ -8,7 +8,10 @@ export class MongoManager {
8
8
  public readonly client: mongo.MongoClient;
9
9
  public readonly db: mongo.Db;
10
10
 
11
- constructor(public options: NormalizedMongoConnectionConfig) {
11
+ constructor(
12
+ public options: NormalizedMongoConnectionConfig,
13
+ overrides?: mongo.MongoClientOptions
14
+ ) {
12
15
  // The pool is lazy - no connections are opened until a query is performed.
13
16
  this.client = new mongo.MongoClient(options.uri, {
14
17
  auth: {
@@ -28,7 +31,8 @@ export class MongoManager {
28
31
  maxPoolSize: 8,
29
32
 
30
33
  maxConnecting: 3,
31
- maxIdleTimeMS: 60_000
34
+ maxIdleTimeMS: 60_000,
35
+ ...overrides
32
36
  });
33
37
  this.db = this.client.db(options.database, {});
34
38
  }