@powersync/service-core 0.2.2 → 0.3.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.
- package/CHANGELOG.md +13 -0
- package/dist/api/diagnostics.js +2 -2
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.js.map +1 -1
- package/dist/auth/CachedKeyCollector.js.map +1 -1
- package/dist/auth/KeySpec.js.map +1 -1
- package/dist/auth/KeyStore.js +2 -2
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/LeakyBucket.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/auth/SupabaseKeyCollector.js.map +1 -1
- package/dist/db/mongo.js.map +1 -1
- package/dist/entry/cli-entry.js +2 -2
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/config-command.js.map +1 -1
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/start-action.js.map +1 -1
- package/dist/entry/commands/teardown-action.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/locks/LockManager.d.ts +10 -0
- package/dist/locks/LockManager.js +7 -0
- package/dist/locks/LockManager.js.map +1 -0
- package/dist/locks/MongoLocks.d.ts +36 -0
- package/dist/locks/MongoLocks.js +81 -0
- package/dist/locks/MongoLocks.js.map +1 -0
- package/dist/locks/locks-index.d.ts +2 -0
- package/dist/locks/locks-index.js +3 -0
- package/dist/locks/locks-index.js.map +1 -0
- package/dist/metrics/Metrics.js +6 -6
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
- package/dist/migrations/definitions.d.ts +18 -0
- package/dist/migrations/definitions.js +6 -0
- package/dist/migrations/definitions.js.map +1 -0
- package/dist/migrations/executor.d.ts +16 -0
- package/dist/migrations/executor.js +64 -0
- package/dist/migrations/executor.js.map +1 -0
- package/dist/migrations/migrations-index.d.ts +3 -0
- package/dist/migrations/migrations-index.js +4 -0
- package/dist/migrations/migrations-index.js.map +1 -0
- package/dist/migrations/migrations.d.ts +1 -1
- package/dist/migrations/migrations.js +4 -8
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/migrations/store/migration-store.d.ts +11 -0
- package/dist/migrations/store/migration-store.js +46 -0
- package/dist/migrations/store/migration-store.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.js.map +1 -1
- package/dist/replication/PgRelation.js.map +1 -1
- package/dist/replication/WalConnection.js.map +1 -1
- package/dist/replication/WalStream.d.ts +0 -1
- package/dist/replication/WalStream.js +21 -25
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamManager.js +12 -13
- package/dist/replication/WalStreamManager.js.map +1 -1
- package/dist/replication/WalStreamRunner.js +8 -8
- package/dist/replication/WalStreamRunner.js.map +1 -1
- package/dist/replication/util.js.map +1 -1
- package/dist/routes/auth.d.ts +8 -10
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +1011 -0
- package/dist/routes/{admin.js → endpoints/admin.js} +33 -18
- package/dist/routes/endpoints/admin.js.map +1 -0
- package/dist/routes/endpoints/checkpointing.d.ts +76 -0
- package/dist/routes/endpoints/checkpointing.js +36 -0
- package/dist/routes/endpoints/checkpointing.js.map +1 -0
- package/dist/routes/endpoints/dev.d.ts +312 -0
- package/dist/routes/{dev.js → endpoints/dev.js} +25 -16
- package/dist/routes/endpoints/dev.js.map +1 -0
- package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
- package/dist/routes/endpoints/route-endpoints-index.js +7 -0
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
- package/dist/routes/endpoints/socket-route.d.ts +2 -0
- package/dist/routes/{socket-route.js → endpoints/socket-route.js} +10 -10
- package/dist/routes/endpoints/socket-route.js.map +1 -0
- package/dist/routes/endpoints/sync-rules.d.ts +174 -0
- package/dist/routes/{sync-rules.js → endpoints/sync-rules.js} +44 -24
- package/dist/routes/endpoints/sync-rules.js.map +1 -0
- package/dist/routes/endpoints/sync-stream.d.ts +132 -0
- package/dist/routes/{sync-stream.js → endpoints/sync-stream.js} +26 -17
- package/dist/routes/endpoints/sync-stream.js.map +1 -0
- package/dist/routes/hooks.d.ts +10 -0
- package/dist/routes/hooks.js +31 -0
- package/dist/routes/hooks.js.map +1 -0
- package/dist/routes/route-register.d.ts +10 -0
- package/dist/routes/route-register.js +87 -0
- package/dist/routes/route-register.js.map +1 -0
- package/dist/routes/router.d.ts +16 -4
- package/dist/routes/router.js +6 -1
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +5 -3
- package/dist/routes/routes-index.js +5 -3
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/runner/teardown.js +9 -9
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +3 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/MongoBucketStorage.js +5 -5
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/mongo/MongoBucketBatch.js +23 -18
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoSyncRulesLock.js +3 -3
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/mongo/OperationBatch.js.map +1 -1
- package/dist/storage/mongo/PersistedBatch.js +2 -2
- package/dist/storage/mongo/PersistedBatch.js.map +1 -1
- package/dist/storage/mongo/db.d.ts +2 -2
- package/dist/storage/mongo/db.js.map +1 -1
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/sync/BroadcastIterable.js.map +1 -1
- package/dist/sync/LastValueSink.js.map +1 -1
- package/dist/sync/merge.js.map +1 -1
- package/dist/sync/safeRace.js.map +1 -1
- package/dist/sync/sync.js +4 -4
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/CorePowerSyncSystem.d.ts +12 -7
- package/dist/system/CorePowerSyncSystem.js +26 -2
- package/dist/system/CorePowerSyncSystem.js.map +1 -1
- package/dist/system/system-index.d.ts +1 -0
- package/dist/system/system-index.js +2 -0
- package/dist/system/system-index.js.map +1 -0
- package/dist/util/Mutex.js.map +1 -1
- package/dist/util/PgManager.js.map +1 -1
- package/dist/util/alerting.d.ts +0 -2
- package/dist/util/alerting.js +0 -6
- package/dist/util/alerting.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +3 -3
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.js +7 -5
- package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.js +4 -4
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
- package/dist/util/config.js.map +1 -1
- package/dist/util/env.d.ts +1 -2
- package/dist/util/env.js +3 -2
- package/dist/util/env.js.map +1 -1
- package/dist/util/memory-tracking.js +2 -2
- package/dist/util/memory-tracking.js.map +1 -1
- package/dist/util/migration_lib.js.map +1 -1
- package/dist/util/pgwire_utils.js +2 -2
- package/dist/util/pgwire_utils.js.map +1 -1
- package/dist/util/populate_test_data.js.map +1 -1
- package/dist/util/secs.js.map +1 -1
- package/dist/util/utils.js +4 -4
- package/dist/util/utils.js.map +1 -1
- package/package.json +12 -9
- package/src/api/diagnostics.ts +5 -5
- package/src/api/schema.ts +1 -1
- package/src/auth/KeyStore.ts +2 -2
- package/src/entry/cli-entry.ts +3 -4
- package/src/entry/commands/config-command.ts +1 -1
- package/src/entry/commands/migrate-action.ts +2 -2
- package/src/entry/commands/start-action.ts +1 -1
- package/src/entry/commands/teardown-action.ts +1 -1
- package/src/index.ts +5 -2
- package/src/locks/LockManager.ts +16 -0
- package/src/locks/MongoLocks.ts +142 -0
- package/src/locks/locks-index.ts +2 -0
- package/src/metrics/Metrics.ts +8 -8
- package/src/migrations/db/migrations/1684951997326-init.ts +3 -3
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +3 -3
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +2 -2
- package/src/migrations/definitions.ts +21 -0
- package/src/migrations/executor.ts +87 -0
- package/src/migrations/migrations-index.ts +3 -0
- package/src/migrations/migrations.ts +7 -11
- package/src/migrations/store/migration-store.ts +63 -0
- package/src/replication/WalConnection.ts +2 -2
- package/src/replication/WalStream.ts +24 -29
- package/src/replication/WalStreamManager.ts +14 -15
- package/src/replication/WalStreamRunner.ts +10 -10
- package/src/replication/util.ts +1 -1
- package/src/routes/auth.ts +22 -16
- package/src/routes/endpoints/admin.ts +237 -0
- package/src/routes/endpoints/checkpointing.ts +41 -0
- package/src/routes/endpoints/dev.ts +199 -0
- package/src/routes/endpoints/route-endpoints-index.ts +6 -0
- package/src/routes/{socket-route.ts → endpoints/socket-route.ts} +11 -11
- package/src/routes/endpoints/sync-rules.ts +227 -0
- package/src/routes/endpoints/sync-stream.ts +101 -0
- package/src/routes/hooks.ts +45 -0
- package/src/routes/route-register.ts +104 -0
- package/src/routes/router.ts +34 -6
- package/src/routes/routes-index.ts +5 -4
- package/src/runner/teardown.ts +9 -9
- package/src/storage/BucketStorage.ts +7 -2
- package/src/storage/ChecksumCache.ts +2 -2
- package/src/storage/MongoBucketStorage.ts +8 -8
- package/src/storage/SourceTable.ts +2 -2
- package/src/storage/mongo/MongoBucketBatch.ts +29 -22
- package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -3
- package/src/storage/mongo/MongoSyncRulesLock.ts +3 -3
- package/src/storage/mongo/OperationBatch.ts +1 -1
- package/src/storage/mongo/PersistedBatch.ts +3 -3
- package/src/storage/mongo/db.ts +3 -4
- package/src/sync/sync.ts +8 -8
- package/src/sync/util.ts +2 -2
- package/src/system/CorePowerSyncSystem.ts +31 -10
- package/src/system/system-index.ts +1 -0
- package/src/util/alerting.ts +0 -8
- package/src/util/config/collectors/config-collector.ts +5 -3
- package/src/util/config/collectors/impl/filesystem-config-collector.ts +8 -6
- package/src/util/config/compound-config-collector.ts +4 -4
- package/src/util/env.ts +4 -2
- package/src/util/memory-tracking.ts +2 -2
- package/src/util/pgwire_utils.ts +3 -3
- package/src/util/utils.ts +5 -5
- package/test/src/auth.test.ts +4 -2
- package/test/src/data_storage.test.ts +177 -0
- package/test/src/env.ts +6 -6
- package/test/src/setup.ts +7 -0
- package/test/src/slow_tests.test.ts +45 -6
- package/test/tsconfig.json +1 -1
- package/tsconfig.json +5 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +1 -3
- package/dist/migrations/db/store.d.ts +0 -3
- package/dist/migrations/db/store.js +0 -10
- package/dist/migrations/db/store.js.map +0 -1
- package/dist/routes/admin.d.ts +0 -7
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/checkpointing.d.ts +0 -3
- package/dist/routes/checkpointing.js +0 -30
- package/dist/routes/checkpointing.js.map +0 -1
- package/dist/routes/dev.d.ts +0 -6
- package/dist/routes/dev.js.map +0 -1
- package/dist/routes/route-generators.d.ts +0 -15
- package/dist/routes/route-generators.js +0 -32
- package/dist/routes/route-generators.js.map +0 -1
- package/dist/routes/socket-route.d.ts +0 -2
- package/dist/routes/socket-route.js.map +0 -1
- package/dist/routes/sync-rules.d.ts +0 -6
- package/dist/routes/sync-rules.js.map +0 -1
- package/dist/routes/sync-stream.d.ts +0 -5
- package/dist/routes/sync-stream.js.map +0 -1
- package/src/migrations/db/store.ts +0 -11
- package/src/routes/admin.ts +0 -229
- package/src/routes/checkpointing.ts +0 -38
- package/src/routes/dev.ts +0 -194
- package/src/routes/route-generators.ts +0 -39
- package/src/routes/sync-rules.ts +0 -210
- package/src/routes/sync-stream.ts +0 -95
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
3
2
|
import { hrtime } from 'node:process';
|
|
4
3
|
|
|
5
|
-
import * as storage from '
|
|
6
|
-
import * as util from '
|
|
4
|
+
import * as storage from '../storage/storage-index.js';
|
|
5
|
+
import * as util from '../util/util-index.js';
|
|
7
6
|
|
|
8
7
|
import { DefaultErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
9
|
-
import { touch } from './WalStream.js';
|
|
10
8
|
import { WalStreamRunner } from './WalStreamRunner.js';
|
|
11
9
|
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
10
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
12
11
|
|
|
13
12
|
// 5 minutes
|
|
14
13
|
const PING_INTERVAL = 1_000_000_000n * 300n;
|
|
@@ -37,8 +36,8 @@ export class WalStreamManager {
|
|
|
37
36
|
|
|
38
37
|
start() {
|
|
39
38
|
this.runLoop().catch((e) => {
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
logger.error(`Fatal WalStream error`, e);
|
|
40
|
+
container.reporter.captureException(e);
|
|
42
41
|
setTimeout(() => {
|
|
43
42
|
process.exit(1);
|
|
44
43
|
}, 1000);
|
|
@@ -58,7 +57,7 @@ export class WalStreamManager {
|
|
|
58
57
|
const configured_sync_rules = await util.loadSyncRules(this.system.config);
|
|
59
58
|
let configured_lock: storage.ReplicationLock | undefined = undefined;
|
|
60
59
|
if (configured_sync_rules != null) {
|
|
61
|
-
|
|
60
|
+
logger.info('Loading sync rules from configuration');
|
|
62
61
|
try {
|
|
63
62
|
// Configure new sync rules, if it has changed.
|
|
64
63
|
// In that case, also immediately take out a lock, so that another process doesn't start replication on it.
|
|
@@ -70,13 +69,13 @@ export class WalStreamManager {
|
|
|
70
69
|
}
|
|
71
70
|
} catch (e) {
|
|
72
71
|
// Log, but continue with previous sync rules
|
|
73
|
-
|
|
72
|
+
logger.error(`Failed to load sync rules from configuration`, e);
|
|
74
73
|
}
|
|
75
74
|
} else {
|
|
76
|
-
|
|
75
|
+
logger.info('No sync rules configured - configure via API');
|
|
77
76
|
}
|
|
78
77
|
while (!this.stopped) {
|
|
79
|
-
await touch();
|
|
78
|
+
await container.probes.touch();
|
|
80
79
|
try {
|
|
81
80
|
const pool = this.system.pgwire_pool;
|
|
82
81
|
if (pool) {
|
|
@@ -93,7 +92,7 @@ export class WalStreamManager {
|
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
} catch (e) {
|
|
96
|
-
|
|
95
|
+
logger.error(`Failed to refresh wal streams`, e);
|
|
97
96
|
}
|
|
98
97
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
99
98
|
}
|
|
@@ -117,7 +116,7 @@ export class WalStreamManager {
|
|
|
117
116
|
try {
|
|
118
117
|
await db.query(`SELECT * FROM pg_logical_emit_message(false, 'powersync', 'ping')`);
|
|
119
118
|
} catch (e) {
|
|
120
|
-
|
|
119
|
+
logger.warn(`Failed to ping`, e);
|
|
121
120
|
}
|
|
122
121
|
this.lastPing = now;
|
|
123
122
|
}
|
|
@@ -168,7 +167,7 @@ export class WalStreamManager {
|
|
|
168
167
|
// for example from stricter validation that was added.
|
|
169
168
|
// This will be retried every couple of seconds.
|
|
170
169
|
// When new (valid) sync rules are deployed and processed, this one be disabled.
|
|
171
|
-
|
|
170
|
+
logger.error(`Failed to start replication for ${syncRules.slot_name}`, e);
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
173
|
}
|
|
@@ -184,7 +183,7 @@ export class WalStreamManager {
|
|
|
184
183
|
await stream.terminate();
|
|
185
184
|
} catch (e) {
|
|
186
185
|
// This will be retried
|
|
187
|
-
|
|
186
|
+
logger.warn(`Failed to terminate ${stream.slot_name}`, e);
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
189
|
|
|
@@ -207,7 +206,7 @@ export class WalStreamManager {
|
|
|
207
206
|
await lock.release();
|
|
208
207
|
}
|
|
209
208
|
} catch (e) {
|
|
210
|
-
|
|
209
|
+
logger.warn(`Failed to terminate ${syncRules.slot_name}`, e);
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
212
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
3
2
|
|
|
4
|
-
import * as storage from '
|
|
5
|
-
import * as util from '
|
|
3
|
+
import * as storage from '../storage/storage-index.js';
|
|
4
|
+
import * as util from '../util/util-index.js';
|
|
6
5
|
|
|
7
6
|
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
8
7
|
import { MissingReplicationSlotError, WalStream } from './WalStream.js';
|
|
9
8
|
import { ResolvedConnection } from '../util/config/types.js';
|
|
9
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
10
10
|
|
|
11
11
|
export interface WalStreamRunnerOptions {
|
|
12
12
|
factory: storage.BucketStorageFactory;
|
|
@@ -46,12 +46,12 @@ export class WalStreamRunner {
|
|
|
46
46
|
await this.replicateLoop();
|
|
47
47
|
} catch (e) {
|
|
48
48
|
// Fatal exception
|
|
49
|
-
|
|
49
|
+
container.reporter.captureException(e, {
|
|
50
50
|
metadata: {
|
|
51
51
|
replication_slot: this.slot_name
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
-
|
|
54
|
+
logger.error(`Replication failed on ${this.slot_name}`, e);
|
|
55
55
|
|
|
56
56
|
if (e instanceof MissingReplicationSlotError) {
|
|
57
57
|
// This stops replication on this slot, and creates a new slot
|
|
@@ -96,7 +96,7 @@ export class WalStreamRunner {
|
|
|
96
96
|
});
|
|
97
97
|
await stream.replicate();
|
|
98
98
|
} catch (e) {
|
|
99
|
-
|
|
99
|
+
logger.error(`Replication error`, e);
|
|
100
100
|
if (e.cause != null) {
|
|
101
101
|
// Example:
|
|
102
102
|
// PgError.conn_ended: Unable to do postgres query on ended connection
|
|
@@ -118,13 +118,13 @@ export class WalStreamRunner {
|
|
|
118
118
|
// [Symbol(pg.ErrorResponse)]: undefined
|
|
119
119
|
// }
|
|
120
120
|
// Without this additional log, the cause would not be visible in the logs.
|
|
121
|
-
|
|
121
|
+
logger.error(`cause`, e.cause);
|
|
122
122
|
}
|
|
123
123
|
if (e instanceof MissingReplicationSlotError) {
|
|
124
124
|
throw e;
|
|
125
125
|
} else {
|
|
126
126
|
// Report the error if relevant, before retrying
|
|
127
|
-
|
|
127
|
+
container.reporter.captureException(e, {
|
|
128
128
|
metadata: {
|
|
129
129
|
replication_slot: this.slot_name
|
|
130
130
|
}
|
|
@@ -144,7 +144,7 @@ export class WalStreamRunner {
|
|
|
144
144
|
* This will also release the lock if start() was called earlier.
|
|
145
145
|
*/
|
|
146
146
|
async stop(options?: { force?: boolean }) {
|
|
147
|
-
|
|
147
|
+
logger.info(`${this.slot_name} Stopping replication`);
|
|
148
148
|
// End gracefully
|
|
149
149
|
this.abortController.abort();
|
|
150
150
|
|
|
@@ -161,7 +161,7 @@ export class WalStreamRunner {
|
|
|
161
161
|
* Stops replication if needed.
|
|
162
162
|
*/
|
|
163
163
|
async terminate(options?: { force?: boolean }) {
|
|
164
|
-
|
|
164
|
+
logger.info(`${this.slot_name} Terminating replication`);
|
|
165
165
|
await this.stop(options);
|
|
166
166
|
|
|
167
167
|
const slotName = this.slot_name;
|
package/src/replication/util.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
2
|
|
|
3
|
-
import * as util from '
|
|
3
|
+
import * as util from '../util/util-index.js';
|
|
4
4
|
import { ReplicationColumn, ReplicationIdentity } from './PgRelation.js';
|
|
5
5
|
|
|
6
6
|
export interface ReplicaIdentityResult {
|
package/src/routes/auth.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import * as micro from '@journeyapps-platform/micro';
|
|
2
|
-
import { FastifyRequest } from 'fastify';
|
|
3
1
|
import * as jose from 'jose';
|
|
4
2
|
|
|
5
|
-
import * as auth from '
|
|
6
|
-
import * as util from '
|
|
7
|
-
import { Context } from './router.js';
|
|
3
|
+
import * as auth from '../auth/auth-index.js';
|
|
4
|
+
import * as util from '../util/util-index.js';
|
|
5
|
+
import { BasicRouterRequest, Context, RequestEndpointHandlerPayload } from './router.js';
|
|
8
6
|
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
9
7
|
|
|
10
|
-
export function endpoint(req:
|
|
8
|
+
export function endpoint(req: BasicRouterRequest) {
|
|
11
9
|
const protocol = req.headers['x-forwarded-proto'] ?? req.protocol;
|
|
12
10
|
const host = req.hostname;
|
|
13
11
|
return `${protocol}://${host}`;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
function devAudience(req:
|
|
14
|
+
function devAudience(req: BasicRouterRequest): string {
|
|
17
15
|
return `${endpoint(req)}/dev`;
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -22,7 +20,7 @@ function devAudience(req: FastifyRequest): string {
|
|
|
22
20
|
*
|
|
23
21
|
* Will be replaced by temporary tokens issued by PowerSync Management service.
|
|
24
22
|
*/
|
|
25
|
-
export async function issueDevToken(req:
|
|
23
|
+
export async function issueDevToken(req: BasicRouterRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
|
|
26
24
|
const iss = devAudience(req);
|
|
27
25
|
const aud = devAudience(req);
|
|
28
26
|
|
|
@@ -42,7 +40,11 @@ export async function issueDevToken(req: FastifyRequest, user_id: string, config
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
/** @deprecated */
|
|
45
|
-
export async function issueLegacyDevToken(
|
|
43
|
+
export async function issueLegacyDevToken(
|
|
44
|
+
req: BasicRouterRequest,
|
|
45
|
+
user_id: string,
|
|
46
|
+
config: util.ResolvedPowerSyncConfig
|
|
47
|
+
) {
|
|
46
48
|
const iss = devAudience(req);
|
|
47
49
|
const aud = config.jwt_audiences[0];
|
|
48
50
|
|
|
@@ -61,7 +63,11 @@ export async function issueLegacyDevToken(req: FastifyRequest, user_id: string,
|
|
|
61
63
|
.sign(key.key);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
export async function issuePowerSyncToken(
|
|
66
|
+
export async function issuePowerSyncToken(
|
|
67
|
+
req: BasicRouterRequest,
|
|
68
|
+
user_id: string,
|
|
69
|
+
config: util.ResolvedPowerSyncConfig
|
|
70
|
+
) {
|
|
65
71
|
const iss = devAudience(req);
|
|
66
72
|
const aud = config.jwt_audiences[0];
|
|
67
73
|
const key = config.dev.dev_key;
|
|
@@ -89,8 +95,8 @@ export function getTokenFromHeader(authHeader: string = ''): string | null {
|
|
|
89
95
|
return token ?? null;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
export const authUser = async (payload:
|
|
93
|
-
return authorizeUser(payload.context, payload.request.headers.authorization);
|
|
98
|
+
export const authUser = async (payload: RequestEndpointHandlerPayload) => {
|
|
99
|
+
return authorizeUser(payload.context, payload.request.headers.authorization as string);
|
|
94
100
|
};
|
|
95
101
|
|
|
96
102
|
export async function authorizeUser(context: Context, authHeader: string = '') {
|
|
@@ -142,9 +148,9 @@ export async function generateContext(system: CorePowerSyncSystem, token: string
|
|
|
142
148
|
/**
|
|
143
149
|
* @deprecated
|
|
144
150
|
*/
|
|
145
|
-
export const authDevUser = async (payload:
|
|
151
|
+
export const authDevUser = async (payload: RequestEndpointHandlerPayload) => {
|
|
146
152
|
const context = payload.context;
|
|
147
|
-
const token = getTokenFromHeader(payload.request.headers.authorization);
|
|
153
|
+
const token = getTokenFromHeader(payload.request.headers.authorization as string);
|
|
148
154
|
if (!context.system.config.dev.demo_auth) {
|
|
149
155
|
return {
|
|
150
156
|
authorized: false,
|
|
@@ -179,7 +185,7 @@ export const authDevUser = async (payload: micro.fastify.FastifyHandlerPayload<a
|
|
|
179
185
|
return { authorized: true };
|
|
180
186
|
};
|
|
181
187
|
|
|
182
|
-
export const authApi = (payload:
|
|
188
|
+
export const authApi = (payload: RequestEndpointHandlerPayload) => {
|
|
183
189
|
const context = payload.context;
|
|
184
190
|
const api_keys = context.system.config.api_tokens;
|
|
185
191
|
if (api_keys.length == 0) {
|
|
@@ -188,7 +194,7 @@ export const authApi = (payload: micro.fastify.FastifyHandlerPayload<any, Contex
|
|
|
188
194
|
errors: ['Authentication disabled']
|
|
189
195
|
};
|
|
190
196
|
}
|
|
191
|
-
const auth = payload.request.headers.authorization ?? '';
|
|
197
|
+
const auth = (payload.request.headers.authorization as string) ?? '';
|
|
192
198
|
|
|
193
199
|
const tokenMatch = /^(Token|Bearer) (\S+)$/.exec(auth);
|
|
194
200
|
if (!tokenMatch) {
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { errors, router, schema } from '@powersync/lib-services-framework';
|
|
2
|
+
import { SqlSyncRules, SqliteValue, StaticSchema, isJsonValue, toSyncRulesValue } from '@powersync/service-sync-rules';
|
|
3
|
+
import { internal_routes } from '@powersync/service-types';
|
|
4
|
+
|
|
5
|
+
import * as api from '../../api/api-index.js';
|
|
6
|
+
import * as util from '../../util/util-index.js';
|
|
7
|
+
|
|
8
|
+
import { routeDefinition } from '../router.js';
|
|
9
|
+
import { PersistedSyncRulesContent } from '../../storage/BucketStorage.js';
|
|
10
|
+
import { authApi } from '../auth.js';
|
|
11
|
+
|
|
12
|
+
const demoCredentials = routeDefinition({
|
|
13
|
+
path: '/api/admin/v1/demo-credentials',
|
|
14
|
+
method: router.HTTPMethod.POST,
|
|
15
|
+
authorize: authApi,
|
|
16
|
+
validator: schema.createTsCodecValidator(internal_routes.DemoCredentialsRequest, {
|
|
17
|
+
allowAdditional: true
|
|
18
|
+
}),
|
|
19
|
+
handler: async (payload) => {
|
|
20
|
+
const connection = payload.context.system.config.connection;
|
|
21
|
+
if (connection == null || !connection.demo_database) {
|
|
22
|
+
return internal_routes.DemoCredentialsResponse.encode({});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const uri = util.buildDemoPgUri(connection);
|
|
26
|
+
return internal_routes.DemoCredentialsResponse.encode({
|
|
27
|
+
credentials: {
|
|
28
|
+
postgres_uri: uri
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const executeSql = routeDefinition({
|
|
35
|
+
path: '/api/admin/v1/execute-sql',
|
|
36
|
+
method: router.HTTPMethod.POST,
|
|
37
|
+
authorize: authApi,
|
|
38
|
+
validator: schema.createTsCodecValidator(internal_routes.ExecuteSqlRequest, { allowAdditional: true }),
|
|
39
|
+
handler: async (payload) => {
|
|
40
|
+
const connection = payload.context.system.config.connection;
|
|
41
|
+
if (connection == null || !connection.debug_api) {
|
|
42
|
+
return internal_routes.ExecuteSqlResponse.encode({
|
|
43
|
+
results: {
|
|
44
|
+
columns: [],
|
|
45
|
+
rows: []
|
|
46
|
+
},
|
|
47
|
+
success: false,
|
|
48
|
+
error: 'SQL querying is not enabled'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pool = payload.context.system.requirePgPool();
|
|
53
|
+
|
|
54
|
+
const { query, args } = payload.params.sql;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = await pool.query({
|
|
58
|
+
statement: query,
|
|
59
|
+
params: args.map(util.autoParameter)
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return internal_routes.ExecuteSqlResponse.encode({
|
|
63
|
+
success: true,
|
|
64
|
+
results: {
|
|
65
|
+
columns: result.columns.map((c) => c.name),
|
|
66
|
+
rows: result.rows.map((row) => {
|
|
67
|
+
return row.map((value) => mapColumnValue(toSyncRulesValue(value)));
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return internal_routes.ExecuteSqlResponse.encode({
|
|
73
|
+
results: {
|
|
74
|
+
columns: [],
|
|
75
|
+
rows: []
|
|
76
|
+
},
|
|
77
|
+
success: false,
|
|
78
|
+
error: e.message
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const diagnostics = routeDefinition({
|
|
85
|
+
path: '/api/admin/v1/diagnostics',
|
|
86
|
+
method: router.HTTPMethod.POST,
|
|
87
|
+
authorize: authApi,
|
|
88
|
+
validator: schema.createTsCodecValidator(internal_routes.DiagnosticsRequest, { allowAdditional: true }),
|
|
89
|
+
handler: async (payload) => {
|
|
90
|
+
const include_content = payload.params.sync_rules_content ?? false;
|
|
91
|
+
const system = payload.context.system;
|
|
92
|
+
|
|
93
|
+
const status = await api.getConnectionStatus(system);
|
|
94
|
+
if (status == null) {
|
|
95
|
+
return internal_routes.DiagnosticsResponse.encode({
|
|
96
|
+
connections: []
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { storage } = system;
|
|
101
|
+
const active = await storage.getActiveSyncRulesContent();
|
|
102
|
+
const next = await storage.getNextSyncRulesContent();
|
|
103
|
+
|
|
104
|
+
const active_status = await api.getSyncRulesStatus(active, system, {
|
|
105
|
+
include_content,
|
|
106
|
+
check_connection: status.connected,
|
|
107
|
+
live_status: true
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const next_status = await api.getSyncRulesStatus(next, system, {
|
|
111
|
+
include_content,
|
|
112
|
+
check_connection: status.connected,
|
|
113
|
+
live_status: true
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return internal_routes.DiagnosticsResponse.encode({
|
|
117
|
+
connections: [status],
|
|
118
|
+
active_sync_rules: active_status,
|
|
119
|
+
deploying_sync_rules: next_status
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export const getSchema = routeDefinition({
|
|
125
|
+
path: '/api/admin/v1/schema',
|
|
126
|
+
method: router.HTTPMethod.POST,
|
|
127
|
+
authorize: authApi,
|
|
128
|
+
validator: schema.createTsCodecValidator(internal_routes.GetSchemaRequest, { allowAdditional: true }),
|
|
129
|
+
handler: async (payload) => {
|
|
130
|
+
const system = payload.context.system;
|
|
131
|
+
|
|
132
|
+
return internal_routes.GetSchemaResponse.encode(await api.getConnectionsSchema(system));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const reprocess = routeDefinition({
|
|
137
|
+
path: '/api/admin/v1/reprocess',
|
|
138
|
+
method: router.HTTPMethod.POST,
|
|
139
|
+
authorize: authApi,
|
|
140
|
+
validator: schema.createTsCodecValidator(internal_routes.ReprocessRequest, { allowAdditional: true }),
|
|
141
|
+
handler: async (payload) => {
|
|
142
|
+
const system = payload.context.system;
|
|
143
|
+
|
|
144
|
+
const storage = system.storage;
|
|
145
|
+
const next = await storage.getNextSyncRules();
|
|
146
|
+
if (next != null) {
|
|
147
|
+
throw new Error(`Busy processing sync rules - cannot reprocess`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const active = await storage.getActiveSyncRules();
|
|
151
|
+
if (active == null) {
|
|
152
|
+
throw new errors.JourneyError({
|
|
153
|
+
status: 422,
|
|
154
|
+
code: 'NO_SYNC_RULES',
|
|
155
|
+
description: 'No active sync rules'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const new_rules = await storage.updateSyncRules({
|
|
160
|
+
content: active.sync_rules.content
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return internal_routes.ReprocessResponse.encode({
|
|
164
|
+
connections: [
|
|
165
|
+
{
|
|
166
|
+
tag: system.config.connection!.tag,
|
|
167
|
+
id: system.config.connection!.id,
|
|
168
|
+
slot_name: new_rules.slot_name
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const validate = routeDefinition({
|
|
176
|
+
path: '/api/admin/v1/validate',
|
|
177
|
+
method: router.HTTPMethod.POST,
|
|
178
|
+
authorize: authApi,
|
|
179
|
+
validator: schema.createTsCodecValidator(internal_routes.ValidateRequest, { allowAdditional: true }),
|
|
180
|
+
handler: async (payload) => {
|
|
181
|
+
const system = payload.context.system;
|
|
182
|
+
|
|
183
|
+
const content = payload.params.sync_rules;
|
|
184
|
+
|
|
185
|
+
const schemaData = await api.getConnectionsSchema(system);
|
|
186
|
+
const schema = new StaticSchema(schemaData.connections);
|
|
187
|
+
|
|
188
|
+
const sync_rules: PersistedSyncRulesContent = {
|
|
189
|
+
// Dummy values
|
|
190
|
+
id: 0,
|
|
191
|
+
slot_name: '',
|
|
192
|
+
|
|
193
|
+
parsed() {
|
|
194
|
+
return {
|
|
195
|
+
...this,
|
|
196
|
+
sync_rules: SqlSyncRules.fromYaml(content, { throwOnError: false, schema })
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
sync_rules_content: content,
|
|
200
|
+
async lock() {
|
|
201
|
+
throw new Error('Lock not implemented');
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const connectionStatus = await api.getConnectionStatus(system);
|
|
206
|
+
if (connectionStatus == null) {
|
|
207
|
+
return internal_routes.ValidateResponse.encode({
|
|
208
|
+
errors: [{ level: 'fatal', message: 'No connection configured' }],
|
|
209
|
+
connections: []
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const status = (await api.getSyncRulesStatus(sync_rules, system, {
|
|
214
|
+
include_content: false,
|
|
215
|
+
check_connection: connectionStatus?.connected,
|
|
216
|
+
live_status: false
|
|
217
|
+
}))!;
|
|
218
|
+
|
|
219
|
+
if (connectionStatus == null) {
|
|
220
|
+
status.errors.push({ level: 'fatal', message: 'No connection configured' });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return internal_routes.ValidateResponse.encode(status);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
function mapColumnValue(value: SqliteValue) {
|
|
228
|
+
if (typeof value == 'bigint') {
|
|
229
|
+
return Number(value);
|
|
230
|
+
} else if (isJsonValue(value)) {
|
|
231
|
+
return value;
|
|
232
|
+
} else {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const ADMIN_ROUTES = [demoCredentials, executeSql, diagnostics, getSchema, reprocess, validate];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as t from 'ts-codec';
|
|
2
|
+
import { router, schema } from '@powersync/lib-services-framework';
|
|
3
|
+
|
|
4
|
+
import * as util from '../../util/util-index.js';
|
|
5
|
+
import { authUser } from '../auth.js';
|
|
6
|
+
import { routeDefinition } from '../router.js';
|
|
7
|
+
|
|
8
|
+
const WriteCheckpointRequest = t.object({});
|
|
9
|
+
|
|
10
|
+
export const writeCheckpoint = routeDefinition({
|
|
11
|
+
path: '/write-checkpoint.json',
|
|
12
|
+
method: router.HTTPMethod.GET,
|
|
13
|
+
authorize: authUser,
|
|
14
|
+
validator: schema.createTsCodecValidator(WriteCheckpointRequest, { allowAdditional: true }),
|
|
15
|
+
handler: async (payload) => {
|
|
16
|
+
const system = payload.context.system;
|
|
17
|
+
const storage = system.storage;
|
|
18
|
+
|
|
19
|
+
const checkpoint = await util.getClientCheckpoint(system.requirePgPool(), storage);
|
|
20
|
+
return {
|
|
21
|
+
checkpoint
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const writeCheckpoint2 = routeDefinition({
|
|
27
|
+
path: '/write-checkpoint2.json',
|
|
28
|
+
method: router.HTTPMethod.GET,
|
|
29
|
+
authorize: authUser,
|
|
30
|
+
validator: schema.createTsCodecValidator(WriteCheckpointRequest, { allowAdditional: true }),
|
|
31
|
+
handler: async (payload) => {
|
|
32
|
+
const { user_id, system } = payload.context;
|
|
33
|
+
const storage = system.storage;
|
|
34
|
+
const write_checkpoint = await util.createWriteCheckpoint(system.requirePgPool(), storage, user_id!);
|
|
35
|
+
return {
|
|
36
|
+
write_checkpoint: String(write_checkpoint)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const CHECKPOINT_ROUTES = [writeCheckpoint, writeCheckpoint2];
|