@powersync/service-core 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.
- package/CHANGELOG.md +65 -4
- package/dist/auth/KeySpec.d.ts +1 -0
- package/dist/auth/KeySpec.js +10 -8
- package/dist/auth/KeySpec.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.js +2 -2
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/entry/commands/compact-action.js +15 -15
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/entry/commands/migrate-action.js +15 -4
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations/PowerSyncMigrationManager.d.ts +17 -0
- package/dist/migrations/PowerSyncMigrationManager.js +22 -0
- package/dist/migrations/PowerSyncMigrationManager.js.map +1 -0
- package/dist/migrations/ensure-automatic-migrations.d.ts +4 -0
- package/dist/migrations/ensure-automatic-migrations.js +14 -0
- package/dist/migrations/ensure-automatic-migrations.js.map +1 -0
- package/dist/migrations/migrations-index.d.ts +2 -3
- package/dist/migrations/migrations-index.js +2 -3
- package/dist/migrations/migrations-index.js.map +1 -1
- package/dist/routes/RouterEngine.js +2 -1
- package/dist/routes/RouterEngine.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +28 -28
- package/dist/routes/endpoints/admin.d.ts +24 -24
- package/dist/storage/BucketStorage.d.ts +41 -1
- package/dist/storage/BucketStorage.js +26 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -14
- package/dist/storage/storage-index.js +2 -14
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/sync.js +12 -3
- package/dist/sync/sync.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +3 -0
- package/dist/system/ServiceContext.js +11 -3
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/util/config/types.d.ts +2 -2
- package/dist/util/utils.d.ts +14 -1
- package/dist/util/utils.js +56 -0
- package/dist/util/utils.js.map +1 -1
- package/package.json +6 -7
- package/src/auth/KeySpec.ts +12 -9
- package/src/auth/RemoteJWKSCollector.ts +2 -2
- package/src/entry/commands/compact-action.ts +20 -15
- package/src/entry/commands/migrate-action.ts +17 -4
- package/src/index.ts +1 -4
- package/src/migrations/PowerSyncMigrationManager.ts +43 -0
- package/src/migrations/ensure-automatic-migrations.ts +15 -0
- package/src/migrations/migrations-index.ts +2 -3
- package/src/routes/RouterEngine.ts +2 -1
- package/src/storage/BucketStorage.ts +44 -1
- package/src/storage/storage-index.ts +3 -15
- package/src/sync/sync.ts +12 -3
- package/src/system/ServiceContext.ts +17 -4
- package/src/util/config/types.ts +2 -2
- package/src/util/utils.ts +59 -1
- package/test/src/auth.test.ts +54 -21
- package/test/src/env.ts +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/db/db-index.d.ts +0 -1
- package/dist/db/db-index.js +0 -2
- package/dist/db/db-index.js.map +0 -1
- package/dist/db/mongo.d.ts +0 -35
- package/dist/db/mongo.js +0 -73
- package/dist/db/mongo.js.map +0 -1
- package/dist/locks/LockManager.d.ts +0 -10
- package/dist/locks/LockManager.js +0 -7
- package/dist/locks/LockManager.js.map +0 -1
- package/dist/locks/MongoLocks.d.ts +0 -36
- package/dist/locks/MongoLocks.js +0 -81
- package/dist/locks/MongoLocks.js.map +0 -1
- package/dist/locks/locks-index.d.ts +0 -2
- package/dist/locks/locks-index.js +0 -3
- package/dist/locks/locks-index.js.map +0 -1
- package/dist/migrations/db/migrations/1684951997326-init.d.ts +0 -3
- package/dist/migrations/db/migrations/1684951997326-init.js +0 -33
- package/dist/migrations/db/migrations/1684951997326-init.js.map +0 -1
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +0 -2
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +0 -5
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +0 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +0 -3
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +0 -56
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +0 -1
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +0 -3
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +0 -29
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +0 -1
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +0 -3
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +0 -31
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +0 -1
- package/dist/migrations/definitions.d.ts +0 -18
- package/dist/migrations/definitions.js +0 -6
- package/dist/migrations/definitions.js.map +0 -1
- package/dist/migrations/executor.d.ts +0 -16
- package/dist/migrations/executor.js +0 -64
- package/dist/migrations/executor.js.map +0 -1
- package/dist/migrations/migrations.d.ts +0 -18
- package/dist/migrations/migrations.js +0 -110
- package/dist/migrations/migrations.js.map +0 -1
- package/dist/migrations/store/migration-store.d.ts +0 -11
- package/dist/migrations/store/migration-store.js +0 -46
- package/dist/migrations/store/migration-store.js.map +0 -1
- package/dist/storage/MongoBucketStorage.d.ts +0 -48
- package/dist/storage/MongoBucketStorage.js +0 -426
- package/dist/storage/MongoBucketStorage.js.map +0 -1
- package/dist/storage/mongo/MongoBucketBatch.d.ts +0 -67
- package/dist/storage/mongo/MongoBucketBatch.js +0 -643
- package/dist/storage/mongo/MongoBucketBatch.js.map +0 -1
- package/dist/storage/mongo/MongoCompactor.d.ts +0 -40
- package/dist/storage/mongo/MongoCompactor.js +0 -309
- package/dist/storage/mongo/MongoCompactor.js.map +0 -1
- package/dist/storage/mongo/MongoIdSequence.d.ts +0 -12
- package/dist/storage/mongo/MongoIdSequence.js +0 -21
- package/dist/storage/mongo/MongoIdSequence.js.map +0 -1
- package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +0 -9
- package/dist/storage/mongo/MongoPersistedSyncRules.js +0 -9
- package/dist/storage/mongo/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +0 -20
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +0 -26
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/mongo/MongoStorageProvider.d.ts +0 -5
- package/dist/storage/mongo/MongoStorageProvider.js +0 -26
- package/dist/storage/mongo/MongoStorageProvider.js.map +0 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +0 -38
- package/dist/storage/mongo/MongoSyncBucketStorage.js +0 -531
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +0 -1
- package/dist/storage/mongo/MongoSyncRulesLock.d.ts +0 -16
- package/dist/storage/mongo/MongoSyncRulesLock.js +0 -65
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +0 -1
- package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +0 -20
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js +0 -103
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +0 -1
- package/dist/storage/mongo/OperationBatch.d.ts +0 -35
- package/dist/storage/mongo/OperationBatch.js +0 -119
- package/dist/storage/mongo/OperationBatch.js.map +0 -1
- package/dist/storage/mongo/PersistedBatch.d.ts +0 -46
- package/dist/storage/mongo/PersistedBatch.js +0 -213
- package/dist/storage/mongo/PersistedBatch.js.map +0 -1
- package/dist/storage/mongo/config.d.ts +0 -19
- package/dist/storage/mongo/config.js +0 -26
- package/dist/storage/mongo/config.js.map +0 -1
- package/dist/storage/mongo/db.d.ts +0 -36
- package/dist/storage/mongo/db.js +0 -47
- package/dist/storage/mongo/db.js.map +0 -1
- package/dist/storage/mongo/models.d.ts +0 -156
- package/dist/storage/mongo/models.js +0 -27
- package/dist/storage/mongo/models.js.map +0 -1
- package/dist/storage/mongo/util.d.ts +0 -40
- package/dist/storage/mongo/util.js +0 -151
- package/dist/storage/mongo/util.js.map +0 -1
- package/src/db/db-index.ts +0 -1
- package/src/db/mongo.ts +0 -81
- package/src/locks/LockManager.ts +0 -16
- package/src/locks/MongoLocks.ts +0 -142
- package/src/locks/locks-index.ts +0 -2
- package/src/migrations/db/migrations/1684951997326-init.ts +0 -38
- package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +0 -5
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +0 -102
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +0 -34
- package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +0 -37
- package/src/migrations/definitions.ts +0 -21
- package/src/migrations/executor.ts +0 -87
- package/src/migrations/migrations.ts +0 -142
- package/src/migrations/store/migration-store.ts +0 -63
- package/src/storage/MongoBucketStorage.ts +0 -540
- package/src/storage/mongo/MongoBucketBatch.ts +0 -841
- package/src/storage/mongo/MongoCompactor.ts +0 -392
- package/src/storage/mongo/MongoIdSequence.ts +0 -24
- package/src/storage/mongo/MongoPersistedSyncRules.ts +0 -16
- package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +0 -50
- package/src/storage/mongo/MongoStorageProvider.ts +0 -31
- package/src/storage/mongo/MongoSyncBucketStorage.ts +0 -636
- package/src/storage/mongo/MongoSyncRulesLock.ts +0 -85
- package/src/storage/mongo/MongoWriteCheckpointAPI.ts +0 -151
- package/src/storage/mongo/OperationBatch.ts +0 -131
- package/src/storage/mongo/PersistedBatch.ts +0 -272
- package/src/storage/mongo/config.ts +0 -40
- package/src/storage/mongo/db.ts +0 -88
- package/src/storage/mongo/models.ts +0 -179
- package/src/storage/mongo/util.ts +0 -158
- package/test/src/__snapshots__/sync.test.ts.snap +0 -332
- package/test/src/bucket_validation.test.ts +0 -142
- package/test/src/bucket_validation.ts +0 -116
- package/test/src/compacting.test.ts +0 -295
- package/test/src/data_storage.test.ts +0 -1499
- package/test/src/stream_utils.ts +0 -42
- package/test/src/sync.test.ts +0 -511
- package/test/src/util.ts +0 -148
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"version": "0.0.0-dev-
|
|
8
|
+
"version": "0.0.0-dev-20241219110735",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"license": "FSL-1.1-Apache-2.0",
|
|
11
11
|
"type": "module",
|
|
@@ -26,18 +26,17 @@
|
|
|
26
26
|
"jose": "^4.15.1",
|
|
27
27
|
"lodash": "^4.17.21",
|
|
28
28
|
"lru-cache": "^10.2.2",
|
|
29
|
-
"mongodb": "^6.7.0",
|
|
30
29
|
"node-fetch": "^3.3.2",
|
|
31
|
-
"ts-codec": "^1.
|
|
30
|
+
"ts-codec": "^1.3.0",
|
|
32
31
|
"uri-js": "^4.4.1",
|
|
33
32
|
"uuid": "^9.0.1",
|
|
34
33
|
"winston": "^3.13.0",
|
|
35
34
|
"yaml": "^2.3.2",
|
|
36
|
-
"@powersync/lib-services-framework": "0.
|
|
35
|
+
"@powersync/lib-services-framework": "0.0.0-dev-20241219110735",
|
|
37
36
|
"@powersync/service-jsonbig": "0.17.10",
|
|
38
|
-
"@powersync/service-rsocket-router": "0.0.
|
|
39
|
-
"@powersync/service-sync-rules": "0.
|
|
40
|
-
"@powersync/service-types": "0.0.0-dev-
|
|
37
|
+
"@powersync/service-rsocket-router": "0.0.0-dev-20241219110735",
|
|
38
|
+
"@powersync/service-sync-rules": "0.0.0-dev-20241219110735",
|
|
39
|
+
"@powersync/service-types": "0.0.0-dev-20241219110735"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
42
|
"@types/async": "^3.2.24",
|
package/src/auth/KeySpec.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as jose from 'jose';
|
|
|
2
2
|
|
|
3
3
|
export const HS_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
|
|
4
4
|
export const RSA_ALGORITHMS = ['RS256', 'RS384', 'RS512'];
|
|
5
|
-
export const
|
|
5
|
+
export const OKP_ALGORITHMS = ['EdDSA'];
|
|
6
|
+
export const SUPPORTED_ALGORITHMS = [...HS_ALGORITHMS, ...RSA_ALGORITHMS, ...OKP_ALGORITHMS];
|
|
6
7
|
|
|
7
8
|
export interface KeyOptions {
|
|
8
9
|
/**
|
|
@@ -38,17 +39,19 @@ export class KeySpec {
|
|
|
38
39
|
return this.source.kid;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
matchesAlgorithm(jwtAlg: string) {
|
|
42
|
+
matchesAlgorithm(jwtAlg: string): boolean {
|
|
42
43
|
if (this.source.alg) {
|
|
43
|
-
return jwtAlg
|
|
44
|
-
} else if (this.source.kty
|
|
44
|
+
return jwtAlg === this.source.alg;
|
|
45
|
+
} else if (this.source.kty === 'RSA') {
|
|
45
46
|
return RSA_ALGORITHMS.includes(jwtAlg);
|
|
46
|
-
} else if (this.source.kty
|
|
47
|
+
} else if (this.source.kty === 'oct') {
|
|
47
48
|
return HS_ALGORITHMS.includes(jwtAlg);
|
|
48
|
-
} else {
|
|
49
|
-
|
|
50
|
-
return false;
|
|
49
|
+
} else if (this.source.kty === 'OKP') {
|
|
50
|
+
return OKP_ALGORITHMS.includes(jwtAlg);
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// 'EC' is unsupported
|
|
54
|
+
return false;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
async isValidSignature(token: string): Promise<boolean> {
|
|
@@ -56,7 +59,7 @@ export class KeySpec {
|
|
|
56
59
|
await jose.compactVerify(token, this.key);
|
|
57
60
|
return true;
|
|
58
61
|
} catch (e) {
|
|
59
|
-
if (e.code
|
|
62
|
+
if (e.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
|
|
60
63
|
return false;
|
|
61
64
|
} else {
|
|
62
65
|
// Token format error most likely
|
|
@@ -73,8 +73,8 @@ export class RemoteJWKSCollector implements KeyCollector {
|
|
|
73
73
|
|
|
74
74
|
let keys: KeySpec[] = [];
|
|
75
75
|
for (let keyData of data.keys) {
|
|
76
|
-
if (keyData.kty != 'RSA') {
|
|
77
|
-
//
|
|
76
|
+
if (keyData.kty != 'RSA' && keyData.kty != 'OKP') {
|
|
77
|
+
// HS (oct) keys not allowed because they are symmetric
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
|
|
3
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
4
4
|
import * as v8 from 'v8';
|
|
5
|
-
import * as
|
|
5
|
+
import * as system from '../../system/system-index.js';
|
|
6
6
|
import * as utils from '../../util/util-index.js';
|
|
7
|
+
|
|
8
|
+
import { modules } from '../../index.js';
|
|
7
9
|
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
8
10
|
|
|
9
11
|
const COMMAND_NAME = 'compact';
|
|
@@ -29,23 +31,25 @@ export function registerCompactAction(program: Command) {
|
|
|
29
31
|
|
|
30
32
|
return compactCommand.description('Compact storage').action(async (options) => {
|
|
31
33
|
const buckets = options.buckets?.split(',');
|
|
32
|
-
if (buckets
|
|
34
|
+
if (buckets == null) {
|
|
33
35
|
logger.info('Compacting storage for all buckets...');
|
|
34
36
|
} else {
|
|
35
|
-
logger.info(`Compacting storage for ${buckets
|
|
37
|
+
logger.info(`Compacting storage for ${buckets?.join(', ')}...`);
|
|
36
38
|
}
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const config = await utils.loadConfig(extractRunnerOptions(options));
|
|
40
|
+
const serviceContext = new system.ServiceContextContainer(config);
|
|
41
|
+
|
|
42
|
+
// Register modules in order to allow custom module compacting
|
|
43
|
+
const moduleManager = container.getImplementation(modules.ModuleManager);
|
|
44
|
+
await moduleManager.initialize(serviceContext);
|
|
45
|
+
|
|
41
46
|
logger.info('Connecting to storage...');
|
|
42
|
-
|
|
43
|
-
const client = psdb.client;
|
|
44
|
-
await client.connect();
|
|
47
|
+
|
|
45
48
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
// Start the storage engine in order to create the appropriate BucketStorage
|
|
50
|
+
await serviceContext.lifeCycleEngine.start();
|
|
51
|
+
const bucketStorage = serviceContext.storageEngine.activeBucketStorage;
|
|
52
|
+
|
|
49
53
|
const active = await bucketStorage.getActiveSyncRulesContent();
|
|
50
54
|
if (active == null) {
|
|
51
55
|
logger.info('No active instance to compact');
|
|
@@ -57,9 +61,10 @@ export function registerCompactAction(program: Command) {
|
|
|
57
61
|
logger.info('Successfully compacted storage.');
|
|
58
62
|
} catch (e) {
|
|
59
63
|
logger.error(`Failed to compact: ${e.toString()}`);
|
|
64
|
+
// Indirectly triggers lifeCycleEngine.stop
|
|
60
65
|
process.exit(1);
|
|
61
66
|
} finally {
|
|
62
|
-
|
|
67
|
+
// Indirectly triggers lifeCycleEngine.stop
|
|
63
68
|
process.exit(0);
|
|
64
69
|
}
|
|
65
70
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
1
|
+
import { container, logger, migrations } from '@powersync/lib-services-framework';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
|
|
4
|
-
import * as
|
|
4
|
+
import * as modules from '../../modules/modules-index.js';
|
|
5
|
+
import * as system from '../../system/system-index.js';
|
|
6
|
+
import * as utils from '../../util/util-index.js';
|
|
5
7
|
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
6
8
|
|
|
7
9
|
const COMMAND_NAME = 'migrate';
|
|
@@ -15,12 +17,23 @@ export function registerMigrationAction(program: Command) {
|
|
|
15
17
|
.description('Run migrations')
|
|
16
18
|
.argument('<direction>', 'Migration direction. `up` or `down`')
|
|
17
19
|
.action(async (direction: migrations.Direction, options) => {
|
|
20
|
+
const config = await utils.loadConfig(extractRunnerOptions(options));
|
|
21
|
+
const serviceContext = new system.ServiceContextContainer(config);
|
|
22
|
+
|
|
23
|
+
// Register modules in order to allow custom module migrations
|
|
24
|
+
const moduleManager = container.getImplementation(modules.ModuleManager);
|
|
25
|
+
await moduleManager.initialize(serviceContext);
|
|
26
|
+
|
|
18
27
|
try {
|
|
19
|
-
await migrations.migrate({
|
|
28
|
+
await serviceContext.migrations.migrate({
|
|
20
29
|
direction,
|
|
21
|
-
|
|
30
|
+
// Give the migrations access to the service context
|
|
31
|
+
migrationContext: {
|
|
32
|
+
service_context: serviceContext
|
|
33
|
+
}
|
|
22
34
|
});
|
|
23
35
|
|
|
36
|
+
await serviceContext.migrations[Symbol.asyncDispose]();
|
|
24
37
|
process.exit(0);
|
|
25
38
|
} catch (e) {
|
|
26
39
|
logger.error(`Migration failure`, e);
|
package/src/index.ts
CHANGED
|
@@ -6,9 +6,6 @@ export * as api from './api/api-index.js';
|
|
|
6
6
|
export * from './auth/auth-index.js';
|
|
7
7
|
export * as auth from './auth/auth-index.js';
|
|
8
8
|
|
|
9
|
-
export * from './db/db-index.js';
|
|
10
|
-
export * as db from './db/db-index.js';
|
|
11
|
-
|
|
12
9
|
export * from './entry/entry-index.js';
|
|
13
10
|
export * as entry from './entry/entry-index.js';
|
|
14
11
|
|
|
@@ -18,8 +15,8 @@ export * as framework from '@powersync/lib-services-framework';
|
|
|
18
15
|
export * from './metrics/Metrics.js';
|
|
19
16
|
export * as metrics from './metrics/Metrics.js';
|
|
20
17
|
|
|
18
|
+
export * from './migrations/migrations-index.js';
|
|
21
19
|
export * as migrations from './migrations/migrations-index.js';
|
|
22
|
-
export * from './migrations/migrations.js';
|
|
23
20
|
|
|
24
21
|
export * from './modules/modules-index.js';
|
|
25
22
|
export * as modules from './modules/modules-index.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as framework from '@powersync/lib-services-framework';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import * as system from '../system/system-index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PowerSync service migrations each have this context available to the `up` and `down` methods.
|
|
8
|
+
*/
|
|
9
|
+
export interface PowerSyncMigrationContext {
|
|
10
|
+
service_context: system.ServiceContext;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PowerSyncMigrationGenerics extends framework.MigrationAgentGenerics {
|
|
14
|
+
MIGRATION_CONTEXT: PowerSyncMigrationContext;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type PowerSyncMigrationFunction = framework.MigrationFunction<PowerSyncMigrationContext>;
|
|
18
|
+
|
|
19
|
+
export abstract class AbstractPowerSyncMigrationAgent extends framework.AbstractMigrationAgent<PowerSyncMigrationGenerics> {
|
|
20
|
+
abstract getInternalScriptsDir(): string;
|
|
21
|
+
|
|
22
|
+
async loadInternalMigrations(): Promise<framework.Migration<PowerSyncMigrationContext>[]> {
|
|
23
|
+
const migrationsDir = this.getInternalScriptsDir();
|
|
24
|
+
const files = await fs.readdir(migrationsDir);
|
|
25
|
+
const migrations = files.filter((file) => {
|
|
26
|
+
// Vitest resolves ts files
|
|
27
|
+
return ['.js', '.ts'].includes(path.extname(file));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return await Promise.all(
|
|
31
|
+
migrations.map(async (migration) => {
|
|
32
|
+
const module = await import(path.resolve(migrationsDir, migration));
|
|
33
|
+
return {
|
|
34
|
+
name: path.basename(migration).replace(path.extname(migration), ''),
|
|
35
|
+
up: module.up,
|
|
36
|
+
down: module.down
|
|
37
|
+
};
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type PowerSyncMigrationManager = framework.MigrationManager<PowerSyncMigrationGenerics>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as framework from '@powersync/lib-services-framework';
|
|
2
|
+
import * as system from '../system/system-index.js';
|
|
3
|
+
|
|
4
|
+
export const ensureAutomaticMigrations = async (options: { serviceContext: system.ServiceContext }) => {
|
|
5
|
+
const { serviceContext } = options;
|
|
6
|
+
if (serviceContext.configuration.migrations?.disable_auto_migration) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
await serviceContext.migrations.migrate({
|
|
10
|
+
direction: framework.migrations.Direction.Up,
|
|
11
|
+
migrationContext: {
|
|
12
|
+
service_context: serviceContext
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './migrations.js';
|
|
1
|
+
export * from './ensure-automatic-migrations.js';
|
|
2
|
+
export * from './PowerSyncMigrationManager.js';
|
|
@@ -4,6 +4,7 @@ import * as api from '../api/api-index.js';
|
|
|
4
4
|
|
|
5
5
|
import { ADMIN_ROUTES } from './endpoints/admin.js';
|
|
6
6
|
import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
|
|
7
|
+
import { PROBES_ROUTES } from './endpoints/probes.js';
|
|
7
8
|
import { syncStreamReactive } from './endpoints/socket-route.js';
|
|
8
9
|
import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
|
|
9
10
|
import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
|
|
@@ -47,7 +48,7 @@ export class RouterEngine {
|
|
|
47
48
|
|
|
48
49
|
// Default routes
|
|
49
50
|
this.routes = {
|
|
50
|
-
api_routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES],
|
|
51
|
+
api_routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...SYNC_RULES_ROUTES, ...PROBES_ROUTES],
|
|
51
52
|
stream_routes: [...SYNC_STREAM_ROUTES],
|
|
52
53
|
socket_routes: [syncStreamReactive]
|
|
53
54
|
};
|
|
@@ -8,13 +8,49 @@ import {
|
|
|
8
8
|
SqliteRow,
|
|
9
9
|
ToastableSqliteRow
|
|
10
10
|
} from '@powersync/service-sync-rules';
|
|
11
|
+
import { BSON } from 'bson';
|
|
11
12
|
import * as util from '../util/util-index.js';
|
|
12
13
|
import { ReplicationEventPayload } from './ReplicationEventPayload.js';
|
|
13
14
|
import { SourceEntityDescriptor } from './SourceEntity.js';
|
|
14
15
|
import { SourceTable } from './SourceTable.js';
|
|
15
|
-
import { BatchedCustomWriteCheckpointOptions
|
|
16
|
+
import { BatchedCustomWriteCheckpointOptions } from './storage-index.js';
|
|
16
17
|
import { SyncStorageWriteCheckpointAPI } from './WriteCheckpointAPI.js';
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Replica id uniquely identifying a row on the source database.
|
|
21
|
+
*
|
|
22
|
+
* Can be any value serializable to BSON.
|
|
23
|
+
*
|
|
24
|
+
* If the value is an entire document, the data serialized to a v5 UUID may be a good choice here.
|
|
25
|
+
*/
|
|
26
|
+
export type ReplicaId = BSON.UUID | BSON.Document | any;
|
|
27
|
+
|
|
28
|
+
export enum SyncRuleState {
|
|
29
|
+
/**
|
|
30
|
+
* New sync rules - needs to be processed (initial replication).
|
|
31
|
+
*
|
|
32
|
+
* While multiple sets of sync rules _can_ be in PROCESSING,
|
|
33
|
+
* it's generally pointless, so we only keep one in that state.
|
|
34
|
+
*/
|
|
35
|
+
PROCESSING = 'PROCESSING',
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sync rule processing is done, and can be used for sync.
|
|
39
|
+
*
|
|
40
|
+
* Only one set of sync rules should be in ACTIVE state.
|
|
41
|
+
*/
|
|
42
|
+
ACTIVE = 'ACTIVE',
|
|
43
|
+
/**
|
|
44
|
+
* This state is used when the sync rules has been replaced,
|
|
45
|
+
* and replication is or should be stopped.
|
|
46
|
+
*/
|
|
47
|
+
STOP = 'STOP',
|
|
48
|
+
/**
|
|
49
|
+
* After sync rules have been stopped, the data needs to be
|
|
50
|
+
* deleted. Once deleted, the state is TERMINATED.
|
|
51
|
+
*/
|
|
52
|
+
TERMINATED = 'TERMINATED'
|
|
53
|
+
}
|
|
18
54
|
export interface BucketStorageFactoryListener extends DisposableListener {
|
|
19
55
|
syncStorageCreated: (storage: SyncRulesBucketStorage) => void;
|
|
20
56
|
replicationEvent: (event: ReplicationEventPayload) => void;
|
|
@@ -211,6 +247,13 @@ export interface StartBatchOptions extends ParseSyncRulesOptions {
|
|
|
211
247
|
* database, for example from MongoDB.
|
|
212
248
|
*/
|
|
213
249
|
storeCurrentData: boolean;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Set to true for initial replication.
|
|
253
|
+
*
|
|
254
|
+
* This will avoid creating new operations for rows previously replicated.
|
|
255
|
+
*/
|
|
256
|
+
skipExistingRows?: boolean;
|
|
214
257
|
}
|
|
215
258
|
|
|
216
259
|
export interface SyncRulesBucketStorageListener extends DisposableListener {
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
export * from './BucketStorage.js';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './ChecksumCache.js';
|
|
3
3
|
export * from './ReplicationEventPayload.js';
|
|
4
4
|
export * from './SourceEntity.js';
|
|
5
5
|
export * from './SourceTable.js';
|
|
6
6
|
export * from './StorageEngine.js';
|
|
7
|
-
|
|
8
|
-
export * from './mongo/config.js';
|
|
9
|
-
export * from './mongo/db.js';
|
|
10
|
-
export * from './mongo/models.js';
|
|
11
|
-
export * from './mongo/MongoBucketBatch.js';
|
|
12
|
-
export * from './mongo/MongoIdSequence.js';
|
|
13
|
-
export * from './mongo/MongoPersistedSyncRules.js';
|
|
14
|
-
export * from './mongo/MongoPersistedSyncRulesContent.js';
|
|
15
|
-
export * from './mongo/MongoStorageProvider.js';
|
|
16
|
-
export * from './mongo/MongoSyncBucketStorage.js';
|
|
17
|
-
export * from './mongo/MongoSyncRulesLock.js';
|
|
18
|
-
export * from './mongo/OperationBatch.js';
|
|
19
|
-
export * from './mongo/PersistedBatch.js';
|
|
20
|
-
export * from './mongo/util.js';
|
|
7
|
+
export * from './StorageProvider.js';
|
|
21
8
|
export * from './WriteCheckpointAPI.js';
|
|
9
|
+
|
package/src/sync/sync.ts
CHANGED
|
@@ -318,9 +318,18 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
|
|
|
318
318
|
// Send the object as is, will most likely be encoded as a BSON document
|
|
319
319
|
send_data = { data: r };
|
|
320
320
|
} else if (raw_data) {
|
|
321
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Data is a raw string - we can use the more efficient JSON.stringify.
|
|
323
|
+
* FIXME: ensure that the data is actually a string
|
|
324
|
+
*/
|
|
322
325
|
const response: util.StreamingSyncData = {
|
|
323
|
-
data:
|
|
326
|
+
data: {
|
|
327
|
+
...r,
|
|
328
|
+
data: r.data.map((entry) => ({
|
|
329
|
+
...entry,
|
|
330
|
+
data: typeof entry.data == 'string' ? entry.data : JSONBig.stringify(entry.data)
|
|
331
|
+
}))
|
|
332
|
+
}
|
|
324
333
|
};
|
|
325
334
|
send_data = JSON.stringify(response);
|
|
326
335
|
} else {
|
|
@@ -375,7 +384,7 @@ function transformLegacyResponse(bucketData: util.SyncBucketData): any {
|
|
|
375
384
|
data: bucketData.data.map((entry) => {
|
|
376
385
|
return {
|
|
377
386
|
...entry,
|
|
378
|
-
data: entry.data == null ? null : new JsonContainer(entry.data
|
|
387
|
+
data: entry.data == null ? null : typeof entry.data == 'string' ? new JsonContainer(entry.data) : entry.data,
|
|
379
388
|
checksum: BigInt(entry.checksum)
|
|
380
389
|
};
|
|
381
390
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { LifeCycledSystem, ServiceIdentifier, container } from '@powersync/lib-services-framework';
|
|
1
|
+
import { LifeCycledSystem, MigrationManager, ServiceIdentifier, container } from '@powersync/lib-services-framework';
|
|
2
2
|
|
|
3
|
+
import { framework } from '../index.js';
|
|
3
4
|
import * as metrics from '../metrics/Metrics.js';
|
|
5
|
+
import { PowerSyncMigrationManager } from '../migrations/PowerSyncMigrationManager.js';
|
|
4
6
|
import * as replication from '../replication/replication-index.js';
|
|
5
7
|
import * as routes from '../routes/routes-index.js';
|
|
6
8
|
import * as storage from '../storage/storage-index.js';
|
|
@@ -13,6 +15,7 @@ export interface ServiceContext {
|
|
|
13
15
|
replicationEngine: replication.ReplicationEngine | null;
|
|
14
16
|
routerEngine: routes.RouterEngine | null;
|
|
15
17
|
storageEngine: storage.StorageEngine;
|
|
18
|
+
migrations: PowerSyncMigrationManager;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -30,13 +33,19 @@ export class ServiceContextContainer implements ServiceContext {
|
|
|
30
33
|
this.storageEngine = new storage.StorageEngine({
|
|
31
34
|
configuration
|
|
32
35
|
});
|
|
36
|
+
|
|
37
|
+
const migrationManager = new MigrationManager();
|
|
38
|
+
container.register(framework.ContainerImplementation.MIGRATION_MANAGER, migrationManager);
|
|
39
|
+
|
|
40
|
+
this.lifeCycleEngine.withLifecycle(migrationManager, {
|
|
41
|
+
// Migrations should be executed before the system starts
|
|
42
|
+
start: () => migrationManager[Symbol.asyncDispose]()
|
|
43
|
+
});
|
|
44
|
+
|
|
33
45
|
this.lifeCycleEngine.withLifecycle(this.storageEngine, {
|
|
34
46
|
start: (storageEngine) => storageEngine.start(),
|
|
35
47
|
stop: (storageEngine) => storageEngine.shutDown()
|
|
36
48
|
});
|
|
37
|
-
|
|
38
|
-
// Mongo storage is available as an option by default TODO: Consider moving this to a Mongo Storage Module
|
|
39
|
-
this.storageEngine.registerProvider(new storage.MongoStorageProvider());
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
get replicationEngine(): replication.ReplicationEngine | null {
|
|
@@ -51,6 +60,10 @@ export class ServiceContextContainer implements ServiceContext {
|
|
|
51
60
|
return container.getOptional(metrics.Metrics);
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
get migrations(): PowerSyncMigrationManager {
|
|
64
|
+
return container.getImplementation(framework.ContainerImplementation.MIGRATION_MANAGER);
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
/**
|
|
55
68
|
* Allows for registering core and generic implementations of services/helpers.
|
|
56
69
|
* This uses the framework container under the hood.
|
package/src/util/config/types.ts
CHANGED
|
@@ -30,8 +30,8 @@ export type SyncRulesConfig = {
|
|
|
30
30
|
|
|
31
31
|
export type ResolvedPowerSyncConfig = {
|
|
32
32
|
base_config: PowerSyncConfig;
|
|
33
|
-
connections?: configFile.
|
|
34
|
-
storage: configFile.
|
|
33
|
+
connections?: configFile.GenericDataSourceConfig[];
|
|
34
|
+
storage: configFile.GenericStorageConfig;
|
|
35
35
|
dev: {
|
|
36
36
|
demo_auth: boolean;
|
|
37
37
|
demo_password?: string;
|
package/src/util/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as sync_rules from '@powersync/service-sync-rules';
|
|
|
2
2
|
import * as bson from 'bson';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import * as uuid from 'uuid';
|
|
5
|
-
import { BucketChecksum, OpId } from './protocol-types.js';
|
|
5
|
+
import { BucketChecksum, OpId, OplogEntry } from './protocol-types.js';
|
|
6
6
|
|
|
7
7
|
import * as storage from '../storage/storage-index.js';
|
|
8
8
|
|
|
@@ -144,3 +144,61 @@ export function checkpointUserId(user_id: string | undefined, client_id: string
|
|
|
144
144
|
}
|
|
145
145
|
return `${user_id}/${client_id}`;
|
|
146
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Reduce a bucket to the final state as stored on the client.
|
|
150
|
+
*
|
|
151
|
+
* This keeps the final state for each row as a PUT operation.
|
|
152
|
+
*
|
|
153
|
+
* All other operations are replaced with a single CLEAR operation,
|
|
154
|
+
* summing their checksums, and using a 0 as an op_id.
|
|
155
|
+
*
|
|
156
|
+
* This is the function $r(B)$, as described in /docs/bucket-properties.md.
|
|
157
|
+
*
|
|
158
|
+
* Used for tests.
|
|
159
|
+
*/
|
|
160
|
+
export function reduceBucket(operations: OplogEntry[]) {
|
|
161
|
+
let rowState = new Map<string, OplogEntry>();
|
|
162
|
+
let otherChecksum = 0;
|
|
163
|
+
|
|
164
|
+
for (let op of operations) {
|
|
165
|
+
const key = rowKey(op);
|
|
166
|
+
if (op.op == 'PUT') {
|
|
167
|
+
const existing = rowState.get(key);
|
|
168
|
+
if (existing) {
|
|
169
|
+
otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
|
|
170
|
+
}
|
|
171
|
+
rowState.set(key, op);
|
|
172
|
+
} else if (op.op == 'REMOVE') {
|
|
173
|
+
const existing = rowState.get(key);
|
|
174
|
+
if (existing) {
|
|
175
|
+
otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
|
|
176
|
+
}
|
|
177
|
+
rowState.delete(key);
|
|
178
|
+
otherChecksum = addChecksums(otherChecksum, op.checksum as number);
|
|
179
|
+
} else if (op.op == 'CLEAR') {
|
|
180
|
+
rowState.clear();
|
|
181
|
+
otherChecksum = op.checksum as number;
|
|
182
|
+
} else if (op.op == 'MOVE') {
|
|
183
|
+
otherChecksum = addChecksums(otherChecksum, op.checksum as number);
|
|
184
|
+
} else {
|
|
185
|
+
throw new Error(`Unknown operation ${op.op}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const puts = [...rowState.values()].sort((a, b) => {
|
|
190
|
+
return Number(BigInt(a.op_id) - BigInt(b.op_id));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
let finalState: OplogEntry[] = [
|
|
194
|
+
// Special operation to indiciate the checksum remainder
|
|
195
|
+
{ op_id: '0', op: 'CLEAR', checksum: otherChecksum },
|
|
196
|
+
...puts
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
return finalState;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function rowKey(entry: OplogEntry) {
|
|
203
|
+
return `${entry.object_type}/${entry.object_id}/${entry.subkey}`;
|
|
204
|
+
}
|