@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,161 @@
|
|
|
1
|
+
import { AbortError } from 'ix/aborterror.js';
|
|
2
|
+
import { LastValueSink } from './LastValueSink.js';
|
|
3
|
+
|
|
4
|
+
export type IterableSource<T> = (signal: AbortSignal) => AsyncIterable<T>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Loosely based on Dart's broadcast streams:
|
|
8
|
+
* https://api.flutter.dev/flutter/dart-async/StreamController/StreamController.broadcast.html
|
|
9
|
+
*
|
|
10
|
+
* 1. Exposes an AsyncIterable interface.
|
|
11
|
+
* 2. Allows multiple concurrent subscribers.
|
|
12
|
+
* 3. Any new subscriber gets new events as they are published.
|
|
13
|
+
* 4. The source iterable is only created once there are subscribers.
|
|
14
|
+
*
|
|
15
|
+
* One notable difference: The last value is buffered and immediately returned for any new subscribers.
|
|
16
|
+
*
|
|
17
|
+
* When all subscribers are stopped, this source iterable is stopped.
|
|
18
|
+
*
|
|
19
|
+
* Any error on the source is passed on to all subscribers. This generally stops all
|
|
20
|
+
* subscribers. Once a new subscriber is then added, a new source is started.
|
|
21
|
+
*
|
|
22
|
+
* Slow subscribers will skip events.
|
|
23
|
+
*
|
|
24
|
+
* Implementation note: It is possible to do this using:
|
|
25
|
+
* 1. rxjs `share` for multicasting and auto-starting + stopping the source.
|
|
26
|
+
* 2. rxjs `BehaviorSubject` to keep the last value.
|
|
27
|
+
* 3. Use `LastValueSink` to convert to an AsyncIterable.
|
|
28
|
+
*/
|
|
29
|
+
export class BroadcastIterable<T> implements AsyncIterable<T> {
|
|
30
|
+
private last: T | undefined = undefined;
|
|
31
|
+
private subscribers: Set<LastValueSink<T>> | undefined = undefined;
|
|
32
|
+
private abortController: AbortController | undefined = undefined;
|
|
33
|
+
|
|
34
|
+
constructor(private source: IterableSource<T>) {}
|
|
35
|
+
|
|
36
|
+
private start(sink: LastValueSink<T>) {
|
|
37
|
+
const abortController = new AbortController();
|
|
38
|
+
const listeners = new Set<LastValueSink<T>>();
|
|
39
|
+
listeners.add(sink);
|
|
40
|
+
|
|
41
|
+
this.abortController = abortController;
|
|
42
|
+
this.subscribers = listeners;
|
|
43
|
+
|
|
44
|
+
this.loop(abortController, listeners);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async loop(abortController: AbortController, sinks: Set<LastValueSink<T>>) {
|
|
48
|
+
try {
|
|
49
|
+
for await (let doc of this.source(abortController.signal)) {
|
|
50
|
+
if (abortController.signal.aborted || sinks.size == 0) {
|
|
51
|
+
throw new AbortError();
|
|
52
|
+
}
|
|
53
|
+
this.last = doc;
|
|
54
|
+
for (let sink of sinks) {
|
|
55
|
+
sink.next(doc);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// End of stream
|
|
60
|
+
for (let sink of sinks) {
|
|
61
|
+
sink.complete();
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Just in case the error is not from the source
|
|
65
|
+
abortController.abort();
|
|
66
|
+
|
|
67
|
+
for (let listener of sinks) {
|
|
68
|
+
listener.error(e);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
// Clear state, so that a new subscription may be started
|
|
72
|
+
if (this.subscribers === sinks) {
|
|
73
|
+
this.subscribers = undefined;
|
|
74
|
+
this.abortController = undefined;
|
|
75
|
+
this.last = undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private removeSink(listener: LastValueSink<T>) {
|
|
81
|
+
this.subscribers?.delete(listener);
|
|
82
|
+
if (this.subscribers?.size == 0) {
|
|
83
|
+
// This is not immediate - there may be a delay until it is fully stopped,
|
|
84
|
+
// depending on the underlying source.
|
|
85
|
+
this.abortController?.abort();
|
|
86
|
+
this.last = undefined;
|
|
87
|
+
this.subscribers = undefined;
|
|
88
|
+
this.abortController = undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private addSink(listener: LastValueSink<T>) {
|
|
93
|
+
if (this.subscribers == null) {
|
|
94
|
+
this.start(listener);
|
|
95
|
+
} else {
|
|
96
|
+
this.subscribers.add(listener);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async *[Symbol.asyncIterator](signal?: AbortSignal): AsyncIterator<T> {
|
|
101
|
+
const sink = new LastValueSink(this.last);
|
|
102
|
+
this.addSink(sink);
|
|
103
|
+
try {
|
|
104
|
+
yield* sink.withSignal(signal);
|
|
105
|
+
} finally {
|
|
106
|
+
this.removeSink(sink);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get active() {
|
|
111
|
+
return this.subscribers != null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// For reference, this is an alternative implementation using rxjs.
|
|
116
|
+
// It still relies on LastValueSink.
|
|
117
|
+
//
|
|
118
|
+
// import { BehaviorSubject, Observable, filter, from, share } from 'rxjs';
|
|
119
|
+
//
|
|
120
|
+
// export class RxBroadcastIterable<T> implements AsyncIterable<T> {
|
|
121
|
+
// private observable: Observable<T>;
|
|
122
|
+
//
|
|
123
|
+
// constructor(source: IterableSource<T>) {
|
|
124
|
+
// const obsSource = new Observable<T>((subscriber) => {
|
|
125
|
+
// const controller = new AbortController();
|
|
126
|
+
// const s = source(controller.signal);
|
|
127
|
+
// const inner = from(s);
|
|
128
|
+
// const subscription = inner.subscribe(subscriber);
|
|
129
|
+
// subscription.add({
|
|
130
|
+
// unsubscribe() {
|
|
131
|
+
// controller.abort();
|
|
132
|
+
// }
|
|
133
|
+
// });
|
|
134
|
+
// return subscription;
|
|
135
|
+
// });
|
|
136
|
+
//
|
|
137
|
+
// const obs = obsSource.pipe(
|
|
138
|
+
// share({ connector: () => new BehaviorSubject(undefined as T) }),
|
|
139
|
+
// filter((v) => v != undefined)
|
|
140
|
+
// );
|
|
141
|
+
// this.observable = obs;
|
|
142
|
+
// }
|
|
143
|
+
//
|
|
144
|
+
// async *[Symbol.asyncIterator](signal?: AbortSignal): AsyncIterator<T> {
|
|
145
|
+
// if (signal?.aborted) {
|
|
146
|
+
// return;
|
|
147
|
+
// }
|
|
148
|
+
// const sink = new LastValueSink<T>(undefined);
|
|
149
|
+
// const subscription = this.observable.subscribe(sink);
|
|
150
|
+
//
|
|
151
|
+
// try {
|
|
152
|
+
// yield* sink.withSignal(signal);
|
|
153
|
+
// } finally {
|
|
154
|
+
// subscription.unsubscribe();
|
|
155
|
+
// }
|
|
156
|
+
// }
|
|
157
|
+
//
|
|
158
|
+
// get active() {
|
|
159
|
+
// return false;
|
|
160
|
+
// }
|
|
161
|
+
// }
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { AbortError } from 'ix/aborterror.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An AsyncIterable that allows pushing values.
|
|
5
|
+
*
|
|
6
|
+
* If the consumer is slower than the source, only the last value is buffered.
|
|
7
|
+
*
|
|
8
|
+
* Similar to ix AsyncSink, except that we only keep the last value.
|
|
9
|
+
*/
|
|
10
|
+
export class LastValueSink<T> implements AsyncIterable<T> {
|
|
11
|
+
buffer: NextResult<T> | undefined;
|
|
12
|
+
nextResolve: undefined | (() => void);
|
|
13
|
+
|
|
14
|
+
constructor(initial: T | undefined) {
|
|
15
|
+
if (initial != null) {
|
|
16
|
+
this.buffer = { value: initial, done: false, error: undefined };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
next(value: T) {
|
|
21
|
+
this.push({
|
|
22
|
+
value,
|
|
23
|
+
done: false,
|
|
24
|
+
error: undefined
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
complete() {
|
|
29
|
+
this.push({
|
|
30
|
+
value: undefined,
|
|
31
|
+
done: true,
|
|
32
|
+
error: undefined
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
error(e: any) {
|
|
37
|
+
this.push({
|
|
38
|
+
value: undefined,
|
|
39
|
+
done: true,
|
|
40
|
+
error: e
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private push(r: NextResult<T>) {
|
|
45
|
+
if (this.buffer?.done) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.buffer = r;
|
|
49
|
+
this.nextResolve?.();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async *withSignal(signal?: AbortSignal): AsyncIterable<T> {
|
|
53
|
+
if (!signal) {
|
|
54
|
+
yield* this;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (signal?.aborted) {
|
|
59
|
+
throw new AbortError();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const onAbort = () => {
|
|
63
|
+
this.error(new AbortError());
|
|
64
|
+
};
|
|
65
|
+
signal?.addEventListener('abort', onAbort);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
yield* this;
|
|
69
|
+
} finally {
|
|
70
|
+
signal?.removeEventListener('abort', onAbort);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async *[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
75
|
+
while (true) {
|
|
76
|
+
if (this.buffer == null) {
|
|
77
|
+
const promise = new Promise<void>((resolve) => {
|
|
78
|
+
this.nextResolve = resolve;
|
|
79
|
+
});
|
|
80
|
+
await promise;
|
|
81
|
+
}
|
|
82
|
+
const n = this.buffer!;
|
|
83
|
+
this.buffer = undefined;
|
|
84
|
+
|
|
85
|
+
if (n.error) {
|
|
86
|
+
throw n.error;
|
|
87
|
+
} else if (n.done) {
|
|
88
|
+
return;
|
|
89
|
+
} else {
|
|
90
|
+
yield n.value!;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface NextResult<T> {
|
|
97
|
+
value: T | undefined;
|
|
98
|
+
done: boolean;
|
|
99
|
+
error: any;
|
|
100
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { throwIfAborted } from 'ix/aborterror.js';
|
|
2
|
+
import { AsyncIterableX } from 'ix/asynciterable/index.js';
|
|
3
|
+
import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
|
|
4
|
+
import { safeRace } from './safeRace.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Merge multiple source AsyncIterables into one output AsyncIterable.
|
|
8
|
+
*
|
|
9
|
+
* The results from all iterables are interleaved.
|
|
10
|
+
*
|
|
11
|
+
* When any iterable is done, the output stops.
|
|
12
|
+
*
|
|
13
|
+
* @param source The source AsyncIterables.
|
|
14
|
+
* @param signal Optional signal to abort iteration.
|
|
15
|
+
* @returns The merged AsyncIterable.
|
|
16
|
+
*/
|
|
17
|
+
export function mergeAsyncIterables<T>(source: AsyncIterable<T>[], signal?: AbortSignal): AsyncIterable<T> {
|
|
18
|
+
return mergeAsyncIterablesNew(source, signal);
|
|
19
|
+
// return mergeAsyncIterablesOld(source, signal);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function mergeAsyncIterablesNew<T>(source: AsyncIterable<T>[], signal?: AbortSignal): AsyncIterable<T> {
|
|
23
|
+
return new MergedAsyncIterable(source, signal);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mergeAsyncIterablesOld<T>(source: AsyncIterable<T>[], signal?: AbortSignal): AsyncIterable<T> {
|
|
27
|
+
return wrapWithAbort(new FixedMergeAsyncIterable(source, { race: true }), signal);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const NEVER_PROMISE = new Promise<never>(() => {});
|
|
31
|
+
|
|
32
|
+
type MergeResult<T> = { value: T; index: number };
|
|
33
|
+
|
|
34
|
+
function wrapPromiseWithIndex<T>(promise: Promise<T>, index: number) {
|
|
35
|
+
return promise.then((value) => ({ value, index })) as Promise<MergeResult<T>>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Re-implementation of FixedMergeAsyncIterable, without using async generators.
|
|
40
|
+
*
|
|
41
|
+
* The functionality should be the same in most cases.
|
|
42
|
+
*/
|
|
43
|
+
class MergedAsyncIterable<T> implements AsyncIterable<T> {
|
|
44
|
+
private _source: AsyncIterable<T>[];
|
|
45
|
+
private _signal?: AbortSignal;
|
|
46
|
+
|
|
47
|
+
constructor(source: AsyncIterable<T>[], signal?: AbortSignal) {
|
|
48
|
+
this._source = source;
|
|
49
|
+
this._signal = signal;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
53
|
+
const signal = this._signal;
|
|
54
|
+
throwIfAborted(signal);
|
|
55
|
+
|
|
56
|
+
const length = this._source.length;
|
|
57
|
+
const iterators = new Array<AsyncIterator<T>>(length);
|
|
58
|
+
const nexts = new Array<Promise<MergeResult<IteratorResult<T>>> | null>(length);
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < length; i++) {
|
|
61
|
+
const iterator = wrapWithAbort(this._source[i], signal)[Symbol.asyncIterator]();
|
|
62
|
+
iterators[i] = iterator;
|
|
63
|
+
nexts[i] = null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const returnIterators = async (_value?: any): Promise<IteratorResult<T>> => {
|
|
67
|
+
for (let iter of iterators) {
|
|
68
|
+
// Do not wait for return() to complete, since it may block
|
|
69
|
+
iter.return?.();
|
|
70
|
+
}
|
|
71
|
+
return { value: undefined, done: true };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
next: async (): Promise<IteratorResult<T>> => {
|
|
76
|
+
for (let i = 0; i < length; i++) {
|
|
77
|
+
if (nexts[i] == null) {
|
|
78
|
+
nexts[i] = wrapPromiseWithIndex(iterators[i].next(), i);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// IMPORTANT! safeRace here acts like Promise.race, but avoids major memory leaks.
|
|
84
|
+
const {
|
|
85
|
+
value: { done, value },
|
|
86
|
+
index
|
|
87
|
+
} = (await safeRace(nexts as Array<Promise<MergeResult<IteratorResult<T>>>>))!;
|
|
88
|
+
if (done) {
|
|
89
|
+
// One of the source iterators is done - return them all
|
|
90
|
+
await returnIterators();
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
value: undefined,
|
|
94
|
+
done: true
|
|
95
|
+
};
|
|
96
|
+
} else {
|
|
97
|
+
// Consume the result, which will cause the next one to be requested on the
|
|
98
|
+
// next iteration.
|
|
99
|
+
nexts[index] = null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
value: value,
|
|
103
|
+
done: false
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// One of the source iterators raised an error - return all others
|
|
108
|
+
// and propagate the error
|
|
109
|
+
await returnIterators();
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
return: () => {
|
|
114
|
+
return returnIterators();
|
|
115
|
+
},
|
|
116
|
+
throw: async (e) => {
|
|
117
|
+
for (let iter of iterators) {
|
|
118
|
+
// Do not wait for throw() to complete, since it may block
|
|
119
|
+
iter.throw?.(e);
|
|
120
|
+
}
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Loosely based on IxJS merge, but with some changes:
|
|
129
|
+
* 1. Fix an issue with uncaught errors.
|
|
130
|
+
* Partially fixed: https://github.com/ReactiveX/IxJS/pull/354
|
|
131
|
+
* Essentially, we only want to call next() on inner iterators when next() is called on the outer one.
|
|
132
|
+
* 2. Call return() on all inner iterators when the outer one returns.
|
|
133
|
+
* 3. Returning when the first iterator returns.
|
|
134
|
+
*
|
|
135
|
+
* https://github.com/ReactiveX/IxJS/blob/f07b7ef4095120f1ef21a4023030c75b36335cd1/src/asynciterable/merge.ts
|
|
136
|
+
*/
|
|
137
|
+
export class FixedMergeAsyncIterable<T> extends AsyncIterableX<T> {
|
|
138
|
+
private _source: AsyncIterable<T>[];
|
|
139
|
+
private _race: boolean;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
*
|
|
143
|
+
* @param source
|
|
144
|
+
* @param options Specify `race: true` to stop the iterator when any inner one returns, instead of waiting for all.
|
|
145
|
+
*/
|
|
146
|
+
constructor(source: AsyncIterable<T>[], options?: { race?: boolean }) {
|
|
147
|
+
super();
|
|
148
|
+
this._source = source;
|
|
149
|
+
this._race = options?.race ?? false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async *[Symbol.asyncIterator](signal?: AbortSignal): AsyncIterator<T> {
|
|
153
|
+
throwIfAborted(signal);
|
|
154
|
+
const length = this._source.length;
|
|
155
|
+
const iterators = new Array<AsyncIterator<T>>(length);
|
|
156
|
+
const nexts = new Array<Promise<MergeResult<IteratorResult<T>>>>(length);
|
|
157
|
+
let active = length;
|
|
158
|
+
for (let i = 0; i < length; i++) {
|
|
159
|
+
const iterator = wrapWithAbort(this._source[i], signal)[Symbol.asyncIterator]();
|
|
160
|
+
iterators[i] = iterator;
|
|
161
|
+
nexts[i] = wrapPromiseWithIndex(iterator.next(), i);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
while (active > 0) {
|
|
166
|
+
// IMPORTANT! safeRace here acts like Promise.race, but avoids major memory leaks.
|
|
167
|
+
const {
|
|
168
|
+
value: { done, value },
|
|
169
|
+
index
|
|
170
|
+
} = await safeRace(nexts);
|
|
171
|
+
if (done) {
|
|
172
|
+
nexts[index] = NEVER_PROMISE;
|
|
173
|
+
active--;
|
|
174
|
+
if (this._race) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const iterator$ = iterators[index];
|
|
179
|
+
nexts[index] = NEVER_PROMISE;
|
|
180
|
+
try {
|
|
181
|
+
yield value;
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// iter.throw() was called on the merged iterator
|
|
184
|
+
// Propagate the error to the source iterators
|
|
185
|
+
for (let iter of iterators) {
|
|
186
|
+
iter.throw?.(e);
|
|
187
|
+
}
|
|
188
|
+
throw e;
|
|
189
|
+
}
|
|
190
|
+
nexts[index] = wrapPromiseWithIndex(iterator$.next(), index);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
for (let iter of iterators) {
|
|
195
|
+
// This may be an early return - return all inner iterators
|
|
196
|
+
iter.return?.();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Source: https://github.com/ReactiveX/IxJS/blob/ff1cef03d91a4a7c527fa8c8dbdac025a652c236/src/util/safeRace.ts
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This is free and unencumbered software released into the public domain.
|
|
5
|
+
|
|
6
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
7
|
+
distribute this software, either in source code form or as a compiled
|
|
8
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
9
|
+
means.
|
|
10
|
+
|
|
11
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
12
|
+
of this software dedicate any and all copyright interest in the
|
|
13
|
+
software to the public domain. We make this dedication for the benefit
|
|
14
|
+
of the public at large and to the detriment of our heirs and
|
|
15
|
+
successors. We intend this dedication to be an overt act of
|
|
16
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
17
|
+
software under copyright law.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
20
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
21
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
22
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
23
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
24
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
For more information, please refer to <http://unlicense.org/>
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// see: https://github.com/nodejs/node/issues/17469#issuecomment-685216777
|
|
31
|
+
// see: https://github.com/ReactiveX/IxJS/pull/323
|
|
32
|
+
|
|
33
|
+
function isPrimitive(value: unknown): boolean {
|
|
34
|
+
return value === null || (typeof value !== 'object' && typeof value !== 'function');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Keys are the values passed to race, values are a record of data containing a
|
|
38
|
+
// set of deferreds and whether the value has settled.
|
|
39
|
+
const wm = new WeakMap<any, any>();
|
|
40
|
+
export function safeRace<T>(contenders: Promise<T>[]): Promise<T> {
|
|
41
|
+
let deferred: any;
|
|
42
|
+
const result = new Promise<T>((resolve, reject) => {
|
|
43
|
+
deferred = { resolve, reject };
|
|
44
|
+
for (const contender of contenders) {
|
|
45
|
+
if (isPrimitive(contender)) {
|
|
46
|
+
// If the contender is a primitive, attempting to use it as a key in the
|
|
47
|
+
// weakmap would throw an error. Luckily, it is safe to call
|
|
48
|
+
// `Promise.resolve(contender).then` on a primitive value multiple times
|
|
49
|
+
// because the promise fulfills immediately.
|
|
50
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let record = wm.get(contender);
|
|
55
|
+
if (record === undefined) {
|
|
56
|
+
record = { deferreds: new Set([deferred]), settled: false };
|
|
57
|
+
wm.set(contender, record);
|
|
58
|
+
// This call to `then` happens once for the lifetime of the value.
|
|
59
|
+
Promise.resolve(contender).then(
|
|
60
|
+
(value) => {
|
|
61
|
+
// eslint-disable-next-line no-shadow
|
|
62
|
+
for (const { resolve } of record.deferreds) {
|
|
63
|
+
resolve(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
record.deferreds.clear();
|
|
67
|
+
record.settled = true;
|
|
68
|
+
},
|
|
69
|
+
(err) => {
|
|
70
|
+
// eslint-disable-next-line no-shadow
|
|
71
|
+
for (const { reject } of record.deferreds) {
|
|
72
|
+
reject(err);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
record.deferreds.clear();
|
|
76
|
+
record.settled = true;
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
} else if (record.settled) {
|
|
80
|
+
// If the value has settled, it is safe to call
|
|
81
|
+
// `Promise.resolve(contender).then` on it.
|
|
82
|
+
Promise.resolve(contender).then(resolve, reject);
|
|
83
|
+
} else {
|
|
84
|
+
record.deferreds.add(deferred);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// The finally callback executes when any value settles, preventing any of
|
|
90
|
+
// the unresolved values from retaining a reference to the resolved value.
|
|
91
|
+
return result.finally(() => {
|
|
92
|
+
for (const contender of contenders) {
|
|
93
|
+
if (!isPrimitive(contender)) {
|
|
94
|
+
const record = wm.get(contender);
|
|
95
|
+
record.deferreds.delete(deferred);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|