@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/test/src/util.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import { normalizeConnection } from '@powersync/service-types';
|
|
3
|
+
import * as mongo from 'mongodb';
|
|
4
|
+
import { BucketStorageFactory } from '../../src/storage/BucketStorage.js';
|
|
5
|
+
import { MongoBucketStorage } from '../../src/storage/MongoBucketStorage.js';
|
|
6
|
+
import { PowerSyncMongo } from '../../src/storage/mongo/db.js';
|
|
7
|
+
import { escapeIdentifier } from '../../src/util/pgwire_utils.js';
|
|
8
|
+
import { env } from './env.js';
|
|
9
|
+
|
|
10
|
+
export const TEST_URI = env.PG_TEST_URL;
|
|
11
|
+
|
|
12
|
+
export type StorageFactory = () => Promise<BucketStorageFactory>;
|
|
13
|
+
|
|
14
|
+
export const MONGO_STORAGE_FACTORY: StorageFactory = async () => {
|
|
15
|
+
const db = await connectMongo();
|
|
16
|
+
await db.clear();
|
|
17
|
+
return new MongoBucketStorage(db, { slot_name_prefix: 'test_' });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function clearTestDb(db: pgwire.PgClient) {
|
|
21
|
+
await db.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
|
22
|
+
try {
|
|
23
|
+
await db.query(`DROP PUBLICATION powersync`);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Ignore
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await db.query(`CREATE PUBLICATION powersync FOR ALL TABLES`);
|
|
29
|
+
|
|
30
|
+
const tableRows = pgwire.pgwireRows(
|
|
31
|
+
await db.query(`SELECT table_name FROM information_schema.tables where table_schema = 'public'`)
|
|
32
|
+
);
|
|
33
|
+
for (let row of tableRows) {
|
|
34
|
+
const name = row.table_name;
|
|
35
|
+
if (name.startsWith('test_')) {
|
|
36
|
+
await db.query(`DROP TABLE public.${escapeIdentifier(name)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const TEST_CONNECTION_OPTIONS = normalizeConnection({
|
|
42
|
+
type: 'postgresql',
|
|
43
|
+
uri: TEST_URI,
|
|
44
|
+
sslmode: 'disable'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export async function connectPgWire(type?: 'replication' | 'standard') {
|
|
48
|
+
const db = await pgwire.connectPgWire(TEST_CONNECTION_OPTIONS, { type });
|
|
49
|
+
return db;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function connectPgPool() {
|
|
53
|
+
const db = pgwire.connectPgWirePool(TEST_CONNECTION_OPTIONS);
|
|
54
|
+
return db;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function connectMongo() {
|
|
58
|
+
// Short timeout for tests, to fail fast when the server is not available.
|
|
59
|
+
// Slightly longer timeouts for CI, to avoid arbitrary test failures
|
|
60
|
+
const client = new mongo.MongoClient(env.MONGO_TEST_URL, {
|
|
61
|
+
connectTimeoutMS: env.CI ? 15_000 : 5_000,
|
|
62
|
+
socketTimeoutMS: env.CI ? 15_000 : 5_000,
|
|
63
|
+
serverSelectionTimeoutMS: env.CI ? 15_000 : 2_500
|
|
64
|
+
});
|
|
65
|
+
const db = new PowerSyncMongo(client);
|
|
66
|
+
return db;
|
|
67
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { MONGO_STORAGE_FACTORY } from './util.js';
|
|
3
|
+
import { walStreamTest } from './wal_stream_utils.js';
|
|
4
|
+
import { WalConnection } from '../../src/replication/WalConnection.js';
|
|
5
|
+
|
|
6
|
+
// Not quite a walStreamTest, but it helps to manage the connection
|
|
7
|
+
test(
|
|
8
|
+
'validate tables',
|
|
9
|
+
walStreamTest(MONGO_STORAGE_FACTORY, async (context) => {
|
|
10
|
+
const { pool } = context;
|
|
11
|
+
|
|
12
|
+
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
13
|
+
|
|
14
|
+
const syncRuleContent = `
|
|
15
|
+
bucket_definitions:
|
|
16
|
+
global:
|
|
17
|
+
data:
|
|
18
|
+
- SELECT id, description FROM "test_data"
|
|
19
|
+
- SELECT * FROM "other"
|
|
20
|
+
- SELECT * FROM "other%"
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const syncRules = await context.factory.updateSyncRules({ content: syncRuleContent });
|
|
24
|
+
|
|
25
|
+
const walConnection = new WalConnection({
|
|
26
|
+
db: pool,
|
|
27
|
+
sync_rules: syncRules.parsed().sync_rules
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const tablePatterns = syncRules.parsed().sync_rules.getSourceTables();
|
|
31
|
+
const tableInfo = await walConnection.getDebugTablesInfo(tablePatterns);
|
|
32
|
+
expect(tableInfo).toEqual([
|
|
33
|
+
{
|
|
34
|
+
schema: 'public',
|
|
35
|
+
pattern: 'test_data',
|
|
36
|
+
wildcard: false,
|
|
37
|
+
table: {
|
|
38
|
+
schema: 'public',
|
|
39
|
+
name: 'test_data',
|
|
40
|
+
replication_id: ['id'],
|
|
41
|
+
pattern: undefined,
|
|
42
|
+
data_queries: true,
|
|
43
|
+
parameter_queries: false,
|
|
44
|
+
errors: []
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
schema: 'public',
|
|
49
|
+
pattern: 'other',
|
|
50
|
+
wildcard: false,
|
|
51
|
+
table: {
|
|
52
|
+
schema: 'public',
|
|
53
|
+
name: 'other',
|
|
54
|
+
replication_id: [],
|
|
55
|
+
data_queries: true,
|
|
56
|
+
parameter_queries: false,
|
|
57
|
+
errors: [{ level: 'warning', message: 'Table "public"."other" not found.' }]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{ schema: 'public', pattern: 'other%', wildcard: true, tables: [] }
|
|
61
|
+
]);
|
|
62
|
+
})
|
|
63
|
+
);
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { BucketStorageFactory } from '../../src/storage/BucketStorage.js';
|
|
4
|
+
import { MONGO_STORAGE_FACTORY } from './util.js';
|
|
5
|
+
import { putOp, removeOp, walStreamTest } from './wal_stream_utils.js';
|
|
6
|
+
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
7
|
+
import { getMetricValueForTests } from '../../src/metrics/metrics.js';
|
|
8
|
+
|
|
9
|
+
type StorageFactory = () => Promise<BucketStorageFactory>;
|
|
10
|
+
|
|
11
|
+
const BASIC_SYNC_RULES = `
|
|
12
|
+
bucket_definitions:
|
|
13
|
+
global:
|
|
14
|
+
data:
|
|
15
|
+
- SELECT id, description FROM "test_data"
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
describe(
|
|
19
|
+
'wal stream - mongodb',
|
|
20
|
+
function () {
|
|
21
|
+
defineWalStreamTests(MONGO_STORAGE_FACTORY);
|
|
22
|
+
},
|
|
23
|
+
{ timeout: 20_000 }
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function defineWalStreamTests(factory: StorageFactory) {
|
|
27
|
+
test(
|
|
28
|
+
'replicating basic values',
|
|
29
|
+
walStreamTest(factory, async (context) => {
|
|
30
|
+
const { pool } = context;
|
|
31
|
+
await context.updateSyncRules(`
|
|
32
|
+
bucket_definitions:
|
|
33
|
+
global:
|
|
34
|
+
data:
|
|
35
|
+
- SELECT id, description, num FROM "test_data"`);
|
|
36
|
+
|
|
37
|
+
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
38
|
+
await pool.query(
|
|
39
|
+
`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text, num int8)`
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await context.replicateSnapshot();
|
|
43
|
+
|
|
44
|
+
const startRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
45
|
+
const startTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
46
|
+
|
|
47
|
+
context.startStreaming();
|
|
48
|
+
|
|
49
|
+
const [{ test_id }] = pgwireRows(
|
|
50
|
+
await pool.query(
|
|
51
|
+
`INSERT INTO test_data(description, num) VALUES('test1', 1152921504606846976) returning id as test_id`
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const data = await context.getBucketData('global[]');
|
|
56
|
+
|
|
57
|
+
expect(data).toMatchObject([
|
|
58
|
+
putOp('test_data', { id: test_id, description: 'test1', num: 1152921504606846976n })
|
|
59
|
+
]);
|
|
60
|
+
const endRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
61
|
+
const endTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
62
|
+
expect(endRowCount - startRowCount).toEqual(1);
|
|
63
|
+
expect(endTxCount - startTxCount).toEqual(1);
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
test(
|
|
68
|
+
'replicating case sensitive table',
|
|
69
|
+
walStreamTest(factory, async (context) => {
|
|
70
|
+
const { pool } = context;
|
|
71
|
+
await context.updateSyncRules(`
|
|
72
|
+
bucket_definitions:
|
|
73
|
+
global:
|
|
74
|
+
data:
|
|
75
|
+
- SELECT id, description FROM "test_DATA"
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
await pool.query(`DROP TABLE IF EXISTS "test_DATA"`);
|
|
79
|
+
await pool.query(`CREATE TABLE "test_DATA"(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
80
|
+
|
|
81
|
+
await context.replicateSnapshot();
|
|
82
|
+
|
|
83
|
+
const startRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
84
|
+
const startTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
85
|
+
|
|
86
|
+
context.startStreaming();
|
|
87
|
+
|
|
88
|
+
const [{ test_id }] = pgwireRows(
|
|
89
|
+
await pool.query(`INSERT INTO "test_DATA"(description) VALUES('test1') returning id as test_id`)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const data = await context.getBucketData('global[]');
|
|
93
|
+
|
|
94
|
+
expect(data).toMatchObject([putOp('test_DATA', { id: test_id, description: 'test1' })]);
|
|
95
|
+
const endRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
96
|
+
const endTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
97
|
+
expect(endRowCount - startRowCount).toEqual(1);
|
|
98
|
+
expect(endTxCount - startTxCount).toEqual(1);
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
test(
|
|
103
|
+
'replicating TOAST values',
|
|
104
|
+
walStreamTest(factory, async (context) => {
|
|
105
|
+
const { pool } = context;
|
|
106
|
+
await context.updateSyncRules(`
|
|
107
|
+
bucket_definitions:
|
|
108
|
+
global:
|
|
109
|
+
data:
|
|
110
|
+
- SELECT id, name, description FROM "test_data"
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
114
|
+
await pool.query(
|
|
115
|
+
`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), name text, description text)`
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
await context.replicateSnapshot();
|
|
119
|
+
context.startStreaming();
|
|
120
|
+
|
|
121
|
+
// Must be > 8kb after compression
|
|
122
|
+
const largeDescription = crypto.randomBytes(20_000).toString('hex');
|
|
123
|
+
const [{ test_id }] = pgwireRows(
|
|
124
|
+
await pool.query({
|
|
125
|
+
statement: `INSERT INTO test_data(name, description) VALUES('test1', $1) returning id as test_id`,
|
|
126
|
+
params: [{ type: 'varchar', value: largeDescription }]
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await pool.query(`UPDATE test_data SET name = 'test2' WHERE id = '${test_id}'`);
|
|
131
|
+
|
|
132
|
+
const data = await context.getBucketData('global[]');
|
|
133
|
+
expect(data.slice(0, 1)).toMatchObject([
|
|
134
|
+
putOp('test_data', { id: test_id, name: 'test1', description: largeDescription })
|
|
135
|
+
]);
|
|
136
|
+
expect(data.slice(1)).toMatchObject([
|
|
137
|
+
putOp('test_data', { id: test_id, name: 'test2', description: largeDescription })
|
|
138
|
+
]);
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
test(
|
|
143
|
+
'replicating TRUNCATE',
|
|
144
|
+
walStreamTest(factory, async (context) => {
|
|
145
|
+
const { pool } = context;
|
|
146
|
+
const syncRuleContent = `
|
|
147
|
+
bucket_definitions:
|
|
148
|
+
global:
|
|
149
|
+
data:
|
|
150
|
+
- SELECT id, description FROM "test_data"
|
|
151
|
+
by_test_data:
|
|
152
|
+
parameters: SELECT id FROM test_data WHERE id = token_parameters.user_id
|
|
153
|
+
data: []
|
|
154
|
+
`;
|
|
155
|
+
await context.updateSyncRules(syncRuleContent);
|
|
156
|
+
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
157
|
+
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
158
|
+
|
|
159
|
+
await context.replicateSnapshot();
|
|
160
|
+
context.startStreaming();
|
|
161
|
+
|
|
162
|
+
const [{ test_id }] = pgwireRows(
|
|
163
|
+
await pool.query(`INSERT INTO test_data(description) VALUES('test1') returning id as test_id`)
|
|
164
|
+
);
|
|
165
|
+
await pool.query(`TRUNCATE test_data`);
|
|
166
|
+
|
|
167
|
+
const data = await context.getBucketData('global[]');
|
|
168
|
+
|
|
169
|
+
expect(data).toMatchObject([
|
|
170
|
+
putOp('test_data', { id: test_id, description: 'test1' }),
|
|
171
|
+
removeOp('test_data', test_id)
|
|
172
|
+
]);
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
test(
|
|
177
|
+
'replicating changing primary key',
|
|
178
|
+
walStreamTest(factory, async (context) => {
|
|
179
|
+
const { pool } = context;
|
|
180
|
+
await context.updateSyncRules(BASIC_SYNC_RULES);
|
|
181
|
+
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
182
|
+
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
183
|
+
|
|
184
|
+
await context.replicateSnapshot();
|
|
185
|
+
context.startStreaming();
|
|
186
|
+
|
|
187
|
+
const [{ test_id }] = pgwireRows(
|
|
188
|
+
await pool.query(`INSERT INTO test_data(description) VALUES('test1') returning id as test_id`)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const [{ test_id: test_id2 }] = pgwireRows(
|
|
192
|
+
await pool.query(
|
|
193
|
+
`UPDATE test_data SET id = uuid_generate_v4(), description = 'test2a' WHERE id = '${test_id}' returning id as test_id`
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// This update may fail replicating with:
|
|
198
|
+
// Error: Update on missing record public.test_data:074a601e-fc78-4c33-a15d-f89fdd4af31d :: {"g":1,"t":"651e9fbe9fec6155895057ec","k":"1a0b34da-fb8c-5e6f-8421-d7a3c5d4df4f"}
|
|
199
|
+
await pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = '${test_id2}'`);
|
|
200
|
+
|
|
201
|
+
// Re-use old id again
|
|
202
|
+
await pool.query(`INSERT INTO test_data(id, description) VALUES('${test_id}', 'test1b')`);
|
|
203
|
+
await pool.query(`UPDATE test_data SET description = 'test1c' WHERE id = '${test_id}'`);
|
|
204
|
+
|
|
205
|
+
const data = await context.getBucketData('global[]');
|
|
206
|
+
expect(data).toMatchObject([
|
|
207
|
+
// Initial insert
|
|
208
|
+
putOp('test_data', { id: test_id, description: 'test1' }),
|
|
209
|
+
// Update id, then description
|
|
210
|
+
removeOp('test_data', test_id),
|
|
211
|
+
putOp('test_data', { id: test_id2, description: 'test2a' }),
|
|
212
|
+
putOp('test_data', { id: test_id2, description: 'test2b' }),
|
|
213
|
+
// Re-use old id
|
|
214
|
+
putOp('test_data', { id: test_id, description: 'test1b' }),
|
|
215
|
+
putOp('test_data', { id: test_id, description: 'test1c' })
|
|
216
|
+
]);
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
test(
|
|
221
|
+
'initial sync',
|
|
222
|
+
walStreamTest(factory, async (context) => {
|
|
223
|
+
const { pool } = context;
|
|
224
|
+
await context.updateSyncRules(BASIC_SYNC_RULES);
|
|
225
|
+
|
|
226
|
+
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
227
|
+
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
228
|
+
|
|
229
|
+
const [{ test_id }] = pgwireRows(
|
|
230
|
+
await pool.query(`INSERT INTO test_data(description) VALUES('test1') returning id as test_id`)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
await context.replicateSnapshot();
|
|
234
|
+
context.startStreaming();
|
|
235
|
+
|
|
236
|
+
const data = await context.getBucketData('global[]');
|
|
237
|
+
expect(data).toMatchObject([putOp('test_data', { id: test_id, description: 'test1' })]);
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
test(
|
|
242
|
+
'record too large',
|
|
243
|
+
walStreamTest(factory, async (context) => {
|
|
244
|
+
await context.updateSyncRules(`bucket_definitions:
|
|
245
|
+
global:
|
|
246
|
+
data:
|
|
247
|
+
- SELECT id, description, other FROM "test_data"`);
|
|
248
|
+
const { pool } = context;
|
|
249
|
+
|
|
250
|
+
await pool.query(`CREATE TABLE test_data(id text primary key, description text, other text)`);
|
|
251
|
+
|
|
252
|
+
await context.replicateSnapshot();
|
|
253
|
+
|
|
254
|
+
// 4MB
|
|
255
|
+
const largeDescription = crypto.randomBytes(2_000_000).toString('hex');
|
|
256
|
+
// 18MB
|
|
257
|
+
const tooLargeDescription = crypto.randomBytes(9_000_000).toString('hex');
|
|
258
|
+
|
|
259
|
+
await pool.query({
|
|
260
|
+
statement: `INSERT INTO test_data(id, description, other) VALUES('t1', $1, 'foo')`,
|
|
261
|
+
params: [{ type: 'varchar', value: tooLargeDescription }]
|
|
262
|
+
});
|
|
263
|
+
await pool.query({
|
|
264
|
+
statement: `UPDATE test_data SET description = $1 WHERE id = 't1'`,
|
|
265
|
+
params: [{ type: 'varchar', value: largeDescription }]
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
context.startStreaming();
|
|
269
|
+
|
|
270
|
+
const data = await context.getBucketData('global[]');
|
|
271
|
+
expect(data.length).toEqual(1);
|
|
272
|
+
const row = JSON.parse(data[0].data as string);
|
|
273
|
+
delete row.description;
|
|
274
|
+
expect(row).toEqual({ id: 't1', other: 'foo' });
|
|
275
|
+
delete data[0].data;
|
|
276
|
+
expect(data[0]).toMatchObject({ object_id: 't1', object_type: 'test_data', op: 'PUT', op_id: '1' });
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
test(
|
|
281
|
+
'table not in sync rules',
|
|
282
|
+
walStreamTest(factory, async (context) => {
|
|
283
|
+
const { pool } = context;
|
|
284
|
+
await context.updateSyncRules(BASIC_SYNC_RULES);
|
|
285
|
+
|
|
286
|
+
await pool.query(`CREATE TABLE test_donotsync(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
287
|
+
|
|
288
|
+
await context.replicateSnapshot();
|
|
289
|
+
|
|
290
|
+
const startRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
291
|
+
const startTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
292
|
+
|
|
293
|
+
context.startStreaming();
|
|
294
|
+
|
|
295
|
+
const [{ test_id }] = pgwireRows(
|
|
296
|
+
await pool.query(`INSERT INTO test_donotsync(description) VALUES('test1') returning id as test_id`)
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const data = await context.getBucketData('global[]');
|
|
300
|
+
|
|
301
|
+
expect(data).toMatchObject([]);
|
|
302
|
+
const endRowCount = (await getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
303
|
+
const endTxCount = (await getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
304
|
+
|
|
305
|
+
// There was a transaction, but we should not replicate any actual data
|
|
306
|
+
expect(endRowCount - startRowCount).toEqual(0);
|
|
307
|
+
expect(endTxCount - startTxCount).toEqual(1);
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import { WalStream, WalStreamOptions } from '../../src/replication/WalStream.js';
|
|
3
|
+
import { BucketStorageFactory, SyncRulesBucketStorage } from '../../src/storage/BucketStorage.js';
|
|
4
|
+
import { OplogEntry } from '../../src/util/protocol-types.js';
|
|
5
|
+
import { getClientCheckpoint } from '../../src/util/utils.js';
|
|
6
|
+
import { TEST_CONNECTION_OPTIONS, clearTestDb } from './util.js';
|
|
7
|
+
import { PgManager } from '../../src/util/PgManager.js';
|
|
8
|
+
import { JSONBig } from '@powersync/service-jsonbig';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests operating on the wal stream need to configure the stream and manage asynchronous
|
|
12
|
+
* replication, which gets a little tricky.
|
|
13
|
+
*
|
|
14
|
+
* This wraps a test in a function that configures all the context, and tears it down afterwards.
|
|
15
|
+
*/
|
|
16
|
+
export function walStreamTest(
|
|
17
|
+
factory: () => Promise<BucketStorageFactory>,
|
|
18
|
+
test: (context: WalStreamTestContext) => Promise<void>
|
|
19
|
+
): () => Promise<void> {
|
|
20
|
+
return async () => {
|
|
21
|
+
const f = await factory();
|
|
22
|
+
const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
23
|
+
await connections.pool.query(
|
|
24
|
+
'select pg_drop_replication_slot(slot_name) from pg_replication_slots where active = false'
|
|
25
|
+
);
|
|
26
|
+
await clearTestDb(connections.pool);
|
|
27
|
+
const context = new WalStreamTestContext(f, connections);
|
|
28
|
+
try {
|
|
29
|
+
await test(context);
|
|
30
|
+
} finally {
|
|
31
|
+
await context.dispose();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class WalStreamTestContext {
|
|
37
|
+
private _walStream?: WalStream;
|
|
38
|
+
private abortController = new AbortController();
|
|
39
|
+
private streamPromise?: Promise<void>;
|
|
40
|
+
public storage?: SyncRulesBucketStorage;
|
|
41
|
+
private replicationConnection?: pgwire.PgConnection;
|
|
42
|
+
|
|
43
|
+
constructor(public factory: BucketStorageFactory, public connections: PgManager) {}
|
|
44
|
+
|
|
45
|
+
async dispose() {
|
|
46
|
+
this.abortController.abort();
|
|
47
|
+
await this.streamPromise;
|
|
48
|
+
this.connections.destroy();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get pool() {
|
|
52
|
+
return this.connections.pool;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async updateSyncRules(content: string) {
|
|
56
|
+
const syncRules = await this.factory.updateSyncRules({ content: content });
|
|
57
|
+
this.storage = this.factory.getInstance(syncRules.parsed());
|
|
58
|
+
return this.storage!;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get walStream() {
|
|
62
|
+
if (this.storage == null) {
|
|
63
|
+
throw new Error('updateSyncRules() first');
|
|
64
|
+
}
|
|
65
|
+
if (this._walStream) {
|
|
66
|
+
return this._walStream;
|
|
67
|
+
}
|
|
68
|
+
const options: WalStreamOptions = {
|
|
69
|
+
storage: this.storage,
|
|
70
|
+
factory: this.factory,
|
|
71
|
+
connections: this.connections,
|
|
72
|
+
abort_signal: this.abortController.signal
|
|
73
|
+
};
|
|
74
|
+
this._walStream = new WalStream(options);
|
|
75
|
+
return this._walStream!;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async replicateSnapshot() {
|
|
79
|
+
this.replicationConnection = await this.connections.replicationConnection();
|
|
80
|
+
await this.walStream.initReplication(this.replicationConnection);
|
|
81
|
+
await this.storage!.autoActivate();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
startStreaming() {
|
|
85
|
+
if (this.replicationConnection == null) {
|
|
86
|
+
throw new Error('Call replicateSnapshot() before startStreaming()');
|
|
87
|
+
}
|
|
88
|
+
this.streamPromise = this.walStream.streamChanges(this.replicationConnection!);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getCheckpoint(options?: { timeout?: number }) {
|
|
92
|
+
let checkpoint = await Promise.race([
|
|
93
|
+
getClientCheckpoint(this.connections.pool, this.factory, { timeout: options?.timeout ?? 15_000 }),
|
|
94
|
+
this.streamPromise
|
|
95
|
+
]);
|
|
96
|
+
if (typeof checkpoint == undefined) {
|
|
97
|
+
// This indicates an issue with the test setup - streamingPromise completed instead
|
|
98
|
+
// of getClientCheckpoint()
|
|
99
|
+
throw new Error('Test failure - streamingPromise completed');
|
|
100
|
+
}
|
|
101
|
+
return checkpoint as string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getBucketsDataBatch(buckets: Record<string, string>, options?: { timeout?: number }) {
|
|
105
|
+
let checkpoint = await this.getCheckpoint(options);
|
|
106
|
+
const map = new Map<string, string>(Object.entries(buckets));
|
|
107
|
+
return fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getBucketData(bucket: string, start?: string, options?: { timeout?: number }) {
|
|
111
|
+
start ??= '0';
|
|
112
|
+
let checkpoint = await this.getCheckpoint(options);
|
|
113
|
+
const map = new Map<string, string>([[bucket, start]]);
|
|
114
|
+
const batch = await this.storage!.getBucketDataBatch(checkpoint, map);
|
|
115
|
+
const batches = await fromAsync(batch);
|
|
116
|
+
return batches[0]?.data ?? [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function putOp(table: string, data: Record<string, any>): Partial<OplogEntry> {
|
|
121
|
+
return {
|
|
122
|
+
op: 'PUT',
|
|
123
|
+
object_type: table,
|
|
124
|
+
object_id: data.id,
|
|
125
|
+
data: JSONBig.stringify(data)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function removeOp(table: string, id: string): Partial<OplogEntry> {
|
|
130
|
+
return {
|
|
131
|
+
op: 'REMOVE',
|
|
132
|
+
object_type: table,
|
|
133
|
+
object_id: id
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function compareIds(a: OplogEntry, b: OplogEntry) {
|
|
138
|
+
return a.object_id!.localeCompare(b.object_id!);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function fromAsync<T>(source: Iterable<T> | AsyncIterable<T>): Promise<T[]> {
|
|
142
|
+
const items: T[] = [];
|
|
143
|
+
for await (const item of source) {
|
|
144
|
+
items.push(item);
|
|
145
|
+
}
|
|
146
|
+
return items;
|
|
147
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@journeyapps-platform/micro-dev/tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"baseUrl": "./",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"paths": {
|
|
11
|
+
"@/*": ["../src/*"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"],
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "../"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@journeyapps-platform/micro-dev/tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"baseUrl": ".",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"paths": {
|
|
11
|
+
"@/*": ["./src/*"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"],
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "../types"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|