@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/src/sync/sync.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
2
|
+
import { JSONBig, JsonContainer } from '@powersync/service-jsonbig';
|
|
3
|
+
import { SyncParameters } from '@powersync/service-sync-rules';
|
|
4
|
+
import { Semaphore } from 'async-mutex';
|
|
5
|
+
import { AbortError } from 'ix/aborterror.js';
|
|
6
|
+
|
|
7
|
+
import * as auth from '@/auth/auth-index.js';
|
|
8
|
+
import * as storage from '@/storage/storage-index.js';
|
|
9
|
+
import * as util from '@/util/util-index.js';
|
|
10
|
+
|
|
11
|
+
import { operations_synced_total } from '../metrics/metrics.js';
|
|
12
|
+
import { mergeAsyncIterables } from './merge.js';
|
|
13
|
+
import { TokenStreamOptions, tokenStream } from './util.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Maximum number of connections actively fetching data.
|
|
17
|
+
*/
|
|
18
|
+
const MAX_ACTIVE_CONNECTIONS = 10;
|
|
19
|
+
const syncSemaphore = new Semaphore(MAX_ACTIVE_CONNECTIONS);
|
|
20
|
+
|
|
21
|
+
export interface SyncStreamParameters {
|
|
22
|
+
storage: storage.BucketStorageFactory;
|
|
23
|
+
params: util.StreamingSyncRequest;
|
|
24
|
+
syncParams: SyncParameters;
|
|
25
|
+
token: auth.JwtPayload;
|
|
26
|
+
/**
|
|
27
|
+
* If this signal is aborted, the stream response ends as soon as possible, without error.
|
|
28
|
+
*/
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
tokenStreamOptions?: Partial<TokenStreamOptions>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function* streamResponse(
|
|
34
|
+
options: SyncStreamParameters
|
|
35
|
+
): AsyncIterable<util.StreamingSyncLine | string | null> {
|
|
36
|
+
const { storage, params, syncParams, token, tokenStreamOptions, signal } = options;
|
|
37
|
+
// We also need to be able to abort, so we create our own controller.
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
if (signal) {
|
|
40
|
+
signal.addEventListener(
|
|
41
|
+
'abort',
|
|
42
|
+
() => {
|
|
43
|
+
controller.abort();
|
|
44
|
+
},
|
|
45
|
+
{ once: true }
|
|
46
|
+
);
|
|
47
|
+
if (signal.aborted) {
|
|
48
|
+
controller.abort();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const ki = tokenStream(token, controller.signal, tokenStreamOptions);
|
|
52
|
+
const stream = streamResponseInner(storage, params, syncParams, controller.signal);
|
|
53
|
+
// Merge the two streams, and abort as soon as one of the streams end.
|
|
54
|
+
const merged = mergeAsyncIterables([stream, ki], controller.signal);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
yield* merged;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e instanceof AbortError) {
|
|
60
|
+
return;
|
|
61
|
+
} else {
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
// This ensures all the underlying streams are aborted as soon as possible if the
|
|
66
|
+
// parent loop stops.
|
|
67
|
+
controller.abort();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function* streamResponseInner(
|
|
72
|
+
storage: storage.BucketStorageFactory,
|
|
73
|
+
params: util.StreamingSyncRequest,
|
|
74
|
+
syncParams: SyncParameters,
|
|
75
|
+
signal: AbortSignal
|
|
76
|
+
): AsyncGenerator<util.StreamingSyncLine | string | null> {
|
|
77
|
+
// Bucket state of bucket id -> op_id.
|
|
78
|
+
// This starts with the state from the client. May contain buckets that the user do not have access to (anymore).
|
|
79
|
+
let dataBuckets = new Map<string, string>();
|
|
80
|
+
|
|
81
|
+
let last_checksums: util.BucketChecksum[] | null = null;
|
|
82
|
+
let last_write_checkpoint: bigint | null = null;
|
|
83
|
+
|
|
84
|
+
const { raw_data, binary_data } = params;
|
|
85
|
+
|
|
86
|
+
if (params.buckets) {
|
|
87
|
+
for (let { name, after: start } of params.buckets) {
|
|
88
|
+
dataBuckets.set(name, start);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const stream = storage.watchWriteCheckpoint(syncParams.token_parameters.user_id as string, signal);
|
|
93
|
+
for await (const next of stream) {
|
|
94
|
+
const { base, writeCheckpoint } = next;
|
|
95
|
+
const checkpoint = base.checkpoint;
|
|
96
|
+
|
|
97
|
+
const storage = await base.getBucketStorage();
|
|
98
|
+
if (storage == null) {
|
|
99
|
+
// Sync rules deleted in the meantime - try again with the next checkpoint.
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const sync_rules = storage.sync_rules;
|
|
103
|
+
|
|
104
|
+
const allBuckets = await sync_rules.queryBucketIds({
|
|
105
|
+
getParameterSets(lookups) {
|
|
106
|
+
return storage.getParameterSets(checkpoint, lookups);
|
|
107
|
+
},
|
|
108
|
+
parameters: syncParams
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (allBuckets.length > 1000) {
|
|
112
|
+
// TODO: Limit number of buckets even before we get to this point
|
|
113
|
+
throw new Error(`Too many buckets: ${allBuckets.length}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let checksums: util.BucketChecksum[] | undefined = undefined;
|
|
117
|
+
|
|
118
|
+
let dataBucketsNew = new Map<string, string>();
|
|
119
|
+
for (let bucket of allBuckets) {
|
|
120
|
+
dataBucketsNew.set(bucket, dataBuckets.get(bucket) ?? '0');
|
|
121
|
+
}
|
|
122
|
+
dataBuckets = dataBucketsNew;
|
|
123
|
+
|
|
124
|
+
checksums = await storage.getChecksums(checkpoint, [...dataBuckets.keys()]);
|
|
125
|
+
|
|
126
|
+
if (last_checksums) {
|
|
127
|
+
const diff = util.checksumsDiff(last_checksums, checksums);
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
last_write_checkpoint == writeCheckpoint &&
|
|
131
|
+
diff.removed_buckets.length == 0 &&
|
|
132
|
+
diff.updated_buckets.length == 0
|
|
133
|
+
) {
|
|
134
|
+
// No changes - don't send anything to the client
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let message = `Updated checkpoint: ${checkpoint} | write: ${writeCheckpoint} | `;
|
|
139
|
+
message += `buckets: ${allBuckets.length} | `;
|
|
140
|
+
message += `updated: ${limitedBuckets(diff.updated_buckets, 20)} | `;
|
|
141
|
+
message += `removed: ${limitedBuckets(diff.removed_buckets, 20)} | `;
|
|
142
|
+
micro.logger.info(message);
|
|
143
|
+
|
|
144
|
+
const checksum_line: util.StreamingSyncCheckpointDiff = {
|
|
145
|
+
checkpoint_diff: {
|
|
146
|
+
last_op_id: checkpoint,
|
|
147
|
+
write_checkpoint: writeCheckpoint ? String(writeCheckpoint) : undefined,
|
|
148
|
+
...diff
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
yield checksum_line;
|
|
153
|
+
} else {
|
|
154
|
+
let message = `New checkpoint: ${checkpoint} | write: ${writeCheckpoint} | `;
|
|
155
|
+
message += `buckets: ${allBuckets.length} ${limitedBuckets(allBuckets, 20)}`;
|
|
156
|
+
micro.logger.info(message);
|
|
157
|
+
const checksum_line: util.StreamingSyncCheckpoint = {
|
|
158
|
+
checkpoint: {
|
|
159
|
+
last_op_id: checkpoint,
|
|
160
|
+
write_checkpoint: writeCheckpoint ? String(writeCheckpoint) : undefined,
|
|
161
|
+
buckets: checksums
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
yield checksum_line;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
last_checksums = checksums;
|
|
168
|
+
last_write_checkpoint = writeCheckpoint;
|
|
169
|
+
|
|
170
|
+
yield* bucketDataInBatches(storage, checkpoint, dataBuckets, raw_data, binary_data, signal);
|
|
171
|
+
|
|
172
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function* bucketDataInBatches(
|
|
177
|
+
storage: storage.SyncRulesBucketStorage,
|
|
178
|
+
checkpoint: string,
|
|
179
|
+
dataBuckets: Map<string, string>,
|
|
180
|
+
raw_data: boolean | undefined,
|
|
181
|
+
binary_data: boolean | undefined,
|
|
182
|
+
signal: AbortSignal
|
|
183
|
+
) {
|
|
184
|
+
let isDone = false;
|
|
185
|
+
while (!signal.aborted && !isDone) {
|
|
186
|
+
// The code below is functionally the same as this for-await loop below.
|
|
187
|
+
// However, the for-await loop appears to have a memory leak, so we avoid it.
|
|
188
|
+
// for await (const { done, data } of bucketDataBatch(storage, checkpoint, dataBuckets, raw_data, signal)) {
|
|
189
|
+
// yield data;
|
|
190
|
+
// if (done) {
|
|
191
|
+
// isDone = true;
|
|
192
|
+
// }
|
|
193
|
+
// break;
|
|
194
|
+
// }
|
|
195
|
+
const iter = bucketDataBatch(storage, checkpoint, dataBuckets, raw_data, binary_data, signal);
|
|
196
|
+
try {
|
|
197
|
+
while (true) {
|
|
198
|
+
const { value, done: iterDone } = await iter.next();
|
|
199
|
+
if (iterDone) {
|
|
200
|
+
break;
|
|
201
|
+
} else {
|
|
202
|
+
const { done, data } = value;
|
|
203
|
+
yield data;
|
|
204
|
+
if (done) {
|
|
205
|
+
isDone = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
await iter.return();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Extracted as a separate internal function just to avoid memory leaks.
|
|
217
|
+
*/
|
|
218
|
+
async function* bucketDataBatch(
|
|
219
|
+
storage: storage.SyncRulesBucketStorage,
|
|
220
|
+
checkpoint: string,
|
|
221
|
+
dataBuckets: Map<string, string>,
|
|
222
|
+
raw_data: boolean | undefined,
|
|
223
|
+
binary_data: boolean | undefined,
|
|
224
|
+
signal: AbortSignal
|
|
225
|
+
) {
|
|
226
|
+
const [_, release] = await syncSemaphore.acquire();
|
|
227
|
+
try {
|
|
228
|
+
const data = storage.getBucketDataBatch(checkpoint, dataBuckets);
|
|
229
|
+
|
|
230
|
+
let has_more = false;
|
|
231
|
+
|
|
232
|
+
for await (let r of data) {
|
|
233
|
+
if (signal.aborted) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (r.has_more) {
|
|
237
|
+
has_more = true;
|
|
238
|
+
}
|
|
239
|
+
if (r.data.length == 0) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
micro.logger.debug(`Sending data for ${r.bucket}`);
|
|
243
|
+
|
|
244
|
+
let send_data: any;
|
|
245
|
+
if (binary_data) {
|
|
246
|
+
// Send the object as is, will most likely be encoded as a BSON document
|
|
247
|
+
send_data = { data: r };
|
|
248
|
+
} else if (raw_data) {
|
|
249
|
+
// Data is a raw string - we can use the more efficient JSON.stringify.
|
|
250
|
+
const response: util.StreamingSyncData = {
|
|
251
|
+
data: r
|
|
252
|
+
};
|
|
253
|
+
send_data = JSON.stringify(response);
|
|
254
|
+
} else {
|
|
255
|
+
// We need to preserve the embedded data exactly, so this uses a JsonContainer
|
|
256
|
+
// and JSONBig to stringify.
|
|
257
|
+
const response: util.StreamingSyncData = {
|
|
258
|
+
data: transformLegacyResponse(r)
|
|
259
|
+
};
|
|
260
|
+
send_data = JSONBig.stringify(response);
|
|
261
|
+
}
|
|
262
|
+
yield { data: send_data, done: false };
|
|
263
|
+
if (send_data.length > 50_000) {
|
|
264
|
+
// IMPORTANT: This does not affect the output stream, but is used to flush
|
|
265
|
+
// iterator memory in case if large data sent.
|
|
266
|
+
yield { data: null, done: false };
|
|
267
|
+
}
|
|
268
|
+
operations_synced_total.add(r.data.length);
|
|
269
|
+
|
|
270
|
+
dataBuckets.set(r.bucket, r.next_after);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!has_more) {
|
|
274
|
+
const line: util.StreamingSyncCheckpointComplete = {
|
|
275
|
+
checkpoint_complete: {
|
|
276
|
+
last_op_id: checkpoint
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
yield { data: line, done: true };
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
release();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function transformLegacyResponse(bucketData: util.SyncBucketData): any {
|
|
287
|
+
return {
|
|
288
|
+
...bucketData,
|
|
289
|
+
data: bucketData.data.map((entry) => {
|
|
290
|
+
return {
|
|
291
|
+
...entry,
|
|
292
|
+
data: entry.data == null ? null : new JsonContainer(entry.data as string),
|
|
293
|
+
checksum: BigInt(entry.checksum)
|
|
294
|
+
};
|
|
295
|
+
})
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function limitedBuckets(buckets: string[] | util.BucketChecksum[], limit: number) {
|
|
300
|
+
buckets = buckets.map((b) => {
|
|
301
|
+
if (typeof b != 'string') {
|
|
302
|
+
return b.bucket;
|
|
303
|
+
} else {
|
|
304
|
+
return b;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
if (buckets.length <= limit) {
|
|
308
|
+
return JSON.stringify(buckets);
|
|
309
|
+
}
|
|
310
|
+
const limited = buckets.slice(0, limit);
|
|
311
|
+
return `${JSON.stringify(limited)}...`;
|
|
312
|
+
}
|
package/src/sync/util.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as timers from 'timers/promises';
|
|
2
|
+
|
|
3
|
+
import * as util from '@/util/util-index.js';
|
|
4
|
+
import { data_synced_bytes } from '../metrics/metrics.js';
|
|
5
|
+
|
|
6
|
+
export type TokenStreamOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Adds periodic keepalive events
|
|
9
|
+
*/
|
|
10
|
+
keep_alive: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Warn before the token is going to expire
|
|
13
|
+
*/
|
|
14
|
+
expire_warning_period: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const KEEPALIVE_INTERVAL = 20_000;
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TOKEN_STREAM_OPTIONS: TokenStreamOptions = {
|
|
20
|
+
keep_alive: true,
|
|
21
|
+
expire_warning_period: 20_000
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* An iterator that periodically yields token and optionally keepalive events, and returns once the
|
|
26
|
+
* provided token expiry is reached.
|
|
27
|
+
*
|
|
28
|
+
* @param token exp is expiry as a unix timestamp (seconds)
|
|
29
|
+
* @param signal abort the iterator with this
|
|
30
|
+
* @param options configure keepalive and expire warnings
|
|
31
|
+
*/
|
|
32
|
+
export async function* tokenStream(
|
|
33
|
+
token: { exp: number },
|
|
34
|
+
signal: AbortSignal,
|
|
35
|
+
options?: Partial<TokenStreamOptions>
|
|
36
|
+
): AsyncGenerator<util.StreamingSyncKeepalive> {
|
|
37
|
+
const resolved_options: TokenStreamOptions = {
|
|
38
|
+
...DEFAULT_TOKEN_STREAM_OPTIONS,
|
|
39
|
+
...(options ?? {})
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const { keep_alive, expire_warning_period } = resolved_options;
|
|
43
|
+
|
|
44
|
+
// Real tokens always have an integer for exp.
|
|
45
|
+
// In tests, we may use fractional seconds to reduce the delay.
|
|
46
|
+
// Both cases are handled here.
|
|
47
|
+
const expires_at = token.exp * 1000;
|
|
48
|
+
const expire_warning_at = expires_at - expire_warning_period;
|
|
49
|
+
|
|
50
|
+
let first_expire_test = true;
|
|
51
|
+
|
|
52
|
+
while (!signal.aborted) {
|
|
53
|
+
const token_expires_in = Math.max(0, Math.ceil(token.exp - Date.now() / 1000));
|
|
54
|
+
if (first_expire_test && token_expires_in > 0) {
|
|
55
|
+
first_expire_test = false;
|
|
56
|
+
} else {
|
|
57
|
+
yield { token_expires_in: token_expires_in };
|
|
58
|
+
if (token_expires_in == 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const keep_alive_delay = KEEPALIVE_INTERVAL * (0.9 + Math.random() * 0.2);
|
|
64
|
+
|
|
65
|
+
// Add margin due to setTimeout inaccuracies
|
|
66
|
+
const expiry_delay = Math.max(0, expires_at - Date.now() + 3);
|
|
67
|
+
const expiry_warning_delay = Math.max(0, expire_warning_at - Date.now() + 3);
|
|
68
|
+
// Either the warning has past or it's before
|
|
69
|
+
const relevant_expiry_delay = expiry_warning_delay != 0 ? expiry_warning_delay : expiry_delay;
|
|
70
|
+
|
|
71
|
+
const delay = keep_alive ? Math.min(relevant_expiry_delay, keep_alive_delay) : relevant_expiry_delay;
|
|
72
|
+
await timers.setTimeout(delay, null, { signal }).catch(() => {
|
|
73
|
+
// Ignore AbortError
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function* ndjson(iterator: AsyncIterable<string | null | Record<string, any>>): AsyncGenerator<string> {
|
|
79
|
+
for await (let data of iterator) {
|
|
80
|
+
if (data == null) {
|
|
81
|
+
// Empty value to flush iterator memory
|
|
82
|
+
continue;
|
|
83
|
+
} else if (typeof data == 'string') {
|
|
84
|
+
// Pre-serialized value
|
|
85
|
+
yield data + '\n';
|
|
86
|
+
} else {
|
|
87
|
+
yield JSON.stringify(data) + '\n';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function* transformToBytesTracked(iterator: AsyncIterable<string>): AsyncGenerator<Buffer> {
|
|
93
|
+
for await (let data of iterator) {
|
|
94
|
+
const encoded = Buffer.from(data, 'utf8');
|
|
95
|
+
data_synced_bytes.add(encoded.length);
|
|
96
|
+
yield encoded;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
3
|
+
|
|
4
|
+
import * as auth from '@/auth/auth-index.js';
|
|
5
|
+
import * as storage from '@/storage/storage-index.js';
|
|
6
|
+
import * as utils from '@/util/util-index.js';
|
|
7
|
+
|
|
8
|
+
export abstract class CorePowerSyncSystem extends micro.system.MicroSystem {
|
|
9
|
+
abstract storage: storage.BucketStorageFactory;
|
|
10
|
+
abstract client_keystore: auth.KeyStore;
|
|
11
|
+
abstract dev_client_keystore: auth.KeyStore;
|
|
12
|
+
abstract pgwire_pool?: pgwire.PgClient;
|
|
13
|
+
|
|
14
|
+
protected stopHandlers: Set<() => void> = new Set();
|
|
15
|
+
|
|
16
|
+
closed: boolean;
|
|
17
|
+
|
|
18
|
+
constructor(public config: utils.ResolvedPowerSyncConfig) {
|
|
19
|
+
super();
|
|
20
|
+
this.closed = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
abstract addTerminationHandler(): void;
|
|
24
|
+
|
|
25
|
+
addStopHandler(handler: () => void): () => void {
|
|
26
|
+
if (this.closed) {
|
|
27
|
+
handler();
|
|
28
|
+
return () => {};
|
|
29
|
+
}
|
|
30
|
+
this.stopHandlers.add(handler);
|
|
31
|
+
return () => {
|
|
32
|
+
this.stopHandlers.delete(handler);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
requirePgPool() {
|
|
37
|
+
if (this.pgwire_pool == null) {
|
|
38
|
+
throw new Error('No source connection configured');
|
|
39
|
+
} else {
|
|
40
|
+
return this.pgwire_pool!;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export type PromiseFunction<T> = (context: MutexContext) => Promise<T>;
|
|
2
|
+
export type SharedPromiseFunction<T> = (context: SharedMutexContext) => Promise<T>;
|
|
3
|
+
|
|
4
|
+
const DEBUG_MUTEX = false;
|
|
5
|
+
|
|
6
|
+
interface Task {
|
|
7
|
+
exclusive: boolean;
|
|
8
|
+
execute: (context: SharedMutexContext) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SharedMutexContext {
|
|
12
|
+
sharedLock<T>(promiseFn: SharedPromiseFunction<T>): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MutexContext extends SharedMutexContext {
|
|
16
|
+
exclusiveLock<T>(promiseFn: PromiseFunction<T>): Promise<T>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class ExclusiveContext implements MutexContext {
|
|
20
|
+
constructor(private mutex: Mutex) {}
|
|
21
|
+
|
|
22
|
+
exclusiveLock<T>(promiseFn: PromiseFunction<T>): Promise<T> {
|
|
23
|
+
return promiseFn(this);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
sharedLock<T>(promiseFn: PromiseFunction<T>): Promise<T> {
|
|
27
|
+
return promiseFn(this);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class SharedContext implements SharedMutexContext {
|
|
32
|
+
constructor(private mutex: Mutex) {}
|
|
33
|
+
|
|
34
|
+
async exclusiveLock<T>(promiseFn: PromiseFunction<T>): Promise<T> {
|
|
35
|
+
throw new Error('Cannot upgrade a shared lock to an exclusive lock.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sharedLock<T>(promiseFn: SharedPromiseFunction<T>): Promise<T> {
|
|
39
|
+
return promiseFn(this);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mutex maintains a queue of Promise-returning functions that
|
|
45
|
+
* are executed sequentially (whereas normally they would execute their async code concurrently).
|
|
46
|
+
*/
|
|
47
|
+
export class Mutex implements MutexContext {
|
|
48
|
+
private queue: Task[];
|
|
49
|
+
private sharedCount: number;
|
|
50
|
+
private exclusiveLocked: boolean;
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
this.queue = [];
|
|
54
|
+
this.sharedCount = 0;
|
|
55
|
+
this.exclusiveLocked = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Place a function on the queue.
|
|
60
|
+
* The function may either return a Promise or a value.
|
|
61
|
+
* Return a Promise that is resolved with the result of the function.
|
|
62
|
+
*/
|
|
63
|
+
async exclusiveLock<T>(promiseFn: PromiseFunction<T>): Promise<T> {
|
|
64
|
+
return this.lock(promiseFn as any, true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Place a function on the queue.
|
|
69
|
+
* This function may execute in parallel with other "multi" functions, but not with other functions on the exclusive
|
|
70
|
+
* queue.
|
|
71
|
+
*/
|
|
72
|
+
sharedLock<T>(promiseFn: SharedPromiseFunction<T>): Promise<T> {
|
|
73
|
+
return this.lock(promiseFn, false);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async lock<T>(promiseFn: SharedPromiseFunction<T>, exclusive?: boolean): Promise<T> {
|
|
77
|
+
const context = await this._lockNext(exclusive);
|
|
78
|
+
let timeout;
|
|
79
|
+
try {
|
|
80
|
+
if (DEBUG_MUTEX) {
|
|
81
|
+
const stack = new Error().stack;
|
|
82
|
+
timeout = setTimeout(() => {
|
|
83
|
+
console.warn('Mutex not released in 10 seconds\n', stack);
|
|
84
|
+
}, 10000);
|
|
85
|
+
}
|
|
86
|
+
return await promiseFn(context);
|
|
87
|
+
} finally {
|
|
88
|
+
if (DEBUG_MUTEX) {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
}
|
|
91
|
+
if (!exclusive) {
|
|
92
|
+
this.sharedCount -= 1;
|
|
93
|
+
} else {
|
|
94
|
+
this.exclusiveLocked = false;
|
|
95
|
+
}
|
|
96
|
+
this._tryNext();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convert a normal Promise-returning function into one that is automatically enqueued.
|
|
102
|
+
* The signature of the function stays the same - only the execution is potentially delayed.
|
|
103
|
+
* The only exception is that if the function would have returned a scalar value, it now
|
|
104
|
+
* returns a Promise.
|
|
105
|
+
*/
|
|
106
|
+
qu<T>(fn: PromiseFunction<T>): () => Promise<T> {
|
|
107
|
+
var self = this;
|
|
108
|
+
return function () {
|
|
109
|
+
var args = arguments;
|
|
110
|
+
return self.exclusiveLock(function () {
|
|
111
|
+
return fn.apply(null, args as any);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wait until we are ready to execute the next task on the queue.
|
|
118
|
+
*
|
|
119
|
+
* This places a "Task" marker on the queue, and waits until we get to it.
|
|
120
|
+
*
|
|
121
|
+
* @param exclusive
|
|
122
|
+
*/
|
|
123
|
+
private _lockNext(exclusive?: boolean): Promise<SharedMutexContext> {
|
|
124
|
+
var self = this;
|
|
125
|
+
return new Promise<SharedMutexContext>(function (resolve, reject) {
|
|
126
|
+
const task: Task = {
|
|
127
|
+
execute: resolve,
|
|
128
|
+
exclusive: exclusive ?? false
|
|
129
|
+
};
|
|
130
|
+
self.queue.push(task);
|
|
131
|
+
self._tryNext();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private _tryNext() {
|
|
136
|
+
if (this.queue.length == 0) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.exclusiveLocked) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var task = this.queue[0];
|
|
145
|
+
if (!task.exclusive) {
|
|
146
|
+
this.sharedCount += 1;
|
|
147
|
+
this.queue.shift();
|
|
148
|
+
task.execute(new SharedContext(this));
|
|
149
|
+
} else if (this.sharedCount == 0) {
|
|
150
|
+
this.exclusiveLocked = true;
|
|
151
|
+
this.queue.shift();
|
|
152
|
+
task.execute(new ExclusiveContext(this));
|
|
153
|
+
} else {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import { NormalizedPostgresConnection } from '@powersync/service-types';
|
|
3
|
+
|
|
4
|
+
export class PgManager {
|
|
5
|
+
/**
|
|
6
|
+
* Do not use this for any transactions.
|
|
7
|
+
*/
|
|
8
|
+
public readonly pool: pgwire.PgClient;
|
|
9
|
+
|
|
10
|
+
private connectionPromises: Promise<pgwire.PgConnection>[] = [];
|
|
11
|
+
|
|
12
|
+
constructor(public options: NormalizedPostgresConnection, public poolOptions: pgwire.PgPoolOptions) {
|
|
13
|
+
// The pool is lazy - no connections are opened until a query is performed.
|
|
14
|
+
this.pool = pgwire.connectPgWirePool(this.options, poolOptions);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a new replication connection.
|
|
19
|
+
*/
|
|
20
|
+
async replicationConnection(): Promise<pgwire.PgConnection> {
|
|
21
|
+
const p = pgwire.connectPgWire(this.options, { type: 'replication' });
|
|
22
|
+
this.connectionPromises.push(p);
|
|
23
|
+
return await p;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a new standard connection, used for initial snapshot.
|
|
28
|
+
*
|
|
29
|
+
* This connection must not be shared between multiple async contexts.
|
|
30
|
+
*/
|
|
31
|
+
async snapshotConnection(): Promise<pgwire.PgConnection> {
|
|
32
|
+
const p = pgwire.connectPgWire(this.options, { type: 'standard' });
|
|
33
|
+
this.connectionPromises.push(p);
|
|
34
|
+
return await p;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async end() {
|
|
38
|
+
for (let result of await Promise.allSettled([
|
|
39
|
+
this.pool.end(),
|
|
40
|
+
...this.connectionPromises.map((promise) => {
|
|
41
|
+
return promise.then((connection) => connection.end());
|
|
42
|
+
})
|
|
43
|
+
])) {
|
|
44
|
+
// Throw the first error, if any
|
|
45
|
+
if (result.status == 'rejected') {
|
|
46
|
+
throw result.reason;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async destroy() {
|
|
52
|
+
this.pool.destroy();
|
|
53
|
+
for (let result of await Promise.allSettled([
|
|
54
|
+
...this.connectionPromises.map((promise) => {
|
|
55
|
+
return promise.then((connection) => connection.destroy());
|
|
56
|
+
})
|
|
57
|
+
])) {
|
|
58
|
+
// Throw the first error, if any
|
|
59
|
+
if (result.status == 'rejected') {
|
|
60
|
+
throw result.reason;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
2
|
+
|
|
3
|
+
let globalTags: Record<string, string> = {};
|
|
4
|
+
|
|
5
|
+
export function setTags(tags: Record<string, string>) {
|
|
6
|
+
globalTags = tags;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getGlobalTags() {
|
|
10
|
+
return globalTags;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function captureException(error: any, options?: micro.alerts.CaptureOptions) {
|
|
14
|
+
micro.alerts.captureException(error, {
|
|
15
|
+
...options
|
|
16
|
+
});
|
|
17
|
+
}
|