@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,79 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
|
|
3
|
+
export type MigrationFunction = (db: pgwire.PgConnection) => Promise<void>;
|
|
4
|
+
|
|
5
|
+
interface Migration {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
up: MigrationFunction;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Very loosely based on https://github.com/porsager/postgres-shift/
|
|
12
|
+
export class Migrations {
|
|
13
|
+
private migrations: Migration[] = [];
|
|
14
|
+
|
|
15
|
+
add(id: number, name: string, up: MigrationFunction) {
|
|
16
|
+
if (this.migrations.length > 0 && this.migrations[this.migrations.length - 1].id >= id) {
|
|
17
|
+
throw new Error('Migration ids must be strictly incrementing');
|
|
18
|
+
}
|
|
19
|
+
this.migrations.push({ id, up, name });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async up(db: pgwire.PgConnection) {
|
|
23
|
+
await db.query('BEGIN');
|
|
24
|
+
try {
|
|
25
|
+
await this.ensureMigrationsTable(db);
|
|
26
|
+
const current = await this.getCurrentMigration(db);
|
|
27
|
+
let currentId = current ? current.id : 0;
|
|
28
|
+
|
|
29
|
+
for (let migration of this.migrations) {
|
|
30
|
+
if (migration.id <= currentId) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
await migration.up(db);
|
|
34
|
+
|
|
35
|
+
await db.query({
|
|
36
|
+
statement: `
|
|
37
|
+
insert into migrations (
|
|
38
|
+
migration_id,
|
|
39
|
+
name
|
|
40
|
+
) values (
|
|
41
|
+
$1,
|
|
42
|
+
$2
|
|
43
|
+
)
|
|
44
|
+
`,
|
|
45
|
+
params: [
|
|
46
|
+
{ type: 'int4', value: migration.id },
|
|
47
|
+
{ type: 'varchar', value: migration.name }
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await db.query('COMMIT');
|
|
53
|
+
} catch (e) {
|
|
54
|
+
await db.query('ROLLBACK');
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getCurrentMigration(db: pgwire.PgConnection) {
|
|
60
|
+
return db
|
|
61
|
+
.query(
|
|
62
|
+
`
|
|
63
|
+
select migration_id as id from migrations
|
|
64
|
+
order by migration_id desc
|
|
65
|
+
limit 1
|
|
66
|
+
`
|
|
67
|
+
)
|
|
68
|
+
.then((results) => ({ id: results.rows[0][0] as number }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async ensureMigrationsTable(db: pgwire.PgConnection) {
|
|
72
|
+
await db.query(`create table if not exists migrations (
|
|
73
|
+
migration_id serial primary key,
|
|
74
|
+
created_at timestamp with time zone not null default now(),
|
|
75
|
+
name text
|
|
76
|
+
)
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Adapted from https://github.com/kagis/pgwire/blob/0dc927f9f8990a903f238737326e53ba1c8d094f/mod.js#L2218
|
|
2
|
+
|
|
3
|
+
import * as bson from 'bson';
|
|
4
|
+
import * as uuid from 'uuid';
|
|
5
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
6
|
+
import { SqliteJsonValue, SqliteRow, ToastableSqliteRow, toSyncRulesRow } from '@powersync/service-sync-rules';
|
|
7
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
8
|
+
|
|
9
|
+
import * as replication from '@/replication/replication-index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* pgwire message -> SQLite row.
|
|
13
|
+
* @param message
|
|
14
|
+
*/
|
|
15
|
+
export function constructAfterRecord(message: pgwire.PgoutputInsert | pgwire.PgoutputUpdate): SqliteRow {
|
|
16
|
+
const rawData = (message as any).afterRaw;
|
|
17
|
+
|
|
18
|
+
const record = pgwire.decodeTuple(message.relation, rawData);
|
|
19
|
+
return toSyncRulesRow(record);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasToastedValues(row: ToastableSqliteRow) {
|
|
23
|
+
for (let key in row) {
|
|
24
|
+
if (typeof row[key] == 'undefined') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isCompleteRow(row: ToastableSqliteRow): row is SqliteRow {
|
|
32
|
+
return !hasToastedValues(row);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* pgwire message -> SQLite row.
|
|
37
|
+
* @param message
|
|
38
|
+
*/
|
|
39
|
+
export function constructBeforeRecord(message: pgwire.PgoutputDelete | pgwire.PgoutputUpdate): SqliteRow | undefined {
|
|
40
|
+
const rawData = (message as any).beforeRaw;
|
|
41
|
+
if (rawData == null) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const record = pgwire.decodeTuple(message.relation, rawData);
|
|
45
|
+
return toSyncRulesRow(record);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getRawReplicaIdentity(
|
|
49
|
+
tuple: ToastableSqliteRow,
|
|
50
|
+
columns: replication.ReplicationColumn[]
|
|
51
|
+
): Record<string, any> {
|
|
52
|
+
let result: Record<string, any> = {};
|
|
53
|
+
for (let column of columns) {
|
|
54
|
+
const name = column.name;
|
|
55
|
+
result[name] = tuple[name];
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
const ID_NAMESPACE = 'a396dd91-09fc-4017-a28d-3df722f651e9';
|
|
60
|
+
|
|
61
|
+
export function getUuidReplicaIdentityString(
|
|
62
|
+
tuple: ToastableSqliteRow,
|
|
63
|
+
columns: replication.ReplicationColumn[]
|
|
64
|
+
): string {
|
|
65
|
+
const rawIdentity = getRawReplicaIdentity(tuple, columns);
|
|
66
|
+
|
|
67
|
+
return uuidForRow(rawIdentity);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function uuidForRow(row: SqliteRow): string {
|
|
71
|
+
// Important: This must not change, since it will affect how ids are generated.
|
|
72
|
+
// Use BSON so that it's a well-defined format without encoding ambiguities.
|
|
73
|
+
const repr = bson.serialize(row);
|
|
74
|
+
return uuid.v5(repr, ID_NAMESPACE);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getUuidReplicaIdentityBson(
|
|
78
|
+
tuple: ToastableSqliteRow,
|
|
79
|
+
columns: replication.ReplicationColumn[]
|
|
80
|
+
): bson.UUID {
|
|
81
|
+
if (columns.length == 0) {
|
|
82
|
+
// REPLICA IDENTITY NOTHING - generate random id
|
|
83
|
+
return new bson.UUID(uuid.v4());
|
|
84
|
+
}
|
|
85
|
+
const rawIdentity = getRawReplicaIdentity(tuple, columns);
|
|
86
|
+
|
|
87
|
+
return uuidForRowBson(rawIdentity);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function uuidForRowBson(row: SqliteRow): bson.UUID {
|
|
91
|
+
// Important: This must not change, since it will affect how ids are generated.
|
|
92
|
+
// Use BSON so that it's a well-defined format without encoding ambiguities.
|
|
93
|
+
const repr = bson.serialize(row);
|
|
94
|
+
const buffer = Buffer.alloc(16);
|
|
95
|
+
return new bson.UUID(uuid.v5(repr, ID_NAMESPACE, buffer));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function escapeIdentifier(identifier: string) {
|
|
99
|
+
return `"${identifier.replace(/"/g, '""').replace(/\./g, '"."')}"`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function autoParameter(arg: SqliteJsonValue | boolean): pgwire.StatementParam {
|
|
103
|
+
if (arg == null) {
|
|
104
|
+
return { type: 'varchar', value: null };
|
|
105
|
+
} else if (typeof arg == 'string') {
|
|
106
|
+
return { type: 'varchar', value: arg };
|
|
107
|
+
} else if (typeof arg == 'number') {
|
|
108
|
+
if (Number.isInteger(arg)) {
|
|
109
|
+
return { type: 'int8', value: arg };
|
|
110
|
+
} else {
|
|
111
|
+
return { type: 'float8', value: arg };
|
|
112
|
+
}
|
|
113
|
+
} else if (typeof arg == 'boolean') {
|
|
114
|
+
return { type: 'bool', value: arg };
|
|
115
|
+
} else if (typeof arg == 'bigint') {
|
|
116
|
+
return { type: 'int8', value: arg };
|
|
117
|
+
} else {
|
|
118
|
+
throw new Error(`Unsupported query parameter: ${typeof arg}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function retriedQuery(db: pgwire.PgClient, ...statements: pgwire.Statement[]): Promise<pgwire.PgResult>;
|
|
123
|
+
export async function retriedQuery(db: pgwire.PgClient, query: string): Promise<pgwire.PgResult>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Retry a simple query - up to 2 attempts total.
|
|
127
|
+
*/
|
|
128
|
+
export async function retriedQuery(db: pgwire.PgClient, ...args: any[]) {
|
|
129
|
+
for (let tries = 2; ; tries--) {
|
|
130
|
+
try {
|
|
131
|
+
return await db.query(...args);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
if (tries == 1) {
|
|
134
|
+
throw e;
|
|
135
|
+
}
|
|
136
|
+
micro.logger.warn('Query error, retrying', e);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
3
|
+
|
|
4
|
+
import { connectPgWire } from '@powersync/service-jpgwire';
|
|
5
|
+
import { NormalizedPostgresConnection } from '@powersync/service-types';
|
|
6
|
+
|
|
7
|
+
// This util is actually for tests only, but we need it compiled to JS for the service to work, so it's placed in the service.
|
|
8
|
+
|
|
9
|
+
export interface PopulateDataOptions {
|
|
10
|
+
connection: NormalizedPostgresConnection;
|
|
11
|
+
num_transactions: number;
|
|
12
|
+
per_transaction: number;
|
|
13
|
+
size: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (isMainThread || parentPort == null) {
|
|
17
|
+
// Not a worker - ignore
|
|
18
|
+
} else {
|
|
19
|
+
try {
|
|
20
|
+
const options = workerData as PopulateDataOptions;
|
|
21
|
+
|
|
22
|
+
const result = await populateDataInner(options);
|
|
23
|
+
parentPort.postMessage(result);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// This is a bug, not a connection issue
|
|
27
|
+
console.error(e);
|
|
28
|
+
// Only closes the Worker thread
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function populateDataInner(options: PopulateDataOptions) {
|
|
34
|
+
// Dedicated connection so we can release the memory easily
|
|
35
|
+
const initialDb = await connectPgWire(options.connection, { type: 'standard' });
|
|
36
|
+
const largeDescription = crypto.randomBytes(options.size / 2).toString('hex');
|
|
37
|
+
let operation_count = 0;
|
|
38
|
+
for (let i = 0; i < options.num_transactions; i++) {
|
|
39
|
+
const prefix = `test${i}K`;
|
|
40
|
+
|
|
41
|
+
await initialDb.query({
|
|
42
|
+
statement: `INSERT INTO test_data(id, description, other) SELECT $1 || i, $2, 'foo' FROM generate_series(1, $3) i`,
|
|
43
|
+
params: [
|
|
44
|
+
{ type: 'varchar', value: prefix },
|
|
45
|
+
{ type: 'varchar', value: largeDescription },
|
|
46
|
+
{ type: 'int4', value: options.per_transaction }
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
operation_count += options.per_transaction;
|
|
50
|
+
}
|
|
51
|
+
await initialDb.end();
|
|
52
|
+
return operation_count;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function populateData(options: PopulateDataOptions) {
|
|
56
|
+
const WORKER_TIMEOUT = 30_000;
|
|
57
|
+
|
|
58
|
+
const worker = new Worker(new URL('./populate_test_data.js', import.meta.url), {
|
|
59
|
+
workerData: options
|
|
60
|
+
});
|
|
61
|
+
const timeout = setTimeout(() => {
|
|
62
|
+
// Exits with code 1 below
|
|
63
|
+
worker.terminate();
|
|
64
|
+
}, WORKER_TIMEOUT);
|
|
65
|
+
try {
|
|
66
|
+
return await new Promise<number>((resolve, reject) => {
|
|
67
|
+
worker.on('message', resolve);
|
|
68
|
+
worker.on('error', reject);
|
|
69
|
+
worker.on('exit', (code) => {
|
|
70
|
+
if (code !== 0) {
|
|
71
|
+
reject(new Error(`Populating data failed with exit code ${code}`));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
} finally {
|
|
76
|
+
clearTimeout(timeout);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as t from 'ts-codec';
|
|
2
|
+
import { SqliteJsonValue } from '@powersync/service-sync-rules';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* For sync2.json
|
|
6
|
+
*/
|
|
7
|
+
export interface ContinueCheckpointRequest {
|
|
8
|
+
/**
|
|
9
|
+
* Existing bucket states. Only these buckets are synchronized.
|
|
10
|
+
*/
|
|
11
|
+
buckets: BucketRequest[];
|
|
12
|
+
|
|
13
|
+
checkpoint_token: string;
|
|
14
|
+
|
|
15
|
+
limit?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SyncNewCheckpointRequest {
|
|
19
|
+
/**
|
|
20
|
+
* Existing bucket states. Used if include_data is specified.
|
|
21
|
+
*/
|
|
22
|
+
buckets?: BucketRequest[];
|
|
23
|
+
|
|
24
|
+
request_checkpoint: {
|
|
25
|
+
/**
|
|
26
|
+
* Whether or not to include an initial data request.
|
|
27
|
+
*/
|
|
28
|
+
include_data: boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether or not to compute a checksum.
|
|
32
|
+
*/
|
|
33
|
+
include_checksum: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SyncRequest = ContinueCheckpointRequest | SyncNewCheckpointRequest;
|
|
40
|
+
|
|
41
|
+
export interface SyncResponse {
|
|
42
|
+
/**
|
|
43
|
+
* Data for the buckets returned. May not have an an entry for each bucket in the request.
|
|
44
|
+
*/
|
|
45
|
+
data?: SyncBucketData[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* True if the response limit has been reached, and another request must be made.
|
|
49
|
+
*/
|
|
50
|
+
has_more: boolean;
|
|
51
|
+
|
|
52
|
+
checkpoint_token?: string;
|
|
53
|
+
|
|
54
|
+
checkpoint?: Checkpoint;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const BucketRequest = t.object({
|
|
58
|
+
name: t.string,
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Base-10 number. Sync all data from this bucket with op_id > after.
|
|
62
|
+
*/
|
|
63
|
+
after: t.string
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export type BucketRequest = t.Decoded<typeof BucketRequest>;
|
|
67
|
+
|
|
68
|
+
export const StreamingSyncRequest = t.object({
|
|
69
|
+
/**
|
|
70
|
+
* Existing bucket states.
|
|
71
|
+
*/
|
|
72
|
+
buckets: t.array(BucketRequest).optional(),
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* If specified, limit the response to only include these buckets.
|
|
76
|
+
*/
|
|
77
|
+
only: t.array(t.string).optional(),
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Whether or not to compute a checksum for each checkpoint
|
|
81
|
+
*/
|
|
82
|
+
include_checksum: t.boolean.optional(),
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* True to keep `data` as a string, instead of nested JSON.
|
|
86
|
+
*/
|
|
87
|
+
raw_data: t.boolean.optional(),
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Data is received in a serialized BSON Buffer
|
|
91
|
+
*/
|
|
92
|
+
binary_data: t.boolean.optional()
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export type StreamingSyncRequest = t.Decoded<typeof StreamingSyncRequest>;
|
|
96
|
+
|
|
97
|
+
export interface StreamingSyncCheckpoint {
|
|
98
|
+
checkpoint: Checkpoint;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface StreamingSyncCheckpointDiff {
|
|
102
|
+
checkpoint_diff: {
|
|
103
|
+
last_op_id: OpId;
|
|
104
|
+
write_checkpoint?: OpId;
|
|
105
|
+
updated_buckets: BucketChecksum[];
|
|
106
|
+
removed_buckets: string[];
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface StreamingSyncData {
|
|
111
|
+
data: SyncBucketData;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface StreamingSyncCheckpointComplete {
|
|
115
|
+
checkpoint_complete: {
|
|
116
|
+
last_op_id: OpId;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface StreamingSyncKeepalive {}
|
|
121
|
+
|
|
122
|
+
export type StreamingSyncLine =
|
|
123
|
+
| StreamingSyncData
|
|
124
|
+
| StreamingSyncCheckpoint
|
|
125
|
+
| StreamingSyncCheckpointDiff
|
|
126
|
+
| StreamingSyncCheckpointComplete
|
|
127
|
+
| StreamingSyncKeepalive;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 64-bit unsigned number, as a base-10 string.
|
|
131
|
+
*/
|
|
132
|
+
export type OpId = string;
|
|
133
|
+
|
|
134
|
+
export interface Checkpoint {
|
|
135
|
+
last_op_id: OpId;
|
|
136
|
+
write_checkpoint?: OpId;
|
|
137
|
+
buckets: BucketChecksum[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface BucketState {
|
|
141
|
+
bucket: string;
|
|
142
|
+
op_id: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface SyncDataBatch {
|
|
146
|
+
buckets: SyncBucketData[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface SyncBucketData {
|
|
150
|
+
bucket: string;
|
|
151
|
+
data: OplogEntry[];
|
|
152
|
+
/**
|
|
153
|
+
* True if the response does not contain all the data for this bucket, and another request must be made.
|
|
154
|
+
*/
|
|
155
|
+
has_more: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* The `after` specified in the request.
|
|
158
|
+
*/
|
|
159
|
+
after: OpId;
|
|
160
|
+
/**
|
|
161
|
+
* Use this for the next request.
|
|
162
|
+
*/
|
|
163
|
+
next_after: OpId;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface OplogEntry {
|
|
167
|
+
op_id: OpId;
|
|
168
|
+
op: 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR';
|
|
169
|
+
object_type?: string;
|
|
170
|
+
object_id?: string;
|
|
171
|
+
data?: Record<string, SqliteJsonValue> | string | null;
|
|
172
|
+
checksum: number | bigint;
|
|
173
|
+
subkey?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface BucketChecksum {
|
|
177
|
+
bucket: string;
|
|
178
|
+
/**
|
|
179
|
+
* 32-bit unsigned hash.
|
|
180
|
+
*/
|
|
181
|
+
checksum: number;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Count of operations - informational only.
|
|
185
|
+
*/
|
|
186
|
+
count: number;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function isContinueCheckpointRequest(request: SyncRequest): request is ContinueCheckpointRequest {
|
|
190
|
+
return (
|
|
191
|
+
Array.isArray((request as ContinueCheckpointRequest).buckets) &&
|
|
192
|
+
typeof (request as ContinueCheckpointRequest).checkpoint_token == 'string'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isSyncNewCheckpointRequest(request: SyncRequest): request is SyncNewCheckpointRequest {
|
|
197
|
+
return typeof (request as SyncNewCheckpointRequest).request_checkpoint == 'object';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* For crud.json
|
|
202
|
+
*/
|
|
203
|
+
export interface CrudRequest {
|
|
204
|
+
data: CrudEntry[];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface CrudEntry {
|
|
208
|
+
op: 'PUT' | 'PATCH' | 'DELETE';
|
|
209
|
+
type: string;
|
|
210
|
+
id: string;
|
|
211
|
+
data: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface CrudResponse {
|
|
215
|
+
/**
|
|
216
|
+
* A sync response with a checkpoint >= this checkpoint would contain all the changes in this request.
|
|
217
|
+
*
|
|
218
|
+
* Any earlier checkpoint may or may not contain these changes.
|
|
219
|
+
*
|
|
220
|
+
* May be empty when the request contains no ops.
|
|
221
|
+
*/
|
|
222
|
+
checkpoint?: OpId;
|
|
223
|
+
}
|
package/src/util/secs.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// From https://github.com/panva/jose/blob/1c2ae17a15838757ae2bfda9d1d8389d435f74b5/src/lib/secs.ts#L8
|
|
2
|
+
// MIT license.
|
|
3
|
+
// Copied here since it's not exported by jose.
|
|
4
|
+
|
|
5
|
+
const minute = 60;
|
|
6
|
+
const hour = minute * 60;
|
|
7
|
+
const day = hour * 24;
|
|
8
|
+
const week = day * 7;
|
|
9
|
+
const year = day * 365.25;
|
|
10
|
+
|
|
11
|
+
const REGEX = /^(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)$/i;
|
|
12
|
+
|
|
13
|
+
export default (str: string): number => {
|
|
14
|
+
const matched = REGEX.exec(str);
|
|
15
|
+
|
|
16
|
+
if (!matched) {
|
|
17
|
+
throw new TypeError('Invalid time period format');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const value = parseFloat(matched[1]);
|
|
21
|
+
const unit = matched[2].toLowerCase();
|
|
22
|
+
|
|
23
|
+
switch (unit) {
|
|
24
|
+
case 'sec':
|
|
25
|
+
case 'secs':
|
|
26
|
+
case 'second':
|
|
27
|
+
case 'seconds':
|
|
28
|
+
case 's':
|
|
29
|
+
return Math.round(value);
|
|
30
|
+
case 'minute':
|
|
31
|
+
case 'minutes':
|
|
32
|
+
case 'min':
|
|
33
|
+
case 'mins':
|
|
34
|
+
case 'm':
|
|
35
|
+
return Math.round(value * minute);
|
|
36
|
+
case 'hour':
|
|
37
|
+
case 'hours':
|
|
38
|
+
case 'hr':
|
|
39
|
+
case 'hrs':
|
|
40
|
+
case 'h':
|
|
41
|
+
return Math.round(value * hour);
|
|
42
|
+
case 'day':
|
|
43
|
+
case 'days':
|
|
44
|
+
case 'd':
|
|
45
|
+
return Math.round(value * day);
|
|
46
|
+
case 'week':
|
|
47
|
+
case 'weeks':
|
|
48
|
+
case 'w':
|
|
49
|
+
return Math.round(value * week);
|
|
50
|
+
// years matched
|
|
51
|
+
default:
|
|
52
|
+
return Math.round(value * year);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * from './alerting.js';
|
|
2
|
+
export * from './env.js';
|
|
3
|
+
export * from './memory-tracking.js';
|
|
4
|
+
export * from './migration_lib.js';
|
|
5
|
+
export * from './Mutex.js';
|
|
6
|
+
export * from './PgManager.js';
|
|
7
|
+
export * from './pgwire_utils.js';
|
|
8
|
+
export * from './populate_test_data.js';
|
|
9
|
+
export * from './protocol-types.js';
|
|
10
|
+
export * from './secs.js';
|
|
11
|
+
export * from './utils.js';
|
|
12
|
+
|
|
13
|
+
export * from './config.js';
|
|
14
|
+
export * from './config/types.js';
|
|
15
|
+
export * from './config/compound-config-collector.js';
|
|
16
|
+
|
|
17
|
+
export * from './config/collectors/config-collector.js';
|
|
18
|
+
export * from './config/collectors/impl/base64-config-collector.js';
|
|
19
|
+
export * from './config/collectors/impl/fallback-config-collector.js';
|
|
20
|
+
export * from './config/collectors/impl/filesystem-config-collector.js';
|
|
21
|
+
|
|
22
|
+
export * from './config/sync-rules/sync-collector.js';
|
|
23
|
+
export * from './config/sync-rules/impl/base64-sync-rules-collector.js';
|
|
24
|
+
export * from './config/sync-rules/impl/filesystem-sync-rules-collector.js';
|
|
25
|
+
export * from './config/sync-rules/impl/inline-sync-rules-collector.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
3
|
+
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
4
|
+
import * as micro from '@journeyapps-platform/micro';
|
|
5
|
+
|
|
6
|
+
import * as storage from '@/storage/storage-index.js';
|
|
7
|
+
import { BucketChecksum, OpId } from './protocol-types.js';
|
|
8
|
+
import { retriedQuery } from './pgwire_utils.js';
|
|
9
|
+
|
|
10
|
+
export function hashData(type: string, id: string, data: string): number {
|
|
11
|
+
const hash = crypto.createHash('sha256');
|
|
12
|
+
hash.update(`put.${type}.${id}.${data}`);
|
|
13
|
+
const buffer = hash.digest();
|
|
14
|
+
return buffer.readUInt32LE(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hashDelete(sourceKey: string) {
|
|
18
|
+
const hash = crypto.createHash('sha256');
|
|
19
|
+
hash.update(`delete.${sourceKey}`);
|
|
20
|
+
const buffer = hash.digest();
|
|
21
|
+
return buffer.readUInt32LE(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function timestampToOpId(ts: bigint): OpId {
|
|
25
|
+
// Dynamic values are passed in in some cases, so we make extra sure that the
|
|
26
|
+
// number is a bigint and not number or Long.
|
|
27
|
+
if (typeof ts != 'bigint') {
|
|
28
|
+
throw new Error(`bigint expected, got: ${ts} (${typeof ts})`);
|
|
29
|
+
}
|
|
30
|
+
return ts.toString(10);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function checksumsDiff(previous: BucketChecksum[], current: BucketChecksum[]) {
|
|
34
|
+
const updated_buckets: BucketChecksum[] = [];
|
|
35
|
+
|
|
36
|
+
const previousBuckets = new Map<string, BucketChecksum>();
|
|
37
|
+
for (let checksum of previous) {
|
|
38
|
+
previousBuckets.set(checksum.bucket, checksum);
|
|
39
|
+
}
|
|
40
|
+
for (let checksum of current) {
|
|
41
|
+
if (!previousBuckets.has(checksum.bucket)) {
|
|
42
|
+
updated_buckets.push(checksum);
|
|
43
|
+
} else {
|
|
44
|
+
const p = previousBuckets.get(checksum.bucket);
|
|
45
|
+
if (p?.checksum != checksum.checksum || p?.count != checksum.count) {
|
|
46
|
+
updated_buckets.push(checksum);
|
|
47
|
+
}
|
|
48
|
+
previousBuckets.delete(checksum.bucket);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const removed_buckets: string[] = [...previousBuckets.keys()];
|
|
53
|
+
return {
|
|
54
|
+
updated_buckets,
|
|
55
|
+
removed_buckets
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function getClientCheckpoint(
|
|
60
|
+
db: pgwire.PgClient,
|
|
61
|
+
bucketStorage: storage.BucketStorageFactory,
|
|
62
|
+
options?: { timeout?: number }
|
|
63
|
+
): Promise<OpId> {
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
|
|
66
|
+
const [{ lsn }] = pgwireRows(await db.query(`SELECT pg_logical_emit_message(false, 'powersync', 'ping') as lsn`));
|
|
67
|
+
|
|
68
|
+
// This old API needs a persisted checkpoint id.
|
|
69
|
+
// Since we don't use LSNs anymore, the only way to get that is to wait.
|
|
70
|
+
|
|
71
|
+
const timeout = options?.timeout ?? 50_000;
|
|
72
|
+
|
|
73
|
+
micro.logger.info(`Waiting for LSN checkpoint: ${lsn}`);
|
|
74
|
+
while (Date.now() - start < timeout) {
|
|
75
|
+
const cp = await bucketStorage.getActiveCheckpoint();
|
|
76
|
+
if (!cp.hasSyncRules()) {
|
|
77
|
+
throw new Error('No sync rules available');
|
|
78
|
+
}
|
|
79
|
+
if (cp.lsn >= lsn) {
|
|
80
|
+
micro.logger.info(`Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
|
|
81
|
+
return cp.checkpoint;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error('Timeout while waiting for checkpoint');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function createWriteCheckpoint(
|
|
91
|
+
db: pgwire.PgClient,
|
|
92
|
+
bucketStorage: storage.BucketStorageFactory,
|
|
93
|
+
user_id: string
|
|
94
|
+
): Promise<bigint> {
|
|
95
|
+
const [{ lsn }] = pgwireRows(
|
|
96
|
+
await retriedQuery(db, `SELECT pg_logical_emit_message(false, 'powersync', 'ping') as lsn`)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const id = await bucketStorage.createWriteCheckpoint(user_id, { '1': lsn });
|
|
100
|
+
micro.logger.info(`Write checkpoint 2: ${JSON.stringify({ lsn, id: String(id) })}`);
|
|
101
|
+
return id;
|
|
102
|
+
}
|