@powersync/service-core 0.2.2 → 0.4.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 +31 -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/JwtPayload.d.ts +6 -2
- package/dist/auth/KeySpec.js.map +1 -1
- package/dist/auth/KeyStore.js +3 -9
- 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 +12 -4
- 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 +12 -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} +12 -12
- 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} +28 -19
- 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.d.ts +2 -2
- package/dist/sync/sync.js +5 -5
- 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 +13 -10
- package/src/api/diagnostics.ts +5 -5
- package/src/api/schema.ts +1 -1
- package/src/auth/JwtPayload.ts +6 -2
- package/src/auth/KeyStore.ts +3 -9
- package/src/entry/cli-entry.ts +3 -4
- package/src/entry/commands/config-command.ts +1 -1
- package/src/entry/commands/migrate-action.ts +14 -6
- 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 +15 -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} +13 -16
- package/src/routes/endpoints/sync-rules.ts +227 -0
- package/src/routes/endpoints/sync-stream.ts +98 -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 +11 -11
- 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 +181 -19
- 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/src/sync.test.ts +6 -5
- 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
- package/test/src/sql_functions.test.ts +0 -254
- package/test/src/sql_operators.test.ts +0 -132
- package/test/src/sync_rules.test.ts +0 -1053
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
1
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
3
|
-
import
|
|
4
|
-
import { logger } from '@journeyapps-platform/micro';
|
|
2
|
+
import { container, errors, logger } from '@powersync/lib-services-framework';
|
|
5
3
|
import { SqliteRow, SqlSyncRules, TablePattern, toSyncRulesRow } from '@powersync/service-sync-rules';
|
|
6
4
|
|
|
7
|
-
import * as storage from '
|
|
8
|
-
import * as util from '
|
|
5
|
+
import * as storage from '../storage/storage-index.js';
|
|
6
|
+
import * as util from '../util/util-index.js';
|
|
9
7
|
|
|
10
8
|
import { getPgOutputRelation, getRelId, PgRelation } from './PgRelation.js';
|
|
11
9
|
import { getReplicationIdentityColumns } from './util.js';
|
|
12
10
|
import { WalConnection } from './WalConnection.js';
|
|
13
|
-
import { Metrics } from '
|
|
11
|
+
import { Metrics } from '../metrics/Metrics.js';
|
|
14
12
|
|
|
15
13
|
export const ZERO_LSN = '00000000/00000000';
|
|
16
14
|
|
|
17
15
|
export interface WalStreamOptions {
|
|
18
16
|
connections: util.PgManager;
|
|
19
|
-
|
|
20
17
|
factory: storage.BucketStorageFactory;
|
|
21
18
|
storage: storage.SyncRulesBucketStorage;
|
|
22
19
|
abort_signal: AbortSignal;
|
|
@@ -160,7 +157,7 @@ export class WalStream {
|
|
|
160
157
|
]
|
|
161
158
|
});
|
|
162
159
|
if (rs.rows.length == 0) {
|
|
163
|
-
|
|
160
|
+
logger.info(`Skipping ${tablePattern.schema}.${name} - not part of ${this.publication_name} publication`);
|
|
164
161
|
continue;
|
|
165
162
|
}
|
|
166
163
|
|
|
@@ -190,7 +187,7 @@ export class WalStream {
|
|
|
190
187
|
|
|
191
188
|
const status = await this.storage.getStatus();
|
|
192
189
|
if (status.snapshot_done && status.checkpoint_lsn) {
|
|
193
|
-
|
|
190
|
+
logger.info(`${slotName} Initial replication already done`);
|
|
194
191
|
|
|
195
192
|
let last_error = null;
|
|
196
193
|
|
|
@@ -199,8 +196,8 @@ export class WalStream {
|
|
|
199
196
|
await touch();
|
|
200
197
|
|
|
201
198
|
if (i == 0) {
|
|
202
|
-
|
|
203
|
-
level:
|
|
199
|
+
container.reporter.captureException(last_error, {
|
|
200
|
+
level: errors.ErrorSeverity.ERROR,
|
|
204
201
|
metadata: {
|
|
205
202
|
replication_slot: slotName
|
|
206
203
|
}
|
|
@@ -222,11 +219,11 @@ export class WalStream {
|
|
|
222
219
|
]
|
|
223
220
|
});
|
|
224
221
|
// Success
|
|
225
|
-
|
|
222
|
+
logger.info(`Slot ${slotName} appears healthy`);
|
|
226
223
|
return { needsInitialSync: false };
|
|
227
224
|
} catch (e) {
|
|
228
225
|
last_error = e;
|
|
229
|
-
|
|
226
|
+
logger.warn(`${slotName} Replication slot error`, e);
|
|
230
227
|
|
|
231
228
|
if (this.stopped) {
|
|
232
229
|
throw e;
|
|
@@ -240,8 +237,8 @@ export class WalStream {
|
|
|
240
237
|
/replication slot.*does not exist/.test(e.message) ||
|
|
241
238
|
/publication.*does not exist/.test(e.message)
|
|
242
239
|
) {
|
|
243
|
-
|
|
244
|
-
level:
|
|
240
|
+
container.reporter.captureException(e, {
|
|
241
|
+
level: errors.ErrorSeverity.WARNING,
|
|
245
242
|
metadata: {
|
|
246
243
|
try_index: i,
|
|
247
244
|
replication_slot: slotName
|
|
@@ -253,7 +250,7 @@ export class WalStream {
|
|
|
253
250
|
// Sample: publication "powersync" does not exist
|
|
254
251
|
// Happens when publication deleted or never created.
|
|
255
252
|
// Slot must be re-created in this case.
|
|
256
|
-
|
|
253
|
+
logger.info(`${slotName} does not exist anymore, will create new slot`);
|
|
257
254
|
|
|
258
255
|
throw new MissingReplicationSlotError(`Replication slot ${slotName} does not exist anymore`);
|
|
259
256
|
}
|
|
@@ -316,7 +313,7 @@ WHERE oid = $1::regclass`,
|
|
|
316
313
|
// with streaming replication.
|
|
317
314
|
const lsn = pgwire.lsnMakeComparable(row[1]);
|
|
318
315
|
const snapshot = row[2];
|
|
319
|
-
|
|
316
|
+
logger.info(`Created replication slot ${slotName} at ${lsn} with snapshot ${snapshot}`);
|
|
320
317
|
|
|
321
318
|
// https://stackoverflow.com/questions/70160769/postgres-logical-replication-starting-from-given-lsn
|
|
322
319
|
await db.query('BEGIN');
|
|
@@ -338,9 +335,9 @@ WHERE oid = $1::regclass`,
|
|
|
338
335
|
// On Supabase, the default is 2 minutes.
|
|
339
336
|
await db.query(`set local statement_timeout = 0`);
|
|
340
337
|
|
|
341
|
-
|
|
338
|
+
logger.info(`${slotName} Starting initial replication`);
|
|
342
339
|
await this.initialReplication(db, lsn);
|
|
343
|
-
|
|
340
|
+
logger.info(`${slotName} Initial replication done`);
|
|
344
341
|
await db.query('COMMIT');
|
|
345
342
|
} catch (e) {
|
|
346
343
|
await db.query('ROLLBACK');
|
|
@@ -371,7 +368,7 @@ WHERE oid = $1::regclass`,
|
|
|
371
368
|
}
|
|
372
369
|
|
|
373
370
|
private async snapshotTable(batch: storage.BucketStorageBatch, db: pgwire.PgConnection, table: storage.SourceTable) {
|
|
374
|
-
|
|
371
|
+
logger.info(`${this.slot_name} Replicating ${table.qualifiedName}`);
|
|
375
372
|
const estimatedCount = await this.estimatedCount(db, table);
|
|
376
373
|
let at = 0;
|
|
377
374
|
let lastLogIndex = 0;
|
|
@@ -397,7 +394,7 @@ WHERE oid = $1::regclass`,
|
|
|
397
394
|
return q;
|
|
398
395
|
});
|
|
399
396
|
if (rows.length > 0 && at - lastLogIndex >= 5000) {
|
|
400
|
-
|
|
397
|
+
logger.info(`${this.slot_name} Replicating ${table.qualifiedName} ${at}/${estimatedCount}`);
|
|
401
398
|
lastLogIndex = at;
|
|
402
399
|
}
|
|
403
400
|
if (this.abort_signal.aborted) {
|
|
@@ -586,7 +583,7 @@ WHERE oid = $1::regclass`,
|
|
|
586
583
|
await this.ack(msg.lsn!, replicationStream);
|
|
587
584
|
} else {
|
|
588
585
|
if (count % 100 == 0) {
|
|
589
|
-
|
|
586
|
+
logger.info(`${this.slot_name} replicating op ${count} ${msg.lsn}`);
|
|
590
587
|
}
|
|
591
588
|
|
|
592
589
|
count += 1;
|
|
@@ -619,11 +616,9 @@ WHERE oid = $1::regclass`,
|
|
|
619
616
|
}
|
|
620
617
|
}
|
|
621
618
|
|
|
622
|
-
|
|
623
|
-
// FIXME: The probe does not actually check the timestamp on this.
|
|
624
|
-
// FIXME: We need a timeout of around 5+ minutes if we do start checking the timestamp,
|
|
625
|
-
// or reduce PING_INTERVAL.
|
|
626
|
-
|
|
627
|
-
// FIXME: The above probe touches the wrong file
|
|
628
|
-
await fs.writeFile('.probes/poll', `${Date.now()}`);
|
|
619
|
+
async function touch() {
|
|
620
|
+
// FIXME: The hosted Kubernetes probe does not actually check the timestamp on this.
|
|
621
|
+
// FIXME: We need a timeout of around 5+ minutes in Kubernetes if we do start checking the timestamp,
|
|
622
|
+
// or reduce PING_INTERVAL here.
|
|
623
|
+
return container.probes.touch();
|
|
629
624
|
}
|
|
@@ -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];
|