@dotdo/postgres 0.1.0
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/README.md +868 -0
- package/dist/cdc/change-stream.d.ts +44 -0
- package/dist/cdc/change-stream.d.ts.map +1 -0
- package/dist/cdc/change-stream.js +95 -0
- package/dist/cdc/change-stream.js.map +1 -0
- package/dist/cdc/filter.d.ts +58 -0
- package/dist/cdc/filter.d.ts.map +1 -0
- package/dist/cdc/filter.js +520 -0
- package/dist/cdc/filter.js.map +1 -0
- package/dist/cdc/index.d.ts +47 -0
- package/dist/cdc/index.d.ts.map +1 -0
- package/dist/cdc/index.js +50 -0
- package/dist/cdc/index.js.map +1 -0
- package/dist/cdc/resume-token.d.ts +60 -0
- package/dist/cdc/resume-token.d.ts.map +1 -0
- package/dist/cdc/resume-token.js +228 -0
- package/dist/cdc/resume-token.js.map +1 -0
- package/dist/cdc/transport/index.d.ts +7 -0
- package/dist/cdc/transport/index.d.ts.map +1 -0
- package/dist/cdc/transport/index.js +7 -0
- package/dist/cdc/transport/index.js.map +1 -0
- package/dist/cdc/transport/sse.d.ts +120 -0
- package/dist/cdc/transport/sse.d.ts.map +1 -0
- package/dist/cdc/transport/sse.js +590 -0
- package/dist/cdc/transport/sse.js.map +1 -0
- package/dist/cdc/transport/websocket.d.ts +130 -0
- package/dist/cdc/transport/websocket.d.ts.map +1 -0
- package/dist/cdc/transport/websocket.js +688 -0
- package/dist/cdc/transport/websocket.js.map +1 -0
- package/dist/cdc/types.d.ts +306 -0
- package/dist/cdc/types.d.ts.map +1 -0
- package/dist/cdc/types.js +8 -0
- package/dist/cdc/types.js.map +1 -0
- package/dist/config/index.d.ts +25 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +25 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/memory.d.ts +139 -0
- package/dist/config/memory.d.ts.map +1 -0
- package/dist/config/memory.js +157 -0
- package/dist/config/memory.js.map +1 -0
- package/dist/config/storage.d.ts +157 -0
- package/dist/config/storage.d.ts.map +1 -0
- package/dist/config/storage.js +178 -0
- package/dist/config/storage.js.map +1 -0
- package/dist/config/streaming.d.ts +117 -0
- package/dist/config/streaming.d.ts.map +1 -0
- package/dist/config/streaming.js +132 -0
- package/dist/config/streaming.js.map +1 -0
- package/dist/config/timeouts.d.ts +168 -0
- package/dist/config/timeouts.d.ts.map +1 -0
- package/dist/config/timeouts.js +192 -0
- package/dist/config/timeouts.js.map +1 -0
- package/dist/extensions/config.d.ts +89 -0
- package/dist/extensions/config.d.ts.map +1 -0
- package/dist/extensions/config.js +216 -0
- package/dist/extensions/config.js.map +1 -0
- package/dist/extensions/geo.d.ts +452 -0
- package/dist/extensions/geo.d.ts.map +1 -0
- package/dist/extensions/geo.js +583 -0
- package/dist/extensions/geo.js.map +1 -0
- package/dist/extensions/index.d.ts +167 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +99 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/loader.d.ts +226 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +456 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/pgmq-lite.d.ts +330 -0
- package/dist/extensions/pgmq-lite.d.ts.map +1 -0
- package/dist/extensions/pgmq-lite.js +648 -0
- package/dist/extensions/pgmq-lite.js.map +1 -0
- package/dist/extensions/plugins.d.ts +260 -0
- package/dist/extensions/plugins.d.ts.map +1 -0
- package/dist/extensions/plugins.js +535 -0
- package/dist/extensions/plugins.js.map +1 -0
- package/dist/extensions/registry.d.ts +93 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +182 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/vector.d.ts +106 -0
- package/dist/extensions/vector.d.ts.map +1 -0
- package/dist/extensions/vector.js +129 -0
- package/dist/extensions/vector.js.map +1 -0
- package/dist/iceberg/analytics.d.ts +279 -0
- package/dist/iceberg/analytics.d.ts.map +1 -0
- package/dist/iceberg/analytics.js +448 -0
- package/dist/iceberg/analytics.js.map +1 -0
- package/dist/iceberg/catalog-api.d.ts +39 -0
- package/dist/iceberg/catalog-api.d.ts.map +1 -0
- package/dist/iceberg/catalog-api.js +388 -0
- package/dist/iceberg/catalog-api.js.map +1 -0
- package/dist/iceberg/catalog.d.ts +401 -0
- package/dist/iceberg/catalog.d.ts.map +1 -0
- package/dist/iceberg/catalog.js +677 -0
- package/dist/iceberg/catalog.js.map +1 -0
- package/dist/iceberg/duckdb-wasm.d.ts +447 -0
- package/dist/iceberg/duckdb-wasm.d.ts.map +1 -0
- package/dist/iceberg/duckdb-wasm.js +600 -0
- package/dist/iceberg/duckdb-wasm.js.map +1 -0
- package/dist/iceberg/index.d.ts +92 -0
- package/dist/iceberg/index.d.ts.map +1 -0
- package/dist/iceberg/index.js +119 -0
- package/dist/iceberg/index.js.map +1 -0
- package/dist/iceberg/metadata.d.ts +214 -0
- package/dist/iceberg/metadata.d.ts.map +1 -0
- package/dist/iceberg/metadata.js +535 -0
- package/dist/iceberg/metadata.js.map +1 -0
- package/dist/iceberg/optimizer.d.ts +296 -0
- package/dist/iceberg/optimizer.d.ts.map +1 -0
- package/dist/iceberg/optimizer.js +889 -0
- package/dist/iceberg/optimizer.js.map +1 -0
- package/dist/iceberg/parquet.d.ts +447 -0
- package/dist/iceberg/parquet.d.ts.map +1 -0
- package/dist/iceberg/parquet.js +1225 -0
- package/dist/iceberg/parquet.js.map +1 -0
- package/dist/iceberg/r2-organization.d.ts +422 -0
- package/dist/iceberg/r2-organization.d.ts.map +1 -0
- package/dist/iceberg/r2-organization.js +672 -0
- package/dist/iceberg/r2-organization.js.map +1 -0
- package/dist/iceberg/scheduler-do-example.d.ts +158 -0
- package/dist/iceberg/scheduler-do-example.d.ts.map +1 -0
- package/dist/iceberg/scheduler-do-example.js +261 -0
- package/dist/iceberg/scheduler-do-example.js.map +1 -0
- package/dist/iceberg/scheduler.d.ts +434 -0
- package/dist/iceberg/scheduler.d.ts.map +1 -0
- package/dist/iceberg/scheduler.js +818 -0
- package/dist/iceberg/scheduler.js.map +1 -0
- package/dist/iceberg/schema.d.ts +149 -0
- package/dist/iceberg/schema.d.ts.map +1 -0
- package/dist/iceberg/schema.js +525 -0
- package/dist/iceberg/schema.js.map +1 -0
- package/dist/iceberg/snapshot-manager.d.ts +406 -0
- package/dist/iceberg/snapshot-manager.d.ts.map +1 -0
- package/dist/iceberg/snapshot-manager.js +934 -0
- package/dist/iceberg/snapshot-manager.js.map +1 -0
- package/dist/iceberg/sql-router.d.ts +194 -0
- package/dist/iceberg/sql-router.d.ts.map +1 -0
- package/dist/iceberg/sql-router.js +180 -0
- package/dist/iceberg/sql-router.js.map +1 -0
- package/dist/iceberg/test-fixtures.d.ts +151 -0
- package/dist/iceberg/test-fixtures.d.ts.map +1 -0
- package/dist/iceberg/test-fixtures.js +446 -0
- package/dist/iceberg/test-fixtures.js.map +1 -0
- package/dist/iceberg/time-travel-api.d.ts +102 -0
- package/dist/iceberg/time-travel-api.d.ts.map +1 -0
- package/dist/iceberg/time-travel-api.js +437 -0
- package/dist/iceberg/time-travel-api.js.map +1 -0
- package/dist/iceberg/time-travel.d.ts +293 -0
- package/dist/iceberg/time-travel.d.ts.map +1 -0
- package/dist/iceberg/time-travel.js +689 -0
- package/dist/iceberg/time-travel.js.map +1 -0
- package/dist/iceberg/transformer.d.ts +356 -0
- package/dist/iceberg/transformer.d.ts.map +1 -0
- package/dist/iceberg/transformer.js +770 -0
- package/dist/iceberg/transformer.js.map +1 -0
- package/dist/iceberg/types.d.ts +318 -0
- package/dist/iceberg/types.d.ts.map +1 -0
- package/dist/iceberg/types.js +9 -0
- package/dist/iceberg/types.js.map +1 -0
- package/dist/iceberg/writer.d.ts +144 -0
- package/dist/iceberg/writer.d.ts.map +1 -0
- package/dist/iceberg/writer.js +452 -0
- package/dist/iceberg/writer.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/lineage/index.d.ts +11 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +11 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/integration.d.ts +134 -0
- package/dist/lineage/integration.d.ts.map +1 -0
- package/dist/lineage/integration.js +258 -0
- package/dist/lineage/integration.js.map +1 -0
- package/dist/lineage/tracker.d.ts +189 -0
- package/dist/lineage/tracker.d.ts.map +1 -0
- package/dist/lineage/tracker.js +1352 -0
- package/dist/lineage/tracker.js.map +1 -0
- package/dist/lineage/types.d.ts +318 -0
- package/dist/lineage/types.d.ts.map +1 -0
- package/dist/lineage/types.js +9 -0
- package/dist/lineage/types.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +16 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +397 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +507 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/migration-tooling/external-migration.d.ts +601 -0
- package/dist/migration-tooling/external-migration.d.ts.map +1 -0
- package/dist/migration-tooling/external-migration.js +1612 -0
- package/dist/migration-tooling/external-migration.js.map +1 -0
- package/dist/migration-tooling/index.d.ts +19 -0
- package/dist/migration-tooling/index.d.ts.map +1 -0
- package/dist/migration-tooling/index.js +19 -0
- package/dist/migration-tooling/index.js.map +1 -0
- package/dist/migrations/auto-migrator.d.ts +289 -0
- package/dist/migrations/auto-migrator.d.ts.map +1 -0
- package/dist/migrations/auto-migrator.js +396 -0
- package/dist/migrations/auto-migrator.js.map +1 -0
- package/dist/migrations/bulk-orchestrator.d.ts +403 -0
- package/dist/migrations/bulk-orchestrator.d.ts.map +1 -0
- package/dist/migrations/bulk-orchestrator.js +646 -0
- package/dist/migrations/bulk-orchestrator.js.map +1 -0
- package/dist/migrations/compatibility.d.ts +216 -0
- package/dist/migrations/compatibility.d.ts.map +1 -0
- package/dist/migrations/compatibility.js +651 -0
- package/dist/migrations/compatibility.js.map +1 -0
- package/dist/migrations/do-migrations.d.ts +101 -0
- package/dist/migrations/do-migrations.d.ts.map +1 -0
- package/dist/migrations/do-migrations.js +1060 -0
- package/dist/migrations/do-migrations.js.map +1 -0
- package/dist/migrations/do-migrations.types.d.ts +550 -0
- package/dist/migrations/do-migrations.types.d.ts.map +1 -0
- package/dist/migrations/do-migrations.types.js +15 -0
- package/dist/migrations/do-migrations.types.js.map +1 -0
- package/dist/migrations/drizzle-compat.d.ts +163 -0
- package/dist/migrations/drizzle-compat.d.ts.map +1 -0
- package/dist/migrations/drizzle-compat.js +273 -0
- package/dist/migrations/drizzle-compat.js.map +1 -0
- package/dist/migrations/index.d.ts +109 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +127 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-api.d.ts +161 -0
- package/dist/migrations/migration-api.d.ts.map +1 -0
- package/dist/migrations/migration-api.js +499 -0
- package/dist/migrations/migration-api.js.map +1 -0
- package/dist/migrations/progress-tracker-do.d.ts +195 -0
- package/dist/migrations/progress-tracker-do.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-do.js +339 -0
- package/dist/migrations/progress-tracker-do.js.map +1 -0
- package/dist/migrations/progress-tracker-kv.d.ts +103 -0
- package/dist/migrations/progress-tracker-kv.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-kv.js +231 -0
- package/dist/migrations/progress-tracker-kv.js.map +1 -0
- package/dist/migrations/progress-tracker.d.ts +320 -0
- package/dist/migrations/progress-tracker.d.ts.map +1 -0
- package/dist/migrations/progress-tracker.js +443 -0
- package/dist/migrations/progress-tracker.js.map +1 -0
- package/dist/migrations/registry.d.ts +231 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +376 -0
- package/dist/migrations/registry.js.map +1 -0
- package/dist/migrations/runner.d.ts +197 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +1167 -0
- package/dist/migrations/runner.js.map +1 -0
- package/dist/migrations/schema-generator.d.ts +111 -0
- package/dist/migrations/schema-generator.d.ts.map +1 -0
- package/dist/migrations/schema-generator.js +335 -0
- package/dist/migrations/schema-generator.js.map +1 -0
- package/dist/migrations/testing.d.ts +321 -0
- package/dist/migrations/testing.d.ts.map +1 -0
- package/dist/migrations/testing.js +645 -0
- package/dist/migrations/testing.js.map +1 -0
- package/dist/migrations/types.d.ts +503 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +11 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/migrations/validator.d.ts +215 -0
- package/dist/migrations/validator.d.ts.map +1 -0
- package/dist/migrations/validator.js +494 -0
- package/dist/migrations/validator.js.map +1 -0
- package/dist/observability/alerting.d.ts +116 -0
- package/dist/observability/alerting.d.ts.map +1 -0
- package/dist/observability/alerting.js +353 -0
- package/dist/observability/alerting.js.map +1 -0
- package/dist/observability/analytics-engine.d.ts +357 -0
- package/dist/observability/analytics-engine.d.ts.map +1 -0
- package/dist/observability/analytics-engine.js +430 -0
- package/dist/observability/analytics-engine.js.map +1 -0
- package/dist/observability/cost-metrics.d.ts +269 -0
- package/dist/observability/cost-metrics.d.ts.map +1 -0
- package/dist/observability/cost-metrics.js +560 -0
- package/dist/observability/cost-metrics.js.map +1 -0
- package/dist/observability/cross-do-tracing.d.ts +305 -0
- package/dist/observability/cross-do-tracing.d.ts.map +1 -0
- package/dist/observability/cross-do-tracing.js +431 -0
- package/dist/observability/cross-do-tracing.js.map +1 -0
- package/dist/observability/error-rate-collector.d.ts +163 -0
- package/dist/observability/error-rate-collector.d.ts.map +1 -0
- package/dist/observability/error-rate-collector.js +306 -0
- package/dist/observability/error-rate-collector.js.map +1 -0
- package/dist/observability/exporters.d.ts +231 -0
- package/dist/observability/exporters.d.ts.map +1 -0
- package/dist/observability/exporters.js +479 -0
- package/dist/observability/exporters.js.map +1 -0
- package/dist/observability/health-check.d.ts +106 -0
- package/dist/observability/health-check.d.ts.map +1 -0
- package/dist/observability/health-check.js +243 -0
- package/dist/observability/health-check.js.map +1 -0
- package/dist/observability/index.d.ts +297 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +455 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/instrumentation.d.ts +222 -0
- package/dist/observability/instrumentation.d.ts.map +1 -0
- package/dist/observability/instrumentation.js +532 -0
- package/dist/observability/instrumentation.js.map +1 -0
- package/dist/observability/memory-metrics.d.ts +227 -0
- package/dist/observability/memory-metrics.d.ts.map +1 -0
- package/dist/observability/memory-metrics.js +688 -0
- package/dist/observability/memory-metrics.js.map +1 -0
- package/dist/observability/metrics-endpoint.d.ts +91 -0
- package/dist/observability/metrics-endpoint.d.ts.map +1 -0
- package/dist/observability/metrics-endpoint.js +246 -0
- package/dist/observability/metrics-endpoint.js.map +1 -0
- package/dist/observability/metrics.d.ts +88 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +253 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/observability-features.d.ts +488 -0
- package/dist/observability/observability-features.d.ts.map +1 -0
- package/dist/observability/observability-features.js +773 -0
- package/dist/observability/observability-features.js.map +1 -0
- package/dist/observability/prometheus.d.ts +39 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/prometheus.js +120 -0
- package/dist/observability/prometheus.js.map +1 -0
- package/dist/observability/propagation.d.ts +126 -0
- package/dist/observability/propagation.d.ts.map +1 -0
- package/dist/observability/propagation.js +234 -0
- package/dist/observability/propagation.js.map +1 -0
- package/dist/observability/query-latency.d.ts +243 -0
- package/dist/observability/query-latency.d.ts.map +1 -0
- package/dist/observability/query-latency.js +292 -0
- package/dist/observability/query-latency.js.map +1 -0
- package/dist/observability/query-performance.d.ts +169 -0
- package/dist/observability/query-performance.d.ts.map +1 -0
- package/dist/observability/query-performance.js +290 -0
- package/dist/observability/query-performance.js.map +1 -0
- package/dist/observability/storage-tier-metrics.d.ts +174 -0
- package/dist/observability/storage-tier-metrics.d.ts.map +1 -0
- package/dist/observability/storage-tier-metrics.js +306 -0
- package/dist/observability/storage-tier-metrics.js.map +1 -0
- package/dist/observability/tier-cost-optimizer.d.ts +155 -0
- package/dist/observability/tier-cost-optimizer.d.ts.map +1 -0
- package/dist/observability/tier-cost-optimizer.js +536 -0
- package/dist/observability/tier-cost-optimizer.js.map +1 -0
- package/dist/observability/tracer.d.ts +149 -0
- package/dist/observability/tracer.d.ts.map +1 -0
- package/dist/observability/tracer.js +435 -0
- package/dist/observability/tracer.js.map +1 -0
- package/dist/observability/types.d.ts +402 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +103 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/pglite/workers-pglite.d.ts +138 -0
- package/dist/pglite/workers-pglite.d.ts.map +1 -0
- package/dist/pglite/workers-pglite.js +143 -0
- package/dist/pglite/workers-pglite.js.map +1 -0
- package/dist/pglite-assets/pglite.data +0 -0
- package/dist/pglite-assets/pglite.wasm +0 -0
- package/dist/playground/index.d.ts +52 -0
- package/dist/playground/index.d.ts.map +1 -0
- package/dist/playground/index.js +55 -0
- package/dist/playground/index.js.map +1 -0
- package/dist/playground/keyboard-shortcuts.d.ts +116 -0
- package/dist/playground/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/playground/keyboard-shortcuts.js +588 -0
- package/dist/playground/keyboard-shortcuts.js.map +1 -0
- package/dist/playground/playground.d.ts +82 -0
- package/dist/playground/playground.d.ts.map +1 -0
- package/dist/playground/playground.js +271 -0
- package/dist/playground/playground.js.map +1 -0
- package/dist/playground/query-executor.d.ts +115 -0
- package/dist/playground/query-executor.d.ts.map +1 -0
- package/dist/playground/query-executor.js +558 -0
- package/dist/playground/query-executor.js.map +1 -0
- package/dist/playground/query-history.d.ts +92 -0
- package/dist/playground/query-history.d.ts.map +1 -0
- package/dist/playground/query-history.js +259 -0
- package/dist/playground/query-history.js.map +1 -0
- package/dist/playground/result-formatter.d.ts +59 -0
- package/dist/playground/result-formatter.d.ts.map +1 -0
- package/dist/playground/result-formatter.js +341 -0
- package/dist/playground/result-formatter.js.map +1 -0
- package/dist/playground/sample-datasets.d.ts +77 -0
- package/dist/playground/sample-datasets.d.ts.map +1 -0
- package/dist/playground/sample-datasets.js +641 -0
- package/dist/playground/sample-datasets.js.map +1 -0
- package/dist/playground/sample-queries.d.ts +73 -0
- package/dist/playground/sample-queries.d.ts.map +1 -0
- package/dist/playground/sample-queries.js +1095 -0
- package/dist/playground/sample-queries.js.map +1 -0
- package/dist/playground/schema-explorer.d.ts +55 -0
- package/dist/playground/schema-explorer.d.ts.map +1 -0
- package/dist/playground/schema-explorer.js +473 -0
- package/dist/playground/schema-explorer.js.map +1 -0
- package/dist/playground/types.d.ts +430 -0
- package/dist/playground/types.d.ts.map +1 -0
- package/dist/playground/types.js +10 -0
- package/dist/playground/types.js.map +1 -0
- package/dist/readonly/cache-reader.d.ts +145 -0
- package/dist/readonly/cache-reader.d.ts.map +1 -0
- package/dist/readonly/cache-reader.js +198 -0
- package/dist/readonly/cache-reader.js.map +1 -0
- package/dist/readonly/config.d.ts +74 -0
- package/dist/readonly/config.d.ts.map +1 -0
- package/dist/readonly/config.js +67 -0
- package/dist/readonly/config.js.map +1 -0
- package/dist/readonly/index.d.ts +22 -0
- package/dist/readonly/index.d.ts.map +1 -0
- package/dist/readonly/index.js +17 -0
- package/dist/readonly/index.js.map +1 -0
- package/dist/readonly/pglite-wrapper.d.ts +82 -0
- package/dist/readonly/pglite-wrapper.d.ts.map +1 -0
- package/dist/readonly/pglite-wrapper.js +123 -0
- package/dist/readonly/pglite-wrapper.js.map +1 -0
- package/dist/readonly/worker.d.ts +142 -0
- package/dist/readonly/worker.d.ts.map +1 -0
- package/dist/readonly/worker.js +187 -0
- package/dist/readonly/worker.js.map +1 -0
- package/dist/readonly/write-blocker.d.ts +47 -0
- package/dist/readonly/write-blocker.d.ts.map +1 -0
- package/dist/readonly/write-blocker.js +136 -0
- package/dist/readonly/write-blocker.js.map +1 -0
- package/dist/recovery/disaster-recovery.d.ts +326 -0
- package/dist/recovery/disaster-recovery.d.ts.map +1 -0
- package/dist/recovery/disaster-recovery.js +799 -0
- package/dist/recovery/disaster-recovery.js.map +1 -0
- package/dist/recovery/index.d.ts +12 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +12 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/parquet-parser.d.ts +321 -0
- package/dist/recovery/parquet-parser.d.ts.map +1 -0
- package/dist/recovery/parquet-parser.js +797 -0
- package/dist/recovery/parquet-parser.js.map +1 -0
- package/dist/retention/index.d.ts +50 -0
- package/dist/retention/index.d.ts.map +1 -0
- package/dist/retention/index.js +50 -0
- package/dist/retention/index.js.map +1 -0
- package/dist/retention/policy.d.ts +344 -0
- package/dist/retention/policy.d.ts.map +1 -0
- package/dist/retention/policy.js +472 -0
- package/dist/retention/policy.js.map +1 -0
- package/dist/retention/purger.d.ts +187 -0
- package/dist/retention/purger.d.ts.map +1 -0
- package/dist/retention/purger.js +411 -0
- package/dist/retention/purger.js.map +1 -0
- package/dist/rls/auth-integration.d.ts +280 -0
- package/dist/rls/auth-integration.d.ts.map +1 -0
- package/dist/rls/auth-integration.js +399 -0
- package/dist/rls/auth-integration.js.map +1 -0
- package/dist/rls/generator.d.ts +249 -0
- package/dist/rls/generator.d.ts.map +1 -0
- package/dist/rls/generator.js +495 -0
- package/dist/rls/generator.js.map +1 -0
- package/dist/rls/index.d.ts +26 -0
- package/dist/rls/index.d.ts.map +1 -0
- package/dist/rls/index.js +58 -0
- package/dist/rls/index.js.map +1 -0
- package/dist/rls/policy.d.ts +116 -0
- package/dist/rls/policy.d.ts.map +1 -0
- package/dist/rls/policy.js +77 -0
- package/dist/rls/policy.js.map +1 -0
- package/dist/rls/validator.d.ts +155 -0
- package/dist/rls/validator.d.ts.map +1 -0
- package/dist/rls/validator.js +792 -0
- package/dist/rls/validator.js.map +1 -0
- package/dist/routing/adaptive-router.d.ts +317 -0
- package/dist/routing/adaptive-router.d.ts.map +1 -0
- package/dist/routing/adaptive-router.js +554 -0
- package/dist/routing/adaptive-router.js.map +1 -0
- package/dist/routing/circuit-breaker.d.ts +339 -0
- package/dist/routing/circuit-breaker.d.ts.map +1 -0
- package/dist/routing/circuit-breaker.js +620 -0
- package/dist/routing/circuit-breaker.js.map +1 -0
- package/dist/routing/cost-metrics.d.ts +133 -0
- package/dist/routing/cost-metrics.d.ts.map +1 -0
- package/dist/routing/cost-metrics.js +259 -0
- package/dist/routing/cost-metrics.js.map +1 -0
- package/dist/routing/do-connection-pool.d.ts +243 -0
- package/dist/routing/do-connection-pool.d.ts.map +1 -0
- package/dist/routing/do-connection-pool.js +572 -0
- package/dist/routing/do-connection-pool.js.map +1 -0
- package/dist/routing/index.d.ts +59 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +59 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/query-complexity-estimator.d.ts +73 -0
- package/dist/routing/query-complexity-estimator.d.ts.map +1 -0
- package/dist/routing/query-complexity-estimator.js +327 -0
- package/dist/routing/query-complexity-estimator.js.map +1 -0
- package/dist/routing/request-coalescing.d.ts +178 -0
- package/dist/routing/request-coalescing.d.ts.map +1 -0
- package/dist/routing/request-coalescing.js +325 -0
- package/dist/routing/request-coalescing.js.map +1 -0
- package/dist/routing/runtime-router.d.ts +107 -0
- package/dist/routing/runtime-router.d.ts.map +1 -0
- package/dist/routing/runtime-router.js +246 -0
- package/dist/routing/runtime-router.js.map +1 -0
- package/dist/routing/tenant-router.d.ts +848 -0
- package/dist/routing/tenant-router.d.ts.map +1 -0
- package/dist/routing/tenant-router.js +1056 -0
- package/dist/routing/tenant-router.js.map +1 -0
- package/dist/routing/websocket-pool.d.ts +119 -0
- package/dist/routing/websocket-pool.d.ts.map +1 -0
- package/dist/routing/websocket-pool.js +436 -0
- package/dist/routing/websocket-pool.js.map +1 -0
- package/dist/storage/cache-layer.d.ts +159 -0
- package/dist/storage/cache-layer.d.ts.map +1 -0
- package/dist/storage/cache-layer.js +245 -0
- package/dist/storage/cache-layer.js.map +1 -0
- package/dist/storage/cost-aware-tiering.d.ts +258 -0
- package/dist/storage/cost-aware-tiering.d.ts.map +1 -0
- package/dist/storage/cost-aware-tiering.js +526 -0
- package/dist/storage/cost-aware-tiering.js.map +1 -0
- package/dist/storage/index.d.ts +87 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +78 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interfaces.d.ts +856 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +69 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/r2-layer.d.ts +226 -0
- package/dist/storage/r2-layer.d.ts.map +1 -0
- package/dist/storage/r2-layer.js +307 -0
- package/dist/storage/r2-layer.js.map +1 -0
- package/dist/storage/r2-overflow.d.ts +344 -0
- package/dist/storage/r2-overflow.d.ts.map +1 -0
- package/dist/storage/r2-overflow.js +730 -0
- package/dist/storage/r2-overflow.js.map +1 -0
- package/dist/storage/r2-page-vfs.d.ts +374 -0
- package/dist/storage/r2-page-vfs.d.ts.map +1 -0
- package/dist/storage/r2-page-vfs.js +754 -0
- package/dist/storage/r2-page-vfs.js.map +1 -0
- package/dist/storage/swr-cache.d.ts +181 -0
- package/dist/storage/swr-cache.d.ts.map +1 -0
- package/dist/storage/swr-cache.js +295 -0
- package/dist/storage/swr-cache.js.map +1 -0
- package/dist/storage/tiered-orchestrator.d.ts +951 -0
- package/dist/storage/tiered-orchestrator.d.ts.map +1 -0
- package/dist/storage/tiered-orchestrator.js +1731 -0
- package/dist/storage/tiered-orchestrator.js.map +1 -0
- package/dist/storage/tiered-vfs-swr.d.ts +279 -0
- package/dist/storage/tiered-vfs-swr.d.ts.map +1 -0
- package/dist/storage/tiered-vfs-swr.js +584 -0
- package/dist/storage/tiered-vfs-swr.js.map +1 -0
- package/dist/storage/tiered-vfs.d.ts +405 -0
- package/dist/storage/tiered-vfs.d.ts.map +1 -0
- package/dist/storage/tiered-vfs.js +833 -0
- package/dist/storage/tiered-vfs.js.map +1 -0
- package/dist/streaming/backpressure-controller.d.ts +173 -0
- package/dist/streaming/backpressure-controller.d.ts.map +1 -0
- package/dist/streaming/backpressure-controller.js +344 -0
- package/dist/streaming/backpressure-controller.js.map +1 -0
- package/dist/streaming/buffer-pool.d.ts +241 -0
- package/dist/streaming/buffer-pool.d.ts.map +1 -0
- package/dist/streaming/buffer-pool.js +381 -0
- package/dist/streaming/buffer-pool.js.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts +272 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.js +408 -0
- package/dist/streaming/cdc-iceberg-connector.js.map +1 -0
- package/dist/streaming/index.d.ts +111 -0
- package/dist/streaming/index.d.ts.map +1 -0
- package/dist/streaming/index.js +128 -0
- package/dist/streaming/index.js.map +1 -0
- package/dist/streaming/live-cdc-stream.d.ts +400 -0
- package/dist/streaming/live-cdc-stream.d.ts.map +1 -0
- package/dist/streaming/live-cdc-stream.js +703 -0
- package/dist/streaming/live-cdc-stream.js.map +1 -0
- package/dist/streaming/memory-bounded-stream.d.ts +207 -0
- package/dist/streaming/memory-bounded-stream.d.ts.map +1 -0
- package/dist/streaming/memory-bounded-stream.js +340 -0
- package/dist/streaming/memory-bounded-stream.js.map +1 -0
- package/dist/streaming/query-streamer.d.ts +379 -0
- package/dist/streaming/query-streamer.d.ts.map +1 -0
- package/dist/streaming/query-streamer.js +495 -0
- package/dist/streaming/query-streamer.js.map +1 -0
- package/dist/streaming/response-streaming.d.ts +203 -0
- package/dist/streaming/response-streaming.d.ts.map +1 -0
- package/dist/streaming/response-streaming.js +449 -0
- package/dist/streaming/response-streaming.js.map +1 -0
- package/dist/types/branded.d.ts +859 -0
- package/dist/types/branded.d.ts.map +1 -0
- package/dist/types/branded.js +891 -0
- package/dist/types/branded.js.map +1 -0
- package/dist/types/utilities.d.ts +757 -0
- package/dist/types/utilities.d.ts.map +1 -0
- package/dist/types/utilities.js +447 -0
- package/dist/types/utilities.js.map +1 -0
- package/dist/wal/replay-engine.d.ts +344 -0
- package/dist/wal/replay-engine.d.ts.map +1 -0
- package/dist/wal/replay-engine.js +975 -0
- package/dist/wal/replay-engine.js.map +1 -0
- package/dist/worker/__mocks__/capnweb.d.ts +13 -0
- package/dist/worker/__mocks__/capnweb.d.ts.map +1 -0
- package/dist/worker/__mocks__/capnweb.js +15 -0
- package/dist/worker/__mocks__/capnweb.js.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts +31 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.js +33 -0
- package/dist/worker/__mocks__/cloudflare-workers.js.map +1 -0
- package/dist/worker/__mocks__/pglite.data.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.data.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.data.js +20 -0
- package/dist/worker/__mocks__/pglite.data.js.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.js +30 -0
- package/dist/worker/__mocks__/pglite.wasm.js.map +1 -0
- package/dist/worker/auth-rate-limiter.d.ts +270 -0
- package/dist/worker/auth-rate-limiter.d.ts.map +1 -0
- package/dist/worker/auth-rate-limiter.js +332 -0
- package/dist/worker/auth-rate-limiter.js.map +1 -0
- package/dist/worker/auth.d.ts +345 -0
- package/dist/worker/auth.d.ts.map +1 -0
- package/dist/worker/auth.js +837 -0
- package/dist/worker/auth.js.map +1 -0
- package/dist/worker/cdc-backpressure.d.ts +338 -0
- package/dist/worker/cdc-backpressure.d.ts.map +1 -0
- package/dist/worker/cdc-backpressure.js +619 -0
- package/dist/worker/cdc-backpressure.js.map +1 -0
- package/dist/worker/cdc-sse.d.ts +277 -0
- package/dist/worker/cdc-sse.d.ts.map +1 -0
- package/dist/worker/cdc-sse.js +528 -0
- package/dist/worker/cdc-sse.js.map +1 -0
- package/dist/worker/cdc-websocket.d.ts +252 -0
- package/dist/worker/cdc-websocket.d.ts.map +1 -0
- package/dist/worker/cdc-websocket.js +940 -0
- package/dist/worker/cdc-websocket.js.map +1 -0
- package/dist/worker/cdc.d.ts +95 -0
- package/dist/worker/cdc.d.ts.map +1 -0
- package/dist/worker/cdc.js +211 -0
- package/dist/worker/cdc.js.map +1 -0
- package/dist/worker/concerns/auth-concern.d.ts +50 -0
- package/dist/worker/concerns/auth-concern.d.ts.map +1 -0
- package/dist/worker/concerns/auth-concern.js +131 -0
- package/dist/worker/concerns/auth-concern.js.map +1 -0
- package/dist/worker/concerns/cdc-concern.d.ts +99 -0
- package/dist/worker/concerns/cdc-concern.d.ts.map +1 -0
- package/dist/worker/concerns/cdc-concern.js +137 -0
- package/dist/worker/concerns/cdc-concern.js.map +1 -0
- package/dist/worker/concerns/index.d.ts +22 -0
- package/dist/worker/concerns/index.d.ts.map +1 -0
- package/dist/worker/concerns/index.js +13 -0
- package/dist/worker/concerns/index.js.map +1 -0
- package/dist/worker/concerns/query-execution-concern.d.ts +104 -0
- package/dist/worker/concerns/query-execution-concern.d.ts.map +1 -0
- package/dist/worker/concerns/query-execution-concern.js +95 -0
- package/dist/worker/concerns/query-execution-concern.js.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts +78 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.js +240 -0
- package/dist/worker/concerns/storage-orchestration-concern.js.map +1 -0
- package/dist/worker/do-auth-manager.d.ts +108 -0
- package/dist/worker/do-auth-manager.d.ts.map +1 -0
- package/dist/worker/do-auth-manager.js +212 -0
- package/dist/worker/do-auth-manager.js.map +1 -0
- package/dist/worker/do-pglite-manager.d.ts +137 -0
- package/dist/worker/do-pglite-manager.d.ts.map +1 -0
- package/dist/worker/do-pglite-manager.js +228 -0
- package/dist/worker/do-pglite-manager.js.map +1 -0
- package/dist/worker/do.d.ts +556 -0
- package/dist/worker/do.d.ts.map +1 -0
- package/dist/worker/do.js +1441 -0
- package/dist/worker/do.js.map +1 -0
- package/dist/worker/entry.d.ts +23 -0
- package/dist/worker/entry.d.ts.map +1 -0
- package/dist/worker/entry.js +362 -0
- package/dist/worker/entry.js.map +1 -0
- package/dist/worker/errors.d.ts +106 -0
- package/dist/worker/errors.d.ts.map +1 -0
- package/dist/worker/errors.js +178 -0
- package/dist/worker/errors.js.map +1 -0
- package/dist/worker/health-check-manager.d.ts +141 -0
- package/dist/worker/health-check-manager.d.ts.map +1 -0
- package/dist/worker/health-check-manager.js +145 -0
- package/dist/worker/health-check-manager.js.map +1 -0
- package/dist/worker/index.d.ts +60 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +67 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/memory-pressure.d.ts +892 -0
- package/dist/worker/memory-pressure.d.ts.map +1 -0
- package/dist/worker/memory-pressure.js +1990 -0
- package/dist/worker/memory-pressure.js.map +1 -0
- package/dist/worker/migration-manager.d.ts +153 -0
- package/dist/worker/migration-manager.d.ts.map +1 -0
- package/dist/worker/migration-manager.js +461 -0
- package/dist/worker/migration-manager.js.map +1 -0
- package/dist/worker/plugin-manager.d.ts +147 -0
- package/dist/worker/plugin-manager.d.ts.map +1 -0
- package/dist/worker/plugin-manager.js +408 -0
- package/dist/worker/plugin-manager.js.map +1 -0
- package/dist/worker/proxy.d.ts +330 -0
- package/dist/worker/proxy.d.ts.map +1 -0
- package/dist/worker/proxy.js +504 -0
- package/dist/worker/proxy.js.map +1 -0
- package/dist/worker/query-execution-manager.d.ts +107 -0
- package/dist/worker/query-execution-manager.d.ts.map +1 -0
- package/dist/worker/query-execution-manager.js +155 -0
- package/dist/worker/query-execution-manager.js.map +1 -0
- package/dist/worker/query-executor.d.ts +163 -0
- package/dist/worker/query-executor.d.ts.map +1 -0
- package/dist/worker/query-executor.js +413 -0
- package/dist/worker/query-executor.js.map +1 -0
- package/dist/worker/query-stats-manager.d.ts +117 -0
- package/dist/worker/query-stats-manager.d.ts.map +1 -0
- package/dist/worker/query-stats-manager.js +162 -0
- package/dist/worker/query-stats-manager.js.map +1 -0
- package/dist/worker/result-handler.d.ts +192 -0
- package/dist/worker/result-handler.d.ts.map +1 -0
- package/dist/worker/result-handler.js +346 -0
- package/dist/worker/result-handler.js.map +1 -0
- package/dist/worker/routes.d.ts +135 -0
- package/dist/worker/routes.d.ts.map +1 -0
- package/dist/worker/routes.js +460 -0
- package/dist/worker/routes.js.map +1 -0
- package/dist/worker/rpc-methods-manager.d.ts +142 -0
- package/dist/worker/rpc-methods-manager.d.ts.map +1 -0
- package/dist/worker/rpc-methods-manager.js +195 -0
- package/dist/worker/rpc-methods-manager.js.map +1 -0
- package/dist/worker/rpc.d.ts +259 -0
- package/dist/worker/rpc.d.ts.map +1 -0
- package/dist/worker/rpc.js +398 -0
- package/dist/worker/rpc.js.map +1 -0
- package/dist/worker/schema-version.d.ts +209 -0
- package/dist/worker/schema-version.d.ts.map +1 -0
- package/dist/worker/schema-version.js +450 -0
- package/dist/worker/schema-version.js.map +1 -0
- package/dist/worker/session-manager.d.ts +282 -0
- package/dist/worker/session-manager.d.ts.map +1 -0
- package/dist/worker/session-manager.js +523 -0
- package/dist/worker/session-manager.js.map +1 -0
- package/dist/worker/shutdown-manager.d.ts +188 -0
- package/dist/worker/shutdown-manager.d.ts.map +1 -0
- package/dist/worker/shutdown-manager.js +347 -0
- package/dist/worker/shutdown-manager.js.map +1 -0
- package/dist/worker/sql-transform.d.ts +61 -0
- package/dist/worker/sql-transform.d.ts.map +1 -0
- package/dist/worker/sql-transform.js +312 -0
- package/dist/worker/sql-transform.js.map +1 -0
- package/dist/worker/types.d.ts +738 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +6 -0
- package/dist/worker/types.js.map +1 -0
- package/dist/worker/user-routes.d.ts +76 -0
- package/dist/worker/user-routes.d.ts.map +1 -0
- package/dist/worker/user-routes.js +188 -0
- package/dist/worker/user-routes.js.map +1 -0
- package/dist/worker/wal-facade.d.ts +138 -0
- package/dist/worker/wal-facade.d.ts.map +1 -0
- package/dist/worker/wal-facade.js +184 -0
- package/dist/worker/wal-facade.js.map +1 -0
- package/dist/worker/wal-r2.d.ts +271 -0
- package/dist/worker/wal-r2.d.ts.map +1 -0
- package/dist/worker/wal-r2.js +689 -0
- package/dist/worker/wal-r2.js.map +1 -0
- package/dist/worker/wal-replay.d.ts +361 -0
- package/dist/worker/wal-replay.d.ts.map +1 -0
- package/dist/worker/wal-replay.js +628 -0
- package/dist/worker/wal-replay.js.map +1 -0
- package/dist/worker/wal-retention.d.ts +389 -0
- package/dist/worker/wal-retention.d.ts.map +1 -0
- package/dist/worker/wal-retention.js +763 -0
- package/dist/worker/wal-retention.js.map +1 -0
- package/dist/worker/wal.d.ts +278 -0
- package/dist/worker/wal.d.ts.map +1 -0
- package/dist/worker/wal.js +467 -0
- package/dist/worker/wal.js.map +1 -0
- package/dist/worker/websocket.d.ts +85 -0
- package/dist/worker/websocket.d.ts.map +1 -0
- package/dist/worker/websocket.js +227 -0
- package/dist/worker/websocket.js.map +1 -0
- package/package.json +108 -0
- package/src/cdc/change-stream.ts +137 -0
- package/src/cdc/filter.ts +646 -0
- package/src/cdc/index.ts +112 -0
- package/src/cdc/resume-token.ts +280 -0
- package/src/cdc/transport/index.ts +7 -0
- package/src/cdc/transport/sse.ts +723 -0
- package/src/cdc/transport/websocket.ts +873 -0
- package/src/cdc/types.ts +346 -0
- package/src/config/index.ts +25 -0
- package/src/config/memory.ts +177 -0
- package/src/config/storage.ts +204 -0
- package/src/config/streaming.ts +147 -0
- package/src/config/timeouts.ts +221 -0
- package/src/extensions/config.test.ts +187 -0
- package/src/extensions/config.ts +278 -0
- package/src/extensions/geo.test.ts +455 -0
- package/src/extensions/geo.ts +858 -0
- package/src/extensions/index.test.ts +259 -0
- package/src/extensions/index.ts +227 -0
- package/src/extensions/loader.test.ts +555 -0
- package/src/extensions/loader.ts +588 -0
- package/src/extensions/pgmq-lite.test.ts +727 -0
- package/src/extensions/pgmq-lite.ts +770 -0
- package/src/extensions/plugins.test.ts +528 -0
- package/src/extensions/plugins.ts +718 -0
- package/src/extensions/registry.test.ts +202 -0
- package/src/extensions/registry.ts +267 -0
- package/src/extensions/vector.test.ts +195 -0
- package/src/extensions/vector.ts +217 -0
- package/src/iceberg/SCHEDULER.md +580 -0
- package/src/iceberg/analytics.test.ts +703 -0
- package/src/iceberg/analytics.ts +727 -0
- package/src/iceberg/catalog-api.test.ts +838 -0
- package/src/iceberg/catalog-api.ts +520 -0
- package/src/iceberg/catalog.test.ts +680 -0
- package/src/iceberg/catalog.ts +1007 -0
- package/src/iceberg/iceberg.test.ts +705 -0
- package/src/iceberg/index.ts +406 -0
- package/src/iceberg/metadata.test.ts +632 -0
- package/src/iceberg/metadata.ts +649 -0
- package/src/iceberg/optimizer.test.ts +868 -0
- package/src/iceberg/optimizer.ts +1287 -0
- package/src/iceberg/parquet.test.ts +899 -0
- package/src/iceberg/parquet.ts +1640 -0
- package/src/iceberg/r2-organization.test.ts +615 -0
- package/src/iceberg/r2-organization.ts +951 -0
- package/src/iceberg/scheduler-do-example.ts +364 -0
- package/src/iceberg/scheduler.test.ts +861 -0
- package/src/iceberg/scheduler.ts +1201 -0
- package/src/iceberg/schema.test.ts +547 -0
- package/src/iceberg/schema.ts +616 -0
- package/src/iceberg/snapshot-manager.test.ts +919 -0
- package/src/iceberg/snapshot-manager.ts +1369 -0
- package/src/iceberg/sql-router.test.ts +334 -0
- package/src/iceberg/sql-router.ts +337 -0
- package/src/iceberg/test-fixtures.ts +605 -0
- package/src/iceberg/time-travel-api.test.ts +1029 -0
- package/src/iceberg/time-travel-api.ts +731 -0
- package/src/iceberg/time-travel.test.ts +1218 -0
- package/src/iceberg/time-travel.ts +1052 -0
- package/src/iceberg/transformer.test.ts +689 -0
- package/src/iceberg/transformer.ts +1029 -0
- package/src/iceberg/types.ts +373 -0
- package/src/iceberg/writer.test.ts +716 -0
- package/src/iceberg/writer.ts +590 -0
- package/src/index.ts +212 -0
- package/src/lineage/index.ts +42 -0
- package/src/lineage/integration.ts +334 -0
- package/src/lineage/tracker.ts +1618 -0
- package/src/lineage/types.ts +354 -0
- package/src/middleware/index.ts +36 -0
- package/src/middleware/rate-limit-concurrent.test.ts +794 -0
- package/src/middleware/rate-limit.test.ts +1568 -0
- package/src/middleware/rate-limit.ts +840 -0
- package/src/migration-tooling/external-migration.test.ts +1864 -0
- package/src/migration-tooling/external-migration.ts +2355 -0
- package/src/migration-tooling/index.ts +19 -0
- package/src/migrations/ARCHITECTURE.md +474 -0
- package/src/migrations/PROGRESS_TRACKING.md +485 -0
- package/src/migrations/auto-migrator.test.ts +732 -0
- package/src/migrations/auto-migrator.ts +531 -0
- package/src/migrations/bulk-orchestrator.test.ts +801 -0
- package/src/migrations/bulk-orchestrator.ts +1039 -0
- package/src/migrations/compatibility.test.ts +958 -0
- package/src/migrations/compatibility.ts +902 -0
- package/src/migrations/do-migrations.test.ts +2620 -0
- package/src/migrations/do-migrations.ts +1289 -0
- package/src/migrations/do-migrations.types.ts +715 -0
- package/src/migrations/drizzle-compat.test.ts +210 -0
- package/src/migrations/drizzle-compat.ts +337 -0
- package/src/migrations/index.ts +334 -0
- package/src/migrations/migration-api.test.ts +438 -0
- package/src/migrations/migration-api.ts +704 -0
- package/src/migrations/progress-tracker-do.ts +518 -0
- package/src/migrations/progress-tracker-kv.ts +305 -0
- package/src/migrations/progress-tracker.test.ts +937 -0
- package/src/migrations/progress-tracker.ts +665 -0
- package/src/migrations/registry.test.ts +331 -0
- package/src/migrations/registry.ts +468 -0
- package/src/migrations/rollback.test.ts +644 -0
- package/src/migrations/runner.test.ts +807 -0
- package/src/migrations/runner.test.ts.backup +759 -0
- package/src/migrations/runner.ts +1459 -0
- package/src/migrations/schema-generator.test.ts +649 -0
- package/src/migrations/schema-generator.ts +513 -0
- package/src/migrations/testing.ts +1037 -0
- package/src/migrations/types.ts +573 -0
- package/src/migrations/validator.test.ts +660 -0
- package/src/migrations/validator.ts +741 -0
- package/src/observability/alerting.test.ts +1133 -0
- package/src/observability/alerting.ts +455 -0
- package/src/observability/analytics-engine.ts +733 -0
- package/src/observability/cost-metrics.ts +804 -0
- package/src/observability/cross-do-tracing.test.ts +516 -0
- package/src/observability/cross-do-tracing.ts +588 -0
- package/src/observability/dashboards/postgres-do-overview.json +1656 -0
- package/src/observability/error-rate-collector.test.ts +977 -0
- package/src/observability/error-rate-collector.ts +518 -0
- package/src/observability/exporters.test.ts +365 -0
- package/src/observability/exporters.ts +650 -0
- package/src/observability/health-check.test.ts +353 -0
- package/src/observability/health-check.ts +341 -0
- package/src/observability/index.test.ts +298 -0
- package/src/observability/index.ts +885 -0
- package/src/observability/instrumentation.test.ts +428 -0
- package/src/observability/instrumentation.ts +788 -0
- package/src/observability/memory-metrics.test.ts +355 -0
- package/src/observability/memory-metrics.ts +990 -0
- package/src/observability/metrics-endpoint.test.ts +402 -0
- package/src/observability/metrics-endpoint.ts +374 -0
- package/src/observability/metrics.test.ts +291 -0
- package/src/observability/metrics.ts +315 -0
- package/src/observability/observability-features.ts +1296 -0
- package/src/observability/prometheus.test.ts +292 -0
- package/src/observability/prometheus.ts +170 -0
- package/src/observability/propagation.test.ts +417 -0
- package/src/observability/propagation.ts +294 -0
- package/src/observability/query-latency.ts +586 -0
- package/src/observability/query-performance.test.ts +406 -0
- package/src/observability/query-performance.ts +491 -0
- package/src/observability/storage-tier-metrics.test.ts +633 -0
- package/src/observability/storage-tier-metrics.ts +570 -0
- package/src/observability/tier-cost-optimizer.ts +740 -0
- package/src/observability/tracer.test.ts +346 -0
- package/src/observability/tracer.ts +585 -0
- package/src/observability/types.test.ts +726 -0
- package/src/observability/types.ts +434 -0
- package/src/pglite/auto-demotion.test.ts +477 -0
- package/src/pglite/auto-demotion.ts +385 -0
- package/src/pglite/auto-promotion.test.ts +824 -0
- package/src/pglite/auto-promotion.ts +547 -0
- package/src/pglite/cache-layer.test.ts +469 -0
- package/src/pglite/cache-layer.ts +271 -0
- package/src/pglite/cold-start-manager.ts +1260 -0
- package/src/pglite/cold-start-optimizer.test.ts +937 -0
- package/src/pglite/cold-start-optimizer.ts +1895 -0
- package/src/pglite/dovfs-adapter.ts +1122 -0
- package/src/pglite/dovfs.ts +1258 -0
- package/src/pglite/etag-cache.test.ts +844 -0
- package/src/pglite/etag-cache.ts +526 -0
- package/src/pglite/index.ts +442 -0
- package/src/pglite/init.test.ts +455 -0
- package/src/pglite/init.ts +574 -0
- package/src/pglite/lifecycle.test.ts +599 -0
- package/src/pglite/lifecycle.ts +704 -0
- package/src/pglite/parallel-loader.test.ts +586 -0
- package/src/pglite/parallel-loader.ts +481 -0
- package/src/pglite/production-pglite.test.ts +666 -0
- package/src/pglite/production-pglite.ts +537 -0
- package/src/pglite/query-executor.ts +614 -0
- package/src/pglite/r2-layer.test.ts +501 -0
- package/src/pglite/r2-layer.ts +322 -0
- package/src/pglite/tiered-init.test.ts +725 -0
- package/src/pglite/tiered-init.ts +556 -0
- package/src/pglite/tiered-vfs.test.ts +726 -0
- package/src/pglite/tiered-vfs.ts +33 -0
- package/src/pglite/tiering-stats.test.ts +531 -0
- package/src/pglite/tiering-stats.ts +407 -0
- package/src/pglite/transaction-hooks.ts +343 -0
- package/src/pglite/warm-loader.test.ts +1701 -0
- package/src/pglite/warm-loader.ts +528 -0
- package/src/pglite/workers-pglite.ts +224 -0
- package/src/pglite-assets/pglite.data +0 -0
- package/src/pglite-assets/pglite.wasm +0 -0
- package/src/pglite.d.ts +47 -0
- package/src/playground/index.ts +137 -0
- package/src/playground/keyboard-shortcuts.ts +677 -0
- package/src/playground/playground.ts +323 -0
- package/src/playground/query-executor.ts +669 -0
- package/src/playground/query-history.ts +328 -0
- package/src/playground/result-formatter.ts +420 -0
- package/src/playground/sample-datasets.ts +674 -0
- package/src/playground/sample-queries.ts +1168 -0
- package/src/playground/schema-explorer.ts +558 -0
- package/src/playground/types.ts +518 -0
- package/src/readonly/cache-reader.test.ts +460 -0
- package/src/readonly/cache-reader.ts +313 -0
- package/src/readonly/config.test.ts +187 -0
- package/src/readonly/config.ts +128 -0
- package/src/readonly/index.ts +50 -0
- package/src/readonly/pglite-wrapper.test.ts +278 -0
- package/src/readonly/pglite-wrapper.ts +184 -0
- package/src/readonly/worker.test.ts +533 -0
- package/src/readonly/worker.ts +341 -0
- package/src/readonly/write-blocker.test.ts +459 -0
- package/src/readonly/write-blocker.ts +175 -0
- package/src/recovery/disaster-recovery.test.ts +618 -0
- package/src/recovery/disaster-recovery.ts +1181 -0
- package/src/recovery/index.ts +43 -0
- package/src/recovery/parquet-parser.ts +974 -0
- package/src/retention/index.ts +74 -0
- package/src/retention/policy.test.ts +571 -0
- package/src/retention/policy.ts +774 -0
- package/src/retention/purger.test.ts +465 -0
- package/src/retention/purger.ts +558 -0
- package/src/rls/auth-integration.test.ts +752 -0
- package/src/rls/auth-integration.ts +533 -0
- package/src/rls/generator.test.ts +829 -0
- package/src/rls/generator.ts +573 -0
- package/src/rls/index.ts +128 -0
- package/src/rls/policy.ts +208 -0
- package/src/rls/rls.test.ts +1071 -0
- package/src/rls/validator.test.ts +930 -0
- package/src/rls/validator.ts +895 -0
- package/src/routing/adaptive-router.test.ts +884 -0
- package/src/routing/adaptive-router.ts +845 -0
- package/src/routing/circuit-breaker.test.ts +1505 -0
- package/src/routing/circuit-breaker.ts +852 -0
- package/src/routing/cost-metrics.test.ts +565 -0
- package/src/routing/cost-metrics.ts +408 -0
- package/src/routing/do-connection-pool.test.ts +1109 -0
- package/src/routing/do-connection-pool.ts +828 -0
- package/src/routing/index.ts +158 -0
- package/src/routing/query-complexity-estimator.test.ts +356 -0
- package/src/routing/query-complexity-estimator.ts +444 -0
- package/src/routing/request-coalescing.test.ts +738 -0
- package/src/routing/request-coalescing.ts +475 -0
- package/src/routing/runtime-router.test.ts +436 -0
- package/src/routing/runtime-router.ts +357 -0
- package/src/routing/tenant-router.test.ts +2493 -0
- package/src/routing/tenant-router.ts +1908 -0
- package/src/routing/websocket-pool.test.ts +551 -0
- package/src/routing/websocket-pool.ts +577 -0
- package/src/storage/access-pattern-tracker.test.ts +874 -0
- package/src/storage/cache-layer.test.ts +560 -0
- package/src/storage/cache-layer.ts +328 -0
- package/src/storage/cost-aware-tiering.test.ts +652 -0
- package/src/storage/cost-aware-tiering.ts +794 -0
- package/src/storage/do-sqlite-blobs.test.ts +937 -0
- package/src/storage/index.ts +272 -0
- package/src/storage/interfaces.ts +974 -0
- package/src/storage/r2-layer.test.ts +653 -0
- package/src/storage/r2-layer.ts +434 -0
- package/src/storage/r2-overflow.ts +920 -0
- package/src/storage/r2-page-vfs.test.ts +2348 -0
- package/src/storage/r2-page-vfs.ts +1054 -0
- package/src/storage/swr-cache.test.ts +832 -0
- package/src/storage/swr-cache.ts +398 -0
- package/src/storage/swr-tiered-integration.test.ts +617 -0
- package/src/storage/tiered-orchestrator.test.ts +2441 -0
- package/src/storage/tiered-orchestrator.ts +2081 -0
- package/src/storage/tiered-vfs-swr.test.ts +736 -0
- package/src/storage/tiered-vfs-swr.ts +735 -0
- package/src/storage/tiered-vfs.test.ts +793 -0
- package/src/storage/tiered-vfs.ts +1082 -0
- package/src/streaming/backpressure-controller.ts +452 -0
- package/src/streaming/buffer-pool.ts +484 -0
- package/src/streaming/cdc-iceberg-connector.ts +605 -0
- package/src/streaming/index.ts +225 -0
- package/src/streaming/live-cdc-stream.ts +985 -0
- package/src/streaming/memory-bounded-stream.ts +443 -0
- package/src/streaming/query-streamer.ts +662 -0
- package/src/streaming/response-streaming.ts +557 -0
- package/src/types/branded.ts +1075 -0
- package/src/types/branded.ts.backup +273 -0
- package/src/types/utilities.ts +1023 -0
- package/src/types/wasm.d.ts +30 -0
- package/src/validation/typed-errors.test.ts +420 -0
- package/src/wal/replay-engine.ts +1264 -0
- package/src/worker/__mocks__/capnweb.ts +15 -0
- package/src/worker/__mocks__/pglite.data.ts +22 -0
- package/src/worker/__mocks__/pglite.wasm.ts +33 -0
- package/src/worker/auth-rate-limiter.test.ts +272 -0
- package/src/worker/auth-rate-limiter.ts +448 -0
- package/src/worker/auth.security-red.test.ts +1236 -0
- package/src/worker/auth.security.test.ts +822 -0
- package/src/worker/auth.test.ts +469 -0
- package/src/worker/auth.ts +1104 -0
- package/src/worker/cdc-backpressure.test.ts +726 -0
- package/src/worker/cdc-backpressure.ts +866 -0
- package/src/worker/cdc-sse.test.ts +780 -0
- package/src/worker/cdc-sse.ts +728 -0
- package/src/worker/cdc-websocket.ts +1229 -0
- package/src/worker/cdc-ws.test.ts +1009 -0
- package/src/worker/cdc.test.ts +327 -0
- package/src/worker/cdc.ts +289 -0
- package/src/worker/concerns/auth-concern.ts +179 -0
- package/src/worker/concerns/cdc-concern.ts +247 -0
- package/src/worker/concerns/index.ts +58 -0
- package/src/worker/concerns/query-execution-concern.ts +194 -0
- package/src/worker/concerns/storage-orchestration-concern.ts +373 -0
- package/src/worker/discriminated-types.test.ts +280 -0
- package/src/worker/do-auth-manager.ts +257 -0
- package/src/worker/do-decomposition.test.ts +1236 -0
- package/src/worker/do-pglite-manager.ts +302 -0
- package/src/worker/do.test.ts +2254 -0
- package/src/worker/do.ts +1878 -0
- package/src/worker/entry.ts +417 -0
- package/src/worker/errors.ts +285 -0
- package/src/worker/health-check-manager.test.ts +261 -0
- package/src/worker/health-check-manager.ts +231 -0
- package/src/worker/index.ts +389 -0
- package/src/worker/memory-pressure.test.ts +1460 -0
- package/src/worker/memory-pressure.ts +2650 -0
- package/src/worker/migration-manager.ts +582 -0
- package/src/worker/neon-compat.test.ts +332 -0
- package/src/worker/plugin-manager.ts +485 -0
- package/src/worker/postgres.do-rpc.d.ts +76 -0
- package/src/worker/proxy.ts +694 -0
- package/src/worker/query-execution-manager.test.ts +303 -0
- package/src/worker/query-execution-manager.ts +219 -0
- package/src/worker/query-executor.test.ts +282 -0
- package/src/worker/query-executor.ts +560 -0
- package/src/worker/query-stats-manager.ts +229 -0
- package/src/worker/result-handler.test.ts +364 -0
- package/src/worker/result-handler.ts +510 -0
- package/src/worker/routes.test.ts +795 -0
- package/src/worker/routes.ts +650 -0
- package/src/worker/rpc-methods-manager.test.ts +326 -0
- package/src/worker/rpc-methods-manager.ts +276 -0
- package/src/worker/rpc.ts +524 -0
- package/src/worker/schema-version.ts +605 -0
- package/src/worker/session-manager.test.ts +506 -0
- package/src/worker/session-manager.ts +732 -0
- package/src/worker/shutdown-manager.ts +469 -0
- package/src/worker/sql-transform.test.ts +286 -0
- package/src/worker/sql-transform.ts +368 -0
- package/src/worker/supabase-compat.test.ts +621 -0
- package/src/worker/types.test.ts +292 -0
- package/src/worker/types.ts +873 -0
- package/src/worker/user-routes.test.ts +703 -0
- package/src/worker/user-routes.ts +303 -0
- package/src/worker/wal-facade.ts +235 -0
- package/src/worker/wal-r2.test.ts +570 -0
- package/src/worker/wal-r2.ts +930 -0
- package/src/worker/wal-replay.test.ts +845 -0
- package/src/worker/wal-replay.ts +897 -0
- package/src/worker/wal-retention.test.ts +758 -0
- package/src/worker/wal-retention.ts +1075 -0
- package/src/worker/wal.test.ts +618 -0
- package/src/worker/wal.ts +697 -0
- package/src/worker/websocket.test.ts +296 -0
- package/src/worker/websocket.ts +284 -0
|
@@ -0,0 +1,2620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO Migration System Tests
|
|
3
|
+
*
|
|
4
|
+
* TDD test specifications for the Durable Object Migration System.
|
|
5
|
+
*
|
|
6
|
+
* The DO Migration System is designed to handle the unique requirements of
|
|
7
|
+
* running migrations in Cloudflare Durable Objects:
|
|
8
|
+
*
|
|
9
|
+
* - Each DO instance has its own isolated database
|
|
10
|
+
* - Migrations must be fast and idempotent (called on every request)
|
|
11
|
+
* - New DOs should bootstrap quickly with the latest schema
|
|
12
|
+
* - Existing DOs should upgrade incrementally
|
|
13
|
+
* - Memory and CPU constraints of Workers environment
|
|
14
|
+
*
|
|
15
|
+
* @module migrations/do-migrations.test
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
19
|
+
import type { PGlite } from '@dotdo/pglite'
|
|
20
|
+
import {
|
|
21
|
+
createDOMigrationRunner,
|
|
22
|
+
generateDOSchemaSnapshot,
|
|
23
|
+
convertToDOMigration,
|
|
24
|
+
} from './do-migrations'
|
|
25
|
+
import type {
|
|
26
|
+
DOMigration,
|
|
27
|
+
DOMigrationRunner,
|
|
28
|
+
DOMigrationResult,
|
|
29
|
+
DOMigrationConfig,
|
|
30
|
+
DOMigrationState,
|
|
31
|
+
DOSchemaSnapshot,
|
|
32
|
+
DOMigrationEvent,
|
|
33
|
+
DOMigrationBatchResult,
|
|
34
|
+
} from './do-migrations.types'
|
|
35
|
+
import { CI_MULTIPLIER } from '../__tests__/test-utils'
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Mock PGlite Factory
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
interface MockQueryResult<T = unknown> {
|
|
42
|
+
rows: T[]
|
|
43
|
+
fields?: { name: string }[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createMockPGlite(options?: {
|
|
47
|
+
queryResults?: Map<string, MockQueryResult>
|
|
48
|
+
execResults?: Map<string, void | Error>
|
|
49
|
+
onQuery?: (sql: string, params?: unknown[]) => void
|
|
50
|
+
onExec?: (sql: string) => void
|
|
51
|
+
}): PGlite {
|
|
52
|
+
const queryResults = options?.queryResults ?? new Map()
|
|
53
|
+
const execResults = options?.execResults ?? new Map()
|
|
54
|
+
const appliedMigrations = new Map<number, { name: string; checksum: string; applied_at: string; execution_time_ms: number }>()
|
|
55
|
+
let metaTableExists = false
|
|
56
|
+
let lockHeld = false
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
query: vi.fn(async <T>(sql: string, params?: unknown[]): Promise<MockQueryResult<T>> => {
|
|
60
|
+
options?.onQuery?.(sql, params)
|
|
61
|
+
|
|
62
|
+
// Handle lock queries
|
|
63
|
+
if (sql.includes('pg_try_advisory_lock')) {
|
|
64
|
+
lockHeld = true
|
|
65
|
+
return { rows: [{ pg_try_advisory_lock: true }] } as MockQueryResult<T>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (sql.includes('pg_advisory_unlock')) {
|
|
69
|
+
lockHeld = false
|
|
70
|
+
return { rows: [{ pg_advisory_unlock: true }] } as MockQueryResult<T>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handle MAX(version) query
|
|
74
|
+
if (sql.includes('MAX(version)') && sql.includes('FROM')) {
|
|
75
|
+
const versions = Array.from(appliedMigrations.keys())
|
|
76
|
+
const maxVersion = versions.length > 0 ? Math.max(...versions) : null
|
|
77
|
+
return { rows: [{ max: maxVersion }] } as MockQueryResult<T>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle schema version query
|
|
81
|
+
if (sql.includes('MAX(version) as current_version')) {
|
|
82
|
+
const versions = Array.from(appliedMigrations.keys())
|
|
83
|
+
const maxVersion = versions.length > 0 ? Math.max(...versions) : null
|
|
84
|
+
const lastApplied = versions.length > 0
|
|
85
|
+
? Array.from(appliedMigrations.values()).sort((a, b) =>
|
|
86
|
+
new Date(b.applied_at).getTime() - new Date(a.applied_at).getTime()
|
|
87
|
+
)[0]?.applied_at
|
|
88
|
+
: null
|
|
89
|
+
return {
|
|
90
|
+
rows: [{
|
|
91
|
+
current_version: maxVersion,
|
|
92
|
+
applied_count: versions.length,
|
|
93
|
+
last_migration_at: lastApplied,
|
|
94
|
+
}],
|
|
95
|
+
} as MockQueryResult<T>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle SELECT all migrations
|
|
99
|
+
if (sql.includes('SELECT version, name, checksum, applied_at, execution_time_ms')) {
|
|
100
|
+
const rows = Array.from(appliedMigrations.entries())
|
|
101
|
+
.sort(([a], [b]) => a - b)
|
|
102
|
+
.map(([version, data]) => ({
|
|
103
|
+
version,
|
|
104
|
+
name: data.name,
|
|
105
|
+
checksum: data.checksum,
|
|
106
|
+
applied_at: data.applied_at,
|
|
107
|
+
execution_time_ms: data.execution_time_ms,
|
|
108
|
+
}))
|
|
109
|
+
return { rows } as MockQueryResult<T>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle INSERT migration
|
|
113
|
+
if (sql.includes('INSERT INTO') && sql.includes('version, name, checksum, execution_time_ms')) {
|
|
114
|
+
const [version, name, checksum, executionTimeMs] = params as [number, string, string, number]
|
|
115
|
+
appliedMigrations.set(version, {
|
|
116
|
+
name,
|
|
117
|
+
checksum,
|
|
118
|
+
applied_at: new Date().toISOString(),
|
|
119
|
+
execution_time_ms: executionTimeMs,
|
|
120
|
+
})
|
|
121
|
+
return { rows: [] } as MockQueryResult<T>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Handle DELETE migration
|
|
125
|
+
if (sql.includes('DELETE FROM') && sql.includes('WHERE version =')) {
|
|
126
|
+
const [version] = params as [number]
|
|
127
|
+
appliedMigrations.delete(version)
|
|
128
|
+
return { rows: [] } as MockQueryResult<T>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check explicit results
|
|
132
|
+
for (const [pattern, result] of queryResults) {
|
|
133
|
+
if (sql.includes(pattern)) {
|
|
134
|
+
return result as MockQueryResult<T>
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { rows: [] } as MockQueryResult<T>
|
|
139
|
+
}),
|
|
140
|
+
|
|
141
|
+
exec: vi.fn(async (sql: string): Promise<void> => {
|
|
142
|
+
options?.onExec?.(sql)
|
|
143
|
+
|
|
144
|
+
// Track meta table creation
|
|
145
|
+
if (sql.includes('CREATE TABLE IF NOT EXISTS') && sql.includes('_migrations')) {
|
|
146
|
+
metaTableExists = true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Track meta table drop
|
|
150
|
+
if (sql.includes('DROP TABLE IF EXISTS') && sql.includes('_migrations')) {
|
|
151
|
+
metaTableExists = false
|
|
152
|
+
appliedMigrations.clear()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for explicit errors
|
|
156
|
+
for (const [pattern, result] of execResults) {
|
|
157
|
+
if (sql.includes(pattern) && result instanceof Error) {
|
|
158
|
+
throw result
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
// Expose internal state for testing
|
|
164
|
+
_internal: {
|
|
165
|
+
getAppliedMigrations: () => appliedMigrations,
|
|
166
|
+
setAppliedMigrations: (migrations: Map<number, { name: string; checksum: string; applied_at: string; execution_time_ms: number }>) => {
|
|
167
|
+
appliedMigrations.clear()
|
|
168
|
+
for (const [k, v] of migrations) {
|
|
169
|
+
appliedMigrations.set(k, v)
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
isMetaTableCreated: () => metaTableExists,
|
|
173
|
+
isLockHeld: () => lockHeld,
|
|
174
|
+
},
|
|
175
|
+
} as unknown as PGlite
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Test Migrations
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
const createTestMigrations = (): DOMigration[] => [
|
|
183
|
+
{
|
|
184
|
+
version: 1,
|
|
185
|
+
name: 'create_users',
|
|
186
|
+
up: 'CREATE TABLE users (id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE);',
|
|
187
|
+
down: 'DROP TABLE users;',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
version: 2,
|
|
191
|
+
name: 'add_user_name',
|
|
192
|
+
up: 'ALTER TABLE users ADD COLUMN name TEXT;',
|
|
193
|
+
down: 'ALTER TABLE users DROP COLUMN name;',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
version: 3,
|
|
197
|
+
name: 'create_posts',
|
|
198
|
+
up: 'CREATE TABLE posts (id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), title TEXT);',
|
|
199
|
+
down: 'DROP TABLE posts;',
|
|
200
|
+
},
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// DOMigrationRunner Core Functionality
|
|
205
|
+
// =============================================================================
|
|
206
|
+
|
|
207
|
+
describe('DOMigrationRunner', () => {
|
|
208
|
+
describe('initialization', () => {
|
|
209
|
+
it('should create _do_migrations meta table if not exists', async () => {
|
|
210
|
+
const execCalls: string[] = []
|
|
211
|
+
const db = createMockPGlite({
|
|
212
|
+
onExec: (sql) => execCalls.push(sql),
|
|
213
|
+
})
|
|
214
|
+
const migrations = createTestMigrations()
|
|
215
|
+
|
|
216
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
217
|
+
await runner.initialize()
|
|
218
|
+
|
|
219
|
+
expect(execCalls.some(sql => sql.includes('CREATE TABLE IF NOT EXISTS _do_migrations'))).toBe(true)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should create _do_migrations table with correct schema (version, checksum, applied_at)', async () => {
|
|
223
|
+
const execCalls: string[] = []
|
|
224
|
+
const db = createMockPGlite({
|
|
225
|
+
onExec: (sql) => execCalls.push(sql),
|
|
226
|
+
})
|
|
227
|
+
const migrations = createTestMigrations()
|
|
228
|
+
|
|
229
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
230
|
+
await runner.initialize()
|
|
231
|
+
|
|
232
|
+
const createTableSql = execCalls.find(sql => sql.includes('CREATE TABLE IF NOT EXISTS _do_migrations'))
|
|
233
|
+
expect(createTableSql).toContain('version INTEGER PRIMARY KEY')
|
|
234
|
+
expect(createTableSql).toContain('checksum TEXT NOT NULL')
|
|
235
|
+
expect(createTableSql).toContain('applied_at TIMESTAMP')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should read current schema version from meta table', async () => {
|
|
239
|
+
const db = createMockPGlite()
|
|
240
|
+
const migrations = createTestMigrations()
|
|
241
|
+
;(db as any)._internal.setAppliedMigrations(new Map([
|
|
242
|
+
[1, { name: 'create_users', checksum: 'abc123', applied_at: new Date().toISOString(), execution_time_ms: 10 }],
|
|
243
|
+
[2, { name: 'add_user_name', checksum: 'def456', applied_at: new Date().toISOString(), execution_time_ms: 5 }],
|
|
244
|
+
]))
|
|
245
|
+
|
|
246
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
247
|
+
const version = await runner.getCurrentVersion()
|
|
248
|
+
|
|
249
|
+
expect(version).toBe(2)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('should return version 0 for new databases', async () => {
|
|
253
|
+
const db = createMockPGlite()
|
|
254
|
+
const migrations = createTestMigrations()
|
|
255
|
+
|
|
256
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
257
|
+
const version = await runner.getCurrentVersion()
|
|
258
|
+
|
|
259
|
+
expect(version).toBe(0)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('should handle corrupted meta table gracefully', async () => {
|
|
263
|
+
const db = createMockPGlite()
|
|
264
|
+
;(db.query as any).mockImplementationOnce(() => {
|
|
265
|
+
throw new Error('relation "_do_migrations" does not exist')
|
|
266
|
+
})
|
|
267
|
+
const migrations = createTestMigrations()
|
|
268
|
+
|
|
269
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
270
|
+
const version = await runner.getCurrentVersion()
|
|
271
|
+
|
|
272
|
+
// Should fall back to 0 when table doesn't exist
|
|
273
|
+
expect(version).toBe(0)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('should support custom meta table name via config', async () => {
|
|
277
|
+
const execCalls: string[] = []
|
|
278
|
+
const db = createMockPGlite({
|
|
279
|
+
onExec: (sql) => execCalls.push(sql),
|
|
280
|
+
})
|
|
281
|
+
const migrations = createTestMigrations()
|
|
282
|
+
|
|
283
|
+
const runner = createDOMigrationRunner(db, migrations, { metaTableName: '_custom_migrations' })
|
|
284
|
+
await runner.initialize()
|
|
285
|
+
|
|
286
|
+
expect(execCalls.some(sql => sql.includes('CREATE TABLE IF NOT EXISTS _custom_migrations'))).toBe(true)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should be safe to call initialize() multiple times (idempotent)', async () => {
|
|
290
|
+
const execCalls: string[] = []
|
|
291
|
+
const db = createMockPGlite({
|
|
292
|
+
onExec: (sql) => execCalls.push(sql),
|
|
293
|
+
})
|
|
294
|
+
const migrations = createTestMigrations()
|
|
295
|
+
|
|
296
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
297
|
+
await runner.initialize()
|
|
298
|
+
await runner.initialize()
|
|
299
|
+
await runner.initialize()
|
|
300
|
+
|
|
301
|
+
// Should only create table once (cached initialization state)
|
|
302
|
+
const createTableCalls = execCalls.filter(sql => sql.includes('CREATE TABLE IF NOT EXISTS _do_migrations'))
|
|
303
|
+
expect(createTableCalls.length).toBe(1)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should emit "initialized" event after successful initialization', async () => {
|
|
307
|
+
const events: DOMigrationEvent[] = []
|
|
308
|
+
const db = createMockPGlite()
|
|
309
|
+
const migrations = createTestMigrations()
|
|
310
|
+
|
|
311
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
312
|
+
onEvent: (event) => events.push(event),
|
|
313
|
+
})
|
|
314
|
+
await runner.initialize()
|
|
315
|
+
|
|
316
|
+
expect(events.some(e => e.type === 'initialized')).toBe(true)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should cache initialization state to avoid repeated table checks', async () => {
|
|
320
|
+
const db = createMockPGlite()
|
|
321
|
+
const migrations = createTestMigrations()
|
|
322
|
+
|
|
323
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
324
|
+
await runner.initialize()
|
|
325
|
+
await runner.initialize()
|
|
326
|
+
await runner.initialize()
|
|
327
|
+
|
|
328
|
+
// exec should only be called once for CREATE TABLE
|
|
329
|
+
expect(db.exec).toHaveBeenCalledTimes(1)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('migrate()', () => {
|
|
334
|
+
it('should be idempotent - safe to call multiple times', async () => {
|
|
335
|
+
const db = createMockPGlite()
|
|
336
|
+
const migrations = createTestMigrations()
|
|
337
|
+
|
|
338
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
339
|
+
const result1 = await runner.migrate()
|
|
340
|
+
const result2 = await runner.migrate()
|
|
341
|
+
const result3 = await runner.migrate()
|
|
342
|
+
|
|
343
|
+
expect(result1.success).toBe(true)
|
|
344
|
+
expect(result2.success).toBe(true)
|
|
345
|
+
expect(result3.success).toBe(true)
|
|
346
|
+
expect(result1.migrationsRun).toBe(3)
|
|
347
|
+
expect(result2.migrationsRun).toBe(0)
|
|
348
|
+
expect(result3.migrationsRun).toBe(0)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('should run pending migrations in version order', async () => {
|
|
352
|
+
const executedMigrations: number[] = []
|
|
353
|
+
const db = createMockPGlite({
|
|
354
|
+
onExec: (sql) => {
|
|
355
|
+
if (sql.includes('CREATE TABLE users')) executedMigrations.push(1)
|
|
356
|
+
if (sql.includes('ADD COLUMN name')) executedMigrations.push(2)
|
|
357
|
+
if (sql.includes('CREATE TABLE posts')) executedMigrations.push(3)
|
|
358
|
+
},
|
|
359
|
+
})
|
|
360
|
+
const migrations = createTestMigrations()
|
|
361
|
+
|
|
362
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
363
|
+
await runner.migrate()
|
|
364
|
+
|
|
365
|
+
expect(executedMigrations).toEqual([1, 2, 3])
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('should skip already-applied migrations based on checksum', async () => {
|
|
369
|
+
const db = createMockPGlite()
|
|
370
|
+
const migrations = createTestMigrations()
|
|
371
|
+
|
|
372
|
+
// Pre-apply first migration
|
|
373
|
+
;(db as any)._internal.setAppliedMigrations(new Map([
|
|
374
|
+
[1, { name: 'create_users', checksum: 'abc123', applied_at: new Date().toISOString(), execution_time_ms: 10 }],
|
|
375
|
+
]))
|
|
376
|
+
|
|
377
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
378
|
+
const result = await runner.migrate()
|
|
379
|
+
|
|
380
|
+
expect(result.migrationsRun).toBe(2) // Only migrations 2 and 3
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should detect checksum mismatch for applied migrations', async () => {
|
|
384
|
+
const db = createMockPGlite()
|
|
385
|
+
const migrations = createTestMigrations()
|
|
386
|
+
|
|
387
|
+
// Pre-apply migration with different checksum
|
|
388
|
+
;(db as any)._internal.setAppliedMigrations(new Map([
|
|
389
|
+
[1, { name: 'create_users', checksum: 'wrong_checksum', applied_at: new Date().toISOString(), execution_time_ms: 10 }],
|
|
390
|
+
]))
|
|
391
|
+
|
|
392
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
393
|
+
const validation = await runner.validate()
|
|
394
|
+
|
|
395
|
+
expect(validation.issues.some(i => i.message.includes('modified after being applied'))).toBe(true)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('should update version after each successful migration', async () => {
|
|
399
|
+
const db = createMockPGlite()
|
|
400
|
+
const migrations = createTestMigrations()
|
|
401
|
+
|
|
402
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
403
|
+
await runner.migrate()
|
|
404
|
+
|
|
405
|
+
const version = await runner.getCurrentVersion()
|
|
406
|
+
expect(version).toBe(3)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should rollback current migration on failure (transactional)', async () => {
|
|
410
|
+
const execCalls: string[] = []
|
|
411
|
+
const db = createMockPGlite({
|
|
412
|
+
onExec: (sql) => {
|
|
413
|
+
execCalls.push(sql)
|
|
414
|
+
if (sql.includes('ADD COLUMN name')) {
|
|
415
|
+
throw new Error('SQL error')
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
})
|
|
419
|
+
const migrations = createTestMigrations()
|
|
420
|
+
|
|
421
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
422
|
+
const result = await runner.migrate()
|
|
423
|
+
|
|
424
|
+
expect(result.success).toBe(false)
|
|
425
|
+
expect(execCalls).toContain('ROLLBACK')
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should stop migration batch on first failure', async () => {
|
|
429
|
+
const executedMigrations: number[] = []
|
|
430
|
+
const db = createMockPGlite({
|
|
431
|
+
onExec: (sql) => {
|
|
432
|
+
if (sql.includes('CREATE TABLE users')) executedMigrations.push(1)
|
|
433
|
+
if (sql.includes('ADD COLUMN name')) {
|
|
434
|
+
executedMigrations.push(2)
|
|
435
|
+
throw new Error('SQL error')
|
|
436
|
+
}
|
|
437
|
+
if (sql.includes('CREATE TABLE posts')) executedMigrations.push(3)
|
|
438
|
+
},
|
|
439
|
+
})
|
|
440
|
+
const migrations = createTestMigrations()
|
|
441
|
+
|
|
442
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
443
|
+
await runner.migrate()
|
|
444
|
+
|
|
445
|
+
// Migration 3 should not have been attempted
|
|
446
|
+
expect(executedMigrations).not.toContain(3)
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
it('should be fast when already at latest version (<5ms)', async () => {
|
|
450
|
+
const db = createMockPGlite()
|
|
451
|
+
const migrations = createTestMigrations()
|
|
452
|
+
|
|
453
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
454
|
+
await runner.migrate() // First run applies migrations
|
|
455
|
+
|
|
456
|
+
const start = performance.now()
|
|
457
|
+
await runner.migrate() // Second run should be fast
|
|
458
|
+
const elapsed = performance.now() - start
|
|
459
|
+
|
|
460
|
+
expect(elapsed).toBeLessThan(50 * CI_MULTIPLIER) // Allow some slack for test environment
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('should acquire advisory lock before migrating', async () => {
|
|
464
|
+
const queryCalls: string[] = []
|
|
465
|
+
const db = createMockPGlite({
|
|
466
|
+
onQuery: (sql) => queryCalls.push(sql),
|
|
467
|
+
})
|
|
468
|
+
const migrations = createTestMigrations()
|
|
469
|
+
|
|
470
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
471
|
+
await runner.migrate()
|
|
472
|
+
|
|
473
|
+
expect(queryCalls.some(sql => sql.includes('pg_try_advisory_lock'))).toBe(true)
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('should release advisory lock after migration completes', async () => {
|
|
477
|
+
const queryCalls: string[] = []
|
|
478
|
+
const db = createMockPGlite({
|
|
479
|
+
onQuery: (sql) => queryCalls.push(sql),
|
|
480
|
+
})
|
|
481
|
+
const migrations = createTestMigrations()
|
|
482
|
+
|
|
483
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
484
|
+
await runner.migrate()
|
|
485
|
+
|
|
486
|
+
expect(queryCalls.some(sql => sql.includes('pg_advisory_unlock'))).toBe(true)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should fail gracefully if lock cannot be acquired', async () => {
|
|
490
|
+
const db = createMockPGlite()
|
|
491
|
+
// Make lock acquisition fail
|
|
492
|
+
;(db.query as any).mockImplementation(async (sql: string) => {
|
|
493
|
+
if (sql.includes('pg_try_advisory_lock')) {
|
|
494
|
+
return { rows: [{ pg_try_advisory_lock: false }] }
|
|
495
|
+
}
|
|
496
|
+
return { rows: [] }
|
|
497
|
+
})
|
|
498
|
+
const migrations = createTestMigrations()
|
|
499
|
+
|
|
500
|
+
const runner = createDOMigrationRunner(db, migrations, { lockTimeoutMs: 100 })
|
|
501
|
+
const result = await runner.migrate()
|
|
502
|
+
|
|
503
|
+
expect(result.success).toBe(false)
|
|
504
|
+
expect(result.error).toContain('lock')
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('should support lock timeout configuration', async () => {
|
|
508
|
+
const db = createMockPGlite()
|
|
509
|
+
let lockAttempts = 0
|
|
510
|
+
;(db.query as any).mockImplementation(async (sql: string) => {
|
|
511
|
+
if (sql.includes('pg_try_advisory_lock')) {
|
|
512
|
+
lockAttempts++
|
|
513
|
+
return { rows: [{ pg_try_advisory_lock: false }] }
|
|
514
|
+
}
|
|
515
|
+
return { rows: [] }
|
|
516
|
+
})
|
|
517
|
+
const migrations = createTestMigrations()
|
|
518
|
+
|
|
519
|
+
const start = performance.now()
|
|
520
|
+
const runner = createDOMigrationRunner(db, migrations, { lockTimeoutMs: 200 })
|
|
521
|
+
await runner.migrate()
|
|
522
|
+
const elapsed = performance.now() - start
|
|
523
|
+
|
|
524
|
+
expect(elapsed).toBeGreaterThanOrEqual(150) // Should have waited near timeout
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it('should emit progress events during migration', async () => {
|
|
528
|
+
const events: DOMigrationEvent[] = []
|
|
529
|
+
const db = createMockPGlite()
|
|
530
|
+
const migrations = createTestMigrations()
|
|
531
|
+
|
|
532
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
533
|
+
onEvent: (event) => events.push(event),
|
|
534
|
+
})
|
|
535
|
+
await runner.migrate()
|
|
536
|
+
|
|
537
|
+
expect(events.some(e => e.type === 'migration:start')).toBe(true)
|
|
538
|
+
expect(events.some(e => e.type === 'migration:complete')).toBe(true)
|
|
539
|
+
expect(events.some(e => e.type === 'batch:start')).toBe(true)
|
|
540
|
+
expect(events.some(e => e.type === 'batch:complete')).toBe(true)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
it('should track execution time for each migration', async () => {
|
|
544
|
+
const db = createMockPGlite()
|
|
545
|
+
const migrations = createTestMigrations()
|
|
546
|
+
|
|
547
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
548
|
+
const result = await runner.migrate()
|
|
549
|
+
|
|
550
|
+
for (const migrationResult of result.results) {
|
|
551
|
+
expect(typeof migrationResult.executionTimeMs).toBe('number')
|
|
552
|
+
expect(migrationResult.executionTimeMs).toBeGreaterThanOrEqual(0)
|
|
553
|
+
}
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
it('should handle empty migration list', async () => {
|
|
557
|
+
const db = createMockPGlite()
|
|
558
|
+
const migrations: DOMigration[] = []
|
|
559
|
+
|
|
560
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
561
|
+
const result = await runner.migrate()
|
|
562
|
+
|
|
563
|
+
expect(result.success).toBe(true)
|
|
564
|
+
expect(result.migrationsRun).toBe(0)
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it('should handle migrations with no pending changes', async () => {
|
|
568
|
+
const db = createMockPGlite()
|
|
569
|
+
const migrations = createTestMigrations()
|
|
570
|
+
|
|
571
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
572
|
+
await runner.migrate()
|
|
573
|
+
const result = await runner.migrate()
|
|
574
|
+
|
|
575
|
+
expect(result.success).toBe(true)
|
|
576
|
+
expect(result.migrationsRun).toBe(0)
|
|
577
|
+
expect(result.results.length).toBe(0)
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
describe('new DO initialization', () => {
|
|
582
|
+
it('should apply all migrations for brand new DO', async () => {
|
|
583
|
+
const db = createMockPGlite()
|
|
584
|
+
const migrations = createTestMigrations()
|
|
585
|
+
|
|
586
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
587
|
+
const result = await runner.ensureMigrated()
|
|
588
|
+
|
|
589
|
+
expect(result.success).toBe(true)
|
|
590
|
+
expect(result.migrationsRun).toBe(3)
|
|
591
|
+
expect(result.toVersion).toBe(3)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('should optionally apply latest schema directly (skip incremental)', async () => {
|
|
595
|
+
const db = createMockPGlite()
|
|
596
|
+
const migrations = createTestMigrations()
|
|
597
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
598
|
+
|
|
599
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
600
|
+
useSnapshot: true,
|
|
601
|
+
snapshot,
|
|
602
|
+
})
|
|
603
|
+
const result = await runner.ensureMigrated()
|
|
604
|
+
|
|
605
|
+
expect(result.success).toBe(true)
|
|
606
|
+
expect(result.toVersion).toBe(3)
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
it('should use schema snapshot when available and configured', async () => {
|
|
610
|
+
const execCalls: string[] = []
|
|
611
|
+
const db = createMockPGlite({
|
|
612
|
+
onExec: (sql) => execCalls.push(sql),
|
|
613
|
+
})
|
|
614
|
+
const migrations = createTestMigrations()
|
|
615
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
616
|
+
|
|
617
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
618
|
+
useSnapshot: true,
|
|
619
|
+
snapshot,
|
|
620
|
+
})
|
|
621
|
+
await runner.ensureMigrated()
|
|
622
|
+
|
|
623
|
+
// Should apply snapshot DDL, not individual migrations
|
|
624
|
+
expect(execCalls.some(sql => sql.includes(snapshot.schemaDDL))).toBe(true)
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
it('should fall back to incremental migrations if snapshot is stale', async () => {
|
|
628
|
+
const db = createMockPGlite()
|
|
629
|
+
const migrations = createTestMigrations()
|
|
630
|
+
|
|
631
|
+
// Create snapshot with wrong checksum
|
|
632
|
+
const staleSnapshot: DOSchemaSnapshot = {
|
|
633
|
+
targetVersion: 3,
|
|
634
|
+
schemaDDL: 'CREATE TABLE old_schema (id INT);',
|
|
635
|
+
migrationsChecksum: 'wrong_checksum',
|
|
636
|
+
includedVersions: [1, 2, 3],
|
|
637
|
+
generatedAt: new Date(),
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
641
|
+
useSnapshot: true,
|
|
642
|
+
snapshot: staleSnapshot,
|
|
643
|
+
debug: true, // Enable debug to cover console.warn path
|
|
644
|
+
})
|
|
645
|
+
const result = await runner.ensureMigrated()
|
|
646
|
+
|
|
647
|
+
expect(result.success).toBe(true)
|
|
648
|
+
expect(result.migrationsRun).toBe(3) // Should have run incremental
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
it('should validate schema snapshot checksum before applying', async () => {
|
|
652
|
+
const db = createMockPGlite()
|
|
653
|
+
const migrations = createTestMigrations()
|
|
654
|
+
const validSnapshot = await generateDOSchemaSnapshot(migrations)
|
|
655
|
+
const invalidSnapshot: DOSchemaSnapshot = {
|
|
656
|
+
...validSnapshot,
|
|
657
|
+
migrationsChecksum: 'invalid_checksum',
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
661
|
+
useSnapshot: true,
|
|
662
|
+
snapshot: invalidSnapshot,
|
|
663
|
+
})
|
|
664
|
+
const result = await runner.ensureMigrated()
|
|
665
|
+
|
|
666
|
+
// Should fall back to incremental due to checksum mismatch
|
|
667
|
+
expect(result.success).toBe(true)
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
it('should record all migration versions when using snapshot', async () => {
|
|
671
|
+
const db = createMockPGlite()
|
|
672
|
+
const migrations = createTestMigrations()
|
|
673
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
674
|
+
|
|
675
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
676
|
+
useSnapshot: true,
|
|
677
|
+
snapshot,
|
|
678
|
+
})
|
|
679
|
+
await runner.ensureMigrated()
|
|
680
|
+
|
|
681
|
+
const appliedMigrations = await runner.getAppliedMigrations()
|
|
682
|
+
expect(appliedMigrations.map(m => m.version)).toEqual([1, 2, 3])
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
it('should be faster than incremental for many migrations', async () => {
|
|
686
|
+
// Create many migrations
|
|
687
|
+
const manyMigrations: DOMigration[] = Array.from({ length: 20 }, (_, i) => ({
|
|
688
|
+
version: i + 1,
|
|
689
|
+
name: `migration_${i + 1}`,
|
|
690
|
+
up: `CREATE TABLE table_${i + 1} (id INT);`,
|
|
691
|
+
down: `DROP TABLE table_${i + 1};`,
|
|
692
|
+
}))
|
|
693
|
+
|
|
694
|
+
const snapshot = await generateDOSchemaSnapshot(manyMigrations)
|
|
695
|
+
|
|
696
|
+
// Test snapshot application time
|
|
697
|
+
const dbSnapshot = createMockPGlite()
|
|
698
|
+
const runnerSnapshot = createDOMigrationRunner(dbSnapshot, manyMigrations, {
|
|
699
|
+
useSnapshot: true,
|
|
700
|
+
snapshot,
|
|
701
|
+
})
|
|
702
|
+
const startSnapshot = performance.now()
|
|
703
|
+
await runnerSnapshot.ensureMigrated()
|
|
704
|
+
const snapshotTime = performance.now() - startSnapshot
|
|
705
|
+
|
|
706
|
+
// Test incremental application time
|
|
707
|
+
const dbIncremental = createMockPGlite()
|
|
708
|
+
const runnerIncremental = createDOMigrationRunner(dbIncremental, manyMigrations)
|
|
709
|
+
const startIncremental = performance.now()
|
|
710
|
+
await runnerIncremental.migrate()
|
|
711
|
+
const incrementalTime = performance.now() - startIncremental
|
|
712
|
+
|
|
713
|
+
// Snapshot should generally be faster (may not always be true in test env)
|
|
714
|
+
expect(snapshotTime).toBeDefined()
|
|
715
|
+
expect(incrementalTime).toBeDefined()
|
|
716
|
+
})
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
describe('migration validation', () => {
|
|
720
|
+
it('should validate migration has required fields (version, name, up)', async () => {
|
|
721
|
+
const db = createMockPGlite()
|
|
722
|
+
const invalidMigrations: DOMigration[] = [
|
|
723
|
+
{ version: 0, name: '', up: '' } as DOMigration,
|
|
724
|
+
]
|
|
725
|
+
|
|
726
|
+
const runner = createDOMigrationRunner(db, invalidMigrations)
|
|
727
|
+
const result = await runner.validate()
|
|
728
|
+
|
|
729
|
+
expect(result.valid).toBe(false)
|
|
730
|
+
expect(result.issues.some(i => i.message.includes('version'))).toBe(true)
|
|
731
|
+
expect(result.issues.some(i => i.message.includes('name'))).toBe(true)
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
it('should validate version numbers are unique', async () => {
|
|
735
|
+
const db = createMockPGlite()
|
|
736
|
+
const duplicateVersionMigrations: DOMigration[] = [
|
|
737
|
+
{ version: 1, name: 'first', up: 'CREATE TABLE a (id INT);' },
|
|
738
|
+
{ version: 1, name: 'duplicate', up: 'CREATE TABLE b (id INT);' },
|
|
739
|
+
]
|
|
740
|
+
|
|
741
|
+
const runner = createDOMigrationRunner(db, duplicateVersionMigrations)
|
|
742
|
+
const result = await runner.validate()
|
|
743
|
+
|
|
744
|
+
expect(result.valid).toBe(false)
|
|
745
|
+
expect(result.issues.some(i => i.message.includes('Duplicate'))).toBe(true)
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
it('should validate version numbers are sequential (configurable)', async () => {
|
|
749
|
+
const db = createMockPGlite()
|
|
750
|
+
const gappedMigrations: DOMigration[] = [
|
|
751
|
+
{ version: 1, name: 'first', up: 'CREATE TABLE a (id INT);' },
|
|
752
|
+
{ version: 5, name: 'jumped', up: 'CREATE TABLE b (id INT);' },
|
|
753
|
+
]
|
|
754
|
+
|
|
755
|
+
const runner = createDOMigrationRunner(db, gappedMigrations)
|
|
756
|
+
const result = await runner.validate()
|
|
757
|
+
|
|
758
|
+
expect(result.issues.some(i => i.message.includes('Gap'))).toBe(true)
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it('should allow version gaps when configured', async () => {
|
|
762
|
+
const db = createMockPGlite()
|
|
763
|
+
const gappedMigrations: DOMigration[] = [
|
|
764
|
+
{ version: 1, name: 'first', up: 'CREATE TABLE a (id INT);' },
|
|
765
|
+
{ version: 5, name: 'jumped', up: 'CREATE TABLE b (id INT);' },
|
|
766
|
+
]
|
|
767
|
+
|
|
768
|
+
const runner = createDOMigrationRunner(db, gappedMigrations, { allowVersionGaps: true })
|
|
769
|
+
const result = await runner.validate()
|
|
770
|
+
|
|
771
|
+
expect(result.issues.filter(i => i.message.includes('Gap')).length).toBe(0)
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
it('should calculate and store checksum for each migration', async () => {
|
|
775
|
+
const db = createMockPGlite()
|
|
776
|
+
const migrations = createTestMigrations()
|
|
777
|
+
|
|
778
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
779
|
+
const result = await runner.migrate()
|
|
780
|
+
|
|
781
|
+
for (const migrationResult of result.results) {
|
|
782
|
+
expect(migrationResult.checksum).toBeDefined()
|
|
783
|
+
expect(typeof migrationResult.checksum).toBe('string')
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
it('should detect modified migrations via checksum comparison', async () => {
|
|
788
|
+
const db = createMockPGlite()
|
|
789
|
+
const migrations = createTestMigrations()
|
|
790
|
+
|
|
791
|
+
// Pre-apply migration with different content (wrong checksum)
|
|
792
|
+
;(db as any)._internal.setAppliedMigrations(new Map([
|
|
793
|
+
[1, { name: 'create_users', checksum: 'different_checksum', applied_at: new Date().toISOString(), execution_time_ms: 10 }],
|
|
794
|
+
]))
|
|
795
|
+
|
|
796
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
797
|
+
const result = await runner.validate()
|
|
798
|
+
|
|
799
|
+
expect(result.issues.some(i => i.severity === 'error' && i.message.includes('modified'))).toBe(true)
|
|
800
|
+
})
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
describe('rollback support', () => {
|
|
804
|
+
it('should support rollback to specific version', async () => {
|
|
805
|
+
const db = createMockPGlite()
|
|
806
|
+
const migrations = createTestMigrations()
|
|
807
|
+
|
|
808
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
809
|
+
await runner.migrate()
|
|
810
|
+
|
|
811
|
+
const result = await runner.rollbackTo(1)
|
|
812
|
+
|
|
813
|
+
expect(result.success).toBe(true)
|
|
814
|
+
const version = await runner.getCurrentVersion()
|
|
815
|
+
expect(version).toBe(1)
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
it('should execute down migrations in reverse order', async () => {
|
|
819
|
+
const executedRollbacks: number[] = []
|
|
820
|
+
const db = createMockPGlite({
|
|
821
|
+
onExec: (sql) => {
|
|
822
|
+
if (sql.includes('DROP TABLE posts')) executedRollbacks.push(3)
|
|
823
|
+
if (sql.includes('DROP COLUMN name')) executedRollbacks.push(2)
|
|
824
|
+
},
|
|
825
|
+
})
|
|
826
|
+
const migrations = createTestMigrations()
|
|
827
|
+
|
|
828
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
829
|
+
await runner.migrate()
|
|
830
|
+
await runner.rollbackTo(1)
|
|
831
|
+
|
|
832
|
+
expect(executedRollbacks).toEqual([3, 2])
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
it('should skip migrations without down SQL', async () => {
|
|
836
|
+
const db = createMockPGlite()
|
|
837
|
+
const migrationsWithoutDown: DOMigration[] = [
|
|
838
|
+
{ version: 1, name: 'first', up: 'CREATE TABLE a (id INT);', down: 'DROP TABLE a;' },
|
|
839
|
+
{ version: 2, name: 'no_down', up: 'CREATE TABLE b (id INT);' }, // No down
|
|
840
|
+
{ version: 3, name: 'third', up: 'CREATE TABLE c (id INT);', down: 'DROP TABLE c;' },
|
|
841
|
+
]
|
|
842
|
+
|
|
843
|
+
const runner = createDOMigrationRunner(db, migrationsWithoutDown)
|
|
844
|
+
await runner.migrate()
|
|
845
|
+
const result = await runner.rollbackTo(0, { force: true })
|
|
846
|
+
|
|
847
|
+
expect(result.success).toBe(true)
|
|
848
|
+
expect(result.migrationsSkipped).toBe(1) // migration 2 skipped
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
it('should throw error for non-reversible migrations unless forced', async () => {
|
|
852
|
+
const db = createMockPGlite()
|
|
853
|
+
const nonReversibleMigrations: DOMigration[] = [
|
|
854
|
+
{ version: 1, name: 'first', up: 'CREATE TABLE a (id INT);', down: 'DROP TABLE a;' },
|
|
855
|
+
{ version: 2, name: 'non_reversible', up: 'CREATE TABLE b (id INT);', isReversible: false },
|
|
856
|
+
]
|
|
857
|
+
|
|
858
|
+
const runner = createDOMigrationRunner(db, nonReversibleMigrations)
|
|
859
|
+
await runner.migrate()
|
|
860
|
+
|
|
861
|
+
const result = await runner.rollbackTo(0)
|
|
862
|
+
|
|
863
|
+
expect(result.success).toBe(false)
|
|
864
|
+
expect(result.error).toContain('not reversible')
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
it('should support dry-run mode for rollback planning', async () => {
|
|
868
|
+
const execCalls: string[] = []
|
|
869
|
+
const db = createMockPGlite({
|
|
870
|
+
onExec: (sql) => execCalls.push(sql),
|
|
871
|
+
})
|
|
872
|
+
const migrations = createTestMigrations()
|
|
873
|
+
|
|
874
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
875
|
+
await runner.migrate()
|
|
876
|
+
|
|
877
|
+
// Clear exec calls before dry-run
|
|
878
|
+
execCalls.length = 0
|
|
879
|
+
|
|
880
|
+
const result = await runner.rollbackTo(0, { dryRun: true })
|
|
881
|
+
|
|
882
|
+
expect(result.success).toBe(true)
|
|
883
|
+
expect(result.migrationsSkipped).toBe(3) // All skipped in dry-run
|
|
884
|
+
// No actual DROP TABLE calls should have been made
|
|
885
|
+
expect(execCalls.some(sql => sql.includes('DROP TABLE'))).toBe(false)
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
it('should update meta table after successful rollback', async () => {
|
|
889
|
+
const db = createMockPGlite()
|
|
890
|
+
const migrations = createTestMigrations()
|
|
891
|
+
|
|
892
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
893
|
+
await runner.migrate()
|
|
894
|
+
await runner.rollbackTo(1)
|
|
895
|
+
|
|
896
|
+
const applied = await runner.getAppliedMigrations()
|
|
897
|
+
expect(applied.map(m => m.version)).toEqual([1])
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
it('should record rollback history', async () => {
|
|
901
|
+
const events: DOMigrationEvent[] = []
|
|
902
|
+
const db = createMockPGlite()
|
|
903
|
+
const migrations = createTestMigrations()
|
|
904
|
+
|
|
905
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
906
|
+
runner.onEvent((event) => events.push(event))
|
|
907
|
+
await runner.migrate()
|
|
908
|
+
await runner.rollbackTo(1)
|
|
909
|
+
|
|
910
|
+
expect(events.some(e => e.type === 'migration:complete' && e.version === 3)).toBe(true)
|
|
911
|
+
expect(events.some(e => e.type === 'migration:complete' && e.version === 2)).toBe(true)
|
|
912
|
+
})
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
describe('concurrent access', () => {
|
|
916
|
+
it('should serialize concurrent migrate() calls via locking', async () => {
|
|
917
|
+
const db = createMockPGlite()
|
|
918
|
+
const migrations = createTestMigrations()
|
|
919
|
+
|
|
920
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
921
|
+
|
|
922
|
+
// Run migrations concurrently
|
|
923
|
+
const results = await Promise.all([
|
|
924
|
+
runner.migrate(),
|
|
925
|
+
runner.migrate(),
|
|
926
|
+
runner.migrate(),
|
|
927
|
+
])
|
|
928
|
+
|
|
929
|
+
// All should succeed
|
|
930
|
+
expect(results.every(r => r.success)).toBe(true)
|
|
931
|
+
// Total migrations run should equal the number of migrations
|
|
932
|
+
// (concurrent calls are serialized via locking, so only one actually runs them)
|
|
933
|
+
const totalRun = results.reduce((sum, r) => sum + r.migrationsRun, 0)
|
|
934
|
+
expect(totalRun).toBeGreaterThanOrEqual(3)
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
it('should handle lock contention gracefully', async () => {
|
|
938
|
+
let lockAttempts = 0
|
|
939
|
+
const db = createMockPGlite()
|
|
940
|
+
;(db.query as any).mockImplementation(async (sql: string) => {
|
|
941
|
+
if (sql.includes('pg_try_advisory_lock')) {
|
|
942
|
+
lockAttempts++
|
|
943
|
+
// Fail first two attempts, succeed on third
|
|
944
|
+
return { rows: [{ pg_try_advisory_lock: lockAttempts >= 3 }] }
|
|
945
|
+
}
|
|
946
|
+
if (sql.includes('pg_advisory_unlock')) {
|
|
947
|
+
return { rows: [{ pg_advisory_unlock: true }] }
|
|
948
|
+
}
|
|
949
|
+
return { rows: [] }
|
|
950
|
+
})
|
|
951
|
+
const migrations = createTestMigrations()
|
|
952
|
+
|
|
953
|
+
const runner = createDOMigrationRunner(db, migrations, { lockTimeoutMs: 5000 })
|
|
954
|
+
const result = await runner.migrate()
|
|
955
|
+
|
|
956
|
+
expect(result.success).toBe(true)
|
|
957
|
+
expect(lockAttempts).toBeGreaterThanOrEqual(3)
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
it('should not deadlock on rapid sequential calls', async () => {
|
|
961
|
+
const db = createMockPGlite()
|
|
962
|
+
const migrations = createTestMigrations()
|
|
963
|
+
|
|
964
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
965
|
+
|
|
966
|
+
// Rapid sequential calls
|
|
967
|
+
for (let i = 0; i < 10; i++) {
|
|
968
|
+
const result = await runner.migrate()
|
|
969
|
+
expect(result.success).toBe(true)
|
|
970
|
+
}
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
it('should release lock on error', async () => {
|
|
974
|
+
const queryCalls: string[] = []
|
|
975
|
+
const db = createMockPGlite({
|
|
976
|
+
onQuery: (sql) => queryCalls.push(sql),
|
|
977
|
+
onExec: (sql) => {
|
|
978
|
+
if (sql.includes('ADD COLUMN name')) {
|
|
979
|
+
throw new Error('SQL error')
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
})
|
|
983
|
+
const migrations = createTestMigrations()
|
|
984
|
+
|
|
985
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
986
|
+
await runner.migrate()
|
|
987
|
+
|
|
988
|
+
// Lock should have been released
|
|
989
|
+
expect(queryCalls.filter(sql => sql.includes('pg_advisory_unlock')).length).toBeGreaterThanOrEqual(1)
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
it('should support configurable lock key per DO class', async () => {
|
|
993
|
+
// The lock key is hardcoded in the implementation, but the test verifies
|
|
994
|
+
// that locking is used when configured
|
|
995
|
+
const db = createMockPGlite()
|
|
996
|
+
const migrations = createTestMigrations()
|
|
997
|
+
|
|
998
|
+
const runner = createDOMigrationRunner(db, migrations, { useLocking: true })
|
|
999
|
+
await runner.migrate()
|
|
1000
|
+
|
|
1001
|
+
expect(db.query).toHaveBeenCalledWith(expect.stringContaining('pg_try_advisory_lock'))
|
|
1002
|
+
})
|
|
1003
|
+
})
|
|
1004
|
+
|
|
1005
|
+
describe('performance', () => {
|
|
1006
|
+
it('should complete version check in <1ms for cached state', async () => {
|
|
1007
|
+
const db = createMockPGlite()
|
|
1008
|
+
const migrations = createTestMigrations()
|
|
1009
|
+
|
|
1010
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1011
|
+
await runner.getCurrentVersion() // First call to cache
|
|
1012
|
+
|
|
1013
|
+
const start = performance.now()
|
|
1014
|
+
await runner.getCurrentVersion()
|
|
1015
|
+
const elapsed = performance.now() - start
|
|
1016
|
+
|
|
1017
|
+
expect(elapsed).toBeLessThan(10 * CI_MULTIPLIER) // Allow some slack
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
it('should complete no-op migrate() in <5ms', async () => {
|
|
1021
|
+
const db = createMockPGlite()
|
|
1022
|
+
const migrations = createTestMigrations()
|
|
1023
|
+
|
|
1024
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1025
|
+
await runner.migrate() // First run applies migrations
|
|
1026
|
+
|
|
1027
|
+
const start = performance.now()
|
|
1028
|
+
await runner.migrate()
|
|
1029
|
+
const elapsed = performance.now() - start
|
|
1030
|
+
|
|
1031
|
+
expect(elapsed).toBeLessThan(50 * CI_MULTIPLIER) // Allow slack for test environment
|
|
1032
|
+
})
|
|
1033
|
+
|
|
1034
|
+
it('should support lazy initialization', async () => {
|
|
1035
|
+
const db = createMockPGlite()
|
|
1036
|
+
const migrations = createTestMigrations()
|
|
1037
|
+
|
|
1038
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1039
|
+
|
|
1040
|
+
// No DB calls should have been made yet
|
|
1041
|
+
expect(db.exec).not.toHaveBeenCalled()
|
|
1042
|
+
expect(db.query).not.toHaveBeenCalled()
|
|
1043
|
+
|
|
1044
|
+
// Now trigger initialization
|
|
1045
|
+
await runner.getCurrentVersion()
|
|
1046
|
+
|
|
1047
|
+
expect(db.exec).toHaveBeenCalled()
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
it('should minimize database round-trips', async () => {
|
|
1051
|
+
const db = createMockPGlite()
|
|
1052
|
+
const migrations = createTestMigrations()
|
|
1053
|
+
|
|
1054
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1055
|
+
await runner.migrate()
|
|
1056
|
+
|
|
1057
|
+
// Count query/exec calls
|
|
1058
|
+
const queryCount = (db.query as any).mock.calls.length
|
|
1059
|
+
const execCount = (db.exec as any).mock.calls.length
|
|
1060
|
+
|
|
1061
|
+
// Should be reasonable number of calls for 3 migrations
|
|
1062
|
+
expect(queryCount + execCount).toBeLessThan(30)
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
it('should batch multiple migrations in single transaction when safe', async () => {
|
|
1066
|
+
const execCalls: string[] = []
|
|
1067
|
+
const db = createMockPGlite({
|
|
1068
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1069
|
+
})
|
|
1070
|
+
const migrations = createTestMigrations()
|
|
1071
|
+
|
|
1072
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1073
|
+
await runner.migrate()
|
|
1074
|
+
|
|
1075
|
+
// Each migration should have its own transaction (BEGIN/COMMIT per migration)
|
|
1076
|
+
const beginCount = execCalls.filter(sql => sql === 'BEGIN').length
|
|
1077
|
+
const commitCount = execCalls.filter(sql => sql === 'COMMIT').length
|
|
1078
|
+
expect(beginCount).toBe(commitCount)
|
|
1079
|
+
})
|
|
1080
|
+
})
|
|
1081
|
+
|
|
1082
|
+
describe('error handling', () => {
|
|
1083
|
+
it('should provide detailed error messages for SQL syntax errors', async () => {
|
|
1084
|
+
const db = createMockPGlite({
|
|
1085
|
+
onExec: (sql) => {
|
|
1086
|
+
if (sql.includes('CREATE TABLE users')) {
|
|
1087
|
+
throw new Error('syntax error at or near "CREAET"')
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
})
|
|
1091
|
+
const migrations = createTestMigrations()
|
|
1092
|
+
|
|
1093
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1094
|
+
const result = await runner.migrate()
|
|
1095
|
+
|
|
1096
|
+
expect(result.success).toBe(false)
|
|
1097
|
+
expect(result.error).toContain('syntax error')
|
|
1098
|
+
})
|
|
1099
|
+
|
|
1100
|
+
it('should include migration id and version in error context', async () => {
|
|
1101
|
+
const db = createMockPGlite({
|
|
1102
|
+
onExec: (sql) => {
|
|
1103
|
+
if (sql.includes('ADD COLUMN name')) {
|
|
1104
|
+
throw new Error('column "name" already exists')
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
})
|
|
1108
|
+
const migrations = createTestMigrations()
|
|
1109
|
+
|
|
1110
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1111
|
+
const result = await runner.migrate()
|
|
1112
|
+
|
|
1113
|
+
const failedResult = result.results.find(r => !r.success)
|
|
1114
|
+
expect(failedResult?.version).toBe(2)
|
|
1115
|
+
expect(failedResult?.name).toBe('add_user_name')
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
it('should preserve original error stack trace', async () => {
|
|
1119
|
+
const db = createMockPGlite({
|
|
1120
|
+
onExec: (sql) => {
|
|
1121
|
+
// Migration SQL for users table (not the meta table)
|
|
1122
|
+
if (sql.includes('CREATE TABLE users') && !sql.includes('_do_migrations')) {
|
|
1123
|
+
throw new Error('Original error')
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
})
|
|
1127
|
+
const migrations = createTestMigrations()
|
|
1128
|
+
|
|
1129
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1130
|
+
const result = await runner.migrate()
|
|
1131
|
+
|
|
1132
|
+
expect(result.error).toContain('Original error')
|
|
1133
|
+
})
|
|
1134
|
+
|
|
1135
|
+
it('should handle database connection errors', async () => {
|
|
1136
|
+
const db = createMockPGlite()
|
|
1137
|
+
;(db.exec as any).mockRejectedValue(new Error('Connection refused'))
|
|
1138
|
+
const migrations = createTestMigrations()
|
|
1139
|
+
|
|
1140
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1141
|
+
|
|
1142
|
+
await expect(runner.initialize()).rejects.toThrow('Connection refused')
|
|
1143
|
+
})
|
|
1144
|
+
|
|
1145
|
+
it('should handle out-of-memory conditions gracefully', async () => {
|
|
1146
|
+
const db = createMockPGlite({
|
|
1147
|
+
onExec: (sql) => {
|
|
1148
|
+
// Migration SQL for users table (not the meta table)
|
|
1149
|
+
if (sql.includes('CREATE TABLE users') && !sql.includes('_do_migrations')) {
|
|
1150
|
+
throw new Error('out of memory')
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
})
|
|
1154
|
+
const migrations = createTestMigrations()
|
|
1155
|
+
|
|
1156
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1157
|
+
const result = await runner.migrate()
|
|
1158
|
+
|
|
1159
|
+
expect(result.success).toBe(false)
|
|
1160
|
+
expect(result.error).toContain('out of memory')
|
|
1161
|
+
})
|
|
1162
|
+
|
|
1163
|
+
it('should emit error events for monitoring', async () => {
|
|
1164
|
+
const events: DOMigrationEvent[] = []
|
|
1165
|
+
const db = createMockPGlite({
|
|
1166
|
+
onExec: (sql) => {
|
|
1167
|
+
if (sql.includes('CREATE TABLE users')) {
|
|
1168
|
+
throw new Error('SQL error')
|
|
1169
|
+
}
|
|
1170
|
+
},
|
|
1171
|
+
})
|
|
1172
|
+
const migrations = createTestMigrations()
|
|
1173
|
+
|
|
1174
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1175
|
+
onEvent: (event) => events.push(event),
|
|
1176
|
+
})
|
|
1177
|
+
await runner.migrate()
|
|
1178
|
+
|
|
1179
|
+
expect(events.some(e => e.type === 'migration:error')).toBe(true)
|
|
1180
|
+
expect(events.some(e => e.type === 'batch:error')).toBe(true)
|
|
1181
|
+
})
|
|
1182
|
+
})
|
|
1183
|
+
|
|
1184
|
+
describe('events and observability', () => {
|
|
1185
|
+
it('should emit "migration:start" event before each migration', async () => {
|
|
1186
|
+
const events: DOMigrationEvent[] = []
|
|
1187
|
+
const db = createMockPGlite()
|
|
1188
|
+
const migrations = createTestMigrations()
|
|
1189
|
+
|
|
1190
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1191
|
+
onEvent: (event) => events.push(event),
|
|
1192
|
+
})
|
|
1193
|
+
await runner.migrate()
|
|
1194
|
+
|
|
1195
|
+
const startEvents = events.filter(e => e.type === 'migration:start')
|
|
1196
|
+
expect(startEvents.length).toBe(3)
|
|
1197
|
+
expect(startEvents.map(e => e.version)).toEqual([1, 2, 3])
|
|
1198
|
+
})
|
|
1199
|
+
|
|
1200
|
+
it('should emit "migration:complete" event after each migration', async () => {
|
|
1201
|
+
const events: DOMigrationEvent[] = []
|
|
1202
|
+
const db = createMockPGlite()
|
|
1203
|
+
const migrations = createTestMigrations()
|
|
1204
|
+
|
|
1205
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1206
|
+
onEvent: (event) => events.push(event),
|
|
1207
|
+
})
|
|
1208
|
+
await runner.migrate()
|
|
1209
|
+
|
|
1210
|
+
const completeEvents = events.filter(e => e.type === 'migration:complete')
|
|
1211
|
+
expect(completeEvents.length).toBe(3)
|
|
1212
|
+
})
|
|
1213
|
+
|
|
1214
|
+
it('should emit "migration:error" event on failure', async () => {
|
|
1215
|
+
const events: DOMigrationEvent[] = []
|
|
1216
|
+
const db = createMockPGlite({
|
|
1217
|
+
onExec: (sql) => {
|
|
1218
|
+
if (sql.includes('CREATE TABLE users')) {
|
|
1219
|
+
throw new Error('SQL error')
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
})
|
|
1223
|
+
const migrations = createTestMigrations()
|
|
1224
|
+
|
|
1225
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1226
|
+
onEvent: (event) => events.push(event),
|
|
1227
|
+
})
|
|
1228
|
+
await runner.migrate()
|
|
1229
|
+
|
|
1230
|
+
const errorEvent = events.find(e => e.type === 'migration:error')
|
|
1231
|
+
expect(errorEvent).toBeDefined()
|
|
1232
|
+
expect(errorEvent?.error).toBeInstanceOf(Error)
|
|
1233
|
+
})
|
|
1234
|
+
|
|
1235
|
+
it('should emit "batch:start" event before batch', async () => {
|
|
1236
|
+
const events: DOMigrationEvent[] = []
|
|
1237
|
+
const db = createMockPGlite()
|
|
1238
|
+
const migrations = createTestMigrations()
|
|
1239
|
+
|
|
1240
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1241
|
+
onEvent: (event) => events.push(event),
|
|
1242
|
+
})
|
|
1243
|
+
await runner.migrate()
|
|
1244
|
+
|
|
1245
|
+
expect(events.some(e => e.type === 'batch:start')).toBe(true)
|
|
1246
|
+
})
|
|
1247
|
+
|
|
1248
|
+
it('should emit "batch:complete" event after batch', async () => {
|
|
1249
|
+
const events: DOMigrationEvent[] = []
|
|
1250
|
+
const db = createMockPGlite()
|
|
1251
|
+
const migrations = createTestMigrations()
|
|
1252
|
+
|
|
1253
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1254
|
+
onEvent: (event) => events.push(event),
|
|
1255
|
+
})
|
|
1256
|
+
await runner.migrate()
|
|
1257
|
+
|
|
1258
|
+
expect(events.some(e => e.type === 'batch:complete')).toBe(true)
|
|
1259
|
+
})
|
|
1260
|
+
|
|
1261
|
+
it('should include timing information in events', async () => {
|
|
1262
|
+
const events: DOMigrationEvent[] = []
|
|
1263
|
+
const db = createMockPGlite()
|
|
1264
|
+
const migrations = createTestMigrations()
|
|
1265
|
+
|
|
1266
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1267
|
+
onEvent: (event) => events.push(event),
|
|
1268
|
+
})
|
|
1269
|
+
await runner.migrate()
|
|
1270
|
+
|
|
1271
|
+
const completeEvents = events.filter(e => e.type === 'migration:complete')
|
|
1272
|
+
for (const event of completeEvents) {
|
|
1273
|
+
expect(event.durationMs).toBeDefined()
|
|
1274
|
+
expect(typeof event.durationMs).toBe('number')
|
|
1275
|
+
}
|
|
1276
|
+
})
|
|
1277
|
+
|
|
1278
|
+
it('should support custom event handlers', async () => {
|
|
1279
|
+
const handler1Events: DOMigrationEvent[] = []
|
|
1280
|
+
const handler2Events: DOMigrationEvent[] = []
|
|
1281
|
+
const db = createMockPGlite()
|
|
1282
|
+
const migrations = createTestMigrations()
|
|
1283
|
+
|
|
1284
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1285
|
+
runner.onEvent((event) => handler1Events.push(event))
|
|
1286
|
+
runner.onEvent((event) => handler2Events.push(event))
|
|
1287
|
+
await runner.migrate()
|
|
1288
|
+
|
|
1289
|
+
expect(handler1Events.length).toBeGreaterThan(0)
|
|
1290
|
+
expect(handler2Events.length).toBeGreaterThan(0)
|
|
1291
|
+
expect(handler1Events.length).toBe(handler2Events.length)
|
|
1292
|
+
})
|
|
1293
|
+
})
|
|
1294
|
+
})
|
|
1295
|
+
|
|
1296
|
+
// =============================================================================
|
|
1297
|
+
// DOMigration Type and Definition
|
|
1298
|
+
// =============================================================================
|
|
1299
|
+
|
|
1300
|
+
describe('DOMigration', () => {
|
|
1301
|
+
describe('definition', () => {
|
|
1302
|
+
it('should accept SQL string for up migration', async () => {
|
|
1303
|
+
const db = createMockPGlite()
|
|
1304
|
+
const migrations: DOMigration[] = [
|
|
1305
|
+
{ version: 1, name: 'test', up: 'CREATE TABLE test (id INT);' },
|
|
1306
|
+
]
|
|
1307
|
+
|
|
1308
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1309
|
+
const result = await runner.migrate()
|
|
1310
|
+
|
|
1311
|
+
expect(result.success).toBe(true)
|
|
1312
|
+
})
|
|
1313
|
+
|
|
1314
|
+
it('should accept function returning SQL for up migration', async () => {
|
|
1315
|
+
const execCalls: string[] = []
|
|
1316
|
+
const db = createMockPGlite({
|
|
1317
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1318
|
+
})
|
|
1319
|
+
const migrations: DOMigration[] = [
|
|
1320
|
+
{ version: 1, name: 'test', up: () => 'CREATE TABLE test (id INT);' },
|
|
1321
|
+
]
|
|
1322
|
+
|
|
1323
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1324
|
+
const result = await runner.migrate()
|
|
1325
|
+
|
|
1326
|
+
expect(result.success).toBe(true)
|
|
1327
|
+
expect(execCalls.some(sql => sql.includes('CREATE TABLE test'))).toBe(true)
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
it('should accept async function for up migration', async () => {
|
|
1331
|
+
const execCalls: string[] = []
|
|
1332
|
+
const db = createMockPGlite({
|
|
1333
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1334
|
+
})
|
|
1335
|
+
const migrations: DOMigration[] = [
|
|
1336
|
+
{ version: 1, name: 'test', up: async () => 'CREATE TABLE test (id INT);' },
|
|
1337
|
+
]
|
|
1338
|
+
|
|
1339
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1340
|
+
const result = await runner.migrate()
|
|
1341
|
+
|
|
1342
|
+
expect(result.success).toBe(true)
|
|
1343
|
+
expect(execCalls.some(sql => sql.includes('CREATE TABLE test'))).toBe(true)
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
it('should support optional down migration', async () => {
|
|
1347
|
+
const db = createMockPGlite()
|
|
1348
|
+
const migrations: DOMigration[] = [
|
|
1349
|
+
{ version: 1, name: 'test', up: 'CREATE TABLE test (id INT);' },
|
|
1350
|
+
]
|
|
1351
|
+
|
|
1352
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1353
|
+
const result = await runner.migrate()
|
|
1354
|
+
|
|
1355
|
+
expect(result.success).toBe(true)
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
it('should support migration metadata (tags, description)', async () => {
|
|
1359
|
+
const db = createMockPGlite()
|
|
1360
|
+
const migrations: DOMigration[] = [
|
|
1361
|
+
{
|
|
1362
|
+
version: 1,
|
|
1363
|
+
name: 'test',
|
|
1364
|
+
up: 'CREATE TABLE test (id INT);',
|
|
1365
|
+
tags: ['schema', 'initial'],
|
|
1366
|
+
description: 'Creates the initial test table',
|
|
1367
|
+
},
|
|
1368
|
+
]
|
|
1369
|
+
|
|
1370
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1371
|
+
const allMigrations = runner.getMigrations()
|
|
1372
|
+
|
|
1373
|
+
expect(allMigrations[0].tags).toEqual(['schema', 'initial'])
|
|
1374
|
+
expect(allMigrations[0].description).toBe('Creates the initial test table')
|
|
1375
|
+
})
|
|
1376
|
+
|
|
1377
|
+
it('should support non-transactional flag', async () => {
|
|
1378
|
+
const execCalls: string[] = []
|
|
1379
|
+
const db = createMockPGlite({
|
|
1380
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1381
|
+
})
|
|
1382
|
+
const migrations: DOMigration[] = [
|
|
1383
|
+
{
|
|
1384
|
+
version: 1,
|
|
1385
|
+
name: 'test',
|
|
1386
|
+
up: 'CREATE INDEX CONCURRENTLY idx ON test (id);',
|
|
1387
|
+
transactional: false,
|
|
1388
|
+
},
|
|
1389
|
+
]
|
|
1390
|
+
|
|
1391
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1392
|
+
await runner.migrate()
|
|
1393
|
+
|
|
1394
|
+
// Should not have BEGIN/COMMIT around the migration
|
|
1395
|
+
const beginIndex = execCalls.indexOf('BEGIN')
|
|
1396
|
+
const indexCreateIndex = execCalls.findIndex(sql => sql.includes('CREATE INDEX'))
|
|
1397
|
+
const commitIndex = execCalls.indexOf('COMMIT')
|
|
1398
|
+
|
|
1399
|
+
// If transactional: false, no BEGIN/COMMIT should surround the migration
|
|
1400
|
+
expect(beginIndex).toBe(-1)
|
|
1401
|
+
expect(commitIndex).toBe(-1)
|
|
1402
|
+
})
|
|
1403
|
+
|
|
1404
|
+
it('should support custom timeout per migration', async () => {
|
|
1405
|
+
const db = createMockPGlite()
|
|
1406
|
+
const migrations: DOMigration[] = [
|
|
1407
|
+
{
|
|
1408
|
+
version: 1,
|
|
1409
|
+
name: 'test',
|
|
1410
|
+
up: 'CREATE TABLE test (id INT);',
|
|
1411
|
+
timeoutMs: 60000,
|
|
1412
|
+
},
|
|
1413
|
+
]
|
|
1414
|
+
|
|
1415
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1416
|
+
const allMigrations = runner.getMigrations()
|
|
1417
|
+
|
|
1418
|
+
expect(allMigrations[0].timeoutMs).toBe(60000)
|
|
1419
|
+
})
|
|
1420
|
+
})
|
|
1421
|
+
|
|
1422
|
+
describe('validation', () => {
|
|
1423
|
+
it('should require version to be positive integer', async () => {
|
|
1424
|
+
const db = createMockPGlite()
|
|
1425
|
+
const migrations: DOMigration[] = [
|
|
1426
|
+
{ version: 0, name: 'invalid', up: 'SELECT 1;' },
|
|
1427
|
+
]
|
|
1428
|
+
|
|
1429
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1430
|
+
const result = await runner.validate()
|
|
1431
|
+
|
|
1432
|
+
expect(result.valid).toBe(false)
|
|
1433
|
+
expect(result.issues.some(i => i.message.includes('positive integer'))).toBe(true)
|
|
1434
|
+
})
|
|
1435
|
+
|
|
1436
|
+
it('should require name to be non-empty string', async () => {
|
|
1437
|
+
const db = createMockPGlite()
|
|
1438
|
+
const migrations: DOMigration[] = [
|
|
1439
|
+
{ version: 1, name: '', up: 'SELECT 1;' },
|
|
1440
|
+
]
|
|
1441
|
+
|
|
1442
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1443
|
+
const result = await runner.validate()
|
|
1444
|
+
|
|
1445
|
+
expect(result.valid).toBe(false)
|
|
1446
|
+
expect(result.issues.some(i => i.message.includes('name'))).toBe(true)
|
|
1447
|
+
})
|
|
1448
|
+
|
|
1449
|
+
it('should require up to be string or function', async () => {
|
|
1450
|
+
const db = createMockPGlite()
|
|
1451
|
+
const migrations: DOMigration[] = [
|
|
1452
|
+
{ version: 1, name: 'test', up: undefined as any },
|
|
1453
|
+
]
|
|
1454
|
+
|
|
1455
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1456
|
+
const result = await runner.validate()
|
|
1457
|
+
|
|
1458
|
+
expect(result.valid).toBe(false)
|
|
1459
|
+
expect(result.issues.some(i => i.message.includes('up must be'))).toBe(true)
|
|
1460
|
+
})
|
|
1461
|
+
|
|
1462
|
+
it('should validate down is string or function if provided', async () => {
|
|
1463
|
+
const db = createMockPGlite()
|
|
1464
|
+
const migrations: DOMigration[] = [
|
|
1465
|
+
{ version: 1, name: 'test', up: 'SELECT 1;', down: 123 as any },
|
|
1466
|
+
]
|
|
1467
|
+
|
|
1468
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1469
|
+
const result = await runner.validate()
|
|
1470
|
+
|
|
1471
|
+
expect(result.valid).toBe(false)
|
|
1472
|
+
expect(result.issues.some(i => i.message.includes('down must be'))).toBe(true)
|
|
1473
|
+
})
|
|
1474
|
+
})
|
|
1475
|
+
})
|
|
1476
|
+
|
|
1477
|
+
// =============================================================================
|
|
1478
|
+
// Schema Snapshot System
|
|
1479
|
+
// =============================================================================
|
|
1480
|
+
|
|
1481
|
+
describe('DOSchemaSnapshot', () => {
|
|
1482
|
+
describe('creation', () => {
|
|
1483
|
+
it('should generate snapshot from migration list', async () => {
|
|
1484
|
+
const migrations = createTestMigrations()
|
|
1485
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1486
|
+
|
|
1487
|
+
expect(snapshot).toBeDefined()
|
|
1488
|
+
expect(snapshot.targetVersion).toBe(3)
|
|
1489
|
+
})
|
|
1490
|
+
|
|
1491
|
+
it('should include combined schema DDL', async () => {
|
|
1492
|
+
const migrations = createTestMigrations()
|
|
1493
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1494
|
+
|
|
1495
|
+
expect(snapshot.schemaDDL).toContain('CREATE TABLE users')
|
|
1496
|
+
expect(snapshot.schemaDDL).toContain('ADD COLUMN name')
|
|
1497
|
+
expect(snapshot.schemaDDL).toContain('CREATE TABLE posts')
|
|
1498
|
+
})
|
|
1499
|
+
|
|
1500
|
+
it('should include target version number', async () => {
|
|
1501
|
+
const migrations = createTestMigrations()
|
|
1502
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1503
|
+
|
|
1504
|
+
expect(snapshot.targetVersion).toBe(3)
|
|
1505
|
+
})
|
|
1506
|
+
|
|
1507
|
+
it('should include checksum of all migrations', async () => {
|
|
1508
|
+
const migrations = createTestMigrations()
|
|
1509
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1510
|
+
|
|
1511
|
+
expect(snapshot.migrationsChecksum).toBeDefined()
|
|
1512
|
+
expect(typeof snapshot.migrationsChecksum).toBe('string')
|
|
1513
|
+
expect(snapshot.migrationsChecksum.length).toBeGreaterThan(0)
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
it('should include list of applied migration versions', async () => {
|
|
1517
|
+
const migrations = createTestMigrations()
|
|
1518
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1519
|
+
|
|
1520
|
+
expect(snapshot.includedVersions).toEqual([1, 2, 3])
|
|
1521
|
+
})
|
|
1522
|
+
})
|
|
1523
|
+
|
|
1524
|
+
describe('application', () => {
|
|
1525
|
+
it('should apply snapshot in single transaction', async () => {
|
|
1526
|
+
const execCalls: string[] = []
|
|
1527
|
+
const db = createMockPGlite({
|
|
1528
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1529
|
+
})
|
|
1530
|
+
const migrations = createTestMigrations()
|
|
1531
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1532
|
+
|
|
1533
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1534
|
+
useSnapshot: true,
|
|
1535
|
+
snapshot,
|
|
1536
|
+
})
|
|
1537
|
+
await runner.ensureMigrated()
|
|
1538
|
+
|
|
1539
|
+
// Should have BEGIN and COMMIT for snapshot application
|
|
1540
|
+
expect(execCalls.includes('BEGIN')).toBe(true)
|
|
1541
|
+
expect(execCalls.includes('COMMIT')).toBe(true)
|
|
1542
|
+
})
|
|
1543
|
+
|
|
1544
|
+
it('should record all migration versions in meta table', async () => {
|
|
1545
|
+
const db = createMockPGlite()
|
|
1546
|
+
const migrations = createTestMigrations()
|
|
1547
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1548
|
+
|
|
1549
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1550
|
+
useSnapshot: true,
|
|
1551
|
+
snapshot,
|
|
1552
|
+
})
|
|
1553
|
+
await runner.ensureMigrated()
|
|
1554
|
+
|
|
1555
|
+
const applied = await runner.getAppliedMigrations()
|
|
1556
|
+
expect(applied.map(m => m.version)).toEqual([1, 2, 3])
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1559
|
+
it('should validate snapshot checksum before applying', async () => {
|
|
1560
|
+
const db = createMockPGlite()
|
|
1561
|
+
const migrations = createTestMigrations()
|
|
1562
|
+
const invalidSnapshot: DOSchemaSnapshot = {
|
|
1563
|
+
targetVersion: 3,
|
|
1564
|
+
schemaDDL: 'invalid sql',
|
|
1565
|
+
migrationsChecksum: 'invalid_checksum',
|
|
1566
|
+
includedVersions: [1, 2, 3],
|
|
1567
|
+
generatedAt: new Date(),
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1571
|
+
useSnapshot: true,
|
|
1572
|
+
snapshot: invalidSnapshot,
|
|
1573
|
+
})
|
|
1574
|
+
const result = await runner.ensureMigrated()
|
|
1575
|
+
|
|
1576
|
+
// Should fall back to incremental migrations
|
|
1577
|
+
expect(result.success).toBe(true)
|
|
1578
|
+
})
|
|
1579
|
+
|
|
1580
|
+
it('should fail cleanly if snapshot is invalid', async () => {
|
|
1581
|
+
const db = createMockPGlite({
|
|
1582
|
+
onExec: (sql) => {
|
|
1583
|
+
if (sql.includes('invalid sql')) {
|
|
1584
|
+
throw new Error('syntax error')
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
})
|
|
1588
|
+
const migrations = createTestMigrations()
|
|
1589
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1590
|
+
// Modify snapshot to have valid checksum but invalid SQL
|
|
1591
|
+
const invalidSnapshot: DOSchemaSnapshot = {
|
|
1592
|
+
...snapshot,
|
|
1593
|
+
schemaDDL: 'invalid sql',
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1597
|
+
useSnapshot: true,
|
|
1598
|
+
snapshot: invalidSnapshot,
|
|
1599
|
+
})
|
|
1600
|
+
|
|
1601
|
+
// Should fall back to incremental on snapshot failure
|
|
1602
|
+
const result = await runner.ensureMigrated()
|
|
1603
|
+
expect(result.success).toBe(true) // Falls back to incremental
|
|
1604
|
+
})
|
|
1605
|
+
})
|
|
1606
|
+
|
|
1607
|
+
describe('validation', () => {
|
|
1608
|
+
it('should detect stale snapshot (version mismatch)', async () => {
|
|
1609
|
+
const db = createMockPGlite()
|
|
1610
|
+
const migrations = createTestMigrations()
|
|
1611
|
+
const staleSnapshot: DOSchemaSnapshot = {
|
|
1612
|
+
targetVersion: 2, // Old version
|
|
1613
|
+
schemaDDL: 'CREATE TABLE old (id INT);',
|
|
1614
|
+
migrationsChecksum: 'old_checksum',
|
|
1615
|
+
includedVersions: [1, 2],
|
|
1616
|
+
generatedAt: new Date(Date.now() - 86400000), // 1 day ago
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1620
|
+
useSnapshot: true,
|
|
1621
|
+
snapshot: staleSnapshot,
|
|
1622
|
+
})
|
|
1623
|
+
const result = await runner.ensureMigrated()
|
|
1624
|
+
|
|
1625
|
+
// Should fall back to incremental
|
|
1626
|
+
expect(result.success).toBe(true)
|
|
1627
|
+
expect(result.toVersion).toBe(3)
|
|
1628
|
+
})
|
|
1629
|
+
|
|
1630
|
+
it('should detect corrupted snapshot (checksum mismatch)', async () => {
|
|
1631
|
+
const db = createMockPGlite()
|
|
1632
|
+
const migrations = createTestMigrations()
|
|
1633
|
+
const corruptedSnapshot: DOSchemaSnapshot = {
|
|
1634
|
+
targetVersion: 3,
|
|
1635
|
+
schemaDDL: 'corrupted sql',
|
|
1636
|
+
migrationsChecksum: 'wrong_checksum',
|
|
1637
|
+
includedVersions: [1, 2, 3],
|
|
1638
|
+
generatedAt: new Date(),
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1642
|
+
useSnapshot: true,
|
|
1643
|
+
snapshot: corruptedSnapshot,
|
|
1644
|
+
})
|
|
1645
|
+
const result = await runner.ensureMigrated()
|
|
1646
|
+
|
|
1647
|
+
// Should fall back to incremental due to checksum mismatch
|
|
1648
|
+
expect(result.success).toBe(true)
|
|
1649
|
+
})
|
|
1650
|
+
|
|
1651
|
+
it('should verify snapshot is compatible with current migrations', async () => {
|
|
1652
|
+
const db = createMockPGlite()
|
|
1653
|
+
const migrations = createTestMigrations()
|
|
1654
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
1655
|
+
|
|
1656
|
+
// Add a new migration
|
|
1657
|
+
const updatedMigrations = [
|
|
1658
|
+
...migrations,
|
|
1659
|
+
{ version: 4, name: 'new_migration', up: 'CREATE TABLE new (id INT);' },
|
|
1660
|
+
]
|
|
1661
|
+
|
|
1662
|
+
const runner = createDOMigrationRunner(db, updatedMigrations, {
|
|
1663
|
+
useSnapshot: true,
|
|
1664
|
+
snapshot,
|
|
1665
|
+
})
|
|
1666
|
+
const result = await runner.ensureMigrated()
|
|
1667
|
+
|
|
1668
|
+
// Should fall back to incremental due to migration list change
|
|
1669
|
+
expect(result.success).toBe(true)
|
|
1670
|
+
})
|
|
1671
|
+
})
|
|
1672
|
+
})
|
|
1673
|
+
|
|
1674
|
+
// =============================================================================
|
|
1675
|
+
// DO-Specific Optimizations
|
|
1676
|
+
// =============================================================================
|
|
1677
|
+
|
|
1678
|
+
describe('DO-Specific Optimizations', () => {
|
|
1679
|
+
describe('lazy migration', () => {
|
|
1680
|
+
it('should defer migration until first database access', async () => {
|
|
1681
|
+
const db = createMockPGlite()
|
|
1682
|
+
const migrations = createTestMigrations()
|
|
1683
|
+
|
|
1684
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1685
|
+
|
|
1686
|
+
// No DB operations yet
|
|
1687
|
+
expect(db.exec).not.toHaveBeenCalled()
|
|
1688
|
+
|
|
1689
|
+
// Trigger migration
|
|
1690
|
+
await runner.migrate()
|
|
1691
|
+
|
|
1692
|
+
expect(db.exec).toHaveBeenCalled()
|
|
1693
|
+
})
|
|
1694
|
+
|
|
1695
|
+
it('should support eager migration via config', async () => {
|
|
1696
|
+
const db = createMockPGlite()
|
|
1697
|
+
const migrations = createTestMigrations()
|
|
1698
|
+
|
|
1699
|
+
// ensureMigrated is the eager approach
|
|
1700
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1701
|
+
await runner.ensureMigrated()
|
|
1702
|
+
|
|
1703
|
+
expect(db.exec).toHaveBeenCalled()
|
|
1704
|
+
})
|
|
1705
|
+
|
|
1706
|
+
it('should track migration status in memory', async () => {
|
|
1707
|
+
const db = createMockPGlite()
|
|
1708
|
+
const migrations = createTestMigrations()
|
|
1709
|
+
|
|
1710
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1711
|
+
await runner.migrate()
|
|
1712
|
+
|
|
1713
|
+
// Second call should use cached state
|
|
1714
|
+
const queryCountBefore = (db.query as any).mock.calls.length
|
|
1715
|
+
await runner.getCurrentVersion()
|
|
1716
|
+
const queryCountAfter = (db.query as any).mock.calls.length
|
|
1717
|
+
|
|
1718
|
+
// Should not have made additional queries due to caching
|
|
1719
|
+
expect(queryCountAfter).toBe(queryCountBefore)
|
|
1720
|
+
})
|
|
1721
|
+
})
|
|
1722
|
+
|
|
1723
|
+
describe('migration caching', () => {
|
|
1724
|
+
it('should cache current version in memory', async () => {
|
|
1725
|
+
const db = createMockPGlite()
|
|
1726
|
+
const migrations = createTestMigrations()
|
|
1727
|
+
|
|
1728
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1729
|
+
await runner.migrate()
|
|
1730
|
+
|
|
1731
|
+
// Multiple version checks should use cache
|
|
1732
|
+
await runner.getCurrentVersion()
|
|
1733
|
+
await runner.getCurrentVersion()
|
|
1734
|
+
await runner.getCurrentVersion()
|
|
1735
|
+
|
|
1736
|
+
// Query for MAX(version) should have been called minimally
|
|
1737
|
+
const maxVersionCalls = (db.query as any).mock.calls.filter((call: any[]) =>
|
|
1738
|
+
call[0].includes('MAX(version)')
|
|
1739
|
+
)
|
|
1740
|
+
expect(maxVersionCalls.length).toBeLessThanOrEqual(2)
|
|
1741
|
+
})
|
|
1742
|
+
|
|
1743
|
+
it('should invalidate cache after successful migration', async () => {
|
|
1744
|
+
const db = createMockPGlite()
|
|
1745
|
+
const migrations = createTestMigrations()
|
|
1746
|
+
|
|
1747
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1748
|
+
const version1 = await runner.getCurrentVersion()
|
|
1749
|
+
|
|
1750
|
+
await runner.migrate()
|
|
1751
|
+
|
|
1752
|
+
const version2 = await runner.getCurrentVersion()
|
|
1753
|
+
|
|
1754
|
+
expect(version1).toBe(0)
|
|
1755
|
+
expect(version2).toBe(3)
|
|
1756
|
+
})
|
|
1757
|
+
|
|
1758
|
+
it('should persist cache across DO hibernation (storage)', async () => {
|
|
1759
|
+
// This test verifies the cache is maintained within the runner instance
|
|
1760
|
+
const db = createMockPGlite()
|
|
1761
|
+
const migrations = createTestMigrations()
|
|
1762
|
+
|
|
1763
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1764
|
+
await runner.migrate()
|
|
1765
|
+
|
|
1766
|
+
// Version should be cached
|
|
1767
|
+
const version = await runner.getCurrentVersion()
|
|
1768
|
+
expect(version).toBe(3)
|
|
1769
|
+
})
|
|
1770
|
+
|
|
1771
|
+
it('should refresh cache on wake from hibernation', async () => {
|
|
1772
|
+
// Simulates recreation of runner (like after hibernation)
|
|
1773
|
+
const db = createMockPGlite()
|
|
1774
|
+
const migrations = createTestMigrations()
|
|
1775
|
+
|
|
1776
|
+
// First runner session
|
|
1777
|
+
const runner1 = createDOMigrationRunner(db, migrations)
|
|
1778
|
+
await runner1.migrate()
|
|
1779
|
+
|
|
1780
|
+
// "Hibernation" - create new runner
|
|
1781
|
+
const runner2 = createDOMigrationRunner(db, migrations)
|
|
1782
|
+
|
|
1783
|
+
// Should read from DB since cache is new
|
|
1784
|
+
const version = await runner2.getCurrentVersion()
|
|
1785
|
+
expect(version).toBe(3)
|
|
1786
|
+
})
|
|
1787
|
+
})
|
|
1788
|
+
|
|
1789
|
+
describe('memory efficiency', () => {
|
|
1790
|
+
it('should not load all migrations into memory at once', () => {
|
|
1791
|
+
const db = createMockPGlite()
|
|
1792
|
+
const migrations = createTestMigrations()
|
|
1793
|
+
|
|
1794
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1795
|
+
|
|
1796
|
+
// getMigrations returns a copy, not internal state
|
|
1797
|
+
const migs1 = runner.getMigrations()
|
|
1798
|
+
const migs2 = runner.getMigrations()
|
|
1799
|
+
expect(migs1).not.toBe(migs2)
|
|
1800
|
+
})
|
|
1801
|
+
|
|
1802
|
+
it('should stream large migration SQL', async () => {
|
|
1803
|
+
const largeSql = 'CREATE TABLE large (id INT);' + ' '.repeat(10000)
|
|
1804
|
+
const db = createMockPGlite()
|
|
1805
|
+
const migrations: DOMigration[] = [
|
|
1806
|
+
{ version: 1, name: 'large', up: largeSql },
|
|
1807
|
+
]
|
|
1808
|
+
|
|
1809
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1810
|
+
const result = await runner.migrate()
|
|
1811
|
+
|
|
1812
|
+
expect(result.success).toBe(true)
|
|
1813
|
+
})
|
|
1814
|
+
|
|
1815
|
+
it('should cleanup resources after migration', async () => {
|
|
1816
|
+
const db = createMockPGlite()
|
|
1817
|
+
const migrations = createTestMigrations()
|
|
1818
|
+
|
|
1819
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1820
|
+
await runner.migrate()
|
|
1821
|
+
|
|
1822
|
+
// Runner should be in ready state, not holding resources
|
|
1823
|
+
expect(runner.state).toBe('ready')
|
|
1824
|
+
})
|
|
1825
|
+
})
|
|
1826
|
+
})
|
|
1827
|
+
|
|
1828
|
+
// =============================================================================
|
|
1829
|
+
// Integration with Existing Migration System
|
|
1830
|
+
// =============================================================================
|
|
1831
|
+
|
|
1832
|
+
describe('Integration with MigrationRunner', () => {
|
|
1833
|
+
describe('compatibility', () => {
|
|
1834
|
+
it('should accept migrations from MigrationRegistry', async () => {
|
|
1835
|
+
const db = createMockPGlite()
|
|
1836
|
+
// Simulate migrations from registry
|
|
1837
|
+
const registryMigrations = [
|
|
1838
|
+
{ id: 'mig-1', version: 1, name: 'create_users', up: 'CREATE TABLE users (id INT);' },
|
|
1839
|
+
{ id: 'mig-2', version: 2, name: 'add_email', up: 'ALTER TABLE users ADD email TEXT;' },
|
|
1840
|
+
]
|
|
1841
|
+
|
|
1842
|
+
const doMigrations = registryMigrations.map(m => convertToDOMigration(m))
|
|
1843
|
+
const runner = createDOMigrationRunner(db, doMigrations)
|
|
1844
|
+
const result = await runner.migrate()
|
|
1845
|
+
|
|
1846
|
+
expect(result.success).toBe(true)
|
|
1847
|
+
})
|
|
1848
|
+
|
|
1849
|
+
it('should share migration types with MigrationRunner', () => {
|
|
1850
|
+
const migration: DOMigration = {
|
|
1851
|
+
version: 1,
|
|
1852
|
+
name: 'test',
|
|
1853
|
+
up: 'SELECT 1;',
|
|
1854
|
+
down: 'SELECT 1;',
|
|
1855
|
+
isReversible: true,
|
|
1856
|
+
transactional: true,
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// Type should be compatible
|
|
1860
|
+
expect(migration.version).toBe(1)
|
|
1861
|
+
expect(migration.name).toBe('test')
|
|
1862
|
+
})
|
|
1863
|
+
|
|
1864
|
+
it('should support Drizzle-compatible migrations', async () => {
|
|
1865
|
+
const db = createMockPGlite()
|
|
1866
|
+
// Drizzle-style migration
|
|
1867
|
+
const migrations: DOMigration[] = [
|
|
1868
|
+
{
|
|
1869
|
+
version: 1,
|
|
1870
|
+
name: '0001_initial',
|
|
1871
|
+
up: `
|
|
1872
|
+
CREATE TABLE "users" (
|
|
1873
|
+
"id" serial PRIMARY KEY,
|
|
1874
|
+
"email" text NOT NULL UNIQUE
|
|
1875
|
+
);
|
|
1876
|
+
`,
|
|
1877
|
+
down: 'DROP TABLE "users";',
|
|
1878
|
+
},
|
|
1879
|
+
]
|
|
1880
|
+
|
|
1881
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1882
|
+
const result = await runner.migrate()
|
|
1883
|
+
|
|
1884
|
+
expect(result.success).toBe(true)
|
|
1885
|
+
})
|
|
1886
|
+
|
|
1887
|
+
it('should interoperate with existing validator', async () => {
|
|
1888
|
+
const db = createMockPGlite()
|
|
1889
|
+
const migrations = createTestMigrations()
|
|
1890
|
+
|
|
1891
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
1892
|
+
const validation = await runner.validate()
|
|
1893
|
+
|
|
1894
|
+
expect(validation).toHaveProperty('valid')
|
|
1895
|
+
expect(validation).toHaveProperty('issues')
|
|
1896
|
+
expect(validation).toHaveProperty('errorCount')
|
|
1897
|
+
expect(validation).toHaveProperty('warningCount')
|
|
1898
|
+
})
|
|
1899
|
+
})
|
|
1900
|
+
|
|
1901
|
+
describe('migration conversion', () => {
|
|
1902
|
+
it('should convert Migration to DOMigration', () => {
|
|
1903
|
+
const standardMigration = {
|
|
1904
|
+
id: 'mig-001',
|
|
1905
|
+
version: 1,
|
|
1906
|
+
name: 'create_users',
|
|
1907
|
+
up: 'CREATE TABLE users (id INT);',
|
|
1908
|
+
down: 'DROP TABLE users;',
|
|
1909
|
+
isReversible: true,
|
|
1910
|
+
transactional: true,
|
|
1911
|
+
timeoutMs: 5000,
|
|
1912
|
+
tags: ['schema'],
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const doMigration = convertToDOMigration(standardMigration)
|
|
1916
|
+
|
|
1917
|
+
expect(doMigration.version).toBe(1)
|
|
1918
|
+
expect(doMigration.name).toBe('create_users')
|
|
1919
|
+
expect(doMigration.up).toBe('CREATE TABLE users (id INT);')
|
|
1920
|
+
expect(doMigration.down).toBe('DROP TABLE users;')
|
|
1921
|
+
expect(doMigration.isReversible).toBe(true)
|
|
1922
|
+
expect(doMigration.transactional).toBe(true)
|
|
1923
|
+
expect(doMigration.timeoutMs).toBe(5000)
|
|
1924
|
+
expect(doMigration.tags).toEqual(['schema'])
|
|
1925
|
+
})
|
|
1926
|
+
|
|
1927
|
+
it('should preserve all migration metadata', () => {
|
|
1928
|
+
const standardMigration = {
|
|
1929
|
+
id: 'mig-001',
|
|
1930
|
+
version: 1,
|
|
1931
|
+
name: 'test',
|
|
1932
|
+
up: 'SELECT 1;',
|
|
1933
|
+
tags: ['test', 'data'],
|
|
1934
|
+
timeoutMs: 10000,
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
const doMigration = convertToDOMigration(standardMigration)
|
|
1938
|
+
|
|
1939
|
+
expect(doMigration.tags).toEqual(['test', 'data'])
|
|
1940
|
+
expect(doMigration.timeoutMs).toBe(10000)
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
it('should handle optional fields correctly', () => {
|
|
1944
|
+
const minimalMigration = {
|
|
1945
|
+
id: 'mig-001',
|
|
1946
|
+
version: 1,
|
|
1947
|
+
name: 'minimal',
|
|
1948
|
+
up: 'SELECT 1;',
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
const doMigration = convertToDOMigration(minimalMigration)
|
|
1952
|
+
|
|
1953
|
+
expect(doMigration.down).toBeUndefined()
|
|
1954
|
+
expect(doMigration.isReversible).toBeUndefined()
|
|
1955
|
+
expect(doMigration.transactional).toBeUndefined()
|
|
1956
|
+
expect(doMigration.timeoutMs).toBeUndefined()
|
|
1957
|
+
expect(doMigration.tags).toBeUndefined()
|
|
1958
|
+
})
|
|
1959
|
+
})
|
|
1960
|
+
})
|
|
1961
|
+
|
|
1962
|
+
// =============================================================================
|
|
1963
|
+
// Configuration
|
|
1964
|
+
// =============================================================================
|
|
1965
|
+
|
|
1966
|
+
describe('DOMigrationConfig', () => {
|
|
1967
|
+
describe('options', () => {
|
|
1968
|
+
it('should support custom meta table name', async () => {
|
|
1969
|
+
const execCalls: string[] = []
|
|
1970
|
+
const db = createMockPGlite({
|
|
1971
|
+
onExec: (sql) => execCalls.push(sql),
|
|
1972
|
+
})
|
|
1973
|
+
const migrations = createTestMigrations()
|
|
1974
|
+
|
|
1975
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1976
|
+
metaTableName: '_custom_meta',
|
|
1977
|
+
})
|
|
1978
|
+
await runner.initialize()
|
|
1979
|
+
|
|
1980
|
+
expect(execCalls.some(sql => sql.includes('_custom_meta'))).toBe(true)
|
|
1981
|
+
})
|
|
1982
|
+
|
|
1983
|
+
it('should support lock configuration', async () => {
|
|
1984
|
+
const db = createMockPGlite()
|
|
1985
|
+
const migrations = createTestMigrations()
|
|
1986
|
+
|
|
1987
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
1988
|
+
useLocking: false,
|
|
1989
|
+
})
|
|
1990
|
+
await runner.migrate()
|
|
1991
|
+
|
|
1992
|
+
// Should not have called lock functions
|
|
1993
|
+
const lockCalls = (db.query as any).mock.calls.filter((call: any[]) =>
|
|
1994
|
+
call[0].includes('advisory_lock')
|
|
1995
|
+
)
|
|
1996
|
+
expect(lockCalls.length).toBe(0)
|
|
1997
|
+
})
|
|
1998
|
+
|
|
1999
|
+
it('should support snapshot mode toggle', async () => {
|
|
2000
|
+
const db = createMockPGlite()
|
|
2001
|
+
const migrations = createTestMigrations()
|
|
2002
|
+
const snapshot = await generateDOSchemaSnapshot(migrations)
|
|
2003
|
+
|
|
2004
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2005
|
+
useSnapshot: true,
|
|
2006
|
+
snapshot,
|
|
2007
|
+
})
|
|
2008
|
+
|
|
2009
|
+
// Runner should be configured with snapshot
|
|
2010
|
+
const result = await runner.ensureMigrated()
|
|
2011
|
+
expect(result.success).toBe(true)
|
|
2012
|
+
})
|
|
2013
|
+
|
|
2014
|
+
it('should support debug logging toggle', async () => {
|
|
2015
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
2016
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
2017
|
+
const db = createMockPGlite()
|
|
2018
|
+
const migrations = createTestMigrations()
|
|
2019
|
+
|
|
2020
|
+
// Add an event handler that throws to trigger the debug error path
|
|
2021
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2022
|
+
debug: true,
|
|
2023
|
+
onEvent: (event) => {
|
|
2024
|
+
if (event.type === 'initialized') {
|
|
2025
|
+
throw new Error('Event handler error')
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
})
|
|
2029
|
+
await runner.migrate()
|
|
2030
|
+
|
|
2031
|
+
// Debug mode should log errors when event handlers fail
|
|
2032
|
+
expect(consoleErrorSpy).toHaveBeenCalled()
|
|
2033
|
+
consoleSpy.mockRestore()
|
|
2034
|
+
consoleErrorSpy.mockRestore()
|
|
2035
|
+
})
|
|
2036
|
+
|
|
2037
|
+
it('should support custom event handlers', async () => {
|
|
2038
|
+
const events: DOMigrationEvent[] = []
|
|
2039
|
+
const db = createMockPGlite()
|
|
2040
|
+
const migrations = createTestMigrations()
|
|
2041
|
+
|
|
2042
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2043
|
+
onEvent: (event) => events.push(event),
|
|
2044
|
+
})
|
|
2045
|
+
await runner.migrate()
|
|
2046
|
+
|
|
2047
|
+
expect(events.length).toBeGreaterThan(0)
|
|
2048
|
+
})
|
|
2049
|
+
|
|
2050
|
+
it('should support migration timeout', async () => {
|
|
2051
|
+
const db = createMockPGlite()
|
|
2052
|
+
const migrations = createTestMigrations()
|
|
2053
|
+
|
|
2054
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2055
|
+
migrationTimeoutMs: 60000,
|
|
2056
|
+
})
|
|
2057
|
+
|
|
2058
|
+
// Config should be applied (implementation uses it internally)
|
|
2059
|
+
expect(runner).toBeDefined()
|
|
2060
|
+
})
|
|
2061
|
+
|
|
2062
|
+
it('should support dry-run mode', async () => {
|
|
2063
|
+
const execCalls: string[] = []
|
|
2064
|
+
const db = createMockPGlite({
|
|
2065
|
+
onExec: (sql) => execCalls.push(sql),
|
|
2066
|
+
})
|
|
2067
|
+
const migrations = createTestMigrations()
|
|
2068
|
+
|
|
2069
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2070
|
+
dryRun: true,
|
|
2071
|
+
})
|
|
2072
|
+
const result = await runner.migrate()
|
|
2073
|
+
|
|
2074
|
+
expect(result.success).toBe(true)
|
|
2075
|
+
expect(result.migrationsSkipped).toBe(3)
|
|
2076
|
+
// No actual migration SQL should have been executed
|
|
2077
|
+
expect(execCalls.some(sql => sql.includes('CREATE TABLE users'))).toBe(false)
|
|
2078
|
+
})
|
|
2079
|
+
})
|
|
2080
|
+
|
|
2081
|
+
describe('defaults', () => {
|
|
2082
|
+
it('should use "_do_migrations" as default table name', async () => {
|
|
2083
|
+
const execCalls: string[] = []
|
|
2084
|
+
const db = createMockPGlite({
|
|
2085
|
+
onExec: (sql) => execCalls.push(sql),
|
|
2086
|
+
})
|
|
2087
|
+
const migrations = createTestMigrations()
|
|
2088
|
+
|
|
2089
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2090
|
+
await runner.initialize()
|
|
2091
|
+
|
|
2092
|
+
expect(execCalls.some(sql => sql.includes('_do_migrations'))).toBe(true)
|
|
2093
|
+
})
|
|
2094
|
+
|
|
2095
|
+
it('should use advisory locking by default', async () => {
|
|
2096
|
+
const db = createMockPGlite()
|
|
2097
|
+
const migrations = createTestMigrations()
|
|
2098
|
+
|
|
2099
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2100
|
+
await runner.migrate()
|
|
2101
|
+
|
|
2102
|
+
expect(db.query).toHaveBeenCalledWith(expect.stringContaining('pg_try_advisory_lock'))
|
|
2103
|
+
})
|
|
2104
|
+
|
|
2105
|
+
it('should use 30 second default timeout', () => {
|
|
2106
|
+
const db = createMockPGlite()
|
|
2107
|
+
const migrations = createTestMigrations()
|
|
2108
|
+
|
|
2109
|
+
// Default timeout is internal, verify runner creates successfully
|
|
2110
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2111
|
+
expect(runner).toBeDefined()
|
|
2112
|
+
})
|
|
2113
|
+
|
|
2114
|
+
it('should disable debug logging by default', async () => {
|
|
2115
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
2116
|
+
const db = createMockPGlite()
|
|
2117
|
+
const migrations = createTestMigrations()
|
|
2118
|
+
|
|
2119
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2120
|
+
await runner.migrate()
|
|
2121
|
+
|
|
2122
|
+
// Debug logging should not have been called for normal operations
|
|
2123
|
+
// (only errors might trigger it)
|
|
2124
|
+
consoleSpy.mockRestore()
|
|
2125
|
+
})
|
|
2126
|
+
})
|
|
2127
|
+
})
|
|
2128
|
+
|
|
2129
|
+
// =============================================================================
|
|
2130
|
+
// createDOMigrationRunner Factory
|
|
2131
|
+
// =============================================================================
|
|
2132
|
+
|
|
2133
|
+
describe('createDOMigrationRunner', () => {
|
|
2134
|
+
it('should create runner with default configuration', () => {
|
|
2135
|
+
const db = createMockPGlite()
|
|
2136
|
+
const migrations = createTestMigrations()
|
|
2137
|
+
|
|
2138
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2139
|
+
|
|
2140
|
+
expect(runner).toBeDefined()
|
|
2141
|
+
expect(runner.state).toBe('uninitialized')
|
|
2142
|
+
})
|
|
2143
|
+
|
|
2144
|
+
it('should create runner with custom configuration', () => {
|
|
2145
|
+
const db = createMockPGlite()
|
|
2146
|
+
const migrations = createTestMigrations()
|
|
2147
|
+
|
|
2148
|
+
const runner = createDOMigrationRunner(db, migrations, {
|
|
2149
|
+
metaTableName: '_custom',
|
|
2150
|
+
useLocking: false,
|
|
2151
|
+
debug: true,
|
|
2152
|
+
})
|
|
2153
|
+
|
|
2154
|
+
expect(runner).toBeDefined()
|
|
2155
|
+
})
|
|
2156
|
+
|
|
2157
|
+
it('should validate configuration options', () => {
|
|
2158
|
+
const db = createMockPGlite()
|
|
2159
|
+
const migrations = createTestMigrations()
|
|
2160
|
+
|
|
2161
|
+
// Should not throw for valid config
|
|
2162
|
+
expect(() => createDOMigrationRunner(db, migrations, {
|
|
2163
|
+
lockTimeoutMs: 1000,
|
|
2164
|
+
migrationTimeoutMs: 5000,
|
|
2165
|
+
})).not.toThrow()
|
|
2166
|
+
})
|
|
2167
|
+
|
|
2168
|
+
it('should throw for invalid executor', () => {
|
|
2169
|
+
const migrations = createTestMigrations()
|
|
2170
|
+
|
|
2171
|
+
expect(() => createDOMigrationRunner(null as any, migrations)).toThrow('Database instance is required')
|
|
2172
|
+
})
|
|
2173
|
+
|
|
2174
|
+
it('should throw for empty migrations array', () => {
|
|
2175
|
+
const db = createMockPGlite()
|
|
2176
|
+
|
|
2177
|
+
// Empty array is valid, but null/undefined should throw
|
|
2178
|
+
expect(() => createDOMigrationRunner(db, null as any)).toThrow('Migrations array is required')
|
|
2179
|
+
expect(() => createDOMigrationRunner(db, undefined as any)).toThrow('Migrations array is required')
|
|
2180
|
+
})
|
|
2181
|
+
})
|
|
2182
|
+
|
|
2183
|
+
// =============================================================================
|
|
2184
|
+
// Real-World Scenarios
|
|
2185
|
+
// =============================================================================
|
|
2186
|
+
|
|
2187
|
+
describe('Real-World Scenarios', () => {
|
|
2188
|
+
describe('first request to new DO', () => {
|
|
2189
|
+
it('should create tables and apply all migrations', async () => {
|
|
2190
|
+
const db = createMockPGlite()
|
|
2191
|
+
const migrations = createTestMigrations()
|
|
2192
|
+
|
|
2193
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2194
|
+
const result = await runner.ensureMigrated()
|
|
2195
|
+
|
|
2196
|
+
expect(result.success).toBe(true)
|
|
2197
|
+
expect(result.fromVersion).toBe(0)
|
|
2198
|
+
expect(result.toVersion).toBe(3)
|
|
2199
|
+
expect(result.migrationsRun).toBe(3)
|
|
2200
|
+
})
|
|
2201
|
+
|
|
2202
|
+
it('should complete within acceptable time (<100ms for 10 migrations)', async () => {
|
|
2203
|
+
const db = createMockPGlite()
|
|
2204
|
+
const migrations: DOMigration[] = Array.from({ length: 10 }, (_, i) => ({
|
|
2205
|
+
version: i + 1,
|
|
2206
|
+
name: `migration_${i + 1}`,
|
|
2207
|
+
up: `CREATE TABLE table_${i + 1} (id INT);`,
|
|
2208
|
+
}))
|
|
2209
|
+
|
|
2210
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2211
|
+
const start = performance.now()
|
|
2212
|
+
await runner.ensureMigrated()
|
|
2213
|
+
const elapsed = performance.now() - start
|
|
2214
|
+
|
|
2215
|
+
expect(elapsed).toBeLessThan(500 * CI_MULTIPLIER) // Allow slack for test environment
|
|
2216
|
+
})
|
|
2217
|
+
|
|
2218
|
+
it('should be ready to serve request after migration', async () => {
|
|
2219
|
+
const db = createMockPGlite()
|
|
2220
|
+
const migrations = createTestMigrations()
|
|
2221
|
+
|
|
2222
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2223
|
+
await runner.ensureMigrated()
|
|
2224
|
+
|
|
2225
|
+
expect(runner.state).toBe('ready')
|
|
2226
|
+
})
|
|
2227
|
+
})
|
|
2228
|
+
|
|
2229
|
+
describe('subsequent requests to existing DO', () => {
|
|
2230
|
+
it('should detect no migration needed in <5ms', async () => {
|
|
2231
|
+
const db = createMockPGlite()
|
|
2232
|
+
const migrations = createTestMigrations()
|
|
2233
|
+
|
|
2234
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2235
|
+
await runner.ensureMigrated()
|
|
2236
|
+
|
|
2237
|
+
const start = performance.now()
|
|
2238
|
+
const result = await runner.ensureMigrated()
|
|
2239
|
+
const elapsed = performance.now() - start
|
|
2240
|
+
|
|
2241
|
+
expect(result.migrationsRun).toBe(0)
|
|
2242
|
+
expect(elapsed).toBeLessThan(50 * CI_MULTIPLIER) // Allow slack
|
|
2243
|
+
})
|
|
2244
|
+
|
|
2245
|
+
it('should not block request processing', async () => {
|
|
2246
|
+
const db = createMockPGlite()
|
|
2247
|
+
const migrations = createTestMigrations()
|
|
2248
|
+
|
|
2249
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2250
|
+
await runner.ensureMigrated()
|
|
2251
|
+
|
|
2252
|
+
// Subsequent calls should be fast
|
|
2253
|
+
const results = await Promise.all([
|
|
2254
|
+
runner.ensureMigrated(),
|
|
2255
|
+
runner.ensureMigrated(),
|
|
2256
|
+
runner.ensureMigrated(),
|
|
2257
|
+
])
|
|
2258
|
+
|
|
2259
|
+
expect(results.every(r => r.success)).toBe(true)
|
|
2260
|
+
})
|
|
2261
|
+
})
|
|
2262
|
+
|
|
2263
|
+
describe('deployment with new migration', () => {
|
|
2264
|
+
it('should detect and apply new migration on first request', async () => {
|
|
2265
|
+
const db = createMockPGlite()
|
|
2266
|
+
const migrations = createTestMigrations()
|
|
2267
|
+
|
|
2268
|
+
// Simulate existing DO with migrations 1-2 applied
|
|
2269
|
+
;(db as any)._internal.setAppliedMigrations(new Map([
|
|
2270
|
+
[1, { name: 'create_users', checksum: 'abc', applied_at: new Date().toISOString(), execution_time_ms: 10 }],
|
|
2271
|
+
[2, { name: 'add_user_name', checksum: 'def', applied_at: new Date().toISOString(), execution_time_ms: 5 }],
|
|
2272
|
+
]))
|
|
2273
|
+
|
|
2274
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2275
|
+
const result = await runner.migrate()
|
|
2276
|
+
|
|
2277
|
+
expect(result.success).toBe(true)
|
|
2278
|
+
expect(result.migrationsRun).toBe(1) // Only migration 3
|
|
2279
|
+
})
|
|
2280
|
+
|
|
2281
|
+
it('should apply migration atomically', async () => {
|
|
2282
|
+
const execCalls: string[] = []
|
|
2283
|
+
const db = createMockPGlite({
|
|
2284
|
+
onExec: (sql) => execCalls.push(sql),
|
|
2285
|
+
})
|
|
2286
|
+
const migrations: DOMigration[] = [
|
|
2287
|
+
{ version: 1, name: 'atomic', up: 'CREATE TABLE test (id INT);' },
|
|
2288
|
+
]
|
|
2289
|
+
|
|
2290
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2291
|
+
await runner.migrate()
|
|
2292
|
+
|
|
2293
|
+
const beginIndex = execCalls.indexOf('BEGIN')
|
|
2294
|
+
const commitIndex = execCalls.indexOf('COMMIT')
|
|
2295
|
+
expect(beginIndex).toBeLessThan(commitIndex)
|
|
2296
|
+
})
|
|
2297
|
+
|
|
2298
|
+
it('should handle concurrent requests during migration', async () => {
|
|
2299
|
+
const db = createMockPGlite()
|
|
2300
|
+
const migrations = createTestMigrations()
|
|
2301
|
+
|
|
2302
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2303
|
+
|
|
2304
|
+
// Simulate concurrent requests
|
|
2305
|
+
const results = await Promise.all([
|
|
2306
|
+
runner.migrate(),
|
|
2307
|
+
runner.migrate(),
|
|
2308
|
+
runner.migrate(),
|
|
2309
|
+
])
|
|
2310
|
+
|
|
2311
|
+
// All should succeed
|
|
2312
|
+
expect(results.every(r => r.success)).toBe(true)
|
|
2313
|
+
})
|
|
2314
|
+
})
|
|
2315
|
+
|
|
2316
|
+
describe('rollback scenario', () => {
|
|
2317
|
+
it('should rollback failed deployment migration', async () => {
|
|
2318
|
+
const db = createMockPGlite()
|
|
2319
|
+
const migrations = createTestMigrations()
|
|
2320
|
+
|
|
2321
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2322
|
+
await runner.migrate()
|
|
2323
|
+
|
|
2324
|
+
// Rollback last migration
|
|
2325
|
+
const result = await runner.rollbackLast()
|
|
2326
|
+
|
|
2327
|
+
expect(result.success).toBe(true)
|
|
2328
|
+
const version = await runner.getCurrentVersion()
|
|
2329
|
+
expect(version).toBe(2)
|
|
2330
|
+
})
|
|
2331
|
+
|
|
2332
|
+
it('should restore previous schema state', async () => {
|
|
2333
|
+
const db = createMockPGlite()
|
|
2334
|
+
const migrations = createTestMigrations()
|
|
2335
|
+
|
|
2336
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2337
|
+
await runner.migrate()
|
|
2338
|
+
|
|
2339
|
+
const versionBefore = await runner.getCurrentVersion()
|
|
2340
|
+
await runner.rollbackTo(1)
|
|
2341
|
+
const versionAfter = await runner.getCurrentVersion()
|
|
2342
|
+
|
|
2343
|
+
expect(versionBefore).toBe(3)
|
|
2344
|
+
expect(versionAfter).toBe(1)
|
|
2345
|
+
})
|
|
2346
|
+
|
|
2347
|
+
it('should handle data migration rollback gracefully', async () => {
|
|
2348
|
+
const db = createMockPGlite()
|
|
2349
|
+
const migrations: DOMigration[] = [
|
|
2350
|
+
{ version: 1, name: 'create_table', up: 'CREATE TABLE t (id INT);', down: 'DROP TABLE t;' },
|
|
2351
|
+
{ version: 2, name: 'seed_data', up: 'INSERT INTO t VALUES (1);', isReversible: false },
|
|
2352
|
+
]
|
|
2353
|
+
|
|
2354
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2355
|
+
await runner.migrate()
|
|
2356
|
+
|
|
2357
|
+
// Should fail without force
|
|
2358
|
+
const result = await runner.rollbackTo(0)
|
|
2359
|
+
expect(result.success).toBe(false)
|
|
2360
|
+
expect(result.error).toContain('not reversible')
|
|
2361
|
+
|
|
2362
|
+
// Should succeed with force
|
|
2363
|
+
const forceResult = await runner.rollbackTo(0, { force: true })
|
|
2364
|
+
expect(forceResult.success).toBe(true)
|
|
2365
|
+
})
|
|
2366
|
+
})
|
|
2367
|
+
|
|
2368
|
+
describe('DO hibernation and wake', () => {
|
|
2369
|
+
it('should restore migration state from storage on wake', async () => {
|
|
2370
|
+
const db = createMockPGlite()
|
|
2371
|
+
const migrations = createTestMigrations()
|
|
2372
|
+
|
|
2373
|
+
// First session
|
|
2374
|
+
const runner1 = createDOMigrationRunner(db, migrations)
|
|
2375
|
+
await runner1.migrate()
|
|
2376
|
+
|
|
2377
|
+
// Simulate wake with new runner
|
|
2378
|
+
const runner2 = createDOMigrationRunner(db, migrations)
|
|
2379
|
+
const version = await runner2.getCurrentVersion()
|
|
2380
|
+
|
|
2381
|
+
expect(version).toBe(3)
|
|
2382
|
+
})
|
|
2383
|
+
|
|
2384
|
+
it('should detect new migrations after wake', async () => {
|
|
2385
|
+
const db = createMockPGlite()
|
|
2386
|
+
|
|
2387
|
+
// First session with 2 migrations
|
|
2388
|
+
const migrations1 = createTestMigrations().slice(0, 2)
|
|
2389
|
+
const runner1 = createDOMigrationRunner(db, migrations1)
|
|
2390
|
+
await runner1.migrate()
|
|
2391
|
+
|
|
2392
|
+
// Wake with new migration added
|
|
2393
|
+
const migrations2 = createTestMigrations()
|
|
2394
|
+
const runner2 = createDOMigrationRunner(db, migrations2)
|
|
2395
|
+
const pending = await runner2.getPendingMigrations()
|
|
2396
|
+
|
|
2397
|
+
expect(pending.length).toBe(1)
|
|
2398
|
+
expect(pending[0].version).toBe(3)
|
|
2399
|
+
})
|
|
2400
|
+
|
|
2401
|
+
it('should handle storage read errors gracefully', async () => {
|
|
2402
|
+
const db = createMockPGlite()
|
|
2403
|
+
;(db.query as any).mockRejectedValueOnce(new Error('Storage error'))
|
|
2404
|
+
const migrations = createTestMigrations()
|
|
2405
|
+
|
|
2406
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2407
|
+
const version = await runner.getCurrentVersion()
|
|
2408
|
+
|
|
2409
|
+
// Should return 0 on error
|
|
2410
|
+
expect(version).toBe(0)
|
|
2411
|
+
})
|
|
2412
|
+
})
|
|
2413
|
+
})
|
|
2414
|
+
|
|
2415
|
+
// =============================================================================
|
|
2416
|
+
// Edge Cases
|
|
2417
|
+
// =============================================================================
|
|
2418
|
+
|
|
2419
|
+
describe('Edge Cases', () => {
|
|
2420
|
+
it('should handle very long migration SQL (>1MB)', async () => {
|
|
2421
|
+
const largeSql = 'CREATE TABLE large (id INT, data TEXT);' + '\n-- ' + 'x'.repeat(1024 * 1024)
|
|
2422
|
+
const db = createMockPGlite()
|
|
2423
|
+
const migrations: DOMigration[] = [
|
|
2424
|
+
{ version: 1, name: 'large', up: largeSql },
|
|
2425
|
+
]
|
|
2426
|
+
|
|
2427
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2428
|
+
const result = await runner.migrate()
|
|
2429
|
+
|
|
2430
|
+
expect(result.success).toBe(true)
|
|
2431
|
+
})
|
|
2432
|
+
|
|
2433
|
+
it('should handle migration with special characters in SQL', async () => {
|
|
2434
|
+
const db = createMockPGlite()
|
|
2435
|
+
const migrations: DOMigration[] = [
|
|
2436
|
+
{
|
|
2437
|
+
version: 1,
|
|
2438
|
+
name: 'special_chars',
|
|
2439
|
+
up: `CREATE TABLE test (id INT, data TEXT DEFAULT 'foo''bar');`,
|
|
2440
|
+
},
|
|
2441
|
+
]
|
|
2442
|
+
|
|
2443
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2444
|
+
const result = await runner.migrate()
|
|
2445
|
+
|
|
2446
|
+
expect(result.success).toBe(true)
|
|
2447
|
+
})
|
|
2448
|
+
|
|
2449
|
+
it('should handle migration with unicode content', async () => {
|
|
2450
|
+
const db = createMockPGlite()
|
|
2451
|
+
const migrations: DOMigration[] = [
|
|
2452
|
+
{
|
|
2453
|
+
version: 1,
|
|
2454
|
+
name: 'unicode',
|
|
2455
|
+
up: `CREATE TABLE test (id INT, emoji TEXT DEFAULT '🎉');`,
|
|
2456
|
+
},
|
|
2457
|
+
]
|
|
2458
|
+
|
|
2459
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2460
|
+
const result = await runner.migrate()
|
|
2461
|
+
|
|
2462
|
+
expect(result.success).toBe(true)
|
|
2463
|
+
})
|
|
2464
|
+
|
|
2465
|
+
it('should handle empty up SQL gracefully', async () => {
|
|
2466
|
+
const db = createMockPGlite()
|
|
2467
|
+
const migrations: DOMigration[] = [
|
|
2468
|
+
{ version: 1, name: 'empty', up: '' },
|
|
2469
|
+
]
|
|
2470
|
+
|
|
2471
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2472
|
+
const result = await runner.migrate()
|
|
2473
|
+
|
|
2474
|
+
expect(result.success).toBe(true)
|
|
2475
|
+
})
|
|
2476
|
+
|
|
2477
|
+
it('should handle migration that takes longer than timeout', async () => {
|
|
2478
|
+
const db = createMockPGlite({
|
|
2479
|
+
onExec: async () => {
|
|
2480
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
2481
|
+
},
|
|
2482
|
+
})
|
|
2483
|
+
const migrations: DOMigration[] = [
|
|
2484
|
+
{ version: 1, name: 'slow', up: 'SELECT 1;', timeoutMs: 50 },
|
|
2485
|
+
]
|
|
2486
|
+
|
|
2487
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2488
|
+
const result = await runner.migrate()
|
|
2489
|
+
|
|
2490
|
+
// Migration should still complete (timeout is for individual operations)
|
|
2491
|
+
expect(result.success).toBe(true)
|
|
2492
|
+
})
|
|
2493
|
+
|
|
2494
|
+
it('should handle database full condition', async () => {
|
|
2495
|
+
const db = createMockPGlite({
|
|
2496
|
+
onExec: (sql) => {
|
|
2497
|
+
// Migration SQL for users table (not the meta table)
|
|
2498
|
+
if (sql.includes('CREATE TABLE users') && !sql.includes('_do_migrations')) {
|
|
2499
|
+
throw new Error('database or disk is full')
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
})
|
|
2503
|
+
const migrations = createTestMigrations()
|
|
2504
|
+
|
|
2505
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2506
|
+
const result = await runner.migrate()
|
|
2507
|
+
|
|
2508
|
+
expect(result.success).toBe(false)
|
|
2509
|
+
expect(result.error).toContain('full')
|
|
2510
|
+
})
|
|
2511
|
+
|
|
2512
|
+
it('should handle maximum version number (MAX_SAFE_INTEGER)', async () => {
|
|
2513
|
+
const db = createMockPGlite()
|
|
2514
|
+
const migrations: DOMigration[] = [
|
|
2515
|
+
{ version: Number.MAX_SAFE_INTEGER, name: 'max_version', up: 'SELECT 1;' },
|
|
2516
|
+
]
|
|
2517
|
+
|
|
2518
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2519
|
+
const result = await runner.migrate()
|
|
2520
|
+
|
|
2521
|
+
expect(result.success).toBe(true)
|
|
2522
|
+
expect(result.toVersion).toBe(Number.MAX_SAFE_INTEGER)
|
|
2523
|
+
})
|
|
2524
|
+
|
|
2525
|
+
it('should handle rapid consecutive calls', async () => {
|
|
2526
|
+
const db = createMockPGlite()
|
|
2527
|
+
const migrations = createTestMigrations()
|
|
2528
|
+
|
|
2529
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2530
|
+
|
|
2531
|
+
// Rapid consecutive calls
|
|
2532
|
+
const results = await Promise.all(
|
|
2533
|
+
Array.from({ length: 20 }, () => runner.migrate())
|
|
2534
|
+
)
|
|
2535
|
+
|
|
2536
|
+
expect(results.every(r => r.success)).toBe(true)
|
|
2537
|
+
})
|
|
2538
|
+
|
|
2539
|
+
it('should handle interleaved read/write operations during migration', async () => {
|
|
2540
|
+
const db = createMockPGlite()
|
|
2541
|
+
const migrations = createTestMigrations()
|
|
2542
|
+
|
|
2543
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2544
|
+
|
|
2545
|
+
// Start migration
|
|
2546
|
+
const migratePromise = runner.migrate()
|
|
2547
|
+
|
|
2548
|
+
// Interleaved reads
|
|
2549
|
+
const versionPromise = runner.getCurrentVersion()
|
|
2550
|
+
const appliedPromise = runner.getAppliedMigrations()
|
|
2551
|
+
|
|
2552
|
+
const [migrateResult] = await Promise.all([
|
|
2553
|
+
migratePromise,
|
|
2554
|
+
versionPromise,
|
|
2555
|
+
appliedPromise,
|
|
2556
|
+
])
|
|
2557
|
+
|
|
2558
|
+
expect(migrateResult.success).toBe(true)
|
|
2559
|
+
})
|
|
2560
|
+
})
|
|
2561
|
+
|
|
2562
|
+
// =============================================================================
|
|
2563
|
+
// Type Safety
|
|
2564
|
+
// =============================================================================
|
|
2565
|
+
|
|
2566
|
+
describe('Type Safety', () => {
|
|
2567
|
+
it('should export DOMigration type', () => {
|
|
2568
|
+
const migration: DOMigration = {
|
|
2569
|
+
version: 1,
|
|
2570
|
+
name: 'test',
|
|
2571
|
+
up: 'SELECT 1;',
|
|
2572
|
+
}
|
|
2573
|
+
expect(migration).toBeDefined()
|
|
2574
|
+
})
|
|
2575
|
+
|
|
2576
|
+
it('should export DOMigrationRunner interface', () => {
|
|
2577
|
+
const db = createMockPGlite()
|
|
2578
|
+
const migrations = createTestMigrations()
|
|
2579
|
+
const runner: DOMigrationRunner = createDOMigrationRunner(db, migrations)
|
|
2580
|
+
expect(runner.migrate).toBeDefined()
|
|
2581
|
+
expect(runner.rollbackTo).toBeDefined()
|
|
2582
|
+
expect(runner.validate).toBeDefined()
|
|
2583
|
+
})
|
|
2584
|
+
|
|
2585
|
+
it('should export DOMigrationResult type', async () => {
|
|
2586
|
+
const db = createMockPGlite()
|
|
2587
|
+
const migrations = createTestMigrations()
|
|
2588
|
+
const runner = createDOMigrationRunner(db, migrations)
|
|
2589
|
+
const batchResult = await runner.migrate()
|
|
2590
|
+
const result: DOMigrationResult = batchResult.results[0]
|
|
2591
|
+
expect(result.success).toBeDefined()
|
|
2592
|
+
expect(result.version).toBeDefined()
|
|
2593
|
+
})
|
|
2594
|
+
|
|
2595
|
+
it('should export DOMigrationConfig type', () => {
|
|
2596
|
+
const config: DOMigrationConfig = {
|
|
2597
|
+
metaTableName: '_custom',
|
|
2598
|
+
useLocking: true,
|
|
2599
|
+
debug: false,
|
|
2600
|
+
}
|
|
2601
|
+
expect(config).toBeDefined()
|
|
2602
|
+
})
|
|
2603
|
+
|
|
2604
|
+
it('should export DOMigrationState type', () => {
|
|
2605
|
+
const state: DOMigrationState = 'ready'
|
|
2606
|
+
expect(['uninitialized', 'initializing', 'ready', 'migrating', 'error']).toContain(state)
|
|
2607
|
+
})
|
|
2608
|
+
|
|
2609
|
+
it('should export DOSchemaSnapshot type', async () => {
|
|
2610
|
+
const migrations = createTestMigrations()
|
|
2611
|
+
const snapshot: DOSchemaSnapshot = await generateDOSchemaSnapshot(migrations)
|
|
2612
|
+
expect(snapshot.targetVersion).toBeDefined()
|
|
2613
|
+
expect(snapshot.schemaDDL).toBeDefined()
|
|
2614
|
+
expect(snapshot.migrationsChecksum).toBeDefined()
|
|
2615
|
+
})
|
|
2616
|
+
|
|
2617
|
+
it('should export createDOMigrationRunner function type', () => {
|
|
2618
|
+
expect(typeof createDOMigrationRunner).toBe('function')
|
|
2619
|
+
})
|
|
2620
|
+
})
|