@powersync/service-core 0.8.7 → 0.9.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 +37 -0
- package/dist/api/RouteAPI.d.ts +67 -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 +170 -158
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.d.ts +3 -5
- package/dist/api/schema.js +14 -80
- 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.d.ts +7 -4
- package/dist/auth/KeyStore.js +1 -1
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/LeakyBucket.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.d.ts +0 -2
- package/dist/auth/RemoteJWKSCollector.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/db/mongo.js +5 -3
- package/dist/db/mongo.js.map +1 -1
- package/dist/entry/cli-entry.js +3 -2
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/compact-action.js +90 -14
- package/dist/entry/commands/compact-action.js.map +1 -1
- 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/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/locks/MongoLocks.js.map +1 -1
- package/dist/metrics/Metrics.d.ts +2 -2
- package/dist/metrics/Metrics.js +5 -13
- 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/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +31 -0
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
- package/dist/migrations/executor.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/migrations/store/migration-store.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 +37 -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 +250 -0
- package/dist/replication/AbstractReplicator.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.d.ts +0 -10
- 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 +51 -0
- package/dist/replication/ReplicationModule.js +68 -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 +37 -7
- package/dist/routes/configure-fastify.js +20 -19
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/configure-rsocket.d.ts +3 -4
- package/dist/routes/configure-rsocket.js +7 -4
- package/dist/routes/configure-rsocket.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +30 -0
- package/dist/routes/endpoints/admin.js +46 -67
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.js +103 -15
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/probes.d.ts +74 -0
- package/dist/routes/endpoints/probes.js +51 -0
- package/dist/routes/endpoints/probes.js.map +1 -0
- package/dist/routes/endpoints/socket-route.js +8 -6
- 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 +0 -1
- package/dist/routes/endpoints/sync-stream.js +8 -8
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/hooks.js.map +1 -1
- package/dist/routes/route-register.js.map +1 -1
- package/dist/routes/router.d.ts +11 -4
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +1 -0
- package/dist/routes/routes-index.js +1 -0
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/runner/teardown.js +109 -76
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +86 -36
- package/dist/storage/BucketStorage.js +6 -10
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +7 -11
- package/dist/storage/MongoBucketStorage.js +48 -41
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/ReplicationEventPayload.d.ts +14 -0
- package/dist/storage/ReplicationEventPayload.js +2 -0
- package/dist/storage/ReplicationEventPayload.js.map +1 -0
- 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 +12 -5
- package/dist/storage/SourceTable.js +12 -5
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/StorageEngine.d.ts +28 -0
- package/dist/storage/StorageEngine.js +45 -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/WriteCheckpointAPI.d.ts +74 -0
- package/dist/storage/WriteCheckpointAPI.js +16 -0
- package/dist/storage/WriteCheckpointAPI.js.map +1 -0
- package/dist/storage/mongo/MongoBucketBatch.d.ts +24 -5
- package/dist/storage/mongo/MongoBucketBatch.js +119 -62
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoCompactor.js +20 -3
- package/dist/storage/mongo/MongoCompactor.js.map +1 -1
- package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
- 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 +18 -10
- package/dist/storage/mongo/MongoSyncBucketStorage.js +140 -25
- 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/MongoWriteCheckpointAPI.d.ts +20 -0
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js +103 -0
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -0
- package/dist/storage/mongo/OperationBatch.d.ts +13 -4
- package/dist/storage/mongo/OperationBatch.js +25 -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/config.d.ts +19 -0
- package/dist/storage/mongo/config.js +26 -0
- package/dist/storage/mongo/config.js.map +1 -0
- package/dist/storage/mongo/db.d.ts +3 -2
- package/dist/storage/mongo/db.js +1 -0
- package/dist/storage/mongo/db.js.map +1 -1
- package/dist/storage/mongo/models.d.ts +20 -5
- package/dist/storage/mongo/models.js.map +1 -1
- package/dist/storage/mongo/util.d.ts +12 -1
- package/dist/storage/mongo/util.js +50 -2
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/storage/storage-index.d.ts +8 -2
- package/dist/storage/storage-index.js +8 -2
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/BroadcastIterable.d.ts +0 -1
- package/dist/sync/BroadcastIterable.js.map +1 -1
- package/dist/sync/LastValueSink.d.ts +0 -1
- package/dist/sync/LastValueSink.js.map +1 -1
- package/dist/sync/merge.d.ts +0 -1
- package/dist/sync/merge.js.map +1 -1
- package/dist/sync/safeRace.js.map +1 -1
- package/dist/sync/sync.d.ts +1 -1
- package/dist/sync/sync.js +5 -5
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +0 -2
- package/dist/sync/util.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/Mutex.js.map +1 -1
- 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.map +1 -1
- package/dist/util/config/compound-config-collector.d.ts +9 -2
- package/dist/util/config/compound-config-collector.js +16 -24
- 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/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 +7 -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/memory-tracking.js.map +1 -1
- package/dist/util/secs.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 -7
- package/dist/util/utils.js +36 -25
- package/dist/util/utils.js.map +1 -1
- package/package.json +8 -12
- package/src/api/RouteAPI.ts +78 -0
- package/src/api/api-index.ts +1 -0
- package/src/api/diagnostics.ts +18 -70
- package/src/api/schema.ts +18 -90
- package/src/auth/KeyStore.ts +9 -6
- package/src/auth/RemoteJWKSCollector.ts +4 -1
- package/src/auth/auth-index.ts +0 -1
- package/src/db/mongo.ts +5 -3
- package/src/entry/cli-entry.ts +3 -2
- package/src/entry/commands/compact-action.ts +24 -12
- package/src/entry/commands/migrate-action.ts +5 -8
- package/src/entry/commands/teardown-action.ts +2 -2
- package/src/index.ts +5 -2
- package/src/metrics/Metrics.ts +6 -16
- 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/db/migrations/1727099539247-custom-write-checkpoint-index.ts +37 -0
- 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 +228 -0
- package/src/replication/ErrorRateLimiter.ts +0 -44
- package/src/replication/ReplicationEngine.ts +43 -0
- package/src/replication/ReplicationModule.ts +122 -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 +28 -28
- package/src/routes/configure-rsocket.ts +13 -8
- package/src/routes/endpoints/admin.ts +72 -76
- package/src/routes/endpoints/checkpointing.ts +51 -11
- package/src/routes/endpoints/probes.ts +58 -0
- package/src/routes/endpoints/socket-route.ts +10 -6
- package/src/routes/endpoints/sync-rules.ts +41 -25
- package/src/routes/endpoints/sync-stream.ts +8 -8
- package/src/routes/router.ts +10 -5
- package/src/routes/routes-index.ts +1 -0
- package/src/runner/teardown.ts +50 -88
- package/src/storage/BucketStorage.ts +103 -41
- package/src/storage/MongoBucketStorage.ts +65 -53
- package/src/storage/ReplicationEventPayload.ts +16 -0
- package/src/storage/SourceEntity.ts +22 -0
- package/src/storage/SourceTable.ts +14 -7
- package/src/storage/StorageEngine.ts +62 -0
- package/src/storage/StorageProvider.ts +27 -0
- package/src/storage/WriteCheckpointAPI.ts +85 -0
- package/src/storage/mongo/MongoBucketBatch.ts +164 -84
- package/src/storage/mongo/MongoCompactor.ts +25 -4
- package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +7 -4
- package/src/storage/mongo/MongoStorageProvider.ts +31 -0
- package/src/storage/mongo/MongoSyncBucketStorage.ts +118 -41
- package/src/storage/mongo/MongoSyncRulesLock.ts +7 -3
- package/src/storage/mongo/MongoWriteCheckpointAPI.ts +151 -0
- package/src/storage/mongo/OperationBatch.ts +28 -12
- package/src/storage/mongo/PersistedBatch.ts +10 -6
- package/src/storage/mongo/config.ts +40 -0
- package/src/storage/mongo/db.ts +4 -1
- package/src/storage/mongo/models.ts +21 -5
- package/src/storage/mongo/util.ts +48 -3
- package/src/storage/storage-index.ts +8 -2
- package/src/sync/sync.ts +7 -4
- package/src/sync/util.ts +0 -1
- package/src/system/ServiceContext.ts +68 -0
- package/src/system/system-index.ts +1 -1
- package/src/util/config/compound-config-collector.ts +31 -31
- package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
- package/src/util/config/types.ts +7 -5
- package/src/util/config.ts +6 -23
- package/src/util/util-index.ts +3 -6
- package/src/util/utils.ts +48 -41
- package/test/src/__snapshots__/sync.test.ts.snap +14 -14
- package/test/src/auth.test.ts +7 -7
- package/test/src/broadcast_iterable.test.ts +1 -1
- package/test/src/compacting.test.ts +50 -40
- package/test/src/data_storage.test.ts +382 -202
- package/test/src/env.ts +1 -3
- package/test/src/merge_iterable.test.ts +1 -6
- package/test/src/routes/probes.integration.test.ts +235 -0
- package/test/src/routes/probes.test.ts +153 -0
- package/test/src/setup.ts +1 -1
- package/test/src/stream_utils.ts +42 -0
- package/test/src/sync.test.ts +115 -39
- package/test/src/util.ts +48 -51
- package/test/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +7 -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 -519
- 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/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 -631
- package/src/replication/WalStreamManager.ts +0 -213
- package/src/replication/WalStreamRunner.ts +0 -180
- package/src/replication/util.ts +0 -76
- 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 -338
- package/test/src/validation.test.ts +0 -63
- package/test/src/wal_stream.test.ts +0 -319
- package/test/src/wal_stream_utils.ts +0 -156
package/src/metrics/Metrics.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Attributes, Counter, ObservableGauge, UpDownCounter, ValueType } from '@opentelemetry/api';
|
|
2
|
-
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
3
|
-
import { MeterProvider, MetricReader, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
4
2
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
5
|
-
import
|
|
6
|
-
import * as util from '../util/util-index.js';
|
|
7
|
-
import * as storage from '../storage/storage-index.js';
|
|
8
|
-
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
3
|
+
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
9
4
|
import { Resource } from '@opentelemetry/resources';
|
|
5
|
+
import { MeterProvider, MetricReader, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
10
6
|
import { logger } from '@powersync/lib-services-framework';
|
|
7
|
+
import * as storage from '../storage/storage-index.js';
|
|
8
|
+
import * as util from '../util/util-index.js';
|
|
11
9
|
|
|
12
10
|
export interface MetricsOptions {
|
|
13
11
|
disable_telemetry_sharing: boolean;
|
|
@@ -202,7 +200,7 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
202
200
|
this.concurrent_connections.add(0);
|
|
203
201
|
}
|
|
204
202
|
|
|
205
|
-
public configureReplicationMetrics(
|
|
203
|
+
public configureReplicationMetrics(bucketStorage: storage.BucketStorageFactory) {
|
|
206
204
|
// Rate limit collection of these stats, since it may be an expensive query
|
|
207
205
|
const MINIMUM_INTERVAL = 60_000;
|
|
208
206
|
|
|
@@ -211,7 +209,7 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
211
209
|
|
|
212
210
|
function getMetrics() {
|
|
213
211
|
if (cachedRequest == null || Date.now() - cacheTimestamp > MINIMUM_INTERVAL) {
|
|
214
|
-
cachedRequest =
|
|
212
|
+
cachedRequest = bucketStorage.getStorageMetrics().catch((e) => {
|
|
215
213
|
logger.error(`Failed to get storage metrics`, e);
|
|
216
214
|
return null;
|
|
217
215
|
});
|
|
@@ -240,14 +238,6 @@ Anonymous telemetry is currently: ${options.disable_telemetry_sharing ? 'disable
|
|
|
240
238
|
result.observe(metrics.replication_size_bytes);
|
|
241
239
|
}
|
|
242
240
|
});
|
|
243
|
-
|
|
244
|
-
const class_scoped_data_replicated_bytes = this.data_replicated_bytes;
|
|
245
|
-
// Record replicated bytes using global jpgwire metrics.
|
|
246
|
-
jpgwire.setMetricsRecorder({
|
|
247
|
-
addBytesRead(bytes) {
|
|
248
|
-
class_scoped_data_replicated_bytes.add(bytes);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
241
|
}
|
|
252
242
|
|
|
253
243
|
public async getMetricValueForTests(name: string): Promise<number | undefined> {
|
|
@@ -2,8 +2,11 @@ import * as mongo from '../../../db/mongo.js';
|
|
|
2
2
|
import * as storage from '../../../storage/storage-index.js';
|
|
3
3
|
import * as utils from '../../../util/util-index.js';
|
|
4
4
|
|
|
5
|
-
export const up = async (context
|
|
6
|
-
const
|
|
5
|
+
export const up = async (context: utils.MigrationContext) => {
|
|
6
|
+
const { runner_config } = context;
|
|
7
|
+
|
|
8
|
+
const config = await utils.loadConfig(runner_config);
|
|
9
|
+
|
|
7
10
|
const database = storage.createPowerSyncMongo(config.storage);
|
|
8
11
|
await mongo.waitForAuth(database.db);
|
|
9
12
|
try {
|
|
@@ -20,8 +23,10 @@ export const up = async (context?: utils.MigrationContext) => {
|
|
|
20
23
|
}
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
export const down = async (context
|
|
24
|
-
const
|
|
26
|
+
export const down = async (context: utils.MigrationContext) => {
|
|
27
|
+
const { runner_config } = context;
|
|
28
|
+
const config = await utils.loadConfig(runner_config);
|
|
29
|
+
|
|
25
30
|
const database = storage.createPowerSyncMongo(config.storage);
|
|
26
31
|
try {
|
|
27
32
|
if (await database.bucket_parameters.indexExists('lookup')) {
|
|
@@ -23,9 +23,11 @@ interface LegacySyncRulesDocument extends storage.SyncRuleDocument {
|
|
|
23
23
|
auto_activate?: boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export const up = async (context
|
|
27
|
-
const
|
|
26
|
+
export const up = async (context: utils.MigrationContext) => {
|
|
27
|
+
const { runner_config } = context;
|
|
28
|
+
const config = await utils.loadConfig(runner_config);
|
|
28
29
|
const db = storage.createPowerSyncMongo(config.storage);
|
|
30
|
+
|
|
29
31
|
await mongo.waitForAuth(db.db);
|
|
30
32
|
try {
|
|
31
33
|
// We keep the old flags for existing deployments still shutting down.
|
|
@@ -68,8 +70,9 @@ export const up = async (context?: utils.MigrationContext) => {
|
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
72
|
|
|
71
|
-
export const down = async (context
|
|
72
|
-
const
|
|
73
|
+
export const down = async (context: utils.MigrationContext) => {
|
|
74
|
+
const { runner_config } = context;
|
|
75
|
+
const config = await utils.loadConfig(runner_config);
|
|
73
76
|
|
|
74
77
|
const db = storage.createPowerSyncMongo(config.storage);
|
|
75
78
|
try {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as storage from '../../../storage/storage-index.js';
|
|
2
2
|
import * as utils from '../../../util/util-index.js';
|
|
3
3
|
|
|
4
|
-
export const up = async (context
|
|
5
|
-
const
|
|
4
|
+
export const up = async (context: utils.MigrationContext) => {
|
|
5
|
+
const { runner_config } = context;
|
|
6
|
+
const config = await utils.loadConfig(runner_config);
|
|
6
7
|
const db = storage.createPowerSyncMongo(config.storage);
|
|
7
8
|
|
|
8
9
|
try {
|
|
@@ -17,8 +18,9 @@ export const up = async (context?: utils.MigrationContext) => {
|
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
export const down = async (context
|
|
21
|
-
const
|
|
21
|
+
export const down = async (context: utils.MigrationContext) => {
|
|
22
|
+
const { runner_config } = context;
|
|
23
|
+
const config = await utils.loadConfig(runner_config);
|
|
22
24
|
|
|
23
25
|
const db = storage.createPowerSyncMongo(config.storage);
|
|
24
26
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
2
|
+
import * as utils from '../../../util/util-index.js';
|
|
3
|
+
|
|
4
|
+
const INDEX_NAME = 'user_sync_rule_unique';
|
|
5
|
+
|
|
6
|
+
export const up = async (context: utils.MigrationContext) => {
|
|
7
|
+
const { runner_config } = context;
|
|
8
|
+
const config = await utils.loadConfig(runner_config);
|
|
9
|
+
const db = storage.createPowerSyncMongo(config.storage);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await db.custom_write_checkpoints.createIndex(
|
|
13
|
+
{
|
|
14
|
+
user_id: 1,
|
|
15
|
+
sync_rules_id: 1
|
|
16
|
+
},
|
|
17
|
+
{ name: INDEX_NAME, unique: true }
|
|
18
|
+
);
|
|
19
|
+
} finally {
|
|
20
|
+
await db.client.close();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const down = async (context: utils.MigrationContext) => {
|
|
25
|
+
const { runner_config } = context;
|
|
26
|
+
const config = await utils.loadConfig(runner_config);
|
|
27
|
+
|
|
28
|
+
const db = storage.createPowerSyncMongo(config.storage);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (await db.custom_write_checkpoints.indexExists(INDEX_NAME)) {
|
|
32
|
+
await db.custom_write_checkpoints.dropIndex(INDEX_NAME);
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
await db.client.close();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -2,13 +2,13 @@ import * as fs from 'fs/promises';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
5
6
|
import * as db from '../db/db-index.js';
|
|
6
|
-
import * as util from '../util/util-index.js';
|
|
7
7
|
import * as locks from '../locks/locks-index.js';
|
|
8
|
+
import * as util from '../util/util-index.js';
|
|
8
9
|
import { Direction } from './definitions.js';
|
|
9
|
-
import { createMongoMigrationStore } from './store/migration-store.js';
|
|
10
10
|
import { execute, writeLogsToStore } from './executor.js';
|
|
11
|
-
import {
|
|
11
|
+
import { createMongoMigrationStore } from './store/migration-store.js';
|
|
12
12
|
|
|
13
13
|
const DEFAULT_MONGO_LOCK_COLLECTION = 'locks';
|
|
14
14
|
const MONGO_LOCK_PROCESS = 'migrations';
|
|
@@ -23,18 +23,23 @@ export type MigrationOptions = {
|
|
|
23
23
|
runner_config: util.RunnerConfig;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
export type AutomaticMigrationParams = {
|
|
27
|
+
config: util.ResolvedPowerSyncConfig;
|
|
28
|
+
runner_config: util.RunnerConfig;
|
|
29
|
+
};
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* Loads migrations and injects a custom context for loading the specified
|
|
28
33
|
* runner configuration.
|
|
29
34
|
*/
|
|
30
|
-
const loadMigrations = async (dir: string,
|
|
35
|
+
const loadMigrations = async (dir: string, runnerConfig: util.RunnerConfig) => {
|
|
31
36
|
const files = await fs.readdir(dir);
|
|
32
37
|
const migrations = files.filter((file) => {
|
|
33
38
|
return path.extname(file) === '.js';
|
|
34
39
|
});
|
|
35
40
|
|
|
36
41
|
const context: util.MigrationContext = {
|
|
37
|
-
runner_config
|
|
42
|
+
runner_config: runnerConfig
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
return await Promise.all(
|
|
@@ -55,14 +60,13 @@ const loadMigrations = async (dir: string, runner_config: util.RunnerConfig) =>
|
|
|
55
60
|
export const migrate = async (options: MigrationOptions) => {
|
|
56
61
|
const { direction, runner_config } = options;
|
|
57
62
|
|
|
63
|
+
const config = await util.loadConfig(runner_config);
|
|
64
|
+
const { storage } = config;
|
|
58
65
|
/**
|
|
59
66
|
* Try and get Mongo from config file.
|
|
60
67
|
* But this might not be available in Journey Micro as we use the standard Mongo.
|
|
61
68
|
*/
|
|
62
69
|
|
|
63
|
-
const config = await util.loadConfig(runner_config);
|
|
64
|
-
const { storage } = config;
|
|
65
|
-
|
|
66
70
|
const client = db.mongo.createMongoClient(storage);
|
|
67
71
|
logger.info('Connecting to MongoDB');
|
|
68
72
|
await client.connect();
|
|
@@ -124,3 +128,15 @@ export const migrate = async (options: MigrationOptions) => {
|
|
|
124
128
|
logger.info('Done with migrations');
|
|
125
129
|
}
|
|
126
130
|
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Ensures automatic migrations are executed
|
|
134
|
+
*/
|
|
135
|
+
export const ensureAutomaticMigrations = async (params: AutomaticMigrationParams) => {
|
|
136
|
+
if (!params.config.migrations?.disable_auto_migration) {
|
|
137
|
+
await migrate({
|
|
138
|
+
direction: Direction.Up,
|
|
139
|
+
runner_config: params.runner_config
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ServiceContextContainer } from '../system/ServiceContext.js';
|
|
2
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import winston from 'winston';
|
|
4
|
+
import { PersistedSyncRulesContent } from '../storage/BucketStorage.js';
|
|
5
|
+
|
|
6
|
+
export interface TearDownOptions {
|
|
7
|
+
/**
|
|
8
|
+
* If required, tear down any configuration/state for the specific sync rules
|
|
9
|
+
*/
|
|
10
|
+
syncRules?: PersistedSyncRulesContent[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AbstractModuleOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export abstract class AbstractModule {
|
|
18
|
+
protected logger: winston.Logger;
|
|
19
|
+
|
|
20
|
+
protected constructor(protected options: AbstractModuleOptions) {
|
|
21
|
+
this.logger = logger.child({ name: `Module:${options.name}` });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the module using any required services from the ServiceContext
|
|
26
|
+
*/
|
|
27
|
+
public abstract initialize(context: ServiceContextContainer): Promise<void>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Permanently clean up and dispose of any configuration or state for this module.
|
|
31
|
+
*/
|
|
32
|
+
public abstract teardown(options: TearDownOptions): Promise<void>;
|
|
33
|
+
|
|
34
|
+
public get name() {
|
|
35
|
+
return this.options.name;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import * as system from '../system/system-index.js';
|
|
3
|
+
import { AbstractModule, TearDownOptions } from './AbstractModule.js';
|
|
4
|
+
/**
|
|
5
|
+
* The module manager keeps track of activated modules
|
|
6
|
+
*/
|
|
7
|
+
export class ModuleManager {
|
|
8
|
+
private readonly modules: Map<string, AbstractModule> = new Map();
|
|
9
|
+
|
|
10
|
+
public register(modules: AbstractModule[]) {
|
|
11
|
+
for (const module of modules) {
|
|
12
|
+
if (this.modules.has(module.name)) {
|
|
13
|
+
logger.warn(`Module ${module.name} already registered, skipping.`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
this.modules.set(module.name, module);
|
|
17
|
+
logger.info(`Successfully registered Module ${module.name}.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async initialize(serviceContext: system.ServiceContextContainer) {
|
|
22
|
+
logger.info(`Initializing modules...`);
|
|
23
|
+
for (const module of this.modules.values()) {
|
|
24
|
+
await module.initialize(serviceContext);
|
|
25
|
+
}
|
|
26
|
+
logger.info(`Successfully Initialized modules.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async tearDown(options: TearDownOptions) {
|
|
30
|
+
for (const module of this.modules.values()) {
|
|
31
|
+
await module.teardown(options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import winston from 'winston';
|
|
3
|
+
import * as storage from '../storage/storage-index.js';
|
|
4
|
+
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
5
|
+
|
|
6
|
+
export interface AbstractReplicationJobOptions {
|
|
7
|
+
id: string;
|
|
8
|
+
storage: storage.SyncRulesBucketStorage;
|
|
9
|
+
lock: storage.ReplicationLock;
|
|
10
|
+
rateLimiter: ErrorRateLimiter;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export abstract class AbstractReplicationJob {
|
|
14
|
+
protected logger: winston.Logger;
|
|
15
|
+
protected abortController = new AbortController();
|
|
16
|
+
protected isReplicatingPromise: Promise<void> | null = null;
|
|
17
|
+
|
|
18
|
+
protected constructor(protected options: AbstractReplicationJobOptions) {
|
|
19
|
+
this.logger = logger.child({ name: `ReplicationJob: ${this.id}` });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Copy the initial data set from the data source if required and then keep it in sync.
|
|
24
|
+
*/
|
|
25
|
+
abstract replicate(): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Ensure the connection to the data source remains active
|
|
29
|
+
*/
|
|
30
|
+
abstract keepAlive(): Promise<void>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start the replication process
|
|
34
|
+
*/
|
|
35
|
+
public async start(): Promise<void> {
|
|
36
|
+
this.isReplicatingPromise = this.replicate()
|
|
37
|
+
.catch((ex) => {
|
|
38
|
+
container.reporter.captureException(ex, {
|
|
39
|
+
metadata: {
|
|
40
|
+
replicator: this.id
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
this.logger.error(`Replication failed.`, ex);
|
|
44
|
+
})
|
|
45
|
+
.finally(async () => {
|
|
46
|
+
this.abortController.abort();
|
|
47
|
+
await this.options.lock.release();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Safely stop the replication process
|
|
53
|
+
*/
|
|
54
|
+
public async stop(): Promise<void> {
|
|
55
|
+
this.logger.info(`Stopping replication job for sync rule iteration: ${this.storage.group_id}`);
|
|
56
|
+
this.abortController.abort();
|
|
57
|
+
await this.isReplicatingPromise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public get id() {
|
|
61
|
+
return this.options.id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public get storage() {
|
|
65
|
+
return this.options.storage;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected get lock() {
|
|
69
|
+
return this.options.lock;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected get rateLimiter() {
|
|
73
|
+
return this.options.rateLimiter;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public get isStopped(): boolean {
|
|
77
|
+
return this.abortController.signal.aborted;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import { hrtime } from 'node:process';
|
|
3
|
+
import winston from 'winston';
|
|
4
|
+
import * as storage from '../storage/storage-index.js';
|
|
5
|
+
import { StorageEngine } from '../storage/storage-index.js';
|
|
6
|
+
import { SyncRulesProvider } from '../util/config/sync-rules/sync-rules-provider.js';
|
|
7
|
+
import { AbstractReplicationJob } from './AbstractReplicationJob.js';
|
|
8
|
+
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
9
|
+
|
|
10
|
+
// 5 minutes
|
|
11
|
+
const PING_INTERVAL = 1_000_000_000n * 300n;
|
|
12
|
+
|
|
13
|
+
export interface CreateJobOptions {
|
|
14
|
+
lock: storage.ReplicationLock;
|
|
15
|
+
storage: storage.SyncRulesBucketStorage;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AbstractReplicatorOptions {
|
|
19
|
+
id: string;
|
|
20
|
+
storageEngine: StorageEngine;
|
|
21
|
+
syncRuleProvider: SyncRulesProvider;
|
|
22
|
+
/**
|
|
23
|
+
* This limits the effect of retries when there is a persistent issue.
|
|
24
|
+
*/
|
|
25
|
+
rateLimiter: ErrorRateLimiter;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A replicator manages the mechanics for replicating data from a data source to a storage bucket.
|
|
30
|
+
* This includes copying across the original data set and then keeping it in sync with the data source using Replication Jobs.
|
|
31
|
+
* It also handles any changes to the sync rules.
|
|
32
|
+
*/
|
|
33
|
+
export abstract class AbstractReplicator<T extends AbstractReplicationJob = AbstractReplicationJob> {
|
|
34
|
+
protected logger: winston.Logger;
|
|
35
|
+
/**
|
|
36
|
+
* Map of replication jobs by sync rule id. Usually there is only one running job, but there could be two when
|
|
37
|
+
* transitioning to a new set of sync rules.
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
private replicationJobs = new Map<number, T>();
|
|
41
|
+
private stopped = false;
|
|
42
|
+
|
|
43
|
+
// First ping is only after 5 minutes, not when starting
|
|
44
|
+
private lastPing = hrtime.bigint();
|
|
45
|
+
|
|
46
|
+
protected constructor(private options: AbstractReplicatorOptions) {
|
|
47
|
+
this.logger = logger.child({ name: `Replicator:${options.id}` });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
abstract createJob(options: CreateJobOptions): T;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clean up any configuration or state for the specified sync rule on the datasource.
|
|
54
|
+
* Should be a no-op if the configuration has already been cleared
|
|
55
|
+
*/
|
|
56
|
+
abstract cleanUp(syncRuleStorage: storage.SyncRulesBucketStorage): Promise<void>;
|
|
57
|
+
|
|
58
|
+
public get id() {
|
|
59
|
+
return this.options.id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected get storage() {
|
|
63
|
+
return this.options.storageEngine.activeBucketStorage;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected get syncRuleProvider() {
|
|
67
|
+
return this.options.syncRuleProvider;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected get rateLimiter() {
|
|
71
|
+
return this.options.rateLimiter;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async start(): Promise<void> {
|
|
75
|
+
this.runLoop().catch((e) => {
|
|
76
|
+
this.logger.error('Data source fatal replication error', e);
|
|
77
|
+
container.reporter.captureException(e);
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}, 1000);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public async stop(): Promise<void> {
|
|
85
|
+
this.stopped = true;
|
|
86
|
+
let promises: Promise<void>[] = [];
|
|
87
|
+
for (const job of this.replicationJobs.values()) {
|
|
88
|
+
promises.push(job.stop());
|
|
89
|
+
}
|
|
90
|
+
await Promise.all(promises);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async runLoop() {
|
|
94
|
+
const syncRules = await this.syncRuleProvider.get();
|
|
95
|
+
let configuredLock: storage.ReplicationLock | undefined = undefined;
|
|
96
|
+
if (syncRules != null) {
|
|
97
|
+
this.logger.info('Loaded sync rules');
|
|
98
|
+
try {
|
|
99
|
+
// Configure new sync rules, if they have changed.
|
|
100
|
+
// In that case, also immediately take out a lock, so that another process doesn't start replication on it.
|
|
101
|
+
const { lock } = await this.storage.configureSyncRules(syncRules, {
|
|
102
|
+
lock: true
|
|
103
|
+
});
|
|
104
|
+
if (lock) {
|
|
105
|
+
configuredLock = lock;
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// Log, but continue with previous sync rules
|
|
109
|
+
this.logger.error(`Failed to update sync rules from configuration`, e);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
this.logger.info('No sync rules configured - configure via API');
|
|
113
|
+
}
|
|
114
|
+
while (!this.stopped) {
|
|
115
|
+
await container.probes.touch();
|
|
116
|
+
try {
|
|
117
|
+
await this.refresh({ configured_lock: configuredLock });
|
|
118
|
+
// The lock is only valid on the first refresh.
|
|
119
|
+
configuredLock = undefined;
|
|
120
|
+
|
|
121
|
+
// Ensure that the replication jobs' connections are kept alive.
|
|
122
|
+
// We don't ping while in error retry back-off, to avoid having too failures.
|
|
123
|
+
if (this.rateLimiter.mayPing()) {
|
|
124
|
+
const now = hrtime.bigint();
|
|
125
|
+
if (now - this.lastPing >= PING_INTERVAL) {
|
|
126
|
+
for (const activeJob of this.replicationJobs.values()) {
|
|
127
|
+
await activeJob.keepAlive();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.lastPing = now;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
this.logger.error('Failed to refresh replication jobs', e);
|
|
135
|
+
}
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async refresh(options?: { configured_lock?: storage.ReplicationLock }) {
|
|
141
|
+
if (this.stopped) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let configuredLock = options?.configured_lock;
|
|
146
|
+
|
|
147
|
+
const existingJobs = new Map<number, T>(this.replicationJobs.entries());
|
|
148
|
+
const replicatingSyncRules = await this.storage.getReplicatingSyncRules();
|
|
149
|
+
const newJobs = new Map<number, T>();
|
|
150
|
+
for (let syncRules of replicatingSyncRules) {
|
|
151
|
+
const existingJob = existingJobs.get(syncRules.id);
|
|
152
|
+
if (existingJob && !existingJob.isStopped) {
|
|
153
|
+
// No change
|
|
154
|
+
existingJobs.delete(syncRules.id);
|
|
155
|
+
newJobs.set(syncRules.id, existingJob);
|
|
156
|
+
} else if (existingJob && existingJob.isStopped) {
|
|
157
|
+
// Stopped (e.g. fatal error).
|
|
158
|
+
// Remove from the list. Next refresh call will restart the job.
|
|
159
|
+
existingJobs.delete(syncRules.id);
|
|
160
|
+
} else {
|
|
161
|
+
// New sync rules were found (or resume after restart)
|
|
162
|
+
try {
|
|
163
|
+
let lock: storage.ReplicationLock;
|
|
164
|
+
if (configuredLock?.sync_rules_id == syncRules.id) {
|
|
165
|
+
lock = configuredLock;
|
|
166
|
+
} else {
|
|
167
|
+
lock = await syncRules.lock();
|
|
168
|
+
}
|
|
169
|
+
const storage = this.storage.getInstance(syncRules);
|
|
170
|
+
const newJob = this.createJob({
|
|
171
|
+
lock: lock,
|
|
172
|
+
storage: storage
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
newJobs.set(syncRules.id, newJob);
|
|
176
|
+
newJob.start();
|
|
177
|
+
} catch (e) {
|
|
178
|
+
// Could be a sync rules parse error,
|
|
179
|
+
// for example from stricter validation that was added.
|
|
180
|
+
// This will be retried every couple of seconds.
|
|
181
|
+
// When new (valid) sync rules are deployed and processed, this one be disabled.
|
|
182
|
+
this.logger.error('Failed to start replication for new sync rules', e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.replicationJobs = newJobs;
|
|
188
|
+
|
|
189
|
+
// Terminate any orphaned jobs that no longer have sync rules
|
|
190
|
+
for (let job of existingJobs.values()) {
|
|
191
|
+
// Old - stop and clean up
|
|
192
|
+
try {
|
|
193
|
+
await job.stop();
|
|
194
|
+
await this.terminateSyncRules(job.storage);
|
|
195
|
+
job.storage[Symbol.dispose]();
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// This will be retried
|
|
198
|
+
this.logger.warn('Failed to terminate old replication job}', e);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Sync rules stopped previously or by a different process.
|
|
203
|
+
const stopped = await this.storage.getStoppedSyncRules();
|
|
204
|
+
for (let syncRules of stopped) {
|
|
205
|
+
try {
|
|
206
|
+
using syncRuleStorage = this.storage.getInstance(syncRules);
|
|
207
|
+
await this.terminateSyncRules(syncRuleStorage);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
this.logger.warn(`Failed clean up replication config for sync rule: ${syncRules.id}`, e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected createJobId(syncRuleId: number) {
|
|
215
|
+
return `${this.id}-${syncRuleId}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected async terminateSyncRules(syncRuleStorage: storage.SyncRulesBucketStorage) {
|
|
219
|
+
this.logger.info(`Terminating sync rules: ${syncRuleStorage.group_id}...`);
|
|
220
|
+
try {
|
|
221
|
+
await this.cleanUp(syncRuleStorage);
|
|
222
|
+
await syncRuleStorage.terminate();
|
|
223
|
+
this.logger.info(`Successfully terminated sync rules: ${syncRuleStorage.group_id}`);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
this.logger.warn(`Failed clean up replication config for sync rules: ${syncRuleStorage.group_id}`, e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -1,50 +1,6 @@
|
|
|
1
|
-
import { setTimeout } from 'timers/promises';
|
|
2
|
-
|
|
3
1
|
export interface ErrorRateLimiter {
|
|
4
2
|
waitUntilAllowed(options?: { signal?: AbortSignal }): Promise<void>;
|
|
5
3
|
reportError(e: any): void;
|
|
6
4
|
|
|
7
5
|
mayPing(): boolean;
|
|
8
6
|
}
|
|
9
|
-
|
|
10
|
-
export class DefaultErrorRateLimiter implements ErrorRateLimiter {
|
|
11
|
-
nextAllowed: number = Date.now();
|
|
12
|
-
|
|
13
|
-
async waitUntilAllowed(options?: { signal?: AbortSignal | undefined } | undefined): Promise<void> {
|
|
14
|
-
const delay = Math.max(0, this.nextAllowed - Date.now());
|
|
15
|
-
// Minimum delay between connections, even without errors
|
|
16
|
-
this.setDelay(500);
|
|
17
|
-
await setTimeout(delay, undefined, { signal: options?.signal });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
mayPing(): boolean {
|
|
21
|
-
return Date.now() >= this.nextAllowed;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
reportError(e: any): void {
|
|
25
|
-
const message = (e.message as string) ?? '';
|
|
26
|
-
if (message.includes('password authentication failed')) {
|
|
27
|
-
// Wait 15 minutes, to avoid triggering Supabase's fail2ban
|
|
28
|
-
this.setDelay(900_000);
|
|
29
|
-
} else if (message.includes('ENOTFOUND')) {
|
|
30
|
-
// DNS lookup issue - incorrect URI or deleted instance
|
|
31
|
-
this.setDelay(120_000);
|
|
32
|
-
} else if (message.includes('ECONNREFUSED')) {
|
|
33
|
-
// Could be fail2ban or similar
|
|
34
|
-
this.setDelay(120_000);
|
|
35
|
-
} else if (
|
|
36
|
-
message.includes('Unable to do postgres query on ended pool') ||
|
|
37
|
-
message.includes('Postgres unexpectedly closed connection')
|
|
38
|
-
) {
|
|
39
|
-
// Connection timed out - ignore / immediately retry
|
|
40
|
-
// We don't explicitly set the delay to 0, since there could have been another error that
|
|
41
|
-
// we need to respect.
|
|
42
|
-
} else {
|
|
43
|
-
this.setDelay(30_000);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private setDelay(delay: number) {
|
|
48
|
-
this.nextAllowed = Math.max(this.nextAllowed, Date.now() + delay);
|
|
49
|
-
}
|
|
50
|
-
}
|