@powersync/service-core 0.0.2
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/.probes/.gitkeep +0 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE +67 -0
- package/README.md +3 -0
- package/dist/api/api-index.d.ts +2 -0
- package/dist/api/api-index.js +3 -0
- package/dist/api/api-index.js.map +1 -0
- package/dist/api/diagnostics.d.ts +21 -0
- package/dist/api/diagnostics.js +183 -0
- package/dist/api/diagnostics.js.map +1 -0
- package/dist/api/schema.d.ts +5 -0
- package/dist/api/schema.js +88 -0
- package/dist/api/schema.js.map +1 -0
- package/dist/auth/CachedKeyCollector.d.ts +46 -0
- package/dist/auth/CachedKeyCollector.js +116 -0
- package/dist/auth/CachedKeyCollector.js.map +1 -0
- package/dist/auth/CompoundKeyCollector.d.ts +8 -0
- package/dist/auth/CompoundKeyCollector.js +23 -0
- package/dist/auth/CompoundKeyCollector.js.map +1 -0
- package/dist/auth/JwtPayload.d.ts +10 -0
- package/dist/auth/JwtPayload.js +2 -0
- package/dist/auth/JwtPayload.js.map +1 -0
- package/dist/auth/KeyCollector.d.ts +24 -0
- package/dist/auth/KeyCollector.js +2 -0
- package/dist/auth/KeyCollector.js.map +1 -0
- package/dist/auth/KeySpec.d.ts +26 -0
- package/dist/auth/KeySpec.js +49 -0
- package/dist/auth/KeySpec.js.map +1 -0
- package/dist/auth/KeyStore.d.ts +39 -0
- package/dist/auth/KeyStore.js +131 -0
- package/dist/auth/KeyStore.js.map +1 -0
- package/dist/auth/LeakyBucket.d.ts +39 -0
- package/dist/auth/LeakyBucket.js +57 -0
- package/dist/auth/LeakyBucket.js.map +1 -0
- package/dist/auth/RemoteJWKSCollector.d.ts +24 -0
- package/dist/auth/RemoteJWKSCollector.js +106 -0
- package/dist/auth/RemoteJWKSCollector.js.map +1 -0
- package/dist/auth/StaticKeyCollector.d.ts +14 -0
- package/dist/auth/StaticKeyCollector.js +19 -0
- package/dist/auth/StaticKeyCollector.js.map +1 -0
- package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
- package/dist/auth/SupabaseKeyCollector.js +61 -0
- package/dist/auth/SupabaseKeyCollector.js.map +1 -0
- package/dist/auth/auth-index.d.ts +10 -0
- package/dist/auth/auth-index.js +11 -0
- package/dist/auth/auth-index.js.map +1 -0
- package/dist/db/db-index.d.ts +1 -0
- package/dist/db/db-index.js +2 -0
- package/dist/db/db-index.js.map +1 -0
- package/dist/db/mongo.d.ts +29 -0
- package/dist/db/mongo.js +65 -0
- package/dist/db/mongo.js.map +1 -0
- package/dist/entry/cli-entry.d.ts +15 -0
- package/dist/entry/cli-entry.js +36 -0
- package/dist/entry/cli-entry.js.map +1 -0
- package/dist/entry/commands/config-command.d.ts +10 -0
- package/dist/entry/commands/config-command.js +21 -0
- package/dist/entry/commands/config-command.js.map +1 -0
- package/dist/entry/commands/migrate-action.d.ts +2 -0
- package/dist/entry/commands/migrate-action.js +18 -0
- package/dist/entry/commands/migrate-action.js.map +1 -0
- package/dist/entry/commands/start-action.d.ts +3 -0
- package/dist/entry/commands/start-action.js +15 -0
- package/dist/entry/commands/start-action.js.map +1 -0
- package/dist/entry/commands/teardown-action.d.ts +2 -0
- package/dist/entry/commands/teardown-action.js +17 -0
- package/dist/entry/commands/teardown-action.js.map +1 -0
- package/dist/entry/entry-index.d.ts +5 -0
- package/dist/entry/entry-index.js +6 -0
- package/dist/entry/entry-index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/metrics.d.ts +16 -0
- package/dist/metrics/metrics.js +139 -0
- package/dist/metrics/metrics.js.map +1 -0
- package/dist/migrations/db/migrations/1684951997326-init.d.ts +3 -0
- package/dist/migrations/db/migrations/1684951997326-init.js +31 -0
- package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -0
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +2 -0
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +5 -0
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -0
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +3 -0
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +54 -0
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -0
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +27 -0
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -0
- package/dist/migrations/db/store.d.ts +3 -0
- package/dist/migrations/db/store.js +10 -0
- package/dist/migrations/db/store.js.map +1 -0
- package/dist/migrations/migrations.d.ts +10 -0
- package/dist/migrations/migrations.js +94 -0
- package/dist/migrations/migrations.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.d.ts +17 -0
- package/dist/replication/ErrorRateLimiter.js +42 -0
- package/dist/replication/ErrorRateLimiter.js.map +1 -0
- package/dist/replication/PgRelation.d.ts +16 -0
- package/dist/replication/PgRelation.js +26 -0
- package/dist/replication/PgRelation.js.map +1 -0
- package/dist/replication/WalConnection.d.ts +34 -0
- package/dist/replication/WalConnection.js +190 -0
- package/dist/replication/WalConnection.js.map +1 -0
- package/dist/replication/WalStream.d.ts +58 -0
- package/dist/replication/WalStream.js +517 -0
- package/dist/replication/WalStream.js.map +1 -0
- package/dist/replication/WalStreamManager.d.ts +30 -0
- package/dist/replication/WalStreamManager.js +199 -0
- package/dist/replication/WalStreamManager.js.map +1 -0
- package/dist/replication/WalStreamRunner.d.ts +38 -0
- package/dist/replication/WalStreamRunner.js +155 -0
- package/dist/replication/WalStreamRunner.js.map +1 -0
- package/dist/replication/replication-index.d.ts +7 -0
- package/dist/replication/replication-index.js +8 -0
- package/dist/replication/replication-index.js.map +1 -0
- package/dist/replication/util.d.ts +9 -0
- package/dist/replication/util.js +62 -0
- package/dist/replication/util.js.map +1 -0
- package/dist/routes/admin.d.ts +7 -0
- package/dist/routes/admin.js +192 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.d.ts +58 -0
- package/dist/routes/auth.js +182 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/checkpointing.d.ts +3 -0
- package/dist/routes/checkpointing.js +30 -0
- package/dist/routes/checkpointing.js.map +1 -0
- package/dist/routes/dev.d.ts +6 -0
- package/dist/routes/dev.js +163 -0
- package/dist/routes/dev.js.map +1 -0
- package/dist/routes/route-generators.d.ts +15 -0
- package/dist/routes/route-generators.js +32 -0
- package/dist/routes/route-generators.js.map +1 -0
- package/dist/routes/router-socket.d.ts +10 -0
- package/dist/routes/router-socket.js +5 -0
- package/dist/routes/router-socket.js.map +1 -0
- package/dist/routes/router.d.ts +13 -0
- package/dist/routes/router.js +2 -0
- package/dist/routes/router.js.map +1 -0
- package/dist/routes/routes-index.d.ts +4 -0
- package/dist/routes/routes-index.js +5 -0
- package/dist/routes/routes-index.js.map +1 -0
- package/dist/routes/socket-route.d.ts +2 -0
- package/dist/routes/socket-route.js +119 -0
- package/dist/routes/socket-route.js.map +1 -0
- package/dist/routes/sync-rules.d.ts +6 -0
- package/dist/routes/sync-rules.js +182 -0
- package/dist/routes/sync-rules.js.map +1 -0
- package/dist/routes/sync-stream.d.ts +5 -0
- package/dist/routes/sync-stream.js +74 -0
- package/dist/routes/sync-stream.js.map +1 -0
- package/dist/runner/teardown.d.ts +2 -0
- package/dist/runner/teardown.js +79 -0
- package/dist/runner/teardown.js.map +1 -0
- package/dist/storage/BucketStorage.d.ts +298 -0
- package/dist/storage/BucketStorage.js +25 -0
- package/dist/storage/BucketStorage.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +51 -0
- package/dist/storage/MongoBucketStorage.js +388 -0
- package/dist/storage/MongoBucketStorage.js.map +1 -0
- package/dist/storage/SourceTable.d.ts +39 -0
- package/dist/storage/SourceTable.js +50 -0
- package/dist/storage/SourceTable.js.map +1 -0
- package/dist/storage/mongo/MongoBucketBatch.d.ts +48 -0
- package/dist/storage/mongo/MongoBucketBatch.js +584 -0
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -0
- package/dist/storage/mongo/MongoIdSequence.d.ts +12 -0
- package/dist/storage/mongo/MongoIdSequence.js +21 -0
- package/dist/storage/mongo/MongoIdSequence.js.map +1 -0
- package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +9 -0
- package/dist/storage/mongo/MongoPersistedSyncRules.js +9 -0
- package/dist/storage/mongo/MongoPersistedSyncRules.js.map +1 -0
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +20 -0
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +26 -0
- package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +27 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.js +379 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -0
- package/dist/storage/mongo/MongoSyncRulesLock.d.ts +16 -0
- package/dist/storage/mongo/MongoSyncRulesLock.js +65 -0
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -0
- package/dist/storage/mongo/OperationBatch.d.ts +26 -0
- package/dist/storage/mongo/OperationBatch.js +101 -0
- package/dist/storage/mongo/OperationBatch.js.map +1 -0
- package/dist/storage/mongo/PersistedBatch.d.ts +42 -0
- package/dist/storage/mongo/PersistedBatch.js +200 -0
- package/dist/storage/mongo/PersistedBatch.js.map +1 -0
- package/dist/storage/mongo/db.d.ts +23 -0
- package/dist/storage/mongo/db.js +34 -0
- package/dist/storage/mongo/db.js.map +1 -0
- package/dist/storage/mongo/models.d.ts +137 -0
- package/dist/storage/mongo/models.js +27 -0
- package/dist/storage/mongo/models.js.map +1 -0
- package/dist/storage/mongo/util.d.ts +26 -0
- package/dist/storage/mongo/util.js +81 -0
- package/dist/storage/mongo/util.js.map +1 -0
- package/dist/storage/storage-index.d.ts +14 -0
- package/dist/storage/storage-index.js +15 -0
- package/dist/storage/storage-index.js.map +1 -0
- package/dist/sync/BroadcastIterable.d.ts +38 -0
- package/dist/sync/BroadcastIterable.js +153 -0
- package/dist/sync/BroadcastIterable.js.map +1 -0
- package/dist/sync/LastValueSink.d.ts +25 -0
- package/dist/sync/LastValueSink.js +84 -0
- package/dist/sync/LastValueSink.js.map +1 -0
- package/dist/sync/merge.d.ts +39 -0
- package/dist/sync/merge.js +175 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/safeRace.d.ts +1 -0
- package/dist/sync/safeRace.js +91 -0
- package/dist/sync/safeRace.js.map +1 -0
- package/dist/sync/sync-index.d.ts +6 -0
- package/dist/sync/sync-index.js +7 -0
- package/dist/sync/sync-index.js.map +1 -0
- package/dist/sync/sync.d.ts +18 -0
- package/dist/sync/sync.js +248 -0
- package/dist/sync/sync.js.map +1 -0
- package/dist/sync/util.d.ts +26 -0
- package/dist/sync/util.js +73 -0
- package/dist/sync/util.js.map +1 -0
- package/dist/system/CorePowerSyncSystem.d.ts +18 -0
- package/dist/system/CorePowerSyncSystem.js +28 -0
- package/dist/system/CorePowerSyncSystem.js.map +1 -0
- package/dist/util/Mutex.d.ts +47 -0
- package/dist/util/Mutex.js +132 -0
- package/dist/util/Mutex.js.map +1 -0
- package/dist/util/PgManager.d.ts +24 -0
- package/dist/util/PgManager.js +55 -0
- package/dist/util/PgManager.js.map +1 -0
- package/dist/util/alerting.d.ts +4 -0
- package/dist/util/alerting.js +14 -0
- package/dist/util/alerting.js.map +1 -0
- package/dist/util/config/collectors/config-collector.d.ts +29 -0
- package/dist/util/config/collectors/config-collector.js +116 -0
- package/dist/util/config/collectors/config-collector.js.map +1 -0
- package/dist/util/config/collectors/impl/base64-config-collector.d.ts +6 -0
- package/dist/util/config/collectors/impl/base64-config-collector.js +15 -0
- package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -0
- package/dist/util/config/collectors/impl/fallback-config-collector.d.ts +11 -0
- package/dist/util/config/collectors/impl/fallback-config-collector.js +19 -0
- package/dist/util/config/collectors/impl/fallback-config-collector.js.map +1 -0
- package/dist/util/config/collectors/impl/filesystem-config-collector.d.ts +6 -0
- package/dist/util/config/collectors/impl/filesystem-config-collector.js +35 -0
- package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -0
- package/dist/util/config/compound-config-collector.d.ts +32 -0
- package/dist/util/config/compound-config-collector.js +126 -0
- package/dist/util/config/compound-config-collector.js.map +1 -0
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.d.ts +7 -0
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +17 -0
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -0
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.d.ts +7 -0
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +21 -0
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -0
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.d.ts +7 -0
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js +17 -0
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -0
- package/dist/util/config/sync-rules/sync-collector.d.ts +6 -0
- package/dist/util/config/sync-rules/sync-collector.js +3 -0
- package/dist/util/config/sync-rules/sync-collector.js.map +1 -0
- package/dist/util/config/types.d.ts +53 -0
- package/dist/util/config/types.js +7 -0
- package/dist/util/config/types.js.map +1 -0
- package/dist/util/config.d.ts +7 -0
- package/dist/util/config.js +35 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/env.d.ts +10 -0
- package/dist/util/env.js +25 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/memory-tracking.d.ts +7 -0
- package/dist/util/memory-tracking.js +58 -0
- package/dist/util/memory-tracking.js.map +1 -0
- package/dist/util/migration_lib.d.ts +11 -0
- package/dist/util/migration_lib.js +64 -0
- package/dist/util/migration_lib.js.map +1 -0
- package/dist/util/pgwire_utils.d.ts +24 -0
- package/dist/util/pgwire_utils.js +117 -0
- package/dist/util/pgwire_utils.js.map +1 -0
- package/dist/util/populate_test_data.d.ts +8 -0
- package/dist/util/populate_test_data.js +65 -0
- package/dist/util/populate_test_data.js.map +1 -0
- package/dist/util/protocol-types.d.ts +178 -0
- package/dist/util/protocol-types.js +38 -0
- package/dist/util/protocol-types.js.map +1 -0
- package/dist/util/secs.d.ts +2 -0
- package/dist/util/secs.js +49 -0
- package/dist/util/secs.js.map +1 -0
- package/dist/util/util-index.d.ts +22 -0
- package/dist/util/util-index.js +23 -0
- package/dist/util/util-index.js.map +1 -0
- package/dist/util/utils.d.ts +14 -0
- package/dist/util/utils.js +75 -0
- package/dist/util/utils.js.map +1 -0
- package/package.json +55 -0
- package/src/api/api-index.ts +2 -0
- package/src/api/diagnostics.ts +221 -0
- package/src/api/schema.ts +99 -0
- package/src/auth/CachedKeyCollector.ts +132 -0
- package/src/auth/CompoundKeyCollector.ts +33 -0
- package/src/auth/JwtPayload.ts +11 -0
- package/src/auth/KeyCollector.ts +27 -0
- package/src/auth/KeySpec.ts +67 -0
- package/src/auth/KeyStore.ts +156 -0
- package/src/auth/LeakyBucket.ts +66 -0
- package/src/auth/RemoteJWKSCollector.ts +130 -0
- package/src/auth/StaticKeyCollector.ts +21 -0
- package/src/auth/SupabaseKeyCollector.ts +67 -0
- package/src/auth/auth-index.ts +10 -0
- package/src/db/db-index.ts +1 -0
- package/src/db/mongo.ts +72 -0
- package/src/entry/cli-entry.ts +41 -0
- package/src/entry/commands/config-command.ts +36 -0
- package/src/entry/commands/migrate-action.ts +25 -0
- package/src/entry/commands/start-action.ts +24 -0
- package/src/entry/commands/teardown-action.ts +23 -0
- package/src/entry/entry-index.ts +5 -0
- package/src/index.ts +37 -0
- package/src/metrics/metrics.ts +169 -0
- package/src/migrations/db/migrations/1684951997326-init.ts +33 -0
- package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +5 -0
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +99 -0
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +32 -0
- package/src/migrations/db/store.ts +11 -0
- package/src/migrations/migrations.ts +122 -0
- package/src/replication/ErrorRateLimiter.ts +49 -0
- package/src/replication/PgRelation.ts +42 -0
- package/src/replication/WalConnection.ts +227 -0
- package/src/replication/WalStream.ts +626 -0
- package/src/replication/WalStreamManager.ts +214 -0
- package/src/replication/WalStreamRunner.ts +180 -0
- package/src/replication/replication-index.ts +7 -0
- package/src/replication/util.ts +76 -0
- package/src/routes/admin.ts +229 -0
- package/src/routes/auth.ts +209 -0
- package/src/routes/checkpointing.ts +38 -0
- package/src/routes/dev.ts +194 -0
- package/src/routes/route-generators.ts +39 -0
- package/src/routes/router-socket.ts +13 -0
- package/src/routes/router.ts +17 -0
- package/src/routes/routes-index.ts +5 -0
- package/src/routes/socket-route.ts +131 -0
- package/src/routes/sync-rules.ts +210 -0
- package/src/routes/sync-stream.ts +92 -0
- package/src/runner/teardown.ts +91 -0
- package/src/storage/BucketStorage.ts +386 -0
- package/src/storage/MongoBucketStorage.ts +493 -0
- package/src/storage/SourceTable.ts +60 -0
- package/src/storage/mongo/MongoBucketBatch.ts +756 -0
- package/src/storage/mongo/MongoIdSequence.ts +24 -0
- package/src/storage/mongo/MongoPersistedSyncRules.ts +16 -0
- package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +47 -0
- package/src/storage/mongo/MongoSyncBucketStorage.ts +517 -0
- package/src/storage/mongo/MongoSyncRulesLock.ts +81 -0
- package/src/storage/mongo/OperationBatch.ts +115 -0
- package/src/storage/mongo/PersistedBatch.ts +245 -0
- package/src/storage/mongo/db.ts +69 -0
- package/src/storage/mongo/models.ts +157 -0
- package/src/storage/mongo/util.ts +88 -0
- package/src/storage/storage-index.ts +15 -0
- package/src/sync/BroadcastIterable.ts +161 -0
- package/src/sync/LastValueSink.ts +100 -0
- package/src/sync/merge.ts +200 -0
- package/src/sync/safeRace.ts +99 -0
- package/src/sync/sync-index.ts +6 -0
- package/src/sync/sync.ts +312 -0
- package/src/sync/util.ts +98 -0
- package/src/system/CorePowerSyncSystem.ts +43 -0
- package/src/util/Mutex.ts +159 -0
- package/src/util/PgManager.ts +64 -0
- package/src/util/alerting.ts +17 -0
- package/src/util/config/collectors/config-collector.ts +141 -0
- package/src/util/config/collectors/impl/base64-config-collector.ts +18 -0
- package/src/util/config/collectors/impl/fallback-config-collector.ts +22 -0
- package/src/util/config/collectors/impl/filesystem-config-collector.ts +41 -0
- package/src/util/config/compound-config-collector.ts +171 -0
- package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +21 -0
- package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +26 -0
- package/src/util/config/sync-rules/impl/inline-sync-rules-collector.ts +21 -0
- package/src/util/config/sync-rules/sync-collector.ts +8 -0
- package/src/util/config/types.ts +60 -0
- package/src/util/config.ts +39 -0
- package/src/util/env.ts +28 -0
- package/src/util/memory-tracking.ts +67 -0
- package/src/util/migration_lib.ts +79 -0
- package/src/util/pgwire_utils.ts +139 -0
- package/src/util/populate_test_data.ts +78 -0
- package/src/util/protocol-types.ts +223 -0
- package/src/util/secs.ts +54 -0
- package/src/util/util-index.ts +25 -0
- package/src/util/utils.ts +102 -0
- package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
- package/test/src/__snapshots__/sync.test.ts.snap +235 -0
- package/test/src/auth.test.ts +340 -0
- package/test/src/broadcast_iterable.test.ts +156 -0
- package/test/src/data_storage.test.ts +1176 -0
- package/test/src/env.ts +8 -0
- package/test/src/large_batch.test.ts +194 -0
- package/test/src/merge_iterable.test.ts +355 -0
- package/test/src/pg_test.test.ts +432 -0
- package/test/src/schema_changes.test.ts +545 -0
- package/test/src/slow_tests.test.ts +257 -0
- package/test/src/sql_functions.test.ts +254 -0
- package/test/src/sql_operators.test.ts +132 -0
- package/test/src/sync.test.ts +293 -0
- package/test/src/sync_rules.test.ts +1051 -0
- package/test/src/util.ts +67 -0
- package/test/src/validation.test.ts +63 -0
- package/test/src/wal_stream.test.ts +310 -0
- package/test/src/wal_stream_utils.ts +147 -0
- package/test/tsconfig.json +20 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import secs from '../util/secs.js';
|
|
3
|
+
import { KeyOptions, KeySpec, SUPPORTED_ALGORITHMS } from './KeySpec.js';
|
|
4
|
+
import { KeyCollector } from './KeyCollector.js';
|
|
5
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
6
|
+
import { JwtPayload } from './JwtPayload.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* KeyStore to get keys and verify tokens.
|
|
10
|
+
*
|
|
11
|
+
*
|
|
12
|
+
* Similar to micro_auth's KeyStore, but with different caching and error handling.
|
|
13
|
+
*
|
|
14
|
+
* We generally assume that:
|
|
15
|
+
* 1. If we have a key kid matching a JWT kid, that is the correct key.
|
|
16
|
+
* We don't look for other keys, even if there are algorithm or other issues.
|
|
17
|
+
* 2. Otherwise, iterate through "wildcard" keys and look for a matching signature.
|
|
18
|
+
* Wildcard keys are any key defined without a kid.
|
|
19
|
+
*
|
|
20
|
+
* # Security considerations
|
|
21
|
+
*
|
|
22
|
+
* Some places for security holes:
|
|
23
|
+
* 1. Signature verification not done correctly: We rely on jose.jwtVerify() to do this correctly.
|
|
24
|
+
* 2. Using a key that has been revoked - see CachedKeyCollector's refresh strategy.
|
|
25
|
+
* 3. Using a key for the wrong purpose (e.g. key.use != 'sig'). Checked in RemoteJWKSCollector.
|
|
26
|
+
* 4. Not checking all attributes, e.g. a JWT trusted by the global firebase key, but has the wrong aud. Correct aud must be configured.
|
|
27
|
+
* 5. Using the incorrect algorithm, e.g. 'none', or using public key as a shared key.
|
|
28
|
+
* We check the algorithm for each JWT against the matching key's configured algorithm or algorithm family.
|
|
29
|
+
*
|
|
30
|
+
* # Errors
|
|
31
|
+
*
|
|
32
|
+
* If we have a matching kid, we can generally get a detailed error (e.g. signature verification failed, invalid algorithm, etc).
|
|
33
|
+
* If we don't have a matching kid, we'll generally just get an error "Could not find an appropriate key...".
|
|
34
|
+
*/
|
|
35
|
+
export class KeyStore {
|
|
36
|
+
private collector: KeyCollector;
|
|
37
|
+
|
|
38
|
+
constructor(collector: KeyCollector) {
|
|
39
|
+
this.collector = collector;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async verifyJwt(token: string, options: { defaultAudiences: string[]; maxAge: string }): Promise<JwtPayload> {
|
|
43
|
+
const { result, keyOptions } = await this.verifyInternal(token, {
|
|
44
|
+
// audience is not checked here, since we vary the allowed audience based on the key
|
|
45
|
+
// audience: options.defaultAudiences,
|
|
46
|
+
clockTolerance: 60,
|
|
47
|
+
// More specific algorithm checking is done when selecting the key to use.
|
|
48
|
+
algorithms: SUPPORTED_ALGORITHMS,
|
|
49
|
+
requiredClaims: ['aud', 'sub', 'iat', 'exp']
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
let audiences = options.defaultAudiences;
|
|
53
|
+
if (keyOptions.requiresAudience) {
|
|
54
|
+
// Replace the audience, don't add
|
|
55
|
+
audiences = keyOptions.requiresAudience;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tokenPayload = result.payload;
|
|
59
|
+
|
|
60
|
+
let aud = tokenPayload.aud!;
|
|
61
|
+
if (!Array.isArray(aud)) {
|
|
62
|
+
aud = [aud];
|
|
63
|
+
}
|
|
64
|
+
if (
|
|
65
|
+
!aud.some((a) => {
|
|
66
|
+
return audiences.includes(a);
|
|
67
|
+
})
|
|
68
|
+
) {
|
|
69
|
+
throw new jose.errors.JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const tokenDuration = tokenPayload.exp! - tokenPayload.iat!;
|
|
73
|
+
|
|
74
|
+
// Implement our own maxAge validation, that rejects the token immediately if expiration
|
|
75
|
+
// is too far into the future.
|
|
76
|
+
const maxAge = keyOptions.maxLifetimeSeconds ?? secs(options.maxAge);
|
|
77
|
+
if (tokenDuration > maxAge) {
|
|
78
|
+
throw new jose.errors.JWTInvalid(`Token must expire in a maximum of ${maxAge} seconds, got ${tokenDuration}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const parameters = tokenPayload.parameters;
|
|
82
|
+
if (parameters != null && (Array.isArray(parameters) || typeof parameters != 'object')) {
|
|
83
|
+
throw new jose.errors.JWTInvalid('parameters must be an object');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...(tokenPayload as any),
|
|
88
|
+
parameters: {
|
|
89
|
+
user_id: tokenPayload.sub,
|
|
90
|
+
...parameters
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async verifyInternal(token: string, options: jose.JWTVerifyOptions) {
|
|
96
|
+
let keyOptions: KeyOptions | undefined = undefined;
|
|
97
|
+
const result = await jose.jwtVerify(
|
|
98
|
+
token,
|
|
99
|
+
async (header) => {
|
|
100
|
+
let key = await this.getCachedKey(token, header);
|
|
101
|
+
keyOptions = key.options;
|
|
102
|
+
return key.key;
|
|
103
|
+
},
|
|
104
|
+
options
|
|
105
|
+
);
|
|
106
|
+
return { result, keyOptions: keyOptions! };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async getCachedKey(token: string, header: jose.JWTHeaderParameters): Promise<KeySpec> {
|
|
110
|
+
const kid = header.kid;
|
|
111
|
+
const { keys, errors } = await this.collector.getKeys();
|
|
112
|
+
if (kid) {
|
|
113
|
+
// key has kid: JWK with exact kid, or JWK without kid
|
|
114
|
+
// key without kid: JWK without kid only
|
|
115
|
+
for (let key of keys) {
|
|
116
|
+
if (key.kid == kid) {
|
|
117
|
+
if (!key.matchesAlgorithm(header.alg)) {
|
|
118
|
+
throw new jose.errors.JOSEAlgNotAllowed(`Unexpected token algorithm ${header.alg}`);
|
|
119
|
+
}
|
|
120
|
+
return key;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (let key of keys) {
|
|
126
|
+
// Checks signature and algorithm
|
|
127
|
+
if (key.kid != null) {
|
|
128
|
+
// Not a wildcard key
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!key.matchesAlgorithm(header.alg)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (await key.isValidSignature(token)) {
|
|
136
|
+
return key;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (errors.length > 0) {
|
|
141
|
+
throw errors[0];
|
|
142
|
+
} else {
|
|
143
|
+
// No key found
|
|
144
|
+
// Trigger refresh of the keys - might be ready by the next request.
|
|
145
|
+
this.collector.noKeyFound?.().catch((e) => {
|
|
146
|
+
// Typically this error would be stored on the collector.
|
|
147
|
+
// This is just a last resort error handling.
|
|
148
|
+
micro.logger.error(`Failed to refresh keys`, e);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
throw new jose.errors.JOSEError(
|
|
152
|
+
'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variation on a leaky bucket rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* The base is a leaky bucket:
|
|
5
|
+
* * The bucket is filled at a certain rate
|
|
6
|
+
* * The bucket has a max capacity
|
|
7
|
+
*
|
|
8
|
+
* This gives an initial burst capacity, then continues
|
|
9
|
+
* at the refill rate when the burst capacity is depleted.
|
|
10
|
+
*
|
|
11
|
+
* This variation introduces an additional quadratic delay
|
|
12
|
+
* during the initial burst, which prevents the burst capacity
|
|
13
|
+
* from being consumed immediately. The steady-state rate
|
|
14
|
+
* is still the same.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* Steady state rate: x requests / ms.
|
|
18
|
+
* Steady state period: 1 / rate
|
|
19
|
+
*
|
|
20
|
+
* capacity: number of requests before steady state
|
|
21
|
+
*
|
|
22
|
+
* capacity == max_capacity: no delay between requests
|
|
23
|
+
* capacity = 0: delay = Steady state period
|
|
24
|
+
*
|
|
25
|
+
* variable_delay = (max_capacity - capacity)^2 / max_capacity^2 * period
|
|
26
|
+
*/
|
|
27
|
+
export class LeakyBucket {
|
|
28
|
+
private capacity: number;
|
|
29
|
+
private lastRequest: number;
|
|
30
|
+
private maxCapacity: number;
|
|
31
|
+
private periodMs: number;
|
|
32
|
+
|
|
33
|
+
public lastGrantedRequest: number;
|
|
34
|
+
|
|
35
|
+
constructor(options: { maxCapacity: number; periodMs: number }) {
|
|
36
|
+
this.capacity = options.maxCapacity;
|
|
37
|
+
this.maxCapacity = options.maxCapacity;
|
|
38
|
+
this.periodMs = options.periodMs;
|
|
39
|
+
this.lastRequest = 0;
|
|
40
|
+
this.lastGrantedRequest = 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
allowed(): boolean {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const elapsed = now - this.lastRequest;
|
|
46
|
+
const leaked = elapsed / this.periodMs;
|
|
47
|
+
this.capacity = Math.min(this.capacity + leaked, this.maxCapacity);
|
|
48
|
+
this.lastRequest = now;
|
|
49
|
+
|
|
50
|
+
const capacityUsed = this.maxCapacity - this.capacity;
|
|
51
|
+
const variableDelay = ((capacityUsed * capacityUsed) / (this.maxCapacity * this.maxCapacity)) * this.periodMs;
|
|
52
|
+
|
|
53
|
+
if (this.capacity >= 1 && variableDelay <= now - this.lastGrantedRequest) {
|
|
54
|
+
this.capacity -= 1;
|
|
55
|
+
this.lastGrantedRequest = now;
|
|
56
|
+
return true;
|
|
57
|
+
} else {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
reset() {
|
|
63
|
+
this.capacity = this.maxCapacity;
|
|
64
|
+
this.lastGrantedRequest = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as https from 'https';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
import * as dns from 'dns/promises';
|
|
4
|
+
import ip from 'ipaddr.js';
|
|
5
|
+
import * as jose from 'jose';
|
|
6
|
+
import * as net from 'net';
|
|
7
|
+
import fetch from 'node-fetch';
|
|
8
|
+
|
|
9
|
+
import { KeySpec } from './KeySpec.js';
|
|
10
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
11
|
+
|
|
12
|
+
export type RemoteJWKSCollectorOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* Blocks IP Ranges from the BLOCKED_IP_RANGES array
|
|
15
|
+
*/
|
|
16
|
+
block_local_ip?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set of keys fetched from JWKS URI.
|
|
21
|
+
*/
|
|
22
|
+
export class RemoteJWKSCollector implements KeyCollector {
|
|
23
|
+
private url: URL;
|
|
24
|
+
|
|
25
|
+
constructor(url: string, protected options?: RemoteJWKSCollectorOptions) {
|
|
26
|
+
try {
|
|
27
|
+
this.url = new URL(url);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
throw new Error(`Invalid jwks_uri: ${url}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// We do support http here for self-hosting use cases.
|
|
33
|
+
// Management service restricts this to https for hosted versions.
|
|
34
|
+
if (this.url.protocol != 'https:' && this.url.protocol != 'http:') {
|
|
35
|
+
throw new Error(`Only http(s) is supported for jwks_uri, got: ${url}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getKeys(): Promise<KeyResult> {
|
|
40
|
+
const abortController = new AbortController();
|
|
41
|
+
const timeout = setTimeout(() => {
|
|
42
|
+
abortController.abort();
|
|
43
|
+
}, 30_000);
|
|
44
|
+
|
|
45
|
+
const res = await fetch(this.url, {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers: {
|
|
48
|
+
Accept: 'application/json'
|
|
49
|
+
},
|
|
50
|
+
signal: abortController.signal,
|
|
51
|
+
agent: await this.resolveAgent()
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
throw new jose.errors.JWKSInvalid(`JWKS request failed with ${res.statusText}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const data = (await res.json()) as any;
|
|
59
|
+
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
|
|
62
|
+
// https://github.com/panva/jose/blob/358e864a0cccf1e0f9928a959f91f18f3f06a7de/src/jwks/local.ts#L36
|
|
63
|
+
if (
|
|
64
|
+
data.keys == null ||
|
|
65
|
+
!Array.isArray(data.keys) ||
|
|
66
|
+
!(data.keys as any[]).every((key) => typeof key == 'object' && !Array.isArray(key))
|
|
67
|
+
) {
|
|
68
|
+
return { keys: [], errors: [new jose.errors.JWKSInvalid(`No keys in found in JWKS response`)] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let keys: KeySpec[] = [];
|
|
72
|
+
for (let keyData of data.keys) {
|
|
73
|
+
if (keyData.kty != 'RSA') {
|
|
74
|
+
// Only RSA public keys supported here
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof keyData.use == 'string') {
|
|
79
|
+
if (keyData.use != 'sig') {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(keyData.key_ops)) {
|
|
84
|
+
if (!keyData.key_ops.includes('verify')) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const key = await KeySpec.importKey(keyData);
|
|
90
|
+
keys.push(key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { keys: keys, errors: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve IP, and check that it is in an allowed range.
|
|
98
|
+
*/
|
|
99
|
+
async resolveAgent(): Promise<http.Agent | https.Agent> {
|
|
100
|
+
const hostname = this.url.hostname;
|
|
101
|
+
let resolved_ip: string;
|
|
102
|
+
if (net.isIPv6(hostname)) {
|
|
103
|
+
throw new Error('IPv6 not supported yet');
|
|
104
|
+
} else if (net.isIPv4(hostname)) {
|
|
105
|
+
// All good
|
|
106
|
+
resolved_ip = hostname;
|
|
107
|
+
} else {
|
|
108
|
+
resolved_ip = (await dns.resolve4(hostname))[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parsed = ip.parse(resolved_ip);
|
|
112
|
+
if (parsed.kind() != 'ipv4' || (this.options?.block_local_ip && parsed.range() !== 'unicast')) {
|
|
113
|
+
// Do not connect to any reserved IPs, including loopback and private ranges
|
|
114
|
+
throw new Error(`IPs in this range are not supported: ${resolved_ip}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const options = {
|
|
118
|
+
// This is the host that the agent connects to
|
|
119
|
+
host: resolved_ip
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
switch (this.url.protocol) {
|
|
123
|
+
case 'http:':
|
|
124
|
+
return new http.Agent(options);
|
|
125
|
+
case 'https:':
|
|
126
|
+
return new https.Agent(options);
|
|
127
|
+
}
|
|
128
|
+
throw new Error('http or or https is required for protocol');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import { KeySpec } from './KeySpec.js';
|
|
3
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set of static keys.
|
|
7
|
+
*
|
|
8
|
+
* A key can be added both with and without a kid, in case wildcard matching is desired.
|
|
9
|
+
*/
|
|
10
|
+
export class StaticKeyCollector implements KeyCollector {
|
|
11
|
+
static async importKeys(keys: jose.JWK[]) {
|
|
12
|
+
const parsedKeys = await Promise.all(keys.map((key) => KeySpec.importKey(key)));
|
|
13
|
+
return new StaticKeyCollector(parsedKeys);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(private keys: KeySpec[]) {}
|
|
17
|
+
|
|
18
|
+
async getKeys(): Promise<KeyResult> {
|
|
19
|
+
return { keys: this.keys, errors: [] };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
3
|
+
import { connectPgWirePool, pgwireRows } from '@powersync/service-jpgwire';
|
|
4
|
+
import { KeyCollector } from './KeyCollector.js';
|
|
5
|
+
import { KeyOptions, KeySpec } from './KeySpec.js';
|
|
6
|
+
import { retriedQuery } from '../util/pgwire_utils.js';
|
|
7
|
+
import { ResolvedConnection } from '../util/config/types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetches key from the Supabase database.
|
|
11
|
+
*
|
|
12
|
+
* Unfortunately, despite the JWTs containing a kid, we have no way to lookup that kid
|
|
13
|
+
* before receiving a valid token.
|
|
14
|
+
*/
|
|
15
|
+
export class SupabaseKeyCollector implements KeyCollector {
|
|
16
|
+
private pool: pgwire.PgClient;
|
|
17
|
+
|
|
18
|
+
private keyOptions: KeyOptions = {
|
|
19
|
+
requiresAudience: ['authenticated'],
|
|
20
|
+
maxLifetimeSeconds: 86400 * 7 + 1200 // 1 week + 20 minutes margin
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
constructor(connection: ResolvedConnection) {
|
|
24
|
+
this.pool = connectPgWirePool(connection, {
|
|
25
|
+
// To avoid overloading the source database with open connections,
|
|
26
|
+
// limit to a single connection, and close the connection shortly
|
|
27
|
+
// after using it.
|
|
28
|
+
idleTimeout: 5_000,
|
|
29
|
+
maxSize: 1
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getKeys() {
|
|
34
|
+
let row: { jwt_secret: string };
|
|
35
|
+
try {
|
|
36
|
+
const rows = pgwireRows(
|
|
37
|
+
await retriedQuery(this.pool, `SELECT current_setting('app.settings.jwt_secret') as jwt_secret`)
|
|
38
|
+
);
|
|
39
|
+
row = rows[0] as any;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
if (e.message?.includes('unrecognized configuration parameter')) {
|
|
42
|
+
throw new jose.errors.JOSEError(`Generate a new JWT secret on Supabase. Cause: ${e.message}`);
|
|
43
|
+
} else {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const secret = row?.jwt_secret as string | undefined;
|
|
48
|
+
if (secret == null) {
|
|
49
|
+
return {
|
|
50
|
+
keys: [],
|
|
51
|
+
errors: [new jose.errors.JWKSNoMatchingKey()]
|
|
52
|
+
};
|
|
53
|
+
} else {
|
|
54
|
+
const key: jose.JWK = {
|
|
55
|
+
kty: 'oct',
|
|
56
|
+
alg: 'HS256',
|
|
57
|
+
// While the secret is valid base64, the base64-encoded form is the secret value.
|
|
58
|
+
k: Buffer.from(secret, 'utf8').toString('base64url')
|
|
59
|
+
};
|
|
60
|
+
const imported = await KeySpec.importKey(key, this.keyOptions);
|
|
61
|
+
return {
|
|
62
|
+
keys: [imported],
|
|
63
|
+
errors: []
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './CachedKeyCollector.js';
|
|
2
|
+
export * from './CompoundKeyCollector.js';
|
|
3
|
+
export * from './JwtPayload.js';
|
|
4
|
+
export * from './KeyCollector.js';
|
|
5
|
+
export * from './KeySpec.js';
|
|
6
|
+
export * from './KeyStore.js';
|
|
7
|
+
export * from './LeakyBucket.js';
|
|
8
|
+
export * from './RemoteJWKSCollector.js';
|
|
9
|
+
export * from './StaticKeyCollector.js';
|
|
10
|
+
export * from './SupabaseKeyCollector.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as mongo from './mongo.js';
|
package/src/db/mongo.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as mongo from 'mongodb';
|
|
2
|
+
import * as timers from 'timers/promises';
|
|
3
|
+
|
|
4
|
+
import { configFile } from '@powersync/service-types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Time for new connection to timeout.
|
|
8
|
+
*/
|
|
9
|
+
export const MONGO_CONNECT_TIMEOUT_MS = 10_000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Time for individual requests to timeout the socket.
|
|
13
|
+
*/
|
|
14
|
+
export const MONGO_SOCKET_TIMEOUT_MS = 60_000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Time for individual requests to timeout the operation.
|
|
18
|
+
*
|
|
19
|
+
* This is time spent on the cursor, not total time.
|
|
20
|
+
*
|
|
21
|
+
* Must be less than MONGO_SOCKET_TIMEOUT_MS to ensure proper error handling.
|
|
22
|
+
*/
|
|
23
|
+
export const MONGO_OPERATION_TIMEOUT_MS = 30_000;
|
|
24
|
+
|
|
25
|
+
export function createMongoClient(config: configFile.PowerSyncConfig['storage']) {
|
|
26
|
+
return new mongo.MongoClient(config.uri, {
|
|
27
|
+
auth: {
|
|
28
|
+
username: config.username,
|
|
29
|
+
password: config.password
|
|
30
|
+
},
|
|
31
|
+
// Time for connection to timeout
|
|
32
|
+
connectTimeoutMS: MONGO_CONNECT_TIMEOUT_MS,
|
|
33
|
+
// Time for individual requests to timeout
|
|
34
|
+
socketTimeoutMS: MONGO_SOCKET_TIMEOUT_MS,
|
|
35
|
+
// How long to wait for new primary selection
|
|
36
|
+
serverSelectionTimeoutMS: 30_000,
|
|
37
|
+
|
|
38
|
+
// Avoid too many connections:
|
|
39
|
+
// 1. It can overwhelm the source database.
|
|
40
|
+
// 2. Processing too many queries in parallel can cause the process to run out of memory.
|
|
41
|
+
maxPoolSize: 8,
|
|
42
|
+
|
|
43
|
+
maxConnecting: 3,
|
|
44
|
+
maxIdleTimeMS: 60_000
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wait up to a minute for authentication errors to resolve.
|
|
50
|
+
*
|
|
51
|
+
* There can be a delay between an Atlas user being created, and that user being
|
|
52
|
+
* available on the database cluster. This works around it.
|
|
53
|
+
*
|
|
54
|
+
* This is specifically relevant for migrations and teardown - other parts of the stack
|
|
55
|
+
* can generate handle these failures and just retry or restart.
|
|
56
|
+
*/
|
|
57
|
+
export async function waitForAuth(db: mongo.Db) {
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
while (Date.now() - start < 60_000) {
|
|
60
|
+
try {
|
|
61
|
+
await db.command({ ping: 1 });
|
|
62
|
+
// Success
|
|
63
|
+
break;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e.codeName == 'AuthenticationFailed') {
|
|
66
|
+
await timers.setTimeout(1_000);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
4
|
+
|
|
5
|
+
import * as utils from '@/util/util-index.js';
|
|
6
|
+
import { registerMigrationAction } from './commands/migrate-action.js';
|
|
7
|
+
import { registerTearDownAction } from './commands/teardown-action.js';
|
|
8
|
+
import { registerStartAction } from './entry-index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generates a Commander program which serves as the entry point
|
|
12
|
+
* for the PowerSync service.
|
|
13
|
+
* This registers standard actions for teardown and migrations.
|
|
14
|
+
* Optionally registers the start command handlers.
|
|
15
|
+
*/
|
|
16
|
+
export function generateEntryProgram(startHandlers?: Record<utils.ServiceRunner, utils.Runner>) {
|
|
17
|
+
const entryProgram = new Command();
|
|
18
|
+
entryProgram.name('powersync-runner').description('CLI to initiate a PowerSync service runner');
|
|
19
|
+
|
|
20
|
+
registerTearDownAction(entryProgram);
|
|
21
|
+
registerMigrationAction(entryProgram);
|
|
22
|
+
|
|
23
|
+
if (startHandlers) {
|
|
24
|
+
registerStartAction(entryProgram, startHandlers);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
program: entryProgram,
|
|
29
|
+
/**
|
|
30
|
+
* Executes the main program. Ends the NodeJS process if an exception was caught.
|
|
31
|
+
*/
|
|
32
|
+
execute: async function runProgram() {
|
|
33
|
+
try {
|
|
34
|
+
await entryProgram.parseAsync();
|
|
35
|
+
} catch (e) {
|
|
36
|
+
micro.logger.error('Fatal error', e);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import * as util from '@/util/util-index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a Command with the standard config options
|
|
7
|
+
*/
|
|
8
|
+
export function wrapConfigCommand(command: Command) {
|
|
9
|
+
return command
|
|
10
|
+
.option(
|
|
11
|
+
`-c, --config-path [path]`,
|
|
12
|
+
'Path (inside container) to YAML config file. Defaults to process.env.POWERSYNC_CONFIG_PATH',
|
|
13
|
+
util.env.POWERSYNC_CONFIG_PATH
|
|
14
|
+
)
|
|
15
|
+
.option(
|
|
16
|
+
`-c64, --config-base64 [base64]`,
|
|
17
|
+
'Base64 encoded YAML or JSON config file. Defaults to process.env.POWERSYNC_CONFIG_B64',
|
|
18
|
+
util.env.POWERSYNC_CONFIG_B64
|
|
19
|
+
)
|
|
20
|
+
.option(
|
|
21
|
+
`-sync64, --sync-base64 [base64]`,
|
|
22
|
+
'Base64 encoded YAML Sync Rules. Defaults to process.env.POWERSYNC_SYNC_RULES_B64',
|
|
23
|
+
util.env.POWERSYNC_SYNC_RULES_B64
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extracts runner configuration params from Command options.
|
|
29
|
+
*/
|
|
30
|
+
export function extractRunnerOptions(options: any): util.RunnerConfig {
|
|
31
|
+
return {
|
|
32
|
+
config_path: options.configPath,
|
|
33
|
+
config_base64: options.configBase64,
|
|
34
|
+
sync_rules_base64: options.syncBase64
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { Direction } from '@journeyapps-platform/micro-migrate';
|
|
3
|
+
|
|
4
|
+
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
5
|
+
import { migrate } from '@/migrations/migrations.js';
|
|
6
|
+
|
|
7
|
+
const COMMAND_NAME = 'migrate';
|
|
8
|
+
|
|
9
|
+
export function registerMigrationAction(program: Command) {
|
|
10
|
+
const migrationCommand = program.command(COMMAND_NAME);
|
|
11
|
+
|
|
12
|
+
wrapConfigCommand(migrationCommand);
|
|
13
|
+
|
|
14
|
+
return migrationCommand
|
|
15
|
+
.description('Run migrations')
|
|
16
|
+
.argument('<direction>', 'Migration direction. `up` or `down`')
|
|
17
|
+
.action(async (direction: Direction, options) => {
|
|
18
|
+
const runnerConfig = extractRunnerOptions(options);
|
|
19
|
+
|
|
20
|
+
await migrate({
|
|
21
|
+
direction,
|
|
22
|
+
runner_config: runnerConfig
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import * as utils from '@/util/util-index.js';
|
|
4
|
+
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
5
|
+
|
|
6
|
+
const COMMAND_NAME = 'start';
|
|
7
|
+
|
|
8
|
+
export function registerStartAction(program: Command, handlers: Record<utils.ServiceRunner, utils.Runner>) {
|
|
9
|
+
const startCommand = program.command(COMMAND_NAME);
|
|
10
|
+
|
|
11
|
+
wrapConfigCommand(startCommand);
|
|
12
|
+
|
|
13
|
+
return startCommand
|
|
14
|
+
.description('Starts a PowerSync service runner.')
|
|
15
|
+
.option(
|
|
16
|
+
`-r, --runner-type [${Object.values(utils.ServiceRunner).join('|')}]`,
|
|
17
|
+
'Type of runner to start. Defaults to unified runner.',
|
|
18
|
+
utils.env.PS_RUNNER_TYPE
|
|
19
|
+
)
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const runner = handlers[options.runnerType as utils.ServiceRunner];
|
|
22
|
+
await runner(extractRunnerOptions(options));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
4
|
+
import { teardown } from '@/runner/teardown.js';
|
|
5
|
+
|
|
6
|
+
const COMMAND_NAME = 'teardown';
|
|
7
|
+
|
|
8
|
+
export function registerTearDownAction(program: Command) {
|
|
9
|
+
const teardownCommand = program.command(COMMAND_NAME);
|
|
10
|
+
|
|
11
|
+
wrapConfigCommand(teardownCommand);
|
|
12
|
+
|
|
13
|
+
return teardownCommand
|
|
14
|
+
.argument('[ack]', 'Type `TEARDOWN` to confirm teardown should occur')
|
|
15
|
+
.description('Terminate all replicating sync rules, deleting the replication slots')
|
|
16
|
+
.action(async (ack, options) => {
|
|
17
|
+
if (ack !== 'TEARDOWN') {
|
|
18
|
+
throw new Error('TEARDOWN was not acknowledged.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await teardown(extractRunnerOptions(options));
|
|
22
|
+
});
|
|
23
|
+
}
|