@powersync/service-core 0.0.0-dev-20240718134716 → 0.0.0-dev-20240918082156
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 +89 -6
- package/dist/api/RouteAPI.d.ts +68 -0
- package/dist/api/RouteAPI.js +2 -0
- package/dist/api/RouteAPI.js.map +1 -0
- package/dist/api/api-index.d.ts +1 -0
- package/dist/api/api-index.js +1 -0
- package/dist/api/api-index.js.map +1 -1
- package/dist/api/diagnostics.d.ts +4 -4
- package/dist/api/diagnostics.js +11 -65
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.d.ts +3 -5
- package/dist/api/schema.js +9 -79
- package/dist/api/schema.js.map +1 -1
- package/dist/auth/KeyStore.d.ts +7 -4
- package/dist/auth/KeyStore.js +1 -1
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/auth-index.d.ts +0 -1
- package/dist/auth/auth-index.js +0 -1
- package/dist/auth/auth-index.js.map +1 -1
- package/dist/entry/cli-entry.js +4 -2
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/compact-action.d.ts +2 -0
- package/dist/entry/commands/compact-action.js +52 -0
- package/dist/entry/commands/compact-action.js.map +1 -0
- package/dist/entry/commands/migrate-action.js +4 -5
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/teardown-action.js +2 -2
- package/dist/entry/commands/teardown-action.js.map +1 -1
- package/dist/entry/entry-index.d.ts +1 -0
- package/dist/entry/entry-index.js +1 -0
- package/dist/entry/entry-index.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/metrics/Metrics.d.ts +6 -5
- package/dist/metrics/Metrics.js +53 -10
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/migrations/db/migrations/1684951997326-init.d.ts +2 -2
- package/dist/migrations/db/migrations/1684951997326-init.js +4 -2
- package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +2 -2
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +4 -2
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +2 -2
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +4 -2
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
- package/dist/migrations/migrations.d.ts +8 -0
- package/dist/migrations/migrations.js +19 -7
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/modules/AbstractModule.d.ts +26 -0
- package/dist/modules/AbstractModule.js +11 -0
- package/dist/modules/AbstractModule.js.map +1 -0
- package/dist/modules/ModuleManager.d.ts +11 -0
- package/dist/modules/ModuleManager.js +32 -0
- package/dist/modules/ModuleManager.js.map +1 -0
- package/dist/modules/modules-index.d.ts +2 -0
- package/dist/modules/modules-index.js +3 -0
- package/dist/modules/modules-index.js.map +1 -0
- package/dist/replication/AbstractReplicationJob.d.ts +38 -0
- package/dist/replication/AbstractReplicationJob.js +51 -0
- package/dist/replication/AbstractReplicationJob.js.map +1 -0
- package/dist/replication/AbstractReplicator.d.ts +53 -0
- package/dist/replication/AbstractReplicator.js +187 -0
- package/dist/replication/AbstractReplicator.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.d.ts +0 -9
- package/dist/replication/ErrorRateLimiter.js +1 -42
- package/dist/replication/ErrorRateLimiter.js.map +1 -1
- package/dist/replication/ReplicationEngine.d.ts +18 -0
- package/dist/replication/ReplicationEngine.js +41 -0
- package/dist/replication/ReplicationEngine.js.map +1 -0
- package/dist/replication/ReplicationModule.d.ts +39 -0
- package/dist/replication/ReplicationModule.js +65 -0
- package/dist/replication/ReplicationModule.js.map +1 -0
- package/dist/replication/replication-index.d.ts +4 -6
- package/dist/replication/replication-index.js +4 -6
- package/dist/replication/replication-index.js.map +1 -1
- package/dist/routes/RouterEngine.d.ts +42 -0
- package/dist/routes/RouterEngine.js +80 -0
- package/dist/routes/RouterEngine.js.map +1 -0
- package/dist/routes/auth.d.ts +2 -2
- package/dist/routes/auth.js +11 -11
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +737 -0
- package/dist/routes/configure-fastify.js +57 -0
- package/dist/routes/configure-fastify.js.map +1 -0
- package/dist/routes/configure-rsocket.d.ts +13 -0
- package/dist/routes/configure-rsocket.js +47 -0
- package/dist/routes/configure-rsocket.js.map +1 -0
- package/dist/routes/endpoints/admin.d.ts +0 -34
- package/dist/routes/endpoints/admin.js +48 -89
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.d.ts +56 -16
- package/dist/routes/endpoints/checkpointing.js +33 -12
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/route-endpoints-index.d.ts +0 -1
- package/dist/routes/endpoints/route-endpoints-index.js +0 -1
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +46 -39
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.d.ts +1 -1
- package/dist/routes/endpoints/sync-rules.js +32 -23
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.d.ts +10 -0
- package/dist/routes/endpoints/sync-stream.js +17 -13
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/route-register.d.ts +1 -1
- package/dist/routes/route-register.js +1 -1
- package/dist/routes/route-register.js.map +1 -1
- package/dist/routes/router-socket.d.ts +5 -4
- package/dist/routes/router-socket.js +2 -1
- package/dist/routes/router-socket.js.map +1 -1
- package/dist/routes/router.d.ts +7 -2
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +3 -0
- package/dist/routes/routes-index.js +3 -0
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/runner/teardown.js +47 -76
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +61 -20
- package/dist/storage/BucketStorage.js +0 -10
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +4 -4
- package/dist/storage/MongoBucketStorage.js +19 -24
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/SourceEntity.d.ts +20 -0
- package/dist/storage/SourceEntity.js +2 -0
- package/dist/storage/SourceEntity.js.map +1 -0
- package/dist/storage/SourceTable.d.ts +4 -5
- package/dist/storage/SourceTable.js +3 -4
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/StorageEngine.d.ts +24 -0
- package/dist/storage/StorageEngine.js +43 -0
- package/dist/storage/StorageEngine.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +21 -0
- package/dist/storage/StorageProvider.js +2 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/mongo/MongoBucketBatch.d.ts +1 -1
- package/dist/storage/mongo/MongoBucketBatch.js +6 -7
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
- package/dist/storage/mongo/MongoCompactor.js +293 -0
- package/dist/storage/mongo/MongoCompactor.js.map +1 -0
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +2 -2
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +2 -2
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/mongo/MongoStorageProvider.d.ts +5 -0
- package/dist/storage/mongo/MongoStorageProvider.js +26 -0
- package/dist/storage/mongo/MongoStorageProvider.js.map +1 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +9 -7
- package/dist/storage/mongo/MongoSyncBucketStorage.js +43 -28
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoSyncRulesLock.js +1 -1
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/mongo/OperationBatch.d.ts +7 -3
- package/dist/storage/mongo/OperationBatch.js +16 -7
- package/dist/storage/mongo/OperationBatch.js.map +1 -1
- package/dist/storage/mongo/PersistedBatch.d.ts +3 -3
- package/dist/storage/mongo/PersistedBatch.js +2 -2
- package/dist/storage/mongo/PersistedBatch.js.map +1 -1
- package/dist/storage/mongo/models.d.ts +17 -7
- package/dist/storage/mongo/models.js.map +1 -1
- package/dist/storage/mongo/util.d.ts +14 -0
- package/dist/storage/mongo/util.js +70 -0
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/storage/storage-index.d.ts +5 -2
- package/dist/storage/storage-index.js +5 -2
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/RequestTracker.js +2 -3
- package/dist/sync/RequestTracker.js.map +1 -1
- package/dist/sync/sync-index.d.ts +1 -0
- package/dist/sync/sync-index.js +1 -0
- package/dist/sync/sync-index.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -1
- package/dist/sync/sync.js +56 -17
- package/dist/sync/sync.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +37 -0
- package/dist/system/ServiceContext.js +48 -0
- package/dist/system/ServiceContext.js.map +1 -0
- package/dist/system/system-index.d.ts +1 -1
- package/dist/system/system-index.js +1 -1
- package/dist/system/system-index.js.map +1 -1
- package/dist/util/config/collectors/config-collector.d.ts +12 -0
- package/dist/util/config/collectors/config-collector.js +43 -0
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.d.ts +10 -29
- package/dist/util/config/compound-config-collector.js +28 -84
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/sync-rules-provider.d.ts +9 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js +15 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -0
- package/dist/util/config/types.d.ts +6 -4
- package/dist/util/config/types.js.map +1 -1
- package/dist/util/config.d.ts +3 -4
- package/dist/util/config.js +5 -20
- package/dist/util/config.js.map +1 -1
- package/dist/util/protocol-types.d.ts +4 -0
- package/dist/util/protocol-types.js +5 -1
- package/dist/util/protocol-types.js.map +1 -1
- package/dist/util/util-index.d.ts +3 -6
- package/dist/util/util-index.js +3 -6
- package/dist/util/util-index.js.map +1 -1
- package/dist/util/utils.d.ts +10 -6
- package/dist/util/utils.js +45 -25
- package/dist/util/utils.js.map +1 -1
- package/package.json +7 -7
- package/src/api/RouteAPI.ts +78 -0
- package/src/api/api-index.ts +1 -0
- package/src/api/diagnostics.ts +16 -71
- package/src/api/schema.ts +13 -89
- package/src/auth/KeyStore.ts +9 -6
- package/src/auth/auth-index.ts +0 -1
- package/src/entry/cli-entry.ts +4 -2
- package/src/entry/commands/compact-action.ts +57 -0
- package/src/entry/commands/migrate-action.ts +5 -8
- package/src/entry/commands/teardown-action.ts +2 -2
- package/src/entry/entry-index.ts +1 -0
- package/src/index.ts +5 -2
- package/src/metrics/Metrics.ts +70 -15
- package/src/migrations/db/migrations/1684951997326-init.ts +9 -4
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -4
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +6 -4
- package/src/migrations/migrations.ts +24 -8
- package/src/modules/AbstractModule.ts +37 -0
- package/src/modules/ModuleManager.ts +34 -0
- package/src/modules/modules-index.ts +2 -0
- package/src/replication/AbstractReplicationJob.ts +79 -0
- package/src/replication/AbstractReplicator.ts +227 -0
- package/src/replication/ErrorRateLimiter.ts +0 -44
- package/src/replication/ReplicationEngine.ts +43 -0
- package/src/replication/ReplicationModule.ts +101 -0
- package/src/replication/replication-index.ts +4 -6
- package/src/routes/RouterEngine.ts +120 -0
- package/src/routes/auth.ts +21 -12
- package/src/routes/configure-fastify.ts +101 -0
- package/src/routes/configure-rsocket.ts +60 -0
- package/src/routes/endpoints/admin.ts +74 -100
- package/src/routes/endpoints/checkpointing.ts +46 -12
- package/src/routes/endpoints/route-endpoints-index.ts +0 -1
- package/src/routes/endpoints/socket-route.ts +50 -42
- package/src/routes/endpoints/sync-rules.ts +41 -25
- package/src/routes/endpoints/sync-stream.ts +17 -13
- package/src/routes/route-register.ts +2 -2
- package/src/routes/router-socket.ts +6 -5
- package/src/routes/router.ts +7 -2
- package/src/routes/routes-index.ts +3 -0
- package/src/runner/teardown.ts +50 -88
- package/src/storage/BucketStorage.ts +74 -26
- package/src/storage/MongoBucketStorage.ts +23 -26
- package/src/storage/SourceEntity.ts +22 -0
- package/src/storage/SourceTable.ts +4 -6
- package/src/storage/StorageEngine.ts +55 -0
- package/src/storage/StorageProvider.ts +27 -0
- package/src/storage/mongo/MongoBucketBatch.ts +8 -8
- package/src/storage/mongo/MongoCompactor.ts +372 -0
- package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +3 -3
- package/src/storage/mongo/MongoStorageProvider.ts +31 -0
- package/src/storage/mongo/MongoSyncBucketStorage.ts +64 -34
- package/src/storage/mongo/MongoSyncRulesLock.ts +1 -1
- package/src/storage/mongo/OperationBatch.ts +18 -11
- package/src/storage/mongo/PersistedBatch.ts +6 -5
- package/src/storage/mongo/models.ts +17 -7
- package/src/storage/mongo/util.ts +71 -1
- package/src/storage/storage-index.ts +5 -2
- package/src/sync/RequestTracker.ts +3 -3
- package/src/sync/sync-index.ts +1 -0
- package/src/sync/sync.ts +66 -17
- package/src/system/ServiceContext.ts +68 -0
- package/src/system/system-index.ts +1 -1
- package/src/util/config/collectors/config-collector.ts +48 -0
- package/src/util/config/compound-config-collector.ts +45 -110
- package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
- package/src/util/config/types.ts +6 -5
- package/src/util/config.ts +6 -23
- package/src/util/protocol-types.ts +6 -1
- package/src/util/util-index.ts +3 -6
- package/src/util/utils.ts +55 -39
- package/test/src/__snapshots__/sync.test.ts.snap +90 -5
- package/test/src/auth.test.ts +7 -7
- package/test/src/broadcast_iterable.test.ts +1 -1
- package/test/src/bucket_validation.test.ts +142 -0
- package/test/src/bucket_validation.ts +116 -0
- package/test/src/checksum_cache.test.ts +3 -3
- package/test/src/compacting.test.ts +216 -0
- package/test/src/data_storage.test.ts +275 -204
- package/test/src/env.ts +1 -3
- package/test/src/merge_iterable.test.ts +1 -6
- package/test/src/setup.ts +1 -1
- package/test/src/stream_utils.ts +42 -0
- package/test/src/sync.test.ts +209 -48
- package/test/src/util.ts +110 -55
- package/test/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/auth/SupabaseKeyCollector.d.ts +0 -22
- package/dist/auth/SupabaseKeyCollector.js +0 -61
- package/dist/auth/SupabaseKeyCollector.js.map +0 -1
- package/dist/replication/PgRelation.d.ts +0 -16
- package/dist/replication/PgRelation.js +0 -26
- package/dist/replication/PgRelation.js.map +0 -1
- package/dist/replication/WalConnection.d.ts +0 -34
- package/dist/replication/WalConnection.js +0 -190
- package/dist/replication/WalConnection.js.map +0 -1
- package/dist/replication/WalStream.d.ts +0 -57
- package/dist/replication/WalStream.js +0 -517
- package/dist/replication/WalStream.js.map +0 -1
- package/dist/replication/WalStreamManager.d.ts +0 -30
- package/dist/replication/WalStreamManager.js +0 -198
- package/dist/replication/WalStreamManager.js.map +0 -1
- package/dist/replication/WalStreamRunner.d.ts +0 -38
- package/dist/replication/WalStreamRunner.js +0 -155
- package/dist/replication/WalStreamRunner.js.map +0 -1
- package/dist/replication/util.d.ts +0 -9
- package/dist/replication/util.js +0 -62
- package/dist/replication/util.js.map +0 -1
- package/dist/routes/endpoints/dev.d.ts +0 -312
- package/dist/routes/endpoints/dev.js +0 -172
- package/dist/routes/endpoints/dev.js.map +0 -1
- package/dist/system/CorePowerSyncSystem.d.ts +0 -23
- package/dist/system/CorePowerSyncSystem.js +0 -52
- package/dist/system/CorePowerSyncSystem.js.map +0 -1
- package/dist/util/PgManager.d.ts +0 -24
- package/dist/util/PgManager.js +0 -55
- package/dist/util/PgManager.js.map +0 -1
- package/dist/util/migration_lib.d.ts +0 -11
- package/dist/util/migration_lib.js +0 -64
- package/dist/util/migration_lib.js.map +0 -1
- package/dist/util/pgwire_utils.d.ts +0 -24
- package/dist/util/pgwire_utils.js +0 -117
- package/dist/util/pgwire_utils.js.map +0 -1
- package/dist/util/populate_test_data.d.ts +0 -8
- package/dist/util/populate_test_data.js +0 -65
- package/dist/util/populate_test_data.js.map +0 -1
- package/src/auth/SupabaseKeyCollector.ts +0 -67
- package/src/replication/PgRelation.ts +0 -42
- package/src/replication/WalConnection.ts +0 -227
- package/src/replication/WalStream.ts +0 -628
- package/src/replication/WalStreamManager.ts +0 -213
- package/src/replication/WalStreamRunner.ts +0 -180
- package/src/replication/util.ts +0 -76
- package/src/routes/endpoints/dev.ts +0 -199
- package/src/system/CorePowerSyncSystem.ts +0 -64
- package/src/util/PgManager.ts +0 -64
- package/src/util/migration_lib.ts +0 -79
- package/src/util/pgwire_utils.ts +0 -139
- package/src/util/populate_test_data.ts +0 -78
- package/test/src/__snapshots__/pg_test.test.ts.snap +0 -256
- package/test/src/large_batch.test.ts +0 -194
- package/test/src/pg_test.test.ts +0 -450
- package/test/src/schema_changes.test.ts +0 -545
- package/test/src/slow_tests.test.ts +0 -296
- package/test/src/validation.test.ts +0 -63
- package/test/src/wal_stream.test.ts +0 -314
- package/test/src/wal_stream_utils.ts +0 -147
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import * as bson from 'bson';
|
|
2
1
|
import { ToastableSqliteRow } from '@powersync/service-sync-rules';
|
|
2
|
+
import * as bson from 'bson';
|
|
3
3
|
|
|
4
|
-
import * as util from '../../util/util-index.js';
|
|
5
4
|
import { SaveOptions } from '../BucketStorage.js';
|
|
5
|
+
import { isUUID } from './util.js';
|
|
6
|
+
import { ReplicaId } from './models.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Maximum number of operations in a batch.
|
|
@@ -63,18 +64,15 @@ export class OperationBatch {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
export class RecordOperation {
|
|
66
|
-
public readonly afterId:
|
|
67
|
-
public readonly beforeId:
|
|
67
|
+
public readonly afterId: ReplicaId | null;
|
|
68
|
+
public readonly beforeId: ReplicaId;
|
|
68
69
|
public readonly internalBeforeKey: string;
|
|
69
70
|
public readonly internalAfterKey: string | null;
|
|
70
71
|
public readonly estimatedSize: number;
|
|
71
72
|
|
|
72
73
|
constructor(public readonly record: SaveOptions) {
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const beforeId = record.before
|
|
76
|
-
? util.getUuidReplicaIdentityBson(record.before, record.sourceTable.replicaIdColumns!)
|
|
77
|
-
: afterId!;
|
|
74
|
+
const afterId = record.afterReplicaId ?? null;
|
|
75
|
+
const beforeId = record.beforeReplicaId ?? record.afterReplicaId;
|
|
78
76
|
this.afterId = afterId;
|
|
79
77
|
this.beforeId = beforeId;
|
|
80
78
|
this.internalBeforeKey = cacheKey(record.sourceTable.id, beforeId);
|
|
@@ -84,8 +82,17 @@ export class RecordOperation {
|
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
/**
|
|
86
|
+
* In-memory cache key - must not be persisted.
|
|
87
|
+
*/
|
|
88
|
+
export function cacheKey(table: bson.ObjectId, id: ReplicaId) {
|
|
89
|
+
if (isUUID(id)) {
|
|
90
|
+
return `${table.toHexString()}.${id.toHexString()}`;
|
|
91
|
+
} else if (typeof id == 'string') {
|
|
92
|
+
return `${table.toHexString()}.${id}`;
|
|
93
|
+
} else {
|
|
94
|
+
return `${table.toHexString()}.${(bson.serialize({ id: id }) as Buffer).toString('base64')}`;
|
|
95
|
+
}
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
/**
|
|
@@ -13,9 +13,10 @@ import {
|
|
|
13
13
|
BucketParameterDocument,
|
|
14
14
|
CurrentBucket,
|
|
15
15
|
CurrentDataDocument,
|
|
16
|
-
SourceKey
|
|
16
|
+
SourceKey,
|
|
17
|
+
ReplicaId
|
|
17
18
|
} from './models.js';
|
|
18
|
-
import { serializeLookup } from './util.js';
|
|
19
|
+
import { replicaIdToSubkey, serializeLookup } from './util.js';
|
|
19
20
|
import { logger } from '@powersync/lib-services-framework';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -59,7 +60,7 @@ export class PersistedBatch {
|
|
|
59
60
|
|
|
60
61
|
saveBucketData(options: {
|
|
61
62
|
op_seq: MongoIdSequence;
|
|
62
|
-
sourceKey:
|
|
63
|
+
sourceKey: ReplicaId;
|
|
63
64
|
table: SourceTable;
|
|
64
65
|
evaluated: EvaluatedRow[];
|
|
65
66
|
before_buckets: CurrentBucket[];
|
|
@@ -70,7 +71,7 @@ export class PersistedBatch {
|
|
|
70
71
|
remaining_buckets.set(key, b);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
const dchecksum = util.hashDelete(
|
|
74
|
+
const dchecksum = util.hashDelete(replicaIdToSubkey(options.table.id, options.sourceKey));
|
|
74
75
|
|
|
75
76
|
for (let k of options.evaluated) {
|
|
76
77
|
const key = currentBucketKey(k);
|
|
@@ -134,7 +135,7 @@ export class PersistedBatch {
|
|
|
134
135
|
|
|
135
136
|
saveParameterData(data: {
|
|
136
137
|
op_seq: MongoIdSequence;
|
|
137
|
-
sourceKey:
|
|
138
|
+
sourceKey: ReplicaId;
|
|
138
139
|
sourceTable: SourceTable;
|
|
139
140
|
evaluated: EvaluatedParameters[];
|
|
140
141
|
existing_lookups: bson.Binary[];
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import * as bson from 'bson';
|
|
2
2
|
import { SqliteJsonValue } from '@powersync/service-sync-rules';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Replica id uniquely identifying a row on the source database.
|
|
6
|
+
*
|
|
7
|
+
* Can be any value serializable to BSON.
|
|
8
|
+
*
|
|
9
|
+
* If the value is an entire document, the data serialized to a v5 UUID may be a good choice here.
|
|
10
|
+
*/
|
|
11
|
+
export type ReplicaId = bson.UUID | bson.Document | any;
|
|
12
|
+
|
|
4
13
|
export interface SourceKey {
|
|
5
14
|
/** group_id */
|
|
6
15
|
g: number;
|
|
7
16
|
/** source table id */
|
|
8
17
|
t: bson.ObjectId;
|
|
9
18
|
/** source key */
|
|
10
|
-
k:
|
|
19
|
+
k: ReplicaId;
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
export interface BucketDataKey {
|
|
@@ -42,12 +51,13 @@ export interface BucketParameterDocument {
|
|
|
42
51
|
export interface BucketDataDocument {
|
|
43
52
|
_id: BucketDataKey;
|
|
44
53
|
op: OpType;
|
|
45
|
-
source_table
|
|
46
|
-
source_key
|
|
47
|
-
table
|
|
48
|
-
row_id
|
|
54
|
+
source_table?: bson.ObjectId;
|
|
55
|
+
source_key?: ReplicaId;
|
|
56
|
+
table?: string;
|
|
57
|
+
row_id?: string;
|
|
49
58
|
checksum: number;
|
|
50
59
|
data: string | null;
|
|
60
|
+
target_op?: bigint | null;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR';
|
|
@@ -56,11 +66,11 @@ export interface SourceTableDocument {
|
|
|
56
66
|
_id: bson.ObjectId;
|
|
57
67
|
group_id: number;
|
|
58
68
|
connection_id: number;
|
|
59
|
-
relation_id: number | undefined;
|
|
69
|
+
relation_id: number | string | undefined;
|
|
60
70
|
schema_name: string;
|
|
61
71
|
table_name: string;
|
|
62
72
|
replica_id_columns: string[] | null;
|
|
63
|
-
replica_id_columns2: { name: string; type_oid
|
|
73
|
+
replica_id_columns2: { name: string; type_oid?: number; type?: string }[] | undefined;
|
|
64
74
|
snapshot_done: boolean | undefined;
|
|
65
75
|
}
|
|
66
76
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { SqliteJsonValue } from '@powersync/service-sync-rules';
|
|
2
2
|
import * as bson from 'bson';
|
|
3
|
-
import * as mongo from 'mongodb';
|
|
4
3
|
import * as crypto from 'crypto';
|
|
4
|
+
import * as mongo from 'mongodb';
|
|
5
|
+
import * as uuid from 'uuid';
|
|
6
|
+
import { OplogEntry } from '../../util/protocol-types.js';
|
|
7
|
+
import { ID_NAMESPACE, timestampToOpId } from '../../util/utils.js';
|
|
8
|
+
import { BucketDataDocument, ReplicaId } from './models.js';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
|
|
@@ -86,3 +90,69 @@ export const BSON_DESERIALIZE_OPTIONS: bson.DeserializeOptions = {
|
|
|
86
90
|
// use bigint instead of Long
|
|
87
91
|
useBigInt64: true
|
|
88
92
|
};
|
|
93
|
+
|
|
94
|
+
export function mapOpEntry(row: BucketDataDocument): OplogEntry {
|
|
95
|
+
if (row.op == 'PUT' || row.op == 'REMOVE') {
|
|
96
|
+
return {
|
|
97
|
+
op_id: timestampToOpId(row._id.o),
|
|
98
|
+
op: row.op,
|
|
99
|
+
object_type: row.table,
|
|
100
|
+
object_id: row.row_id,
|
|
101
|
+
checksum: Number(row.checksum),
|
|
102
|
+
subkey: replicaIdToSubkey(row.source_table!, row.source_key!),
|
|
103
|
+
data: row.data
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
// MOVE, CLEAR
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
op_id: timestampToOpId(row._id.o),
|
|
110
|
+
op: row.op,
|
|
111
|
+
checksum: Number(row.checksum)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns true if two ReplicaId values are the same (serializes to the same BSON value).
|
|
118
|
+
*/
|
|
119
|
+
export function replicaIdEquals(a: ReplicaId, b: ReplicaId) {
|
|
120
|
+
if (a === b) {
|
|
121
|
+
return true;
|
|
122
|
+
} else if (typeof a == 'string' && typeof b == 'string') {
|
|
123
|
+
return a == b;
|
|
124
|
+
} else if (isUUID(a) && isUUID(b)) {
|
|
125
|
+
return a.equals(b);
|
|
126
|
+
} else if (a == null && b == null) {
|
|
127
|
+
return true;
|
|
128
|
+
} else if (a != null || b != null) {
|
|
129
|
+
return false;
|
|
130
|
+
} else {
|
|
131
|
+
// There are many possible primitive values, this covers them all
|
|
132
|
+
return (bson.serialize({ id: a }) as Buffer).equals(bson.serialize({ id: b }));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function replicaIdToSubkey(table: bson.ObjectId, id: ReplicaId): string {
|
|
137
|
+
if (isUUID(id)) {
|
|
138
|
+
// Special case for UUID for backwards-compatiblity
|
|
139
|
+
return `${table.toHexString()}/${id.toHexString()}`;
|
|
140
|
+
} else {
|
|
141
|
+
// Hashed UUID from the table and id
|
|
142
|
+
const repr = bson.serialize({ table, id });
|
|
143
|
+
return uuid.v5(repr, ID_NAMESPACE);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* True if this is a bson.UUID.
|
|
149
|
+
*
|
|
150
|
+
* Works even with multiple copies of the bson package.
|
|
151
|
+
*/
|
|
152
|
+
export function isUUID(value: any): value is bson.UUID {
|
|
153
|
+
if (value == null || typeof value != 'object') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const uuid = value as bson.UUID;
|
|
157
|
+
return uuid._bsontype == 'Binary' && uuid.sub_type == bson.Binary.SUBTYPE_UUID;
|
|
158
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from './SourceTable.js';
|
|
2
|
-
export * from './MongoBucketStorage.js';
|
|
3
1
|
export * from './BucketStorage.js';
|
|
2
|
+
export * from './MongoBucketStorage.js';
|
|
3
|
+
export * from './SourceEntity.js';
|
|
4
|
+
export * from './SourceTable.js';
|
|
5
|
+
export * from './StorageEngine.js';
|
|
4
6
|
|
|
5
7
|
export * from './mongo/db.js';
|
|
6
8
|
export * from './mongo/models.js';
|
|
@@ -8,6 +10,7 @@ export * from './mongo/MongoBucketBatch.js';
|
|
|
8
10
|
export * from './mongo/MongoIdSequence.js';
|
|
9
11
|
export * from './mongo/MongoPersistedSyncRules.js';
|
|
10
12
|
export * from './mongo/MongoPersistedSyncRulesContent.js';
|
|
13
|
+
export * from './mongo/MongoStorageProvider.js';
|
|
11
14
|
export * from './mongo/MongoSyncBucketStorage.js';
|
|
12
15
|
export * from './mongo/MongoSyncRulesLock.js';
|
|
13
16
|
export * from './mongo/OperationBatch.js';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { container } from '@powersync/lib-services-framework';
|
|
2
1
|
import { Metrics } from '../metrics/Metrics.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -10,12 +9,13 @@ export class RequestTracker {
|
|
|
10
9
|
|
|
11
10
|
addOperationsSynced(operations: number) {
|
|
12
11
|
this.operationsSynced += operations;
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
Metrics.getInstance().operations_synced_total.add(operations);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
addDataSynced(bytes: number) {
|
|
17
17
|
this.dataSyncedBytes += bytes;
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Metrics.getInstance().data_synced_bytes.add(bytes);
|
|
20
20
|
}
|
|
21
21
|
}
|
package/src/sync/sync-index.ts
CHANGED
package/src/sync/sync.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { JSONBig, JsonContainer } from '@powersync/service-jsonbig';
|
|
2
2
|
import { RequestParameters } from '@powersync/service-sync-rules';
|
|
3
|
-
import { Semaphore } from 'async-mutex';
|
|
3
|
+
import { Semaphore, withTimeout } from 'async-mutex';
|
|
4
|
+
|
|
4
5
|
import { AbortError } from 'ix/aborterror.js';
|
|
5
6
|
|
|
6
7
|
import * as auth from '../auth/auth-index.js';
|
|
@@ -9,20 +10,33 @@ import * as util from '../util/util-index.js';
|
|
|
9
10
|
|
|
10
11
|
import { logger } from '@powersync/lib-services-framework';
|
|
11
12
|
import { mergeAsyncIterables } from './merge.js';
|
|
12
|
-
import { TokenStreamOptions, tokenStream } from './util.js';
|
|
13
13
|
import { RequestTracker } from './RequestTracker.js';
|
|
14
|
+
import { TokenStreamOptions, tokenStream } from './util.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Maximum number of connections actively fetching data.
|
|
17
18
|
*/
|
|
18
19
|
const MAX_ACTIVE_CONNECTIONS = 10;
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum duration to wait for the mutex to become available.
|
|
23
|
+
*
|
|
24
|
+
* This gives an explicit error if there are mutex issues, rather than just hanging.
|
|
25
|
+
*/
|
|
26
|
+
const MUTEX_ACQUIRE_TIMEOUT = 30_000;
|
|
27
|
+
|
|
28
|
+
const syncSemaphore = withTimeout(
|
|
29
|
+
new Semaphore(MAX_ACTIVE_CONNECTIONS),
|
|
30
|
+
MUTEX_ACQUIRE_TIMEOUT,
|
|
31
|
+
new Error(`Timeout while waiting for data`)
|
|
32
|
+
);
|
|
20
33
|
|
|
21
34
|
export interface SyncStreamParameters {
|
|
22
35
|
storage: storage.BucketStorageFactory;
|
|
23
36
|
params: util.StreamingSyncRequest;
|
|
24
37
|
syncParams: RequestParameters;
|
|
25
38
|
token: auth.JwtPayload;
|
|
39
|
+
parseOptions: storage.ParseSyncRulesOptions;
|
|
26
40
|
/**
|
|
27
41
|
* If this signal is aborted, the stream response ends as soon as possible, without error.
|
|
28
42
|
*/
|
|
@@ -35,7 +49,7 @@ export interface SyncStreamParameters {
|
|
|
35
49
|
export async function* streamResponse(
|
|
36
50
|
options: SyncStreamParameters
|
|
37
51
|
): AsyncIterable<util.StreamingSyncLine | string | null> {
|
|
38
|
-
const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal } = options;
|
|
52
|
+
const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal, parseOptions } = options;
|
|
39
53
|
// We also need to be able to abort, so we create our own controller.
|
|
40
54
|
const controller = new AbortController();
|
|
41
55
|
if (signal) {
|
|
@@ -51,7 +65,7 @@ export async function* streamResponse(
|
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
const ki = tokenStream(token, controller.signal, tokenStreamOptions);
|
|
54
|
-
const stream = streamResponseInner(storage, params, syncParams, tracker, controller.signal);
|
|
68
|
+
const stream = streamResponseInner(storage, params, syncParams, tracker, parseOptions, controller.signal);
|
|
55
69
|
// Merge the two streams, and abort as soon as one of the streams end.
|
|
56
70
|
const merged = mergeAsyncIterables([stream, ki], controller.signal);
|
|
57
71
|
|
|
@@ -75,6 +89,7 @@ async function* streamResponseInner(
|
|
|
75
89
|
params: util.StreamingSyncRequest,
|
|
76
90
|
syncParams: RequestParameters,
|
|
77
91
|
tracker: RequestTracker,
|
|
92
|
+
parseOptions: storage.ParseSyncRulesOptions,
|
|
78
93
|
signal: AbortSignal
|
|
79
94
|
): AsyncGenerator<util.StreamingSyncLine | string | null> {
|
|
80
95
|
// Bucket state of bucket id -> op_id.
|
|
@@ -92,7 +107,8 @@ async function* streamResponseInner(
|
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
|
|
95
|
-
const
|
|
110
|
+
const checkpointUserId = util.checkpointUserId(syncParams.token_parameters.user_id as string, params.client_id);
|
|
111
|
+
const stream = storage.watchWriteCheckpoint(checkpointUserId, signal);
|
|
96
112
|
for await (const next of stream) {
|
|
97
113
|
const { base, writeCheckpoint } = next;
|
|
98
114
|
const checkpoint = base.checkpoint;
|
|
@@ -102,9 +118,9 @@ async function* streamResponseInner(
|
|
|
102
118
|
// Sync rules deleted in the meantime - try again with the next checkpoint.
|
|
103
119
|
continue;
|
|
104
120
|
}
|
|
105
|
-
const
|
|
121
|
+
const syncRules = storage.getParsedSyncRules(parseOptions);
|
|
106
122
|
|
|
107
|
-
const allBuckets = await
|
|
123
|
+
const allBuckets = await syncRules.queryBucketIds({
|
|
108
124
|
getParameterSets(lookups) {
|
|
109
125
|
return storage.getParameterSets(checkpoint, lookups);
|
|
110
126
|
},
|
|
@@ -195,7 +211,8 @@ async function* streamResponseInner(
|
|
|
195
211
|
raw_data,
|
|
196
212
|
binary_data,
|
|
197
213
|
signal,
|
|
198
|
-
tracker
|
|
214
|
+
tracker,
|
|
215
|
+
user_id: syncParams.user_id
|
|
199
216
|
});
|
|
200
217
|
|
|
201
218
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
@@ -212,6 +229,7 @@ interface BucketDataRequest {
|
|
|
212
229
|
binary_data: boolean | undefined;
|
|
213
230
|
tracker: RequestTracker;
|
|
214
231
|
signal: AbortSignal;
|
|
232
|
+
user_id?: string;
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
async function* bucketDataInBatches(request: BucketDataRequest) {
|
|
@@ -257,8 +275,22 @@ interface BucketDataBatchResult {
|
|
|
257
275
|
async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<BucketDataBatchResult, void> {
|
|
258
276
|
const { storage, checkpoint, bucketsToFetch, dataBuckets, raw_data, binary_data, tracker, signal } = request;
|
|
259
277
|
|
|
260
|
-
const
|
|
278
|
+
const checkpointOp = BigInt(checkpoint);
|
|
279
|
+
let checkpointInvalidated = false;
|
|
280
|
+
|
|
281
|
+
if (syncSemaphore.isLocked()) {
|
|
282
|
+
logger.info('Sync concurrency limit reached, waiting for lock', { user_id: request.user_id });
|
|
283
|
+
}
|
|
284
|
+
const [value, release] = await syncSemaphore.acquire();
|
|
261
285
|
try {
|
|
286
|
+
if (value <= 3) {
|
|
287
|
+
// This can be noisy, so we only log when we get close to the
|
|
288
|
+
// concurrency limit.
|
|
289
|
+
logger.info(`Got sync lock. Slots available: ${value - 1}`, {
|
|
290
|
+
user_id: request.user_id,
|
|
291
|
+
sync_data_slots: value - 1
|
|
292
|
+
});
|
|
293
|
+
}
|
|
262
294
|
// Optimization: Only fetch buckets for which the checksums have changed since the last checkpoint
|
|
263
295
|
// For the first batch, this will be all buckets.
|
|
264
296
|
const filteredBuckets = new Map(bucketsToFetch.map((bucket) => [bucket, dataBuckets.get(bucket)!]));
|
|
@@ -266,13 +298,16 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
|
|
|
266
298
|
|
|
267
299
|
let has_more = false;
|
|
268
300
|
|
|
269
|
-
for await (let r of data) {
|
|
301
|
+
for await (let { batch: r, targetOp } of data) {
|
|
270
302
|
if (signal.aborted) {
|
|
271
303
|
return;
|
|
272
304
|
}
|
|
273
305
|
if (r.has_more) {
|
|
274
306
|
has_more = true;
|
|
275
307
|
}
|
|
308
|
+
if (targetOp != null && targetOp > checkpointOp) {
|
|
309
|
+
checkpointInvalidated = true;
|
|
310
|
+
}
|
|
276
311
|
if (r.data.length == 0) {
|
|
277
312
|
continue;
|
|
278
313
|
}
|
|
@@ -308,14 +343,28 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
|
|
|
308
343
|
}
|
|
309
344
|
|
|
310
345
|
if (!has_more) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
346
|
+
if (checkpointInvalidated) {
|
|
347
|
+
// Checkpoint invalidated by a CLEAR or MOVE op.
|
|
348
|
+
// Don't send the checkpoint_complete line in this case.
|
|
349
|
+
// More data should be available immediately for a new checkpoint.
|
|
350
|
+
yield { data: null, done: true };
|
|
351
|
+
} else {
|
|
352
|
+
const line: util.StreamingSyncCheckpointComplete = {
|
|
353
|
+
checkpoint_complete: {
|
|
354
|
+
last_op_id: checkpoint
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
yield { data: line, done: true };
|
|
358
|
+
}
|
|
317
359
|
}
|
|
318
360
|
} finally {
|
|
361
|
+
if (value <= 3) {
|
|
362
|
+
// This can be noisy, so we only log when we get close to the
|
|
363
|
+
// concurrency limit.
|
|
364
|
+
logger.info(`Releasing sync lock`, {
|
|
365
|
+
user_id: request.user_id
|
|
366
|
+
});
|
|
367
|
+
}
|
|
319
368
|
release();
|
|
320
369
|
}
|
|
321
370
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LifeCycledSystem, ServiceIdentifier, container } from '@powersync/lib-services-framework';
|
|
2
|
+
|
|
3
|
+
import * as metrics from '../metrics/Metrics.js';
|
|
4
|
+
import * as replication from '../replication/replication-index.js';
|
|
5
|
+
import * as routes from '../routes/routes-index.js';
|
|
6
|
+
import * as storage from '../storage/storage-index.js';
|
|
7
|
+
import * as utils from '../util/util-index.js';
|
|
8
|
+
|
|
9
|
+
export interface ServiceContext {
|
|
10
|
+
configuration: utils.ResolvedPowerSyncConfig;
|
|
11
|
+
lifeCycleEngine: LifeCycledSystem;
|
|
12
|
+
metrics: metrics.Metrics | null;
|
|
13
|
+
replicationEngine: replication.ReplicationEngine | null;
|
|
14
|
+
routerEngine: routes.RouterEngine | null;
|
|
15
|
+
storageEngine: storage.StorageEngine;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Context which allows for registering and getting implementations
|
|
20
|
+
* of various service engines.
|
|
21
|
+
* This controls registering, initializing and the lifecycle of various services.
|
|
22
|
+
*/
|
|
23
|
+
export class ServiceContextContainer implements ServiceContext {
|
|
24
|
+
lifeCycleEngine: LifeCycledSystem;
|
|
25
|
+
storageEngine: storage.StorageEngine;
|
|
26
|
+
|
|
27
|
+
constructor(public configuration: utils.ResolvedPowerSyncConfig) {
|
|
28
|
+
this.lifeCycleEngine = new LifeCycledSystem();
|
|
29
|
+
|
|
30
|
+
this.storageEngine = new storage.StorageEngine({
|
|
31
|
+
configuration
|
|
32
|
+
});
|
|
33
|
+
this.lifeCycleEngine.withLifecycle(this.storageEngine, {
|
|
34
|
+
start: (storageEngine) => storageEngine.start(),
|
|
35
|
+
stop: (storageEngine) => storageEngine.shutDown()
|
|
36
|
+
});
|
|
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
|
+
}
|
|
41
|
+
|
|
42
|
+
get replicationEngine(): replication.ReplicationEngine | null {
|
|
43
|
+
return container.getOptional(replication.ReplicationEngine);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get routerEngine(): routes.RouterEngine | null {
|
|
47
|
+
return container.getOptional(routes.RouterEngine);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get metrics(): metrics.Metrics | null {
|
|
51
|
+
return container.getOptional(metrics.Metrics);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Allows for registering core and generic implementations of services/helpers.
|
|
56
|
+
* This uses the framework container under the hood.
|
|
57
|
+
*/
|
|
58
|
+
register<T>(identifier: ServiceIdentifier<T>, implementation: T) {
|
|
59
|
+
container.register(identifier, implementation);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets the implementation of an identifiable service.
|
|
64
|
+
*/
|
|
65
|
+
get<T>(identifier: ServiceIdentifier<T>) {
|
|
66
|
+
return container.getImplementation(identifier);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './ServiceContext.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import * as t from 'ts-codec';
|
|
1
2
|
import * as yaml from 'yaml';
|
|
2
3
|
|
|
3
4
|
import { configFile } from '@powersync/service-types';
|
|
5
|
+
import { schema } from '@powersync/lib-services-framework';
|
|
4
6
|
|
|
5
7
|
import { RunnerConfig } from '../types.js';
|
|
6
8
|
|
|
@@ -21,6 +23,13 @@ export enum ConfigFileFormat {
|
|
|
21
23
|
*/
|
|
22
24
|
const YAML_ENV_PREFIX = 'PS_';
|
|
23
25
|
|
|
26
|
+
// ts-codec itself doesn't give great validation errors, so we use json schema for that
|
|
27
|
+
const configSchemaValidator = schema
|
|
28
|
+
.parseJSONSchema(
|
|
29
|
+
t.generateJSONSchema(configFile.powerSyncConfig, { allowAdditional: true, parsers: [configFile.portParser] })
|
|
30
|
+
)
|
|
31
|
+
.validator();
|
|
32
|
+
|
|
24
33
|
export abstract class ConfigCollector {
|
|
25
34
|
abstract get name(): string;
|
|
26
35
|
|
|
@@ -30,6 +39,45 @@ export abstract class ConfigCollector {
|
|
|
30
39
|
*/
|
|
31
40
|
abstract collectSerialized(runnerConfig: RunnerConfig): Promise<configFile.SerializedPowerSyncConfig | null>;
|
|
32
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Collects the PowerSyncConfig settings.
|
|
44
|
+
* Validates and decodes the config.
|
|
45
|
+
* @returns null if this collector cannot provide a config
|
|
46
|
+
*/
|
|
47
|
+
async collect(runner_config: RunnerConfig): Promise<configFile.PowerSyncConfig | null> {
|
|
48
|
+
const serialized = await this.collectSerialized(runner_config);
|
|
49
|
+
if (!serialized) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* After this point a serialized config has been found. Any failures to decode or validate
|
|
55
|
+
* will result in a hard stop.
|
|
56
|
+
*/
|
|
57
|
+
const decoded = this.decode(serialized);
|
|
58
|
+
this.validate(decoded);
|
|
59
|
+
return decoded;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validates input config
|
|
64
|
+
* ts-codec itself doesn't give great validation errors, so we use json schema for that
|
|
65
|
+
*/
|
|
66
|
+
validate(config: configFile.PowerSyncConfig) {
|
|
67
|
+
const valid = configSchemaValidator.validate(config);
|
|
68
|
+
if (!valid.valid) {
|
|
69
|
+
throw new Error(`Failed to validate PowerSync config: ${valid.errors.join(', ')}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
decode(encoded: configFile.SerializedPowerSyncConfig): configFile.PowerSyncConfig {
|
|
74
|
+
try {
|
|
75
|
+
return configFile.powerSyncConfig.decode(encoded);
|
|
76
|
+
} catch (ex) {
|
|
77
|
+
throw new Error(`Failed to decode PowerSync config: ${ex}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
33
81
|
protected parseContent(content: string, contentType?: ConfigFileFormat) {
|
|
34
82
|
switch (contentType) {
|
|
35
83
|
case ConfigFileFormat.YAML:
|