@powersync/service-module-mongodb-storage 0.0.0-dev-20250117095455 → 0.0.0-dev-20250214100224
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 +56 -8
- package/dist/migrations/MongoMigrationAgent.js +3 -0
- package/dist/migrations/MongoMigrationAgent.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +2 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +2 -3
- package/dist/storage/MongoBucketStorage.js +61 -39
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +1 -1
- package/dist/storage/implementation/MongoBucketBatch.js +37 -24
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.js +12 -4
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoIdSequence.js +3 -1
- package/dist/storage/implementation/MongoIdSequence.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.js +4 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +9 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +2 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.js +16 -9
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +7 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +2 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/OperationBatch.js +10 -6
- package/dist/storage/implementation/OperationBatch.js.map +1 -1
- package/dist/storage/implementation/PersistedBatch.js +14 -13
- package/dist/storage/implementation/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.js +12 -0
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/util.js +3 -3
- package/dist/storage/implementation/util.js.map +1 -1
- package/package.json +7 -7
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +2 -1
- package/src/storage/MongoBucketStorage.ts +39 -18
- package/src/storage/implementation/MongoBucketBatch.ts +20 -6
- package/src/storage/implementation/MongoCompactor.ts +4 -2
- package/src/storage/implementation/MongoIdSequence.ts +3 -1
- package/src/storage/implementation/MongoStorageProvider.ts +4 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +3 -3
- package/src/storage/implementation/MongoSyncRulesLock.ts +5 -2
- package/test/src/storage_compacting.test.ts +6 -1
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@powersync/service-module-mongodb-storage",
|
|
3
3
|
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
4
|
"types": "dist/index.d.ts",
|
|
5
|
-
"version": "0.0.0-dev-
|
|
5
|
+
"version": "0.0.0-dev-20250214100224",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "FSL-1.1-Apache-2.0",
|
|
8
8
|
"type": "module",
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"ix": "^5.0.0",
|
|
28
28
|
"lru-cache": "^10.2.2",
|
|
29
29
|
"uuid": "^9.0.1",
|
|
30
|
-
"@powersync/lib-services-framework": "0.
|
|
31
|
-
"@powersync/service-core": "0.0.0-dev-
|
|
30
|
+
"@powersync/lib-services-framework": "0.5.1",
|
|
31
|
+
"@powersync/service-core": "0.0.0-dev-20250214100224",
|
|
32
32
|
"@powersync/service-jsonbig": "0.17.10",
|
|
33
|
-
"@powersync/service-sync-rules": "0.23.
|
|
34
|
-
"@powersync/service-types": "0.
|
|
35
|
-
"@powersync/lib-service-mongodb": "0.
|
|
33
|
+
"@powersync/service-sync-rules": "0.23.4",
|
|
34
|
+
"@powersync/service-types": "0.8.0",
|
|
35
|
+
"@powersync/lib-service-mongodb": "0.4.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/uuid": "^9.0.4",
|
|
39
|
-
"@powersync/service-core-tests": "0.0.0-dev-
|
|
39
|
+
"@powersync/service-core-tests": "0.0.0-dev-20250214100224"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -b",
|
|
@@ -2,6 +2,7 @@ import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import { storage as core_storage, migrations } from '@powersync/service-core';
|
|
3
3
|
import * as storage from '../../../storage/storage-index.js';
|
|
4
4
|
import { MongoStorageConfig } from '../../../types/types.js';
|
|
5
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
5
6
|
|
|
6
7
|
interface LegacySyncRulesDocument extends storage.SyncRuleDocument {
|
|
7
8
|
/**
|
|
@@ -65,7 +66,7 @@ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
|
|
|
65
66
|
const remaining = await db.sync_rules.find({ state: null as any }).toArray();
|
|
66
67
|
if (remaining.length > 0) {
|
|
67
68
|
const slots = remaining.map((doc) => doc.slot_name).join(', ');
|
|
68
|
-
throw new
|
|
69
|
+
throw new ServiceAssertionError(`Invalid state for sync rules: ${slots}`);
|
|
69
70
|
}
|
|
70
71
|
} finally {
|
|
71
72
|
await db.client.close();
|
|
@@ -5,7 +5,7 @@ import * as timers from 'timers/promises';
|
|
|
5
5
|
|
|
6
6
|
import { storage, sync, utils } from '@powersync/service-core';
|
|
7
7
|
|
|
8
|
-
import { DisposableObserver, logger } from '@powersync/lib-services-framework';
|
|
8
|
+
import { DisposableObserver, ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
|
|
9
9
|
import { v4 as uuid } from 'uuid';
|
|
10
10
|
|
|
11
11
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
@@ -84,22 +84,36 @@ export class MongoBucketStorage
|
|
|
84
84
|
return storage;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
async
|
|
87
|
+
async getSystemIdentifier(): Promise<storage.BucketStorageSystemIdentifier> {
|
|
88
|
+
const { setName: id } = await this.db.db.command({
|
|
89
|
+
hello: 1
|
|
90
|
+
});
|
|
91
|
+
if (id == null) {
|
|
92
|
+
throw new ServiceError(
|
|
93
|
+
ErrorCode.PSYNC_S1342,
|
|
94
|
+
'Standalone MongoDB instances are not supported - use a replicaset.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
type: lib_mongo.MONGO_CONNECTION_TYPE
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async configureSyncRules(options: storage.UpdateSyncRulesOptions) {
|
|
88
105
|
const next = await this.getNextSyncRulesContent();
|
|
89
106
|
const active = await this.getActiveSyncRulesContent();
|
|
90
107
|
|
|
91
|
-
if (next?.sync_rules_content ==
|
|
108
|
+
if (next?.sync_rules_content == options.content) {
|
|
92
109
|
logger.info('Sync rules from configuration unchanged');
|
|
93
110
|
return { updated: false };
|
|
94
|
-
} else if (next == null && active?.sync_rules_content ==
|
|
111
|
+
} else if (next == null && active?.sync_rules_content == options.content) {
|
|
95
112
|
logger.info('Sync rules from configuration unchanged');
|
|
96
113
|
return { updated: false };
|
|
97
114
|
} else {
|
|
98
115
|
logger.info('Sync rules updated from configuration');
|
|
99
|
-
const persisted_sync_rules = await this.updateSyncRules(
|
|
100
|
-
content: sync_rules,
|
|
101
|
-
lock: options?.lock
|
|
102
|
-
});
|
|
116
|
+
const persisted_sync_rules = await this.updateSyncRules(options);
|
|
103
117
|
return { updated: true, persisted_sync_rules, lock: persisted_sync_rules.current_lock ?? undefined };
|
|
104
118
|
}
|
|
105
119
|
}
|
|
@@ -113,7 +127,8 @@ export class MongoBucketStorage
|
|
|
113
127
|
if (next != null && next.slot_name == slot_name) {
|
|
114
128
|
// We need to redo the "next" sync rules
|
|
115
129
|
await this.updateSyncRules({
|
|
116
|
-
content: next.sync_rules_content
|
|
130
|
+
content: next.sync_rules_content,
|
|
131
|
+
validate: false
|
|
117
132
|
});
|
|
118
133
|
// Pro-actively stop replicating
|
|
119
134
|
await this.db.sync_rules.updateOne(
|
|
@@ -130,7 +145,8 @@ export class MongoBucketStorage
|
|
|
130
145
|
} else if (next == null && active?.slot_name == slot_name) {
|
|
131
146
|
// Slot removed for "active" sync rules, while there is no "next" one.
|
|
132
147
|
await this.updateSyncRules({
|
|
133
|
-
content: active.sync_rules_content
|
|
148
|
+
content: active.sync_rules_content,
|
|
149
|
+
validate: false
|
|
134
150
|
});
|
|
135
151
|
|
|
136
152
|
// Pro-actively stop replicating
|
|
@@ -149,13 +165,18 @@ export class MongoBucketStorage
|
|
|
149
165
|
}
|
|
150
166
|
|
|
151
167
|
async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedSyncRulesContent> {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
if (options.validate) {
|
|
169
|
+
// Parse and validate before applying any changes
|
|
170
|
+
SqlSyncRules.fromYaml(options.content, {
|
|
171
|
+
// No schema-based validation at this point
|
|
172
|
+
schema: undefined,
|
|
173
|
+
defaultSchema: 'not_applicable', // Not needed for validation
|
|
174
|
+
throwOnError: true
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
// We do not validate sync rules at this point.
|
|
178
|
+
// That is done when using the sync rules, so that the diagnostics API can report the errors.
|
|
179
|
+
}
|
|
159
180
|
|
|
160
181
|
let rules: MongoPersistedSyncRulesContent | undefined = undefined;
|
|
161
182
|
|
|
@@ -433,7 +454,7 @@ export class MongoBucketStorage
|
|
|
433
454
|
clusterTime = time;
|
|
434
455
|
});
|
|
435
456
|
if (clusterTime == null) {
|
|
436
|
-
throw new
|
|
457
|
+
throw new ServiceError(ErrorCode.PSYNC_S2401, 'Could not get clusterTime');
|
|
437
458
|
}
|
|
438
459
|
|
|
439
460
|
if (signal.aborted) {
|
|
@@ -2,7 +2,15 @@ import { mongo } from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import { SqlEventDescriptor, SqliteRow, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
container,
|
|
7
|
+
DisposableObserver,
|
|
8
|
+
ErrorCode,
|
|
9
|
+
errors,
|
|
10
|
+
logger,
|
|
11
|
+
ReplicationAssertionError,
|
|
12
|
+
ServiceError
|
|
13
|
+
} from '@powersync/lib-services-framework';
|
|
6
14
|
import { SaveOperationTag, storage, utils } from '@powersync/service-core';
|
|
7
15
|
import * as timers from 'node:timers/promises';
|
|
8
16
|
import { PowerSyncMongo } from './db.js';
|
|
@@ -140,7 +148,7 @@ export class MongoBucketBatch
|
|
|
140
148
|
this.batch = resumeBatch;
|
|
141
149
|
|
|
142
150
|
if (last_op == null) {
|
|
143
|
-
throw new
|
|
151
|
+
throw new ReplicationAssertionError('Unexpected last_op == null');
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
this.persisted_op = last_op;
|
|
@@ -294,7 +302,7 @@ export class MongoBucketBatch
|
|
|
294
302
|
return null;
|
|
295
303
|
}
|
|
296
304
|
} else {
|
|
297
|
-
throw new
|
|
305
|
+
throw new ReplicationAssertionError(`${record.tag} not supported with skipExistingRows: true`);
|
|
298
306
|
}
|
|
299
307
|
}
|
|
300
308
|
|
|
@@ -348,7 +356,7 @@ export class MongoBucketBatch
|
|
|
348
356
|
afterData = new bson.Binary(bson.serialize(after!));
|
|
349
357
|
// We additionally make sure it's <= 15MB - we need some margin for metadata.
|
|
350
358
|
if (afterData.length() > MAX_ROW_SIZE) {
|
|
351
|
-
throw new
|
|
359
|
+
throw new ServiceError(ErrorCode.PSYNC_S1002, `Row too large: ${afterData.length()}`);
|
|
352
360
|
}
|
|
353
361
|
} catch (e) {
|
|
354
362
|
// Replace with empty values, equivalent to TOAST values
|
|
@@ -548,7 +556,7 @@ export class MongoBucketBatch
|
|
|
548
556
|
logger.info(`${this.slot_name} ${description} - try ${flushTry}`);
|
|
549
557
|
}
|
|
550
558
|
if (flushTry > 20 && Date.now() > lastTry) {
|
|
551
|
-
throw new
|
|
559
|
+
throw new ServiceError(ErrorCode.PSYNC_S1402, 'Max transaction tries exceeded');
|
|
552
560
|
}
|
|
553
561
|
|
|
554
562
|
const next_op_id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
@@ -607,7 +615,9 @@ export class MongoBucketBatch
|
|
|
607
615
|
|
|
608
616
|
private lastWaitingLogThottled = 0;
|
|
609
617
|
|
|
610
|
-
async commit(lsn: string): Promise<boolean> {
|
|
618
|
+
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<boolean> {
|
|
619
|
+
const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
|
|
620
|
+
|
|
611
621
|
await this.flush();
|
|
612
622
|
|
|
613
623
|
if (this.last_checkpoint_lsn != null && lsn < this.last_checkpoint_lsn) {
|
|
@@ -645,6 +655,10 @@ export class MongoBucketBatch
|
|
|
645
655
|
return false;
|
|
646
656
|
}
|
|
647
657
|
|
|
658
|
+
if (!createEmptyCheckpoints && this.persisted_op == null) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
|
|
648
662
|
const now = new Date();
|
|
649
663
|
const update: Partial<SyncRuleDocument> = {
|
|
650
664
|
last_checkpoint_lsn: lsn,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import { logger, ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage, utils } from '@powersync/service-core';
|
|
4
4
|
|
|
5
5
|
import { PowerSyncMongo } from './db.js';
|
|
@@ -335,7 +335,9 @@ export class MongoCompactor {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
} else {
|
|
338
|
-
throw new
|
|
338
|
+
throw new ReplicationAssertionError(
|
|
339
|
+
`Unexpected ${op.op} operation at ${op._id.g}:${op._id.b}:${op._id.o}`
|
|
340
|
+
);
|
|
339
341
|
}
|
|
340
342
|
}
|
|
341
343
|
if (!gotAnOp) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Manages op_id or similar sequence in memory.
|
|
3
5
|
*
|
|
@@ -9,7 +11,7 @@ export class MongoIdSequence {
|
|
|
9
11
|
|
|
10
12
|
constructor(last: bigint) {
|
|
11
13
|
if (typeof last != 'bigint') {
|
|
12
|
-
throw new
|
|
14
|
+
throw new ReplicationAssertionError(`BigInt required, got ${last} ${typeof last}`);
|
|
13
15
|
}
|
|
14
16
|
this._last = last;
|
|
15
17
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import { logger, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
4
|
import { MongoStorageConfig } from '../../types/types.js';
|
|
5
5
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
@@ -16,7 +16,9 @@ export class MongoStorageProvider implements storage.BucketStorageProvider {
|
|
|
16
16
|
const { storage } = resolvedConfig;
|
|
17
17
|
if (storage.type != this.type) {
|
|
18
18
|
// This should not be reached since the generation should be managed externally.
|
|
19
|
-
throw new
|
|
19
|
+
throw new ServiceAssertionError(
|
|
20
|
+
`Cannot create MongoDB bucket storage with provided config ${storage.type} !== ${this.type}`
|
|
21
|
+
);
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const decodedConfig = MongoStorageConfig.decode(storage as any);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
-
import { DisposableObserver, logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import { DisposableObserver, logger, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
4
4
|
import { storage, utils } from '@powersync/service-core';
|
|
5
5
|
import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
6
6
|
import * as bson from 'bson';
|
|
@@ -344,7 +344,7 @@ export class MongoSyncBucketStorage
|
|
|
344
344
|
|
|
345
345
|
start ??= dataBuckets.get(bucket);
|
|
346
346
|
if (start == null) {
|
|
347
|
-
throw new
|
|
347
|
+
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
348
348
|
}
|
|
349
349
|
currentBatch = {
|
|
350
350
|
bucket,
|
|
@@ -479,7 +479,7 @@ export class MongoSyncBucketStorage
|
|
|
479
479
|
}
|
|
480
480
|
);
|
|
481
481
|
if (doc == null) {
|
|
482
|
-
throw new
|
|
482
|
+
throw new ServiceAssertionError('Cannot find sync rules status');
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
|
|
3
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import { ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
|
|
4
4
|
import { storage } from '@powersync/service-core';
|
|
5
5
|
import { PowerSyncMongo } from './db.js';
|
|
6
6
|
|
|
@@ -33,7 +33,10 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
if (doc == null) {
|
|
36
|
-
throw new
|
|
36
|
+
throw new ServiceError(
|
|
37
|
+
ErrorCode.PSYNC_S1003,
|
|
38
|
+
`Sync rules: ${sync_rules.id} have been locked by another process for replication.`
|
|
39
|
+
);
|
|
37
40
|
}
|
|
38
41
|
return new MongoSyncRulesLock(db, sync_rules.id, lockId);
|
|
39
42
|
}
|
|
@@ -3,4 +3,9 @@ import { register } from '@powersync/service-core-tests';
|
|
|
3
3
|
import { describe } from 'vitest';
|
|
4
4
|
import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
|
|
5
5
|
|
|
6
|
-
describe('Mongo Sync Bucket Storage Compact', () =>
|
|
6
|
+
describe('Mongo Sync Bucket Storage Compact', () =>
|
|
7
|
+
register.registerCompactTests<MongoCompactOptions>(INITIALIZED_MONGO_STORAGE_FACTORY, {
|
|
8
|
+
clearBatchLimit: 2,
|
|
9
|
+
moveBatchLimit: 1,
|
|
10
|
+
moveBatchQueryLimit: 1
|
|
11
|
+
}));
|