@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
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@powersync/service-core",
|
|
3
|
+
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
|
+
"types": "dist/index.d.ts",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.0.2",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@journeyapps-platform/micro": "16.1.0",
|
|
14
|
+
"@journeyapps-platform/micro-authorizers": "^5.2.0",
|
|
15
|
+
"@journeyapps-platform/micro-locks": "^2.0.1",
|
|
16
|
+
"@journeyapps-platform/micro-migrate": "^3.1.0",
|
|
17
|
+
"@opentelemetry/api": "~1.6.0",
|
|
18
|
+
"@opentelemetry/exporter-prometheus": "^0.43.0",
|
|
19
|
+
"@opentelemetry/sdk-metrics": "1.17.0",
|
|
20
|
+
"async-mutex": "^0.5.0",
|
|
21
|
+
"bson": "^6.6.0",
|
|
22
|
+
"commander": "^12.0.0",
|
|
23
|
+
"cors": "^2.8.5",
|
|
24
|
+
"fastify": "4.23.2",
|
|
25
|
+
"ipaddr.js": "^2.1.0",
|
|
26
|
+
"ix": "^5.0.0",
|
|
27
|
+
"jose": "^4.15.1",
|
|
28
|
+
"lru-cache": "^10.0.1",
|
|
29
|
+
"mongodb": "^6.5.0",
|
|
30
|
+
"node-fetch": "^3.3.2",
|
|
31
|
+
"pgwire": "github:kagis/pgwire#f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87",
|
|
32
|
+
"ts-codec": "^1.2.2",
|
|
33
|
+
"uuid": "^9.0.1",
|
|
34
|
+
"yaml": "^2.3.2",
|
|
35
|
+
"@powersync/service-jpgwire": "0.17.10",
|
|
36
|
+
"@powersync/service-jsonbig": "0.17.10",
|
|
37
|
+
"@powersync/service-rsocket-router": "0.0.6",
|
|
38
|
+
"@powersync/service-types": "0.0.2",
|
|
39
|
+
"@powersync/service-sync-rules": "0.17.10"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@journeyapps-platform/micro-dev": "^1.6.7",
|
|
43
|
+
"@types/uuid": "^9.0.4",
|
|
44
|
+
"tsc-alias": "^1.8.10",
|
|
45
|
+
"typescript": "^5.2.2",
|
|
46
|
+
"vite-tsconfig-paths": "^4.3.2",
|
|
47
|
+
"vitest": "^0.34.6"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -b && tsc-alias",
|
|
51
|
+
"build:tests": "tsc -b test/tsconfig.json && tsc-alias",
|
|
52
|
+
"test": "vitest --no-threads",
|
|
53
|
+
"clean": "rm -rf ./lib && tsc -b --clean"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
2
|
+
import { DEFAULT_TAG, SourceTableInterface, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
3
|
+
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
4
|
+
import { ConnectionStatus, SyncRulesStatus, TableInfo, baseUri } from '@powersync/service-types';
|
|
5
|
+
|
|
6
|
+
import * as replication from '@/replication/replication-index.js';
|
|
7
|
+
import * as storage from '@/storage/storage-index.js';
|
|
8
|
+
import * as util from '@/util/util-index.js';
|
|
9
|
+
|
|
10
|
+
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
11
|
+
|
|
12
|
+
export async function getConnectionStatus(system: CorePowerSyncSystem): Promise<ConnectionStatus | null> {
|
|
13
|
+
if (system.pgwire_pool == null) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pool = system.requirePgPool();
|
|
18
|
+
|
|
19
|
+
const base = {
|
|
20
|
+
id: system.config.connection!.id,
|
|
21
|
+
postgres_uri: baseUri(system.config.connection!)
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
await util.retriedQuery(pool, `SELECT 'PowerSync connection test'`);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
connected: false,
|
|
29
|
+
errors: [{ level: 'fatal', message: e.message }]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await replication.checkSourceConfiguration(pool);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
connected: true,
|
|
39
|
+
errors: [{ level: 'fatal', message: e.message }]
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...base,
|
|
45
|
+
connected: true,
|
|
46
|
+
errors: []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DiagnosticsOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Include sync rules content in response.
|
|
53
|
+
*/
|
|
54
|
+
include_content?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check against storage database.
|
|
58
|
+
*
|
|
59
|
+
* If false, uses placeholder values for e.g. initial_replication_done.
|
|
60
|
+
*/
|
|
61
|
+
live_status: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check against the source postgres connection.
|
|
65
|
+
*/
|
|
66
|
+
check_connection: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getSyncRulesStatus(
|
|
70
|
+
sync_rules: storage.PersistedSyncRulesContent | null,
|
|
71
|
+
system: CorePowerSyncSystem,
|
|
72
|
+
options: DiagnosticsOptions
|
|
73
|
+
): Promise<SyncRulesStatus | undefined> {
|
|
74
|
+
if (sync_rules == null) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const include_content = options.include_content ?? false;
|
|
79
|
+
const live_status = options.live_status ?? false;
|
|
80
|
+
const check_connection = options.check_connection ?? false;
|
|
81
|
+
|
|
82
|
+
let rules: SqlSyncRules;
|
|
83
|
+
let persisted: storage.PersistedSyncRules;
|
|
84
|
+
try {
|
|
85
|
+
persisted = sync_rules.parsed();
|
|
86
|
+
rules = persisted.sync_rules;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return {
|
|
89
|
+
content: include_content ? sync_rules.sync_rules_content : undefined,
|
|
90
|
+
connections: [],
|
|
91
|
+
errors: [{ level: 'fatal', message: e.message }]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const systemStorage = live_status ? await system.storage.getInstance(persisted) : undefined;
|
|
96
|
+
const status = await systemStorage?.getStatus();
|
|
97
|
+
let replication_lag_bytes: number | undefined = undefined;
|
|
98
|
+
|
|
99
|
+
let tables_flat: TableInfo[] = [];
|
|
100
|
+
|
|
101
|
+
if (check_connection) {
|
|
102
|
+
const pool = system.requirePgPool();
|
|
103
|
+
|
|
104
|
+
const source_table_patterns = rules.getSourceTables();
|
|
105
|
+
const wc = new replication.WalConnection({
|
|
106
|
+
db: pool,
|
|
107
|
+
sync_rules: rules
|
|
108
|
+
});
|
|
109
|
+
const resolved_tables = await wc.getDebugTablesInfo(source_table_patterns);
|
|
110
|
+
tables_flat = resolved_tables.flatMap((info) => {
|
|
111
|
+
if (info.table) {
|
|
112
|
+
return [info.table];
|
|
113
|
+
} else if (info.tables) {
|
|
114
|
+
return info.tables;
|
|
115
|
+
} else {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (systemStorage) {
|
|
121
|
+
try {
|
|
122
|
+
const results = await util.retriedQuery(pool, {
|
|
123
|
+
statement: `SELECT
|
|
124
|
+
slot_name,
|
|
125
|
+
confirmed_flush_lsn,
|
|
126
|
+
pg_current_wal_lsn(),
|
|
127
|
+
(pg_current_wal_lsn() - confirmed_flush_lsn) AS lsn_distance
|
|
128
|
+
FROM pg_replication_slots WHERE slot_name = $1 LIMIT 1;`,
|
|
129
|
+
params: [{ type: 'varchar', value: systemStorage!.slot_name }]
|
|
130
|
+
});
|
|
131
|
+
const [row] = pgwireRows(results);
|
|
132
|
+
if (row) {
|
|
133
|
+
replication_lag_bytes = Number(row.lsn_distance);
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// Ignore
|
|
137
|
+
micro.logger.warn(`Unable to get replication lag`, e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
const source_table_patterns = rules.getSourceTables();
|
|
142
|
+
const tag = system.config.connection!.tag ?? DEFAULT_TAG;
|
|
143
|
+
|
|
144
|
+
tables_flat = source_table_patterns.map((pattern): TableInfo => {
|
|
145
|
+
if (pattern.isWildcard) {
|
|
146
|
+
return {
|
|
147
|
+
schema: pattern.schema,
|
|
148
|
+
name: pattern.tablePrefix,
|
|
149
|
+
pattern: pattern.isWildcard ? pattern.tablePattern : undefined,
|
|
150
|
+
|
|
151
|
+
data_queries: false,
|
|
152
|
+
parameter_queries: false,
|
|
153
|
+
replication_id: [],
|
|
154
|
+
errors: [{ level: 'fatal', message: 'connection failed' }]
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
const source: SourceTableInterface = {
|
|
158
|
+
connectionTag: tag,
|
|
159
|
+
schema: pattern.schema,
|
|
160
|
+
table: pattern.tablePattern
|
|
161
|
+
};
|
|
162
|
+
const syncData = rules.tableSyncsData(source);
|
|
163
|
+
const syncParameters = rules.tableSyncsParameters(source);
|
|
164
|
+
return {
|
|
165
|
+
schema: pattern.schema,
|
|
166
|
+
name: pattern.name,
|
|
167
|
+
data_queries: syncData,
|
|
168
|
+
parameter_queries: syncParameters,
|
|
169
|
+
replication_id: [],
|
|
170
|
+
errors: [{ level: 'fatal', message: 'connection failed' }]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const errors = tables_flat.flatMap((info) => info.errors);
|
|
177
|
+
if (sync_rules.last_fatal_error) {
|
|
178
|
+
errors.push({ level: 'fatal', message: sync_rules.last_fatal_error });
|
|
179
|
+
}
|
|
180
|
+
errors.push(
|
|
181
|
+
...rules.errors.map((e) => {
|
|
182
|
+
return {
|
|
183
|
+
level: e.type,
|
|
184
|
+
message: e.message
|
|
185
|
+
};
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
content: include_content ? sync_rules.sync_rules_content : undefined,
|
|
191
|
+
connections: [
|
|
192
|
+
{
|
|
193
|
+
id: system.config.connection!.id,
|
|
194
|
+
tag: system.config.connection!.tag ?? DEFAULT_TAG,
|
|
195
|
+
slot_name: sync_rules.slot_name,
|
|
196
|
+
initial_replication_done: status?.snapshot_done ?? false,
|
|
197
|
+
// TODO: Rename?
|
|
198
|
+
last_lsn: status?.checkpoint_lsn ?? undefined,
|
|
199
|
+
last_checkpoint_ts: sync_rules.last_checkpoint_ts?.toISOString(),
|
|
200
|
+
last_keepalive_ts: sync_rules.last_keepalive_ts?.toISOString(),
|
|
201
|
+
replication_lag_bytes: replication_lag_bytes,
|
|
202
|
+
tables: tables_flat
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
errors: deduplicate(errors)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function deduplicate(errors: { level: 'warning' | 'fatal'; message: string }[]) {
|
|
210
|
+
let seen = new Set<string>();
|
|
211
|
+
let result: { level: 'warning' | 'fatal'; message: string }[] = [];
|
|
212
|
+
for (let error of errors) {
|
|
213
|
+
const key = JSON.stringify(error);
|
|
214
|
+
if (seen.has(key)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
seen.add(key);
|
|
218
|
+
result.push(error);
|
|
219
|
+
}
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
3
|
+
import { DatabaseSchema, internal_routes } from '@powersync/service-types';
|
|
4
|
+
|
|
5
|
+
import * as util from '@/util/util-index.js';
|
|
6
|
+
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
7
|
+
|
|
8
|
+
export async function getConnectionsSchema(system: CorePowerSyncSystem): Promise<internal_routes.GetSchemaResponse> {
|
|
9
|
+
if (system.config.connection == null) {
|
|
10
|
+
return { connections: [] };
|
|
11
|
+
}
|
|
12
|
+
const schemas = await getConnectionSchema(system.requirePgPool());
|
|
13
|
+
return {
|
|
14
|
+
connections: [
|
|
15
|
+
{
|
|
16
|
+
schemas,
|
|
17
|
+
tag: system.config.connection!.tag,
|
|
18
|
+
id: system.config.connection!.id
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getConnectionSchema(db: pgwire.PgClient): Promise<DatabaseSchema[]> {
|
|
25
|
+
// https://github.com/Borvik/vscode-postgres/blob/88ec5ed061a0c9bced6c5d4ec122d0759c3f3247/src/language/server.ts
|
|
26
|
+
const results = await util.retriedQuery(
|
|
27
|
+
db,
|
|
28
|
+
`SELECT
|
|
29
|
+
tbl.schemaname,
|
|
30
|
+
tbl.tablename,
|
|
31
|
+
tbl.quoted_name,
|
|
32
|
+
json_agg(a ORDER BY attnum) as columns
|
|
33
|
+
FROM
|
|
34
|
+
(
|
|
35
|
+
SELECT
|
|
36
|
+
n.nspname as schemaname,
|
|
37
|
+
c.relname as tablename,
|
|
38
|
+
(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) as quoted_name
|
|
39
|
+
FROM
|
|
40
|
+
pg_catalog.pg_class c
|
|
41
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
42
|
+
WHERE
|
|
43
|
+
c.relkind = 'r'
|
|
44
|
+
AND n.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
|
|
45
|
+
AND n.nspname not like 'pg_temp_%'
|
|
46
|
+
AND n.nspname not like 'pg_toast_temp_%'
|
|
47
|
+
AND c.relnatts > 0
|
|
48
|
+
AND has_schema_privilege(n.oid, 'USAGE') = true
|
|
49
|
+
AND has_table_privilege(quote_ident(n.nspname) || '.' || quote_ident(c.relname), 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') = true
|
|
50
|
+
) as tbl
|
|
51
|
+
LEFT JOIN (
|
|
52
|
+
SELECT
|
|
53
|
+
attrelid,
|
|
54
|
+
attname,
|
|
55
|
+
format_type(atttypid, atttypmod) as data_type,
|
|
56
|
+
(SELECT typname FROM pg_catalog.pg_type WHERE oid = atttypid) as pg_type,
|
|
57
|
+
attnum,
|
|
58
|
+
attisdropped
|
|
59
|
+
FROM
|
|
60
|
+
pg_attribute
|
|
61
|
+
) as a ON (
|
|
62
|
+
a.attrelid = tbl.quoted_name::regclass
|
|
63
|
+
AND a.attnum > 0
|
|
64
|
+
AND NOT a.attisdropped
|
|
65
|
+
AND has_column_privilege(tbl.quoted_name, a.attname, 'SELECT, INSERT, UPDATE, REFERENCES')
|
|
66
|
+
)
|
|
67
|
+
GROUP BY schemaname, tablename, quoted_name`
|
|
68
|
+
);
|
|
69
|
+
const rows = pgwireRows(results);
|
|
70
|
+
|
|
71
|
+
let schemas: Record<string, any> = {};
|
|
72
|
+
|
|
73
|
+
for (let row of rows) {
|
|
74
|
+
const schema = (schemas[row.schemaname] ??= {
|
|
75
|
+
name: row.schemaname,
|
|
76
|
+
tables: []
|
|
77
|
+
});
|
|
78
|
+
const table = {
|
|
79
|
+
name: row.tablename,
|
|
80
|
+
columns: [] as any[]
|
|
81
|
+
};
|
|
82
|
+
schema.tables.push(table);
|
|
83
|
+
|
|
84
|
+
const columnInfo = JSON.parse(row.columns);
|
|
85
|
+
for (let column of columnInfo) {
|
|
86
|
+
let pg_type = column.pg_type as string;
|
|
87
|
+
if (pg_type.startsWith('_')) {
|
|
88
|
+
pg_type = `${pg_type.substring(1)}[]`;
|
|
89
|
+
}
|
|
90
|
+
table.columns.push({
|
|
91
|
+
name: column.attname,
|
|
92
|
+
type: column.data_type,
|
|
93
|
+
pg_type: pg_type
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Object.values(schemas);
|
|
99
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import timers from 'timers/promises';
|
|
3
|
+
import { KeySpec } from './KeySpec.js';
|
|
4
|
+
import { LeakyBucket } from './LeakyBucket.js';
|
|
5
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages caching and refreshing for a key collector.
|
|
9
|
+
*
|
|
10
|
+
* Cache refreshing is activity-based, instead of automatically refreshing.
|
|
11
|
+
*
|
|
12
|
+
* Generally:
|
|
13
|
+
* * If the last refresh was > 5 minutes ago, trigger a background refresh, but use the cached keys.
|
|
14
|
+
* * If the last refresh was > 60 minutes ago, discard the cache, and force a refresh.
|
|
15
|
+
* * If the last refresh resulted in an error, refresh based on a retry delay, but use cached keys.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export class CachedKeyCollector implements KeyCollector {
|
|
19
|
+
private currentKeys: KeySpec[] = [];
|
|
20
|
+
/**
|
|
21
|
+
* The time that currentKeys was set.
|
|
22
|
+
*/
|
|
23
|
+
private keyTimestamp: number = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Refresh every 5 minutes - the default refresh rate.
|
|
27
|
+
*/
|
|
28
|
+
private backgroundRefreshInterval = 300000;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Refresh a _max_ of once every minute at steady state.
|
|
32
|
+
*
|
|
33
|
+
* This controls the refresh rate under error conditions.
|
|
34
|
+
*/
|
|
35
|
+
private rateLimiter = new LeakyBucket({ maxCapacity: 10, periodMs: 60000 });
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Expire keys after an hour, if we failed to refresh in that time.
|
|
39
|
+
*/
|
|
40
|
+
private keyExpiry = 3600000;
|
|
41
|
+
|
|
42
|
+
private currentErrors: jose.errors.JOSEError[] = [];
|
|
43
|
+
/**
|
|
44
|
+
* Indicates a "fatal" error that should be retried.
|
|
45
|
+
*/
|
|
46
|
+
private error = false;
|
|
47
|
+
|
|
48
|
+
private refreshPromise: Promise<void> | undefined = undefined;
|
|
49
|
+
|
|
50
|
+
constructor(private source: KeyCollector) {}
|
|
51
|
+
|
|
52
|
+
async getKeys(): Promise<KeyResult> {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
if (now - this.keyTimestamp > this.keyExpiry) {
|
|
55
|
+
// Keys have expired - clear
|
|
56
|
+
this.currentKeys = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.wantsRefresh()) {
|
|
60
|
+
// Trigger background refresh.
|
|
61
|
+
// This also sets refreshPromise
|
|
62
|
+
this.refresh();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (now - this.keyTimestamp > this.keyExpiry) {
|
|
66
|
+
// Keys have expired - wait for fetching new keys
|
|
67
|
+
// It is possible that the refresh was actually triggered,
|
|
68
|
+
// e.g. in the case of waiting for error retries.
|
|
69
|
+
// In the case of very slow requests, we don't wait for it to complete, but the
|
|
70
|
+
// request can still complete in the background.
|
|
71
|
+
const timeout = timers.setTimeout(3000);
|
|
72
|
+
await Promise.race([this.refreshPromise, timeout]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { keys: this.currentKeys, errors: this.currentErrors };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private refresh() {
|
|
79
|
+
if (this.refreshPromise == null) {
|
|
80
|
+
if (!this.rateLimiter.allowed()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this.refreshPromise = this.refreshInner().finally(() => {
|
|
84
|
+
this.refreshPromise = undefined;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return this.refreshPromise;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async noKeyFound(): Promise<void> {
|
|
91
|
+
// Refresh keys if allowed by the rate limiter
|
|
92
|
+
await this.refresh();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private async refreshInner() {
|
|
96
|
+
try {
|
|
97
|
+
const { keys, errors } = await this.source.getKeys();
|
|
98
|
+
// Partial or full result
|
|
99
|
+
this.currentKeys = keys;
|
|
100
|
+
this.currentErrors = errors;
|
|
101
|
+
this.keyTimestamp = Date.now();
|
|
102
|
+
this.error = false;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
this.error = true;
|
|
105
|
+
// No result - keep previous keys
|
|
106
|
+
if (e instanceof jose.errors.JOSEError) {
|
|
107
|
+
this.currentErrors = [e];
|
|
108
|
+
} else {
|
|
109
|
+
this.currentErrors = [new jose.errors.JOSEError(e.message ?? 'Failed to fetch keys')];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private wantsRefresh() {
|
|
115
|
+
if (this.error) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (Date.now() - this.rateLimiter.lastGrantedRequest >= this.backgroundRefreshInterval) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async addTimeForTests(time: number) {
|
|
127
|
+
this.keyTimestamp -= time;
|
|
128
|
+
this.rateLimiter.reset();
|
|
129
|
+
this.rateLimiter.lastGrantedRequest -= time;
|
|
130
|
+
await this.refreshPromise;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import { KeySpec } from './KeySpec.js';
|
|
3
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
4
|
+
|
|
5
|
+
export class CompoundKeyCollector implements KeyCollector {
|
|
6
|
+
private collectors: KeyCollector[];
|
|
7
|
+
|
|
8
|
+
constructor(collectors?: KeyCollector[]) {
|
|
9
|
+
this.collectors = collectors ?? [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add(collector: KeyCollector) {
|
|
13
|
+
this.collectors.push(collector);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getKeys(): Promise<KeyResult> {
|
|
17
|
+
let keys: KeySpec[] = [];
|
|
18
|
+
let errors: jose.errors.JOSEError[] = [];
|
|
19
|
+
const promises = this.collectors.map((collector) =>
|
|
20
|
+
collector.getKeys().then((result) => {
|
|
21
|
+
keys.push(...result.keys);
|
|
22
|
+
errors.push(...result.errors);
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
await Promise.all(promises);
|
|
26
|
+
return { keys, errors };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async noKeyFound(): Promise<void> {
|
|
30
|
+
const promises = this.collectors.map((collector) => collector.noKeyFound?.());
|
|
31
|
+
await Promise.all(promises);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
import { KeySpec } from './KeySpec.js';
|
|
3
|
+
|
|
4
|
+
export interface KeyCollector {
|
|
5
|
+
/**
|
|
6
|
+
* Fetch keys for this collector.
|
|
7
|
+
*
|
|
8
|
+
* If a partial result is available, return keys and errors array.
|
|
9
|
+
* These errors are not retried, and previous keys not cached.
|
|
10
|
+
*
|
|
11
|
+
* If the request fails completely, throw an error. These errors are retried.
|
|
12
|
+
* In that case, previous keys may be cached and used.
|
|
13
|
+
*/
|
|
14
|
+
getKeys(): Promise<KeyResult>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Indicates that no matching key was found.
|
|
18
|
+
*
|
|
19
|
+
* The collector may use this as a hint to reload keys, although this is not a requirement.
|
|
20
|
+
*/
|
|
21
|
+
noKeyFound?: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface KeyResult {
|
|
25
|
+
errors: jose.errors.JOSEError[];
|
|
26
|
+
keys: KeySpec[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as jose from 'jose';
|
|
2
|
+
|
|
3
|
+
export const HS_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
|
|
4
|
+
export const RSA_ALGORITHMS = ['RS256', 'RS384', 'RS512'];
|
|
5
|
+
export const SUPPORTED_ALGORITHMS = [...HS_ALGORITHMS, ...RSA_ALGORITHMS];
|
|
6
|
+
|
|
7
|
+
export interface KeyOptions {
|
|
8
|
+
/**
|
|
9
|
+
* If configured, JWTs verified by this key must have one of these audiences
|
|
10
|
+
* in the `aud` claim, instead of the default.
|
|
11
|
+
*/
|
|
12
|
+
requiresAudience?: string[];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* If configured, JWTs verified by this key can have a maximum lifetime up to
|
|
16
|
+
* this value, instead of the default.
|
|
17
|
+
*/
|
|
18
|
+
maxLifetimeSeconds?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class KeySpec {
|
|
22
|
+
key: jose.KeyLike;
|
|
23
|
+
source: jose.JWK;
|
|
24
|
+
options: KeyOptions;
|
|
25
|
+
|
|
26
|
+
static async importKey(key: jose.JWK, options?: KeyOptions): Promise<KeySpec> {
|
|
27
|
+
const parsed = (await jose.importJWK(key)) as jose.KeyLike;
|
|
28
|
+
return new KeySpec(key, parsed, options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor(source: jose.JWK, key: jose.KeyLike, options?: KeyOptions) {
|
|
32
|
+
this.source = source;
|
|
33
|
+
this.key = key;
|
|
34
|
+
this.options = options ?? {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get kid(): string | undefined {
|
|
38
|
+
return this.source.kid;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
matchesAlgorithm(jwtAlg: string) {
|
|
42
|
+
if (this.source.alg) {
|
|
43
|
+
return jwtAlg == this.source.alg;
|
|
44
|
+
} else if (this.source.kty == 'RSA') {
|
|
45
|
+
return RSA_ALGORITHMS.includes(jwtAlg);
|
|
46
|
+
} else if (this.source.kty == 'oct') {
|
|
47
|
+
return HS_ALGORITHMS.includes(jwtAlg);
|
|
48
|
+
} else {
|
|
49
|
+
// We don't support 'ec' yet
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async isValidSignature(token: string): Promise<boolean> {
|
|
55
|
+
try {
|
|
56
|
+
await jose.compactVerify(token, this.key);
|
|
57
|
+
return true;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e.code == 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
|
|
60
|
+
return false;
|
|
61
|
+
} else {
|
|
62
|
+
// Token format error most likely
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|