@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,2254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RED PHASE: PostgresDO Core Tests
|
|
3
|
+
* Task: postgres-6u6.3 - PostgresDO Core (constructor, initPGlite, executeQuery, fetch handler)
|
|
4
|
+
*
|
|
5
|
+
* These tests verify the Durable Object core functionality.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
8
|
+
import {
|
|
9
|
+
createMockPGlite,
|
|
10
|
+
createMockDurableObjectState,
|
|
11
|
+
} from '../__tests__/utils'
|
|
12
|
+
|
|
13
|
+
// Mock cloudflare:workers before importing modules that depend on it
|
|
14
|
+
vi.mock('cloudflare:workers', () => ({
|
|
15
|
+
DurableObject: class DurableObject {
|
|
16
|
+
ctx: unknown
|
|
17
|
+
env: unknown
|
|
18
|
+
constructor(ctx: unknown, env: unknown) {
|
|
19
|
+
this.ctx = ctx
|
|
20
|
+
this.env = env
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
// Mock WASM and data imports that Vite can't handle in test environment
|
|
26
|
+
vi.mock('../pglite-assets/pglite.wasm', () => ({ default: {} }))
|
|
27
|
+
vi.mock('../pglite-assets/pglite.data', () => ({ default: {} }))
|
|
28
|
+
|
|
29
|
+
// Mock the workers-pglite module to avoid WASM dependencies
|
|
30
|
+
vi.mock('../pglite/workers-pglite', () => ({
|
|
31
|
+
createWorkersPGLite: vi.fn(),
|
|
32
|
+
WorkersPGLite: class {},
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
import { PostgresDO, createPostgresDO } from './do'
|
|
36
|
+
import type { Env, QueryRequest, QueryResponse, PostgresConfig } from './types'
|
|
37
|
+
|
|
38
|
+
// Local wrapper to match existing test expectations (uses shared utility)
|
|
39
|
+
const createMockState = () => createMockDurableObjectState({
|
|
40
|
+
id: 'test-do-id',
|
|
41
|
+
name: 'test-database',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Mock Env
|
|
45
|
+
const createMockEnv = (): Env => ({
|
|
46
|
+
POSTGRES_DO: {} as DurableObjectNamespace,
|
|
47
|
+
POSTGRES_DEFAULT_DB: 'postgres',
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// MOCK REDUCTION HELPERS (postgres-6qei)
|
|
51
|
+
// These helpers use dependency injection via getPGLiteManager().injectInstance()
|
|
52
|
+
// instead of spying on internal createPGliteInstance method.
|
|
53
|
+
|
|
54
|
+
function createTestDO(options?: {
|
|
55
|
+
config?: PostgresConfig
|
|
56
|
+
mockPGliteSetup?: (mock: ReturnType<typeof createMockPGlite>) => void
|
|
57
|
+
}) {
|
|
58
|
+
const state = createMockState()
|
|
59
|
+
const env = createMockEnv()
|
|
60
|
+
const doo = new PostgresDO(state as any, env, options?.config)
|
|
61
|
+
const mockPGlite = createMockPGlite()
|
|
62
|
+
if (options?.mockPGliteSetup) {
|
|
63
|
+
options.mockPGliteSetup(mockPGlite)
|
|
64
|
+
}
|
|
65
|
+
doo.getPGLiteManager().injectInstance(mockPGlite)
|
|
66
|
+
return { doo, mockPGlite, state, env }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createTestDOWithFactory(options?: {
|
|
70
|
+
config?: PostgresConfig
|
|
71
|
+
mockPGliteSetup?: (mock: ReturnType<typeof createMockPGlite>) => void
|
|
72
|
+
}) {
|
|
73
|
+
const state = createMockState()
|
|
74
|
+
const env = createMockEnv()
|
|
75
|
+
const doo = new PostgresDO(state as any, env, options?.config)
|
|
76
|
+
const mockPGlite = createMockPGlite()
|
|
77
|
+
if (options?.mockPGliteSetup) {
|
|
78
|
+
options.mockPGliteSetup(mockPGlite)
|
|
79
|
+
}
|
|
80
|
+
doo.getPGLiteManager().setCreatePGLite(() => Promise.resolve(mockPGlite))
|
|
81
|
+
return { doo, mockPGlite, state, env }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe('PostgresDO Core', () => {
|
|
85
|
+
describe('constructor', () => {
|
|
86
|
+
it('should create a PostgresDO instance', () => {
|
|
87
|
+
const state = createMockState()
|
|
88
|
+
const env = createMockEnv()
|
|
89
|
+
const doo = new PostgresDO(state as any, env)
|
|
90
|
+
expect(doo).toBeInstanceOf(PostgresDO)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should store state reference', () => {
|
|
94
|
+
const state = createMockState()
|
|
95
|
+
const env = createMockEnv()
|
|
96
|
+
const doo = new PostgresDO(state as any, env)
|
|
97
|
+
expect(doo.getState()).toBe(state)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should store env reference', () => {
|
|
101
|
+
const state = createMockState()
|
|
102
|
+
const env = createMockEnv()
|
|
103
|
+
const doo = new PostgresDO(state as any, env)
|
|
104
|
+
expect(doo.getEnv()).toBe(env)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('initPGlite', () => {
|
|
109
|
+
it('should initialize PGlite on first call', async () => {
|
|
110
|
+
const state = createMockState()
|
|
111
|
+
const env = createMockEnv()
|
|
112
|
+
const doo = new PostgresDO(state as any, env)
|
|
113
|
+
|
|
114
|
+
// Mock the PGlite initialization
|
|
115
|
+
const mockPGlite = createMockPGlite()
|
|
116
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
117
|
+
|
|
118
|
+
await doo.initPGlite()
|
|
119
|
+
expect(doo.isInitialized()).toBe(true)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should not reinitialize if already initialized', async () => {
|
|
123
|
+
const state = createMockState()
|
|
124
|
+
const env = createMockEnv()
|
|
125
|
+
const doo = new PostgresDO(state as any, env)
|
|
126
|
+
|
|
127
|
+
const mockPGlite = createMockPGlite()
|
|
128
|
+
const createSpy = vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
129
|
+
|
|
130
|
+
await doo.initPGlite()
|
|
131
|
+
await doo.initPGlite()
|
|
132
|
+
|
|
133
|
+
expect(createSpy).toHaveBeenCalledTimes(1)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should use blockConcurrencyWhile for initialization', async () => {
|
|
137
|
+
const state = createMockState()
|
|
138
|
+
const env = createMockEnv()
|
|
139
|
+
const doo = new PostgresDO(state as any, env)
|
|
140
|
+
|
|
141
|
+
const mockPGlite = createMockPGlite()
|
|
142
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
143
|
+
|
|
144
|
+
await doo.initPGlite()
|
|
145
|
+
expect(state.blockConcurrencyWhile).toHaveBeenCalled()
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('executeQuery', () => {
|
|
150
|
+
it('should execute a simple SELECT query', async () => {
|
|
151
|
+
const state = createMockState()
|
|
152
|
+
const env = createMockEnv()
|
|
153
|
+
const doo = new PostgresDO(state as any, env)
|
|
154
|
+
|
|
155
|
+
const mockPGlite = createMockPGlite()
|
|
156
|
+
mockPGlite.query.mockResolvedValue({
|
|
157
|
+
rows: [{ id: 1, name: 'Alice' }],
|
|
158
|
+
fields: [
|
|
159
|
+
{ name: 'id', dataTypeID: 23 },
|
|
160
|
+
{ name: 'name', dataTypeID: 25 },
|
|
161
|
+
],
|
|
162
|
+
affectedRows: 1,
|
|
163
|
+
})
|
|
164
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
165
|
+
|
|
166
|
+
const request: QueryRequest = { sql: 'SELECT * FROM users' }
|
|
167
|
+
const result = await doo.executeQuery(request)
|
|
168
|
+
|
|
169
|
+
expect(result.rows).toEqual([{ id: 1, name: 'Alice' }])
|
|
170
|
+
expect(result.fields).toHaveLength(2)
|
|
171
|
+
expect(result.rowCount).toBe(1)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should pass parameters to PGlite', async () => {
|
|
175
|
+
const state = createMockState()
|
|
176
|
+
const env = createMockEnv()
|
|
177
|
+
const doo = new PostgresDO(state as any, env)
|
|
178
|
+
|
|
179
|
+
const mockPGlite = createMockPGlite()
|
|
180
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
181
|
+
|
|
182
|
+
const request: QueryRequest = {
|
|
183
|
+
sql: 'SELECT * FROM users WHERE id = $1',
|
|
184
|
+
params: [42],
|
|
185
|
+
}
|
|
186
|
+
await doo.executeQuery(request)
|
|
187
|
+
|
|
188
|
+
expect(mockPGlite.query).toHaveBeenCalledWith(
|
|
189
|
+
'SELECT * FROM users WHERE id = $1',
|
|
190
|
+
[42]
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should apply SQL transform when preserveCase is true', async () => {
|
|
195
|
+
const state = createMockState()
|
|
196
|
+
const env = createMockEnv()
|
|
197
|
+
const doo = new PostgresDO(state as any, env)
|
|
198
|
+
|
|
199
|
+
const mockPGlite = createMockPGlite()
|
|
200
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
201
|
+
|
|
202
|
+
const request: QueryRequest = {
|
|
203
|
+
sql: 'SELECT userId FROM users',
|
|
204
|
+
preserveCase: true,
|
|
205
|
+
}
|
|
206
|
+
await doo.executeQuery(request)
|
|
207
|
+
|
|
208
|
+
// The SQL should be transformed to quote identifiers
|
|
209
|
+
const calledSql = mockPGlite.query.mock.calls[0]?.[0] as string
|
|
210
|
+
expect(calledSql).toContain('"userId"')
|
|
211
|
+
expect(calledSql).toContain('"users"')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should track query duration', async () => {
|
|
215
|
+
const state = createMockState()
|
|
216
|
+
const env = createMockEnv()
|
|
217
|
+
const doo = new PostgresDO(state as any, env)
|
|
218
|
+
|
|
219
|
+
const mockPGlite = createMockPGlite()
|
|
220
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
221
|
+
|
|
222
|
+
const request: QueryRequest = { sql: 'SELECT 1' }
|
|
223
|
+
const result = await doo.executeQuery(request)
|
|
224
|
+
|
|
225
|
+
expect(result.durationMs).toBeDefined()
|
|
226
|
+
expect(typeof result.durationMs).toBe('number')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('should throw error for invalid SQL', async () => {
|
|
230
|
+
const state = createMockState()
|
|
231
|
+
const env = createMockEnv()
|
|
232
|
+
const doo = new PostgresDO(state as any, env)
|
|
233
|
+
|
|
234
|
+
const mockPGlite = createMockPGlite()
|
|
235
|
+
mockPGlite.query.mockRejectedValue(new Error('syntax error at position 1'))
|
|
236
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
237
|
+
|
|
238
|
+
const request: QueryRequest = { sql: 'SELEC * FROM users' }
|
|
239
|
+
|
|
240
|
+
await expect(doo.executeQuery(request)).rejects.toThrow('syntax error')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('should auto-initialize PGlite if not initialized', async () => {
|
|
244
|
+
const state = createMockState()
|
|
245
|
+
const env = createMockEnv()
|
|
246
|
+
const doo = new PostgresDO(state as any, env)
|
|
247
|
+
|
|
248
|
+
const mockPGlite = createMockPGlite()
|
|
249
|
+
const initSpy = vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
250
|
+
|
|
251
|
+
expect(doo.isInitialized()).toBe(false)
|
|
252
|
+
|
|
253
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
254
|
+
|
|
255
|
+
expect(initSpy).toHaveBeenCalled()
|
|
256
|
+
expect(doo.isInitialized()).toBe(true)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe('executeBatch', () => {
|
|
261
|
+
it('should execute multiple queries', async () => {
|
|
262
|
+
const state = createMockState()
|
|
263
|
+
const env = createMockEnv()
|
|
264
|
+
const doo = new PostgresDO(state as any, env)
|
|
265
|
+
|
|
266
|
+
const mockPGlite = createMockPGlite()
|
|
267
|
+
mockPGlite.query
|
|
268
|
+
.mockResolvedValueOnce({ rows: [{ a: 1 }], fields: [], affectedRows: 1 })
|
|
269
|
+
.mockResolvedValueOnce({ rows: [{ b: 2 }], fields: [], affectedRows: 1 })
|
|
270
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
271
|
+
|
|
272
|
+
const results = await doo.executeBatch({
|
|
273
|
+
queries: [
|
|
274
|
+
{ sql: 'SELECT 1 AS a' },
|
|
275
|
+
{ sql: 'SELECT 2 AS b' },
|
|
276
|
+
],
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
expect(results.results).toHaveLength(2)
|
|
280
|
+
expect(results.results[0].rows).toEqual([{ a: 1 }])
|
|
281
|
+
expect(results.results[1].rows).toEqual([{ b: 2 }])
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('should wrap in transaction when transaction=true', async () => {
|
|
285
|
+
const state = createMockState()
|
|
286
|
+
const env = createMockEnv()
|
|
287
|
+
const doo = new PostgresDO(state as any, env)
|
|
288
|
+
|
|
289
|
+
const mockPGlite = createMockPGlite()
|
|
290
|
+
const queryCalls: string[] = []
|
|
291
|
+
mockPGlite.query.mockImplementation((sql?: string) => {
|
|
292
|
+
queryCalls.push(sql ?? '')
|
|
293
|
+
return Promise.resolve({ rows: [], fields: [], affectedRows: 0 })
|
|
294
|
+
})
|
|
295
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
296
|
+
|
|
297
|
+
await doo.executeBatch({
|
|
298
|
+
queries: [{ sql: 'SELECT 1' }],
|
|
299
|
+
transaction: true,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
expect(queryCalls[0]).toBe('BEGIN')
|
|
303
|
+
expect(queryCalls[queryCalls.length - 1]).toBe('COMMIT')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should rollback on error when transaction=true', async () => {
|
|
307
|
+
const state = createMockState()
|
|
308
|
+
const env = createMockEnv()
|
|
309
|
+
const doo = new PostgresDO(state as any, env)
|
|
310
|
+
|
|
311
|
+
const mockPGlite = createMockPGlite()
|
|
312
|
+
const queryCalls: string[] = []
|
|
313
|
+
mockPGlite.query.mockImplementation((sql?: string) => {
|
|
314
|
+
queryCalls.push(sql ?? '')
|
|
315
|
+
if (sql === 'SELECT 1') {
|
|
316
|
+
return Promise.reject(new Error('test error'))
|
|
317
|
+
}
|
|
318
|
+
return Promise.resolve({ rows: [], fields: [], affectedRows: 0 })
|
|
319
|
+
})
|
|
320
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
321
|
+
|
|
322
|
+
await expect(doo.executeBatch({
|
|
323
|
+
queries: [{ sql: 'SELECT 1' }],
|
|
324
|
+
transaction: true,
|
|
325
|
+
})).rejects.toThrow('test error')
|
|
326
|
+
|
|
327
|
+
expect(queryCalls).toContain('ROLLBACK')
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
describe('getConfig', () => {
|
|
332
|
+
it('should return current configuration', async () => {
|
|
333
|
+
const state = createMockState()
|
|
334
|
+
const env = createMockEnv()
|
|
335
|
+
const doo = new PostgresDO(state as any, env)
|
|
336
|
+
|
|
337
|
+
const config = doo.getConfig()
|
|
338
|
+
expect(config).toBeDefined()
|
|
339
|
+
expect(config.database).toBeDefined()
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('getStats', () => {
|
|
344
|
+
it('should return query statistics', async () => {
|
|
345
|
+
const state = createMockState()
|
|
346
|
+
const env = createMockEnv()
|
|
347
|
+
const doo = new PostgresDO(state as any, env)
|
|
348
|
+
|
|
349
|
+
const mockPGlite = createMockPGlite()
|
|
350
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
351
|
+
|
|
352
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
353
|
+
await doo.executeQuery({ sql: 'SELECT 2' })
|
|
354
|
+
|
|
355
|
+
const stats = doo.getStats()
|
|
356
|
+
expect(stats.queryCount).toBe(2)
|
|
357
|
+
expect(stats.totalDurationMs).toBeGreaterThanOrEqual(0)
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
describe('createPostgresDO factory', () => {
|
|
362
|
+
it('should create a PostgresDO class with custom config', () => {
|
|
363
|
+
const PostgresDOClass = createPostgresDO({
|
|
364
|
+
database: 'mydb',
|
|
365
|
+
extensions: ['vector'],
|
|
366
|
+
preserveCase: true,
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const state = createMockState()
|
|
370
|
+
const env = createMockEnv()
|
|
371
|
+
const doo = new PostgresDOClass(state as any, env)
|
|
372
|
+
|
|
373
|
+
expect(doo).toBeInstanceOf(PostgresDO)
|
|
374
|
+
expect(doo.getConfig().database).toBe('mydb')
|
|
375
|
+
expect(doo.getConfig().extensions).toContain('vector')
|
|
376
|
+
expect(doo.getConfig().preserveCase).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
describe('Graceful Shutdown and Connection Draining', () => {
|
|
381
|
+
describe('shutdown status', () => {
|
|
382
|
+
it('should start with running status', () => {
|
|
383
|
+
const state = createMockState()
|
|
384
|
+
const env = createMockEnv()
|
|
385
|
+
const doo = new PostgresDO(state as any, env)
|
|
386
|
+
|
|
387
|
+
expect(doo.getShutdownStatus()).toBe('running')
|
|
388
|
+
expect(doo.isShuttingDown()).toBe(false)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('should track active query count', async () => {
|
|
392
|
+
const state = createMockState()
|
|
393
|
+
const env = createMockEnv()
|
|
394
|
+
const doo = new PostgresDO(state as any, env)
|
|
395
|
+
|
|
396
|
+
// Create a slow mock PGlite that delays query execution
|
|
397
|
+
const mockPGlite = createMockPGlite()
|
|
398
|
+
let resolveQuery: () => void
|
|
399
|
+
const queryPromise = new Promise<void>((resolve) => {
|
|
400
|
+
resolveQuery = resolve
|
|
401
|
+
})
|
|
402
|
+
mockPGlite.query.mockImplementation(async () => {
|
|
403
|
+
await queryPromise
|
|
404
|
+
return { rows: [], fields: [], affectedRows: 0 }
|
|
405
|
+
})
|
|
406
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
407
|
+
|
|
408
|
+
// Start a query but don't await it
|
|
409
|
+
const queryExecution = doo.executeQuery({ sql: 'SELECT pg_sleep(1)' })
|
|
410
|
+
|
|
411
|
+
// Give time for query to start
|
|
412
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
413
|
+
|
|
414
|
+
expect(doo.getActiveQueryCount()).toBe(1)
|
|
415
|
+
|
|
416
|
+
// Resolve the query and wait for it
|
|
417
|
+
resolveQuery!()
|
|
418
|
+
await queryExecution
|
|
419
|
+
|
|
420
|
+
expect(doo.getActiveQueryCount()).toBe(0)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
it('should return active query info', async () => {
|
|
424
|
+
const state = createMockState()
|
|
425
|
+
const env = createMockEnv()
|
|
426
|
+
const doo = new PostgresDO(state as any, env)
|
|
427
|
+
|
|
428
|
+
const mockPGlite = createMockPGlite()
|
|
429
|
+
let resolveQuery: () => void
|
|
430
|
+
const queryPromise = new Promise<void>((resolve) => {
|
|
431
|
+
resolveQuery = resolve
|
|
432
|
+
})
|
|
433
|
+
mockPGlite.query.mockImplementation(async () => {
|
|
434
|
+
await queryPromise
|
|
435
|
+
return { rows: [], fields: [], affectedRows: 0 }
|
|
436
|
+
})
|
|
437
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
438
|
+
|
|
439
|
+
const queryExecution = doo.executeQuery({ sql: 'SELECT * FROM users WHERE id = 1' })
|
|
440
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
441
|
+
|
|
442
|
+
const activeQueries = doo.getActiveQueries()
|
|
443
|
+
expect(activeQueries.length).toBe(1)
|
|
444
|
+
expect(activeQueries[0].sql).toContain('SELECT * FROM users')
|
|
445
|
+
expect(activeQueries[0].durationMs).toBeGreaterThanOrEqual(0)
|
|
446
|
+
|
|
447
|
+
resolveQuery!()
|
|
448
|
+
await queryExecution
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('shutdown configuration', () => {
|
|
453
|
+
it('should have default shutdown config', () => {
|
|
454
|
+
const state = createMockState()
|
|
455
|
+
const env = createMockEnv()
|
|
456
|
+
const doo = new PostgresDO(state as any, env)
|
|
457
|
+
|
|
458
|
+
const config = doo.getShutdownConfig()
|
|
459
|
+
expect(config.gracePeriodMs).toBe(30000)
|
|
460
|
+
expect(config.drainCheckIntervalMs).toBe(100)
|
|
461
|
+
expect(config.closeTimeoutMs).toBe(5000)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('should allow setting shutdown config', () => {
|
|
465
|
+
const state = createMockState()
|
|
466
|
+
const env = createMockEnv()
|
|
467
|
+
const doo = new PostgresDO(state as any, env)
|
|
468
|
+
|
|
469
|
+
doo.setShutdownConfig({
|
|
470
|
+
gracePeriodMs: 60000,
|
|
471
|
+
drainCheckIntervalMs: 50,
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
const config = doo.getShutdownConfig()
|
|
475
|
+
expect(config.gracePeriodMs).toBe(60000)
|
|
476
|
+
expect(config.drainCheckIntervalMs).toBe(50)
|
|
477
|
+
expect(config.closeTimeoutMs).toBe(5000) // unchanged
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
describe('shutdown event listeners', () => {
|
|
482
|
+
it('should register and unregister event listeners', () => {
|
|
483
|
+
const state = createMockState()
|
|
484
|
+
const env = createMockEnv()
|
|
485
|
+
const doo = new PostgresDO(state as any, env)
|
|
486
|
+
|
|
487
|
+
const listener = vi.fn()
|
|
488
|
+
const unsubscribe = doo.onShutdownEvent(listener)
|
|
489
|
+
|
|
490
|
+
expect(typeof unsubscribe).toBe('function')
|
|
491
|
+
|
|
492
|
+
// Unsubscribe and verify no errors
|
|
493
|
+
unsubscribe()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('should prevent memory leak by limiting listener count', () => {
|
|
497
|
+
const state = createMockState()
|
|
498
|
+
const env = createMockEnv()
|
|
499
|
+
const doo = new PostgresDO(state as any, env)
|
|
500
|
+
|
|
501
|
+
// Simulate adding many listeners without cleanup (memory leak scenario)
|
|
502
|
+
const listenerCount = 150 // More than the default limit of 100
|
|
503
|
+
const cleanupFns: (() => void)[] = []
|
|
504
|
+
|
|
505
|
+
for (let i = 0; i < listenerCount; i++) {
|
|
506
|
+
const cleanup = doo.onShutdownEvent(() => {})
|
|
507
|
+
cleanupFns.push(cleanup)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// The listener count should be capped at maxListeners (default: 100)
|
|
511
|
+
const actualListenerCount = doo.getShutdownListenerCount()
|
|
512
|
+
expect(actualListenerCount).toBeLessThanOrEqual(100)
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should warn when approaching listener limit', () => {
|
|
516
|
+
const state = createMockState()
|
|
517
|
+
const env = createMockEnv()
|
|
518
|
+
const doo = new PostgresDO(state as any, env)
|
|
519
|
+
|
|
520
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
521
|
+
|
|
522
|
+
// Add listeners to reach 80% of the limit (80 out of 100)
|
|
523
|
+
for (let i = 0; i < 85; i++) {
|
|
524
|
+
doo.onShutdownEvent(() => {})
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Should have warned about approaching limit
|
|
528
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
529
|
+
expect.stringContaining('Approaching shutdown event listener limit')
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
consoleSpy.mockRestore()
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
it('should support configurable maxListeners', () => {
|
|
536
|
+
const state = createMockState()
|
|
537
|
+
const env = createMockEnv()
|
|
538
|
+
const doo = new PostgresDO(state as any, env)
|
|
539
|
+
|
|
540
|
+
// Configure custom max listeners
|
|
541
|
+
doo.setShutdownConfig({ maxListeners: 50 })
|
|
542
|
+
|
|
543
|
+
// Add more listeners than the custom limit
|
|
544
|
+
for (let i = 0; i < 60; i++) {
|
|
545
|
+
doo.onShutdownEvent(() => {})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Should be capped at custom limit
|
|
549
|
+
const actualListenerCount = doo.getShutdownListenerCount()
|
|
550
|
+
expect(actualListenerCount).toBeLessThanOrEqual(50)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should emit shutdown events', async () => {
|
|
554
|
+
const state = createMockState()
|
|
555
|
+
const env = createMockEnv()
|
|
556
|
+
const doo = new PostgresDO(state as any, env)
|
|
557
|
+
|
|
558
|
+
const mockPGlite = createMockPGlite()
|
|
559
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
560
|
+
await doo.initPGlite()
|
|
561
|
+
|
|
562
|
+
const events: { type: string; details?: Record<string, unknown> }[] = []
|
|
563
|
+
doo.onShutdownEvent((event) => {
|
|
564
|
+
events.push({ type: event.type, details: event.details })
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
568
|
+
|
|
569
|
+
expect(events.some((e) => e.type === 'shutdown_initiated')).toBe(true)
|
|
570
|
+
expect(events.some((e) => e.type === 'drain_started')).toBe(true)
|
|
571
|
+
expect(events.some((e) => e.type === 'drain_completed')).toBe(true)
|
|
572
|
+
expect(events.some((e) => e.type === 'shutdown_completed')).toBe(true)
|
|
573
|
+
})
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
describe('graceful shutdown', () => {
|
|
577
|
+
it('should set status to draining then shutdown', async () => {
|
|
578
|
+
const state = createMockState()
|
|
579
|
+
const env = createMockEnv()
|
|
580
|
+
const doo = new PostgresDO(state as any, env)
|
|
581
|
+
|
|
582
|
+
const mockPGlite = createMockPGlite()
|
|
583
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
584
|
+
await doo.initPGlite()
|
|
585
|
+
|
|
586
|
+
const statuses: string[] = []
|
|
587
|
+
doo.onShutdownEvent((event) => {
|
|
588
|
+
statuses.push(doo.getShutdownStatus())
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
592
|
+
|
|
593
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
594
|
+
expect(statuses).toContain('draining')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it('should close PGlite during shutdown', async () => {
|
|
598
|
+
const state = createMockState()
|
|
599
|
+
const env = createMockEnv()
|
|
600
|
+
const doo = new PostgresDO(state as any, env)
|
|
601
|
+
|
|
602
|
+
const mockPGlite = createMockPGlite()
|
|
603
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
604
|
+
await doo.initPGlite()
|
|
605
|
+
|
|
606
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
607
|
+
|
|
608
|
+
expect(mockPGlite.close).toHaveBeenCalled()
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
it('should reject new queries after shutdown', async () => {
|
|
612
|
+
const state = createMockState()
|
|
613
|
+
const env = createMockEnv()
|
|
614
|
+
const doo = new PostgresDO(state as any, env)
|
|
615
|
+
|
|
616
|
+
const mockPGlite = createMockPGlite()
|
|
617
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
618
|
+
await doo.initPGlite()
|
|
619
|
+
|
|
620
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
621
|
+
|
|
622
|
+
await expect(doo.executeQuery({ sql: 'SELECT 1' })).rejects.toThrow(
|
|
623
|
+
'PostgresDO is shut down and cannot accept new requests'
|
|
624
|
+
)
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
it('should only perform shutdown once if called multiple times', async () => {
|
|
628
|
+
const state = createMockState()
|
|
629
|
+
const env = createMockEnv()
|
|
630
|
+
const doo = new PostgresDO(state as any, env)
|
|
631
|
+
|
|
632
|
+
const mockPGlite = createMockPGlite()
|
|
633
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
634
|
+
await doo.initPGlite()
|
|
635
|
+
|
|
636
|
+
const shutdownEvents: string[] = []
|
|
637
|
+
doo.onShutdownEvent((event) => shutdownEvents.push(event.type))
|
|
638
|
+
|
|
639
|
+
// Call shutdown multiple times
|
|
640
|
+
const promise1 = doo.shutdown({ gracePeriodMs: 100 })
|
|
641
|
+
const promise2 = doo.shutdown()
|
|
642
|
+
const promise3 = doo.shutdown({ gracePeriodMs: 200 })
|
|
643
|
+
|
|
644
|
+
// Wait for all to complete
|
|
645
|
+
await Promise.all([promise1, promise2, promise3])
|
|
646
|
+
|
|
647
|
+
// Verify shutdown_initiated only happened once
|
|
648
|
+
const initiatedCount = shutdownEvents.filter((e) => e === 'shutdown_initiated').length
|
|
649
|
+
expect(initiatedCount).toBe(1)
|
|
650
|
+
|
|
651
|
+
// Verify shutdown_completed only happened once
|
|
652
|
+
const completedCount = shutdownEvents.filter((e) => e === 'shutdown_completed').length
|
|
653
|
+
expect(completedCount).toBe(1)
|
|
654
|
+
|
|
655
|
+
// Verify PGlite.close was only called once
|
|
656
|
+
expect(mockPGlite.close).toHaveBeenCalledTimes(1)
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
describe('connection draining', () => {
|
|
661
|
+
it('should wait for in-flight queries to complete', async () => {
|
|
662
|
+
const state = createMockState()
|
|
663
|
+
const env = createMockEnv()
|
|
664
|
+
const doo = new PostgresDO(state as any, env)
|
|
665
|
+
|
|
666
|
+
const mockPGlite = createMockPGlite()
|
|
667
|
+
let resolveQuery: () => void
|
|
668
|
+
const queryPromise = new Promise<void>((resolve) => {
|
|
669
|
+
resolveQuery = resolve
|
|
670
|
+
})
|
|
671
|
+
mockPGlite.query.mockImplementation(async () => {
|
|
672
|
+
await queryPromise
|
|
673
|
+
return { rows: [], fields: [], affectedRows: 0 }
|
|
674
|
+
})
|
|
675
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
676
|
+
await doo.initPGlite()
|
|
677
|
+
|
|
678
|
+
// Start a query but don't await it yet
|
|
679
|
+
const queryExecution = doo.executeQuery({ sql: 'SELECT 1' })
|
|
680
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
681
|
+
|
|
682
|
+
expect(doo.getActiveQueryCount()).toBe(1)
|
|
683
|
+
|
|
684
|
+
// Start shutdown (won't complete until query finishes)
|
|
685
|
+
const shutdownPromise = doo.shutdown({ gracePeriodMs: 5000 })
|
|
686
|
+
|
|
687
|
+
expect(doo.getShutdownStatus()).toBe('draining')
|
|
688
|
+
|
|
689
|
+
// Resolve the query
|
|
690
|
+
resolveQuery!()
|
|
691
|
+
await queryExecution
|
|
692
|
+
|
|
693
|
+
// Now shutdown should complete
|
|
694
|
+
await shutdownPromise
|
|
695
|
+
|
|
696
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
it('should force shutdown after grace period', async () => {
|
|
700
|
+
const state = createMockState()
|
|
701
|
+
const env = createMockEnv()
|
|
702
|
+
const doo = new PostgresDO(state as any, env)
|
|
703
|
+
|
|
704
|
+
const mockPGlite = createMockPGlite()
|
|
705
|
+
// This query will never resolve
|
|
706
|
+
mockPGlite.query.mockImplementation(() => new Promise(() => {}))
|
|
707
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
708
|
+
await doo.initPGlite()
|
|
709
|
+
|
|
710
|
+
// Start a query that never completes
|
|
711
|
+
doo.executeQuery({ sql: 'SELECT pg_sleep(9999)' }).catch(() => {})
|
|
712
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
713
|
+
|
|
714
|
+
expect(doo.getActiveQueryCount()).toBe(1)
|
|
715
|
+
|
|
716
|
+
const events: string[] = []
|
|
717
|
+
doo.onShutdownEvent((event) => events.push(event.type))
|
|
718
|
+
|
|
719
|
+
// Shutdown with very short grace period
|
|
720
|
+
await doo.shutdown({
|
|
721
|
+
gracePeriodMs: 50,
|
|
722
|
+
drainCheckIntervalMs: 10,
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
expect(events).toContain('forced_shutdown')
|
|
726
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
it('should emit query_completed_during_drain event', async () => {
|
|
730
|
+
const state = createMockState()
|
|
731
|
+
const env = createMockEnv()
|
|
732
|
+
const doo = new PostgresDO(state as any, env)
|
|
733
|
+
|
|
734
|
+
const mockPGlite = createMockPGlite()
|
|
735
|
+
let resolveQuery: () => void
|
|
736
|
+
const queryPromise = new Promise<void>((resolve) => {
|
|
737
|
+
resolveQuery = resolve
|
|
738
|
+
})
|
|
739
|
+
mockPGlite.query.mockImplementation(async () => {
|
|
740
|
+
await queryPromise
|
|
741
|
+
return { rows: [], fields: [], affectedRows: 0 }
|
|
742
|
+
})
|
|
743
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
744
|
+
await doo.initPGlite()
|
|
745
|
+
|
|
746
|
+
// Start a query
|
|
747
|
+
const queryExecution = doo.executeQuery({ sql: 'SELECT 1' })
|
|
748
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
749
|
+
|
|
750
|
+
const events: { type: string; details?: Record<string, unknown> }[] = []
|
|
751
|
+
doo.onShutdownEvent((event) => events.push({ type: event.type, details: event.details }))
|
|
752
|
+
|
|
753
|
+
// Start shutdown
|
|
754
|
+
const shutdownPromise = doo.shutdown({ gracePeriodMs: 5000 })
|
|
755
|
+
|
|
756
|
+
// Wait for draining to start
|
|
757
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
758
|
+
|
|
759
|
+
// Complete the query
|
|
760
|
+
resolveQuery!()
|
|
761
|
+
await queryExecution
|
|
762
|
+
|
|
763
|
+
await shutdownPromise
|
|
764
|
+
|
|
765
|
+
const drainEvent = events.find((e) => e.type === 'query_completed_during_drain')
|
|
766
|
+
expect(drainEvent).toBeDefined()
|
|
767
|
+
expect(drainEvent?.details?.remainingQueries).toBe(0)
|
|
768
|
+
})
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
describe('websocket handling during shutdown', () => {
|
|
772
|
+
it('should close websockets during shutdown', async () => {
|
|
773
|
+
const mockWebSocket = {
|
|
774
|
+
close: vi.fn(),
|
|
775
|
+
}
|
|
776
|
+
const state = createMockState()
|
|
777
|
+
state.getWebSockets.mockReturnValue([mockWebSocket])
|
|
778
|
+
|
|
779
|
+
const env = createMockEnv()
|
|
780
|
+
const doo = new PostgresDO(state as any, env)
|
|
781
|
+
|
|
782
|
+
const mockPGlite = createMockPGlite()
|
|
783
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
784
|
+
await doo.initPGlite()
|
|
785
|
+
|
|
786
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
787
|
+
|
|
788
|
+
expect(mockWebSocket.close).toHaveBeenCalledWith(1001, 'Server shutting down')
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
it('should emit websocket_closed_during_drain event', async () => {
|
|
792
|
+
const mockWebSocket = {
|
|
793
|
+
close: vi.fn(),
|
|
794
|
+
}
|
|
795
|
+
const state = createMockState()
|
|
796
|
+
state.getWebSockets.mockReturnValue([mockWebSocket])
|
|
797
|
+
|
|
798
|
+
const env = createMockEnv()
|
|
799
|
+
const doo = new PostgresDO(state as any, env)
|
|
800
|
+
|
|
801
|
+
const mockPGlite = createMockPGlite()
|
|
802
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
803
|
+
await doo.initPGlite()
|
|
804
|
+
|
|
805
|
+
const events: string[] = []
|
|
806
|
+
doo.onShutdownEvent((event) => events.push(event.type))
|
|
807
|
+
|
|
808
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
809
|
+
|
|
810
|
+
expect(events).toContain('websocket_closed_during_drain')
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
it('should handle websockets that are already closed', async () => {
|
|
814
|
+
const mockWebSocket = {
|
|
815
|
+
close: vi.fn(() => {
|
|
816
|
+
throw new Error('WebSocket already closed')
|
|
817
|
+
}),
|
|
818
|
+
}
|
|
819
|
+
const state = createMockState()
|
|
820
|
+
state.getWebSockets.mockReturnValue([mockWebSocket])
|
|
821
|
+
|
|
822
|
+
const env = createMockEnv()
|
|
823
|
+
const doo = new PostgresDO(state as any, env)
|
|
824
|
+
|
|
825
|
+
const mockPGlite = createMockPGlite()
|
|
826
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
827
|
+
await doo.initPGlite()
|
|
828
|
+
|
|
829
|
+
// Should not throw
|
|
830
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
831
|
+
|
|
832
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
833
|
+
})
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
describe('PGlite close timeout', () => {
|
|
837
|
+
it('should timeout PGlite close if it takes too long', async () => {
|
|
838
|
+
const state = createMockState()
|
|
839
|
+
const env = createMockEnv()
|
|
840
|
+
const doo = new PostgresDO(state as any, env)
|
|
841
|
+
|
|
842
|
+
const mockPGlite = createMockPGlite()
|
|
843
|
+
// PGlite close that never resolves
|
|
844
|
+
mockPGlite.close.mockImplementation(() => new Promise(() => {}))
|
|
845
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
846
|
+
await doo.initPGlite()
|
|
847
|
+
|
|
848
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
849
|
+
|
|
850
|
+
await doo.shutdown({
|
|
851
|
+
gracePeriodMs: 50,
|
|
852
|
+
closeTimeoutMs: 50,
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
856
|
+
consoleSpy.mockRestore()
|
|
857
|
+
})
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
describe('isShuttingDown', () => {
|
|
861
|
+
it('should return true when draining', async () => {
|
|
862
|
+
const state = createMockState()
|
|
863
|
+
const env = createMockEnv()
|
|
864
|
+
const doo = new PostgresDO(state as any, env)
|
|
865
|
+
|
|
866
|
+
const mockPGlite = createMockPGlite()
|
|
867
|
+
let resolveClose: () => void
|
|
868
|
+
mockPGlite.close.mockImplementation(
|
|
869
|
+
() =>
|
|
870
|
+
new Promise((resolve) => {
|
|
871
|
+
resolveClose = resolve as () => void
|
|
872
|
+
})
|
|
873
|
+
)
|
|
874
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
875
|
+
await doo.initPGlite()
|
|
876
|
+
|
|
877
|
+
const shutdownPromise = doo.shutdown({ gracePeriodMs: 100 })
|
|
878
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
879
|
+
|
|
880
|
+
expect(doo.isShuttingDown()).toBe(true)
|
|
881
|
+
|
|
882
|
+
resolveClose!()
|
|
883
|
+
await shutdownPromise
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
it('should return true when shutdown', async () => {
|
|
887
|
+
const state = createMockState()
|
|
888
|
+
const env = createMockEnv()
|
|
889
|
+
const doo = new PostgresDO(state as any, env)
|
|
890
|
+
|
|
891
|
+
const mockPGlite = createMockPGlite()
|
|
892
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
893
|
+
await doo.initPGlite()
|
|
894
|
+
|
|
895
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
896
|
+
|
|
897
|
+
expect(doo.isShuttingDown()).toBe(true)
|
|
898
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
899
|
+
})
|
|
900
|
+
})
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
describe('Auto-Migration on First Connection', () => {
|
|
904
|
+
describe('schema version tracking', () => {
|
|
905
|
+
it('should return version 0 for fresh database', async () => {
|
|
906
|
+
const state = createMockState()
|
|
907
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
908
|
+
const env = createMockEnv()
|
|
909
|
+
const doo = new PostgresDO(state as any, env)
|
|
910
|
+
|
|
911
|
+
const version = await doo.getSchemaVersion()
|
|
912
|
+
expect(version).toBe(0)
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
it('should return stored version', async () => {
|
|
916
|
+
const state = createMockState()
|
|
917
|
+
state.storage.get.mockResolvedValue(5)
|
|
918
|
+
const env = createMockEnv()
|
|
919
|
+
const doo = new PostgresDO(state as any, env)
|
|
920
|
+
|
|
921
|
+
const version = await doo.getSchemaVersion()
|
|
922
|
+
expect(version).toBe(5)
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
it('should detect fresh database', async () => {
|
|
926
|
+
const state = createMockState()
|
|
927
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
928
|
+
const env = createMockEnv()
|
|
929
|
+
const doo = new PostgresDO(state as any, env)
|
|
930
|
+
|
|
931
|
+
const isFresh = await doo.isFreshDatabase()
|
|
932
|
+
expect(isFresh).toBe(true)
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
it('should detect non-fresh database', async () => {
|
|
936
|
+
const state = createMockState()
|
|
937
|
+
state.storage.get.mockResolvedValue(1)
|
|
938
|
+
const env = createMockEnv()
|
|
939
|
+
const doo = new PostgresDO(state as any, env)
|
|
940
|
+
|
|
941
|
+
const isFresh = await doo.isFreshDatabase()
|
|
942
|
+
expect(isFresh).toBe(false)
|
|
943
|
+
})
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
describe('ensureMigrated', () => {
|
|
947
|
+
it('should return null when no migrations configured', async () => {
|
|
948
|
+
const state = createMockState()
|
|
949
|
+
const env = createMockEnv()
|
|
950
|
+
const doo = new PostgresDO(state as any, env)
|
|
951
|
+
|
|
952
|
+
const mockPGlite = createMockPGlite()
|
|
953
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
954
|
+
await doo.initPGlite()
|
|
955
|
+
|
|
956
|
+
const result = await doo.ensureMigrated()
|
|
957
|
+
expect(result).toBeNull()
|
|
958
|
+
expect(doo.isMigrated()).toBe(true)
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
it('should run migrations on fresh database', async () => {
|
|
962
|
+
const state = createMockState()
|
|
963
|
+
state.storage.get.mockResolvedValue(undefined) // Fresh DB
|
|
964
|
+
const env = createMockEnv()
|
|
965
|
+
const config: PostgresConfig = {
|
|
966
|
+
database: 'test',
|
|
967
|
+
migrations: {
|
|
968
|
+
autoMigrate: false, // Disable auto-migrate to test ensureMigrated directly
|
|
969
|
+
migrations: [
|
|
970
|
+
{
|
|
971
|
+
id: '001_initial',
|
|
972
|
+
name: 'Initial schema',
|
|
973
|
+
version: 1,
|
|
974
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
975
|
+
},
|
|
976
|
+
],
|
|
977
|
+
},
|
|
978
|
+
}
|
|
979
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
980
|
+
|
|
981
|
+
const mockPGlite = createMockPGlite()
|
|
982
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
983
|
+
await doo.initPGlite()
|
|
984
|
+
|
|
985
|
+
const result = await doo.ensureMigrated()
|
|
986
|
+
|
|
987
|
+
expect(result).not.toBeNull()
|
|
988
|
+
expect(result?.success).toBe(true)
|
|
989
|
+
expect(result?.migrationsRun).toBe(1)
|
|
990
|
+
expect(state.storage.put).toHaveBeenCalledWith('__postgres_schema_version__', 1)
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
it('should skip already applied migrations', async () => {
|
|
994
|
+
const state = createMockState()
|
|
995
|
+
state.storage.get.mockResolvedValue(1) // Already at version 1
|
|
996
|
+
const env = createMockEnv()
|
|
997
|
+
const config: PostgresConfig = {
|
|
998
|
+
database: 'test',
|
|
999
|
+
migrations: {
|
|
1000
|
+
autoMigrate: false, // Disable auto-migrate to test ensureMigrated directly
|
|
1001
|
+
migrations: [
|
|
1002
|
+
{
|
|
1003
|
+
id: '001_initial',
|
|
1004
|
+
name: 'Initial schema',
|
|
1005
|
+
version: 1,
|
|
1006
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1007
|
+
},
|
|
1008
|
+
],
|
|
1009
|
+
},
|
|
1010
|
+
}
|
|
1011
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1012
|
+
|
|
1013
|
+
const mockPGlite = createMockPGlite()
|
|
1014
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1015
|
+
await doo.initPGlite()
|
|
1016
|
+
|
|
1017
|
+
const result = await doo.ensureMigrated()
|
|
1018
|
+
|
|
1019
|
+
expect(result).not.toBeNull()
|
|
1020
|
+
expect(result?.success).toBe(true)
|
|
1021
|
+
expect(result?.migrationsSkipped).toBe(1)
|
|
1022
|
+
expect(result?.migrationsRun).toBe(0)
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
it('should run only pending migrations', async () => {
|
|
1026
|
+
const state = createMockState()
|
|
1027
|
+
// Track the schema version in storage
|
|
1028
|
+
let storedVersion: number = 1
|
|
1029
|
+
state.storage.get.mockImplementation(async (key: string) => {
|
|
1030
|
+
if (key === '__postgres_schema_version__') return storedVersion
|
|
1031
|
+
return undefined
|
|
1032
|
+
})
|
|
1033
|
+
state.storage.put.mockImplementation(async (key: string, value: unknown) => {
|
|
1034
|
+
if (key === '__postgres_schema_version__') storedVersion = value as number
|
|
1035
|
+
})
|
|
1036
|
+
const env = createMockEnv()
|
|
1037
|
+
const config: PostgresConfig = {
|
|
1038
|
+
database: 'test',
|
|
1039
|
+
migrations: {
|
|
1040
|
+
autoMigrate: false, // Disable auto-migrate to test ensureMigrated directly
|
|
1041
|
+
migrations: [
|
|
1042
|
+
{
|
|
1043
|
+
id: '001_initial',
|
|
1044
|
+
name: 'Initial schema',
|
|
1045
|
+
version: 1,
|
|
1046
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
id: '002_add_email',
|
|
1050
|
+
name: 'Add email column',
|
|
1051
|
+
version: 2,
|
|
1052
|
+
sql: 'ALTER TABLE users ADD COLUMN email TEXT',
|
|
1053
|
+
},
|
|
1054
|
+
],
|
|
1055
|
+
},
|
|
1056
|
+
}
|
|
1057
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1058
|
+
|
|
1059
|
+
const mockPGlite = createMockPGlite()
|
|
1060
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1061
|
+
await doo.initPGlite()
|
|
1062
|
+
|
|
1063
|
+
const result = await doo.ensureMigrated()
|
|
1064
|
+
|
|
1065
|
+
expect(result?.success).toBe(true)
|
|
1066
|
+
expect(result?.migrationsRun).toBe(1) // Only migration 2
|
|
1067
|
+
expect(result?.migrationsSkipped).toBe(1) // Migration 1 skipped
|
|
1068
|
+
expect(result?.fromVersion).toBe(1)
|
|
1069
|
+
expect(result?.toVersion).toBe(2)
|
|
1070
|
+
})
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
describe('migration error handling', () => {
|
|
1074
|
+
it('should handle migration failures gracefully', async () => {
|
|
1075
|
+
const state = createMockState()
|
|
1076
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1077
|
+
const env = createMockEnv()
|
|
1078
|
+
const config: PostgresConfig = {
|
|
1079
|
+
database: 'test',
|
|
1080
|
+
migrations: {
|
|
1081
|
+
migrations: [
|
|
1082
|
+
{
|
|
1083
|
+
id: '001_bad',
|
|
1084
|
+
name: 'Bad migration',
|
|
1085
|
+
version: 1,
|
|
1086
|
+
sql: 'INVALID SQL SYNTAX',
|
|
1087
|
+
},
|
|
1088
|
+
],
|
|
1089
|
+
},
|
|
1090
|
+
}
|
|
1091
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1092
|
+
|
|
1093
|
+
const mockPGlite = createMockPGlite()
|
|
1094
|
+
mockPGlite.exec.mockRejectedValue(new Error('syntax error'))
|
|
1095
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1096
|
+
await doo.initPGlite()
|
|
1097
|
+
|
|
1098
|
+
const result = await doo.ensureMigrated()
|
|
1099
|
+
|
|
1100
|
+
expect(result?.success).toBe(false)
|
|
1101
|
+
expect(result?.results[0].success).toBe(false)
|
|
1102
|
+
expect(result?.results[0].error).toContain('syntax error')
|
|
1103
|
+
expect(doo.isMigrated()).toBe(false)
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
it('should rollback on migration failure', async () => {
|
|
1107
|
+
const state = createMockState()
|
|
1108
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1109
|
+
const env = createMockEnv()
|
|
1110
|
+
const config: PostgresConfig = {
|
|
1111
|
+
database: 'test',
|
|
1112
|
+
migrations: {
|
|
1113
|
+
migrations: [
|
|
1114
|
+
{
|
|
1115
|
+
id: '001_bad',
|
|
1116
|
+
name: 'Bad migration',
|
|
1117
|
+
version: 1,
|
|
1118
|
+
sql: 'INVALID SQL',
|
|
1119
|
+
},
|
|
1120
|
+
],
|
|
1121
|
+
},
|
|
1122
|
+
}
|
|
1123
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1124
|
+
|
|
1125
|
+
const mockPGlite = createMockPGlite()
|
|
1126
|
+
const queryCalls: string[] = []
|
|
1127
|
+
mockPGlite.query.mockImplementation((sql?: string) => {
|
|
1128
|
+
queryCalls.push(sql ?? '')
|
|
1129
|
+
return Promise.resolve({ rows: [], fields: [], affectedRows: 0 })
|
|
1130
|
+
})
|
|
1131
|
+
mockPGlite.exec.mockRejectedValue(new Error('syntax error'))
|
|
1132
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1133
|
+
await doo.initPGlite()
|
|
1134
|
+
|
|
1135
|
+
await doo.ensureMigrated()
|
|
1136
|
+
|
|
1137
|
+
expect(queryCalls).toContain('BEGIN')
|
|
1138
|
+
expect(queryCalls).toContain('ROLLBACK')
|
|
1139
|
+
})
|
|
1140
|
+
})
|
|
1141
|
+
|
|
1142
|
+
describe('migration progress callbacks', () => {
|
|
1143
|
+
it('should call onProgress for each migration phase', async () => {
|
|
1144
|
+
const state = createMockState()
|
|
1145
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1146
|
+
const env = createMockEnv()
|
|
1147
|
+
|
|
1148
|
+
const progressEvents: Array<{ phase: string; migration: { id: string } }> = []
|
|
1149
|
+
const config: PostgresConfig = {
|
|
1150
|
+
database: 'test',
|
|
1151
|
+
migrations: {
|
|
1152
|
+
migrations: [
|
|
1153
|
+
{
|
|
1154
|
+
id: '001_initial',
|
|
1155
|
+
name: 'Initial schema',
|
|
1156
|
+
version: 1,
|
|
1157
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
onProgress: (event) => {
|
|
1161
|
+
progressEvents.push({ phase: event.phase, migration: { id: event.migration.id } })
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
}
|
|
1165
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1166
|
+
|
|
1167
|
+
const mockPGlite = createMockPGlite()
|
|
1168
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1169
|
+
await doo.initPGlite()
|
|
1170
|
+
|
|
1171
|
+
await doo.ensureMigrated()
|
|
1172
|
+
|
|
1173
|
+
expect(progressEvents.some((e) => e.phase === 'starting')).toBe(true)
|
|
1174
|
+
expect(progressEvents.some((e) => e.phase === 'executing')).toBe(true)
|
|
1175
|
+
expect(progressEvents.some((e) => e.phase === 'completed')).toBe(true)
|
|
1176
|
+
})
|
|
1177
|
+
|
|
1178
|
+
it('should call onProgress with failed phase on error', async () => {
|
|
1179
|
+
const state = createMockState()
|
|
1180
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1181
|
+
const env = createMockEnv()
|
|
1182
|
+
|
|
1183
|
+
const progressEvents: Array<{ phase: string; error?: string }> = []
|
|
1184
|
+
const config: PostgresConfig = {
|
|
1185
|
+
database: 'test',
|
|
1186
|
+
migrations: {
|
|
1187
|
+
migrations: [
|
|
1188
|
+
{
|
|
1189
|
+
id: '001_bad',
|
|
1190
|
+
name: 'Bad migration',
|
|
1191
|
+
version: 1,
|
|
1192
|
+
sql: 'INVALID SQL',
|
|
1193
|
+
},
|
|
1194
|
+
],
|
|
1195
|
+
onProgress: (event) => {
|
|
1196
|
+
progressEvents.push({ phase: event.phase, error: event.error })
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
}
|
|
1200
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1201
|
+
|
|
1202
|
+
const mockPGlite = createMockPGlite()
|
|
1203
|
+
mockPGlite.exec.mockRejectedValue(new Error('syntax error'))
|
|
1204
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1205
|
+
await doo.initPGlite()
|
|
1206
|
+
|
|
1207
|
+
await doo.ensureMigrated()
|
|
1208
|
+
|
|
1209
|
+
const failedEvent = progressEvents.find((e) => e.phase === 'failed')
|
|
1210
|
+
expect(failedEvent).toBeDefined()
|
|
1211
|
+
expect(failedEvent?.error).toContain('syntax error')
|
|
1212
|
+
})
|
|
1213
|
+
})
|
|
1214
|
+
|
|
1215
|
+
describe('auto-migration during initialization', () => {
|
|
1216
|
+
it('should run migrations automatically when autoMigrate is not set (defaults to true)', async () => {
|
|
1217
|
+
const state = createMockState()
|
|
1218
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1219
|
+
const env = createMockEnv()
|
|
1220
|
+
const config: PostgresConfig = {
|
|
1221
|
+
database: 'test',
|
|
1222
|
+
migrations: {
|
|
1223
|
+
migrations: [
|
|
1224
|
+
{
|
|
1225
|
+
id: '001_initial',
|
|
1226
|
+
name: 'Initial schema',
|
|
1227
|
+
version: 1,
|
|
1228
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1229
|
+
},
|
|
1230
|
+
],
|
|
1231
|
+
},
|
|
1232
|
+
}
|
|
1233
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1234
|
+
|
|
1235
|
+
const mockPGlite = createMockPGlite()
|
|
1236
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1237
|
+
|
|
1238
|
+
await doo.initPGlite()
|
|
1239
|
+
|
|
1240
|
+
// Migration should have run automatically
|
|
1241
|
+
expect(doo.isMigrated()).toBe(true)
|
|
1242
|
+
expect(state.storage.put).toHaveBeenCalledWith('__postgres_schema_version__', 1)
|
|
1243
|
+
})
|
|
1244
|
+
|
|
1245
|
+
it('should skip auto-migration when autoMigrate is false', async () => {
|
|
1246
|
+
const state = createMockState()
|
|
1247
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1248
|
+
const env = createMockEnv()
|
|
1249
|
+
const config: PostgresConfig = {
|
|
1250
|
+
database: 'test',
|
|
1251
|
+
migrations: {
|
|
1252
|
+
autoMigrate: false,
|
|
1253
|
+
migrations: [
|
|
1254
|
+
{
|
|
1255
|
+
id: '001_initial',
|
|
1256
|
+
name: 'Initial schema',
|
|
1257
|
+
version: 1,
|
|
1258
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1259
|
+
},
|
|
1260
|
+
],
|
|
1261
|
+
},
|
|
1262
|
+
}
|
|
1263
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1264
|
+
|
|
1265
|
+
const mockPGlite = createMockPGlite()
|
|
1266
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1267
|
+
|
|
1268
|
+
await doo.initPGlite()
|
|
1269
|
+
|
|
1270
|
+
// Migration should NOT have run
|
|
1271
|
+
expect(doo.isMigrated()).toBe(false)
|
|
1272
|
+
expect(state.storage.put).not.toHaveBeenCalledWith('__postgres_schema_version__', 1)
|
|
1273
|
+
})
|
|
1274
|
+
})
|
|
1275
|
+
|
|
1276
|
+
describe('concurrent migration protection', () => {
|
|
1277
|
+
it('should deduplicate concurrent ensureMigrated calls', async () => {
|
|
1278
|
+
const state = createMockState()
|
|
1279
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1280
|
+
const env = createMockEnv()
|
|
1281
|
+
const config: PostgresConfig = {
|
|
1282
|
+
database: 'test',
|
|
1283
|
+
migrations: {
|
|
1284
|
+
autoMigrate: false, // Disable auto-migrate to control manually
|
|
1285
|
+
migrations: [
|
|
1286
|
+
{
|
|
1287
|
+
id: '001_initial',
|
|
1288
|
+
name: 'Initial schema',
|
|
1289
|
+
version: 1,
|
|
1290
|
+
sql: 'CREATE TABLE users (id SERIAL PRIMARY KEY)',
|
|
1291
|
+
},
|
|
1292
|
+
],
|
|
1293
|
+
},
|
|
1294
|
+
}
|
|
1295
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1296
|
+
|
|
1297
|
+
const mockPGlite = createMockPGlite()
|
|
1298
|
+
let execCallCount = 0
|
|
1299
|
+
mockPGlite.exec.mockImplementation(async () => {
|
|
1300
|
+
execCallCount++
|
|
1301
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
1302
|
+
return { rows: [], fields: [] }
|
|
1303
|
+
})
|
|
1304
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1305
|
+
|
|
1306
|
+
await doo.initPGlite()
|
|
1307
|
+
|
|
1308
|
+
// Call ensureMigrated multiple times concurrently
|
|
1309
|
+
const [result1, result2, result3] = await Promise.all([
|
|
1310
|
+
doo.ensureMigrated(),
|
|
1311
|
+
doo.ensureMigrated(),
|
|
1312
|
+
doo.ensureMigrated(),
|
|
1313
|
+
])
|
|
1314
|
+
|
|
1315
|
+
// All should return the same result (first two return the running promise)
|
|
1316
|
+
// The migration SQL should only be executed once
|
|
1317
|
+
expect(execCallCount).toBe(1)
|
|
1318
|
+
|
|
1319
|
+
// Results should be consistent
|
|
1320
|
+
expect(result1?.success || result1 === null).toBe(true)
|
|
1321
|
+
})
|
|
1322
|
+
})
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1325
|
+
describe('WAL Initialization Race Condition', () => {
|
|
1326
|
+
describe('initWAL concurrent calls', () => {
|
|
1327
|
+
it('should only initialize WAL once when called concurrently', async () => {
|
|
1328
|
+
const state = createMockState()
|
|
1329
|
+
const env = createMockEnv()
|
|
1330
|
+
const doo = new PostgresDO(state as any, env)
|
|
1331
|
+
|
|
1332
|
+
// Spy on the WAL facade's initialize method
|
|
1333
|
+
let initializeCallCount = 0
|
|
1334
|
+
const mockWALFacade = {
|
|
1335
|
+
initialize: vi.fn(async () => {
|
|
1336
|
+
initializeCallCount++
|
|
1337
|
+
// Add small delay to simulate async work and increase race window
|
|
1338
|
+
await new Promise((r) => setTimeout(r, 20))
|
|
1339
|
+
}),
|
|
1340
|
+
isEnabled: vi.fn(() => true),
|
|
1341
|
+
setEnabled: vi.fn(),
|
|
1342
|
+
getConfig: vi.fn(() => ({})),
|
|
1343
|
+
setConfig: vi.fn(),
|
|
1344
|
+
getStats: vi.fn(() => ({ entriesInBuffer: 0, totalEntriesFlushed: 0, lastFlushAt: null })),
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
vi.spyOn(doo, 'getWALFacade').mockReturnValue(mockWALFacade as any)
|
|
1348
|
+
|
|
1349
|
+
// Call initWAL concurrently 10 times
|
|
1350
|
+
const promises = Array.from({ length: 10 }, () => doo.initWAL())
|
|
1351
|
+
await Promise.all(promises)
|
|
1352
|
+
|
|
1353
|
+
// Assert: initialize() should only be called ONCE
|
|
1354
|
+
expect(initializeCallCount).toBe(1)
|
|
1355
|
+
expect(mockWALFacade.initialize).toHaveBeenCalledTimes(1)
|
|
1356
|
+
})
|
|
1357
|
+
|
|
1358
|
+
it('should return the same promise for concurrent calls', async () => {
|
|
1359
|
+
const state = createMockState()
|
|
1360
|
+
const env = createMockEnv()
|
|
1361
|
+
const doo = new PostgresDO(state as any, env)
|
|
1362
|
+
|
|
1363
|
+
let initializeCallCount = 0
|
|
1364
|
+
const mockWALFacade = {
|
|
1365
|
+
initialize: vi.fn(async () => {
|
|
1366
|
+
initializeCallCount++
|
|
1367
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
1368
|
+
}),
|
|
1369
|
+
isEnabled: vi.fn(() => true),
|
|
1370
|
+
setEnabled: vi.fn(),
|
|
1371
|
+
getConfig: vi.fn(() => ({})),
|
|
1372
|
+
setConfig: vi.fn(),
|
|
1373
|
+
getStats: vi.fn(() => ({ entriesInBuffer: 0, totalEntriesFlushed: 0, lastFlushAt: null })),
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
vi.spyOn(doo, 'getWALFacade').mockReturnValue(mockWALFacade as any)
|
|
1377
|
+
|
|
1378
|
+
// Start multiple initWAL calls before any complete
|
|
1379
|
+
const promise1 = doo.initWAL()
|
|
1380
|
+
const promise2 = doo.initWAL()
|
|
1381
|
+
const promise3 = doo.initWAL()
|
|
1382
|
+
|
|
1383
|
+
// All promises should complete successfully
|
|
1384
|
+
await Promise.all([promise1, promise2, promise3])
|
|
1385
|
+
|
|
1386
|
+
// Only one actual initialization should have occurred
|
|
1387
|
+
expect(initializeCallCount).toBe(1)
|
|
1388
|
+
})
|
|
1389
|
+
|
|
1390
|
+
it('should allow re-initialization after previous completes if needed', async () => {
|
|
1391
|
+
const state = createMockState()
|
|
1392
|
+
const env = createMockEnv()
|
|
1393
|
+
const doo = new PostgresDO(state as any, env)
|
|
1394
|
+
|
|
1395
|
+
let initializeCallCount = 0
|
|
1396
|
+
const mockWALFacade = {
|
|
1397
|
+
initialize: vi.fn(async () => {
|
|
1398
|
+
initializeCallCount++
|
|
1399
|
+
}),
|
|
1400
|
+
isEnabled: vi.fn(() => true),
|
|
1401
|
+
setEnabled: vi.fn(),
|
|
1402
|
+
getConfig: vi.fn(() => ({})),
|
|
1403
|
+
setConfig: vi.fn(),
|
|
1404
|
+
getStats: vi.fn(() => ({ entriesInBuffer: 0, totalEntriesFlushed: 0, lastFlushAt: null })),
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
vi.spyOn(doo, 'getWALFacade').mockReturnValue(mockWALFacade as any)
|
|
1408
|
+
|
|
1409
|
+
// First call
|
|
1410
|
+
await doo.initWAL()
|
|
1411
|
+
expect(initializeCallCount).toBe(1)
|
|
1412
|
+
|
|
1413
|
+
// Second call after first completes - should still only have 1 init
|
|
1414
|
+
// (because the guard pattern caches the promise)
|
|
1415
|
+
await doo.initWAL()
|
|
1416
|
+
expect(initializeCallCount).toBe(1)
|
|
1417
|
+
})
|
|
1418
|
+
})
|
|
1419
|
+
})
|
|
1420
|
+
|
|
1421
|
+
describe('Lazy Migration (on-demand strategy)', () => {
|
|
1422
|
+
describe('getMigrationStrategy', () => {
|
|
1423
|
+
it('should return "on-create" by default', () => {
|
|
1424
|
+
const state = createMockState()
|
|
1425
|
+
const env = createMockEnv()
|
|
1426
|
+
const doo = new PostgresDO(state as any, env)
|
|
1427
|
+
|
|
1428
|
+
expect(doo.getMigrationStrategy()).toBe('on-create')
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
it('should return configured strategy', () => {
|
|
1432
|
+
const state = createMockState()
|
|
1433
|
+
const env = createMockEnv()
|
|
1434
|
+
const config: PostgresConfig = {
|
|
1435
|
+
database: 'test',
|
|
1436
|
+
migrations: {
|
|
1437
|
+
migrations: [],
|
|
1438
|
+
strategy: 'on-demand',
|
|
1439
|
+
},
|
|
1440
|
+
}
|
|
1441
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1442
|
+
|
|
1443
|
+
expect(doo.getMigrationStrategy()).toBe('on-demand')
|
|
1444
|
+
})
|
|
1445
|
+
})
|
|
1446
|
+
|
|
1447
|
+
describe('getMigrationState', () => {
|
|
1448
|
+
it('should return null when no state exists', async () => {
|
|
1449
|
+
const state = createMockState()
|
|
1450
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1451
|
+
const env = createMockEnv()
|
|
1452
|
+
const doo = new PostgresDO(state as any, env)
|
|
1453
|
+
|
|
1454
|
+
const migrationState = await doo.getMigrationState()
|
|
1455
|
+
expect(migrationState).toBeNull()
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
it('should return stored migration state', async () => {
|
|
1459
|
+
const state = createMockState()
|
|
1460
|
+
const storedState = {
|
|
1461
|
+
status: 'completed',
|
|
1462
|
+
version: 3,
|
|
1463
|
+
lastAttemptAt: '2024-01-01T00:00:00Z',
|
|
1464
|
+
durationMs: 150,
|
|
1465
|
+
}
|
|
1466
|
+
state.storage.get.mockResolvedValue(storedState)
|
|
1467
|
+
const env = createMockEnv()
|
|
1468
|
+
const doo = new PostgresDO(state as any, env)
|
|
1469
|
+
|
|
1470
|
+
const migrationState = await doo.getMigrationState()
|
|
1471
|
+
expect(migrationState).toEqual(storedState)
|
|
1472
|
+
})
|
|
1473
|
+
})
|
|
1474
|
+
|
|
1475
|
+
describe('needsLazyMigration', () => {
|
|
1476
|
+
it('should return false for on-create strategy', async () => {
|
|
1477
|
+
const state = createMockState()
|
|
1478
|
+
const env = createMockEnv()
|
|
1479
|
+
const config: PostgresConfig = {
|
|
1480
|
+
database: 'test',
|
|
1481
|
+
migrations: {
|
|
1482
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'SELECT 1' }],
|
|
1483
|
+
strategy: 'on-create',
|
|
1484
|
+
},
|
|
1485
|
+
}
|
|
1486
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1487
|
+
|
|
1488
|
+
const needs = await doo.needsLazyMigration()
|
|
1489
|
+
expect(needs).toBe(false)
|
|
1490
|
+
})
|
|
1491
|
+
|
|
1492
|
+
it('should return false when no migrations configured', async () => {
|
|
1493
|
+
const state = createMockState()
|
|
1494
|
+
const env = createMockEnv()
|
|
1495
|
+
const config: PostgresConfig = {
|
|
1496
|
+
database: 'test',
|
|
1497
|
+
migrations: {
|
|
1498
|
+
migrations: [],
|
|
1499
|
+
strategy: 'on-demand',
|
|
1500
|
+
},
|
|
1501
|
+
}
|
|
1502
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1503
|
+
|
|
1504
|
+
const needs = await doo.needsLazyMigration()
|
|
1505
|
+
expect(needs).toBe(false)
|
|
1506
|
+
})
|
|
1507
|
+
|
|
1508
|
+
it('should return true when no migration state exists', async () => {
|
|
1509
|
+
const state = createMockState()
|
|
1510
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1511
|
+
const env = createMockEnv()
|
|
1512
|
+
const config: PostgresConfig = {
|
|
1513
|
+
database: 'test',
|
|
1514
|
+
migrations: {
|
|
1515
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'SELECT 1' }],
|
|
1516
|
+
strategy: 'on-demand',
|
|
1517
|
+
},
|
|
1518
|
+
}
|
|
1519
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1520
|
+
|
|
1521
|
+
const needs = await doo.needsLazyMigration()
|
|
1522
|
+
expect(needs).toBe(true)
|
|
1523
|
+
})
|
|
1524
|
+
|
|
1525
|
+
it('should return false when migrations completed and up to date', async () => {
|
|
1526
|
+
const state = createMockState()
|
|
1527
|
+
state.storage.get.mockResolvedValue({
|
|
1528
|
+
status: 'completed',
|
|
1529
|
+
version: 1,
|
|
1530
|
+
lastAttemptAt: '2024-01-01T00:00:00Z',
|
|
1531
|
+
})
|
|
1532
|
+
const env = createMockEnv()
|
|
1533
|
+
const config: PostgresConfig = {
|
|
1534
|
+
database: 'test',
|
|
1535
|
+
migrations: {
|
|
1536
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'SELECT 1' }],
|
|
1537
|
+
strategy: 'on-demand',
|
|
1538
|
+
},
|
|
1539
|
+
}
|
|
1540
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1541
|
+
|
|
1542
|
+
const needs = await doo.needsLazyMigration()
|
|
1543
|
+
expect(needs).toBe(false)
|
|
1544
|
+
})
|
|
1545
|
+
|
|
1546
|
+
it('should return true when new migrations available', async () => {
|
|
1547
|
+
const state = createMockState()
|
|
1548
|
+
state.storage.get.mockResolvedValue({
|
|
1549
|
+
status: 'completed',
|
|
1550
|
+
version: 1,
|
|
1551
|
+
lastAttemptAt: '2024-01-01T00:00:00Z',
|
|
1552
|
+
})
|
|
1553
|
+
const env = createMockEnv()
|
|
1554
|
+
const config: PostgresConfig = {
|
|
1555
|
+
database: 'test',
|
|
1556
|
+
migrations: {
|
|
1557
|
+
migrations: [
|
|
1558
|
+
{ id: '1', name: 'test1', version: 1, sql: 'SELECT 1' },
|
|
1559
|
+
{ id: '2', name: 'test2', version: 2, sql: 'SELECT 2' },
|
|
1560
|
+
],
|
|
1561
|
+
strategy: 'on-demand',
|
|
1562
|
+
},
|
|
1563
|
+
}
|
|
1564
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1565
|
+
|
|
1566
|
+
const needs = await doo.needsLazyMigration()
|
|
1567
|
+
expect(needs).toBe(true)
|
|
1568
|
+
})
|
|
1569
|
+
|
|
1570
|
+
it('should return true when previous migration failed', async () => {
|
|
1571
|
+
const state = createMockState()
|
|
1572
|
+
state.storage.get.mockResolvedValue({
|
|
1573
|
+
status: 'failed',
|
|
1574
|
+
version: 0,
|
|
1575
|
+
lastAttemptAt: '2024-01-01T00:00:00Z',
|
|
1576
|
+
error: 'Previous error',
|
|
1577
|
+
})
|
|
1578
|
+
const env = createMockEnv()
|
|
1579
|
+
const config: PostgresConfig = {
|
|
1580
|
+
database: 'test',
|
|
1581
|
+
migrations: {
|
|
1582
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'SELECT 1' }],
|
|
1583
|
+
strategy: 'on-demand',
|
|
1584
|
+
},
|
|
1585
|
+
}
|
|
1586
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1587
|
+
|
|
1588
|
+
const needs = await doo.needsLazyMigration()
|
|
1589
|
+
expect(needs).toBe(true)
|
|
1590
|
+
})
|
|
1591
|
+
})
|
|
1592
|
+
|
|
1593
|
+
describe('on-demand migration during query', () => {
|
|
1594
|
+
it('should skip migrations during initPGlite for on-demand strategy', async () => {
|
|
1595
|
+
const state = createMockState()
|
|
1596
|
+
state.storage.get.mockResolvedValue(undefined) // No schema version
|
|
1597
|
+
const env = createMockEnv()
|
|
1598
|
+
const config: PostgresConfig = {
|
|
1599
|
+
database: 'test',
|
|
1600
|
+
migrations: {
|
|
1601
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'CREATE TABLE test (id INT)' }],
|
|
1602
|
+
strategy: 'on-demand',
|
|
1603
|
+
},
|
|
1604
|
+
}
|
|
1605
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1606
|
+
|
|
1607
|
+
const mockPGlite = createMockPGlite()
|
|
1608
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1609
|
+
|
|
1610
|
+
await doo.initPGlite()
|
|
1611
|
+
|
|
1612
|
+
// Migration should NOT have been called during init
|
|
1613
|
+
// The exec/query should not have been called for migration SQL
|
|
1614
|
+
const queryCalls = mockPGlite.query.mock.calls as [string, unknown[]][]
|
|
1615
|
+
const migrationQueries = queryCalls.filter(([sql]) =>
|
|
1616
|
+
sql && (sql.includes('CREATE TABLE test') || sql.includes('BEGIN'))
|
|
1617
|
+
)
|
|
1618
|
+
expect(migrationQueries).toHaveLength(0)
|
|
1619
|
+
})
|
|
1620
|
+
|
|
1621
|
+
it('should run migrations on first query for on-demand strategy', async () => {
|
|
1622
|
+
const state = createMockState()
|
|
1623
|
+
let storageData: Record<string, unknown> = {}
|
|
1624
|
+
state.storage.get.mockImplementation((key: string) => Promise.resolve(storageData[key]))
|
|
1625
|
+
state.storage.put.mockImplementation((key: string, value: unknown) => {
|
|
1626
|
+
storageData[key] = value
|
|
1627
|
+
return Promise.resolve()
|
|
1628
|
+
})
|
|
1629
|
+
const env = createMockEnv()
|
|
1630
|
+
const config: PostgresConfig = {
|
|
1631
|
+
database: 'test',
|
|
1632
|
+
migrations: {
|
|
1633
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'CREATE TABLE test (id INT)' }],
|
|
1634
|
+
strategy: 'on-demand',
|
|
1635
|
+
},
|
|
1636
|
+
}
|
|
1637
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1638
|
+
|
|
1639
|
+
const mockPGlite = createMockPGlite()
|
|
1640
|
+
// Mock all queries that migrations need - BEGIN, the migration SQL, COMMIT
|
|
1641
|
+
mockPGlite.query.mockResolvedValue({ rows: [], fields: [], affectedRows: 0 })
|
|
1642
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1643
|
+
|
|
1644
|
+
// Initialize without running migrations
|
|
1645
|
+
await doo.initPGlite()
|
|
1646
|
+
|
|
1647
|
+
// First query should trigger migrations
|
|
1648
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
1649
|
+
|
|
1650
|
+
// Migration state should now be saved
|
|
1651
|
+
const migrationState = storageData['__postgres_migration_state__'] as { status: string; version: number }
|
|
1652
|
+
expect(migrationState).toBeDefined()
|
|
1653
|
+
expect(migrationState.status).toBe('completed')
|
|
1654
|
+
expect(migrationState.version).toBe(1)
|
|
1655
|
+
})
|
|
1656
|
+
|
|
1657
|
+
it('should not run migrations on subsequent queries', async () => {
|
|
1658
|
+
const state = createMockState()
|
|
1659
|
+
let storageData: Record<string, unknown> = {
|
|
1660
|
+
'__postgres_migration_state__': {
|
|
1661
|
+
status: 'completed',
|
|
1662
|
+
version: 1,
|
|
1663
|
+
lastAttemptAt: '2024-01-01T00:00:00Z',
|
|
1664
|
+
},
|
|
1665
|
+
}
|
|
1666
|
+
state.storage.get.mockImplementation((key: string) => Promise.resolve(storageData[key]))
|
|
1667
|
+
state.storage.put.mockImplementation((key: string, value: unknown) => {
|
|
1668
|
+
storageData[key] = value
|
|
1669
|
+
return Promise.resolve()
|
|
1670
|
+
})
|
|
1671
|
+
const env = createMockEnv()
|
|
1672
|
+
const config: PostgresConfig = {
|
|
1673
|
+
database: 'test',
|
|
1674
|
+
migrations: {
|
|
1675
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'CREATE TABLE test (id INT)' }],
|
|
1676
|
+
strategy: 'on-demand',
|
|
1677
|
+
},
|
|
1678
|
+
}
|
|
1679
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1680
|
+
|
|
1681
|
+
const mockPGlite = createMockPGlite()
|
|
1682
|
+
let execCallCount = 0
|
|
1683
|
+
mockPGlite.exec.mockImplementation(() => {
|
|
1684
|
+
execCallCount++
|
|
1685
|
+
return Promise.resolve({ rows: [], fields: [] })
|
|
1686
|
+
})
|
|
1687
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1688
|
+
|
|
1689
|
+
await doo.initPGlite()
|
|
1690
|
+
|
|
1691
|
+
// Run multiple queries
|
|
1692
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
1693
|
+
await doo.executeQuery({ sql: 'SELECT 2' })
|
|
1694
|
+
await doo.executeQuery({ sql: 'SELECT 3' })
|
|
1695
|
+
|
|
1696
|
+
// exec should NOT have been called for migrations (already completed)
|
|
1697
|
+
expect(execCallCount).toBe(0)
|
|
1698
|
+
})
|
|
1699
|
+
})
|
|
1700
|
+
|
|
1701
|
+
describe('rpcQuery with on-demand migrations', () => {
|
|
1702
|
+
it('should run migrations before first RPC query', async () => {
|
|
1703
|
+
const state = createMockState()
|
|
1704
|
+
let storageData: Record<string, unknown> = {}
|
|
1705
|
+
state.storage.get.mockImplementation((key: string) => Promise.resolve(storageData[key]))
|
|
1706
|
+
state.storage.put.mockImplementation((key: string, value: unknown) => {
|
|
1707
|
+
storageData[key] = value
|
|
1708
|
+
return Promise.resolve()
|
|
1709
|
+
})
|
|
1710
|
+
const env = createMockEnv()
|
|
1711
|
+
const config: PostgresConfig = {
|
|
1712
|
+
database: 'test',
|
|
1713
|
+
migrations: {
|
|
1714
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'CREATE TABLE test (id INT)' }],
|
|
1715
|
+
strategy: 'on-demand',
|
|
1716
|
+
},
|
|
1717
|
+
}
|
|
1718
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1719
|
+
|
|
1720
|
+
const mockPGlite = createMockPGlite()
|
|
1721
|
+
// Mock all queries that migrations need
|
|
1722
|
+
mockPGlite.query.mockResolvedValue({ rows: [], fields: [], affectedRows: 0 })
|
|
1723
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1724
|
+
|
|
1725
|
+
await doo.initPGlite()
|
|
1726
|
+
|
|
1727
|
+
// RPC query should trigger migrations
|
|
1728
|
+
await doo.rpcQuery('SELECT 1')
|
|
1729
|
+
|
|
1730
|
+
// Migration state should be saved
|
|
1731
|
+
const migrationState = storageData['__postgres_migration_state__'] as { status: string }
|
|
1732
|
+
expect(migrationState).toBeDefined()
|
|
1733
|
+
expect(migrationState.status).toBe('completed')
|
|
1734
|
+
})
|
|
1735
|
+
})
|
|
1736
|
+
})
|
|
1737
|
+
|
|
1738
|
+
// ============================================================================
|
|
1739
|
+
// RED PHASE: Initialization Decoupling Tests
|
|
1740
|
+
// Task: postgres-ud8w.1 - [RED] Add tests for initialization decoupling
|
|
1741
|
+
//
|
|
1742
|
+
// Tests for decoupling DO and PGLite initialization:
|
|
1743
|
+
// 1. DO can be created without PGLite
|
|
1744
|
+
// 2. PGLite can be created without DO context
|
|
1745
|
+
// 3. Lazy initialization of PGLite
|
|
1746
|
+
// 4. Proper initialization ordering
|
|
1747
|
+
// 5. Error handling when dependencies missing
|
|
1748
|
+
// 6. Dependency injection for testability
|
|
1749
|
+
// 7. Factory pattern for PGLite creation
|
|
1750
|
+
// ============================================================================
|
|
1751
|
+
|
|
1752
|
+
describe('Initialization Decoupling', () => {
|
|
1753
|
+
describe('DO creation without PGLite', () => {
|
|
1754
|
+
it('should create DO instance without initializing PGLite', () => {
|
|
1755
|
+
const state = createMockState()
|
|
1756
|
+
const env = createMockEnv()
|
|
1757
|
+
const doo = new PostgresDO(state as any, env)
|
|
1758
|
+
|
|
1759
|
+
// DO should be created
|
|
1760
|
+
expect(doo).toBeInstanceOf(PostgresDO)
|
|
1761
|
+
// PGLite should NOT be initialized
|
|
1762
|
+
expect(doo.isInitialized()).toBe(false)
|
|
1763
|
+
})
|
|
1764
|
+
|
|
1765
|
+
it('should allow accessing DO methods before PGLite initialization', () => {
|
|
1766
|
+
const state = createMockState()
|
|
1767
|
+
const env = createMockEnv()
|
|
1768
|
+
const doo = new PostgresDO(state as any, env)
|
|
1769
|
+
|
|
1770
|
+
// These methods should work without PGLite
|
|
1771
|
+
expect(doo.getState()).toBe(state)
|
|
1772
|
+
expect(doo.getEnv()).toBe(env)
|
|
1773
|
+
expect(doo.getConfig()).toBeDefined()
|
|
1774
|
+
expect(doo.getShutdownStatus()).toBe('running')
|
|
1775
|
+
expect(doo.isInitialized()).toBe(false)
|
|
1776
|
+
})
|
|
1777
|
+
|
|
1778
|
+
it('should return null from getPGLiteManager.getInstanceOrNull() before initialization', () => {
|
|
1779
|
+
const state = createMockState()
|
|
1780
|
+
const env = createMockEnv()
|
|
1781
|
+
const doo = new PostgresDO(state as any, env)
|
|
1782
|
+
|
|
1783
|
+
const manager = doo.getPGLiteManager()
|
|
1784
|
+
expect(manager.getInstanceOrNull()).toBeNull()
|
|
1785
|
+
})
|
|
1786
|
+
|
|
1787
|
+
it('should throw from getPGLiteManager.getInstance() before initialization', () => {
|
|
1788
|
+
const state = createMockState()
|
|
1789
|
+
const env = createMockEnv()
|
|
1790
|
+
const doo = new PostgresDO(state as any, env)
|
|
1791
|
+
|
|
1792
|
+
const manager = doo.getPGLiteManager()
|
|
1793
|
+
expect(() => manager.getInstance()).toThrow('PGLite not initialized')
|
|
1794
|
+
})
|
|
1795
|
+
})
|
|
1796
|
+
|
|
1797
|
+
describe('PGLiteManager independence from DO', () => {
|
|
1798
|
+
it('should allow creating PGLiteManager without DO context', async () => {
|
|
1799
|
+
// Import PGLiteManager directly
|
|
1800
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
1801
|
+
|
|
1802
|
+
// Create manager without any DO
|
|
1803
|
+
const manager = new PGLiteManager({
|
|
1804
|
+
database: 'test-db',
|
|
1805
|
+
debug: false,
|
|
1806
|
+
})
|
|
1807
|
+
|
|
1808
|
+
expect(manager).toBeDefined()
|
|
1809
|
+
expect(manager.isInitialized()).toBe(false)
|
|
1810
|
+
})
|
|
1811
|
+
|
|
1812
|
+
it('should allow injecting custom PGLite factory for testing', async () => {
|
|
1813
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
1814
|
+
const mockPGlite = createMockPGlite()
|
|
1815
|
+
|
|
1816
|
+
const manager = new PGLiteManager({
|
|
1817
|
+
database: 'test-db',
|
|
1818
|
+
createPGLite: () => Promise.resolve(mockPGlite),
|
|
1819
|
+
})
|
|
1820
|
+
|
|
1821
|
+
await manager.initialize()
|
|
1822
|
+
|
|
1823
|
+
expect(manager.isInitialized()).toBe(true)
|
|
1824
|
+
expect(manager.getInstance()).toBe(mockPGlite)
|
|
1825
|
+
})
|
|
1826
|
+
|
|
1827
|
+
it('should allow injecting pre-created PGLite instance', async () => {
|
|
1828
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
1829
|
+
const mockPGlite = createMockPGlite()
|
|
1830
|
+
|
|
1831
|
+
const manager = new PGLiteManager({ database: 'test-db' })
|
|
1832
|
+
manager.injectInstance(mockPGlite)
|
|
1833
|
+
|
|
1834
|
+
expect(manager.isInitialized()).toBe(true)
|
|
1835
|
+
expect(manager.getInstance()).toBe(mockPGlite)
|
|
1836
|
+
})
|
|
1837
|
+
|
|
1838
|
+
it('should allow setting factory after construction', async () => {
|
|
1839
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
1840
|
+
const mockPGlite = createMockPGlite()
|
|
1841
|
+
|
|
1842
|
+
const manager = new PGLiteManager({ database: 'test-db' })
|
|
1843
|
+
manager.setCreatePGLite(() => Promise.resolve(mockPGlite))
|
|
1844
|
+
await manager.initialize()
|
|
1845
|
+
|
|
1846
|
+
expect(manager.isInitialized()).toBe(true)
|
|
1847
|
+
expect(manager.getInstance()).toBe(mockPGlite)
|
|
1848
|
+
})
|
|
1849
|
+
})
|
|
1850
|
+
|
|
1851
|
+
describe('Lazy initialization', () => {
|
|
1852
|
+
it('should defer PGLite creation until first use', async () => {
|
|
1853
|
+
const state = createMockState()
|
|
1854
|
+
const env = createMockEnv()
|
|
1855
|
+
const doo = new PostgresDO(state as any, env)
|
|
1856
|
+
|
|
1857
|
+
const mockPGlite = createMockPGlite()
|
|
1858
|
+
const createSpy = vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1859
|
+
|
|
1860
|
+
// PGLite should not be created yet
|
|
1861
|
+
expect(createSpy).not.toHaveBeenCalled()
|
|
1862
|
+
expect(doo.isInitialized()).toBe(false)
|
|
1863
|
+
|
|
1864
|
+
// First query triggers initialization
|
|
1865
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
1866
|
+
|
|
1867
|
+
// Now PGLite should be created
|
|
1868
|
+
expect(createSpy).toHaveBeenCalledTimes(1)
|
|
1869
|
+
expect(doo.isInitialized()).toBe(true)
|
|
1870
|
+
})
|
|
1871
|
+
|
|
1872
|
+
it('should not re-initialize on subsequent queries', async () => {
|
|
1873
|
+
const state = createMockState()
|
|
1874
|
+
const env = createMockEnv()
|
|
1875
|
+
const doo = new PostgresDO(state as any, env)
|
|
1876
|
+
|
|
1877
|
+
const mockPGlite = createMockPGlite()
|
|
1878
|
+
const createSpy = vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1879
|
+
|
|
1880
|
+
// Multiple queries
|
|
1881
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
1882
|
+
await doo.executeQuery({ sql: 'SELECT 2' })
|
|
1883
|
+
await doo.executeQuery({ sql: 'SELECT 3' })
|
|
1884
|
+
|
|
1885
|
+
// Should only initialize once
|
|
1886
|
+
expect(createSpy).toHaveBeenCalledTimes(1)
|
|
1887
|
+
})
|
|
1888
|
+
|
|
1889
|
+
it('should support explicit initialization before first use', async () => {
|
|
1890
|
+
const state = createMockState()
|
|
1891
|
+
const env = createMockEnv()
|
|
1892
|
+
const doo = new PostgresDO(state as any, env)
|
|
1893
|
+
|
|
1894
|
+
const mockPGlite = createMockPGlite()
|
|
1895
|
+
const createSpy = vi.spyOn(doo as any, 'createPGliteInstance').mockResolvedValue(mockPGlite)
|
|
1896
|
+
|
|
1897
|
+
// Explicit initialization
|
|
1898
|
+
await doo.initPGlite()
|
|
1899
|
+
|
|
1900
|
+
expect(doo.isInitialized()).toBe(true)
|
|
1901
|
+
expect(createSpy).toHaveBeenCalledTimes(1)
|
|
1902
|
+
|
|
1903
|
+
// Subsequent query should not re-initialize
|
|
1904
|
+
await doo.executeQuery({ sql: 'SELECT 1' })
|
|
1905
|
+
expect(createSpy).toHaveBeenCalledTimes(1)
|
|
1906
|
+
})
|
|
1907
|
+
})
|
|
1908
|
+
|
|
1909
|
+
describe('Initialization ordering', () => {
|
|
1910
|
+
it('should initialize components in correct order: factory -> plugins -> migrations', async () => {
|
|
1911
|
+
const state = createMockState()
|
|
1912
|
+
state.storage.get.mockResolvedValue(undefined)
|
|
1913
|
+
const env = createMockEnv()
|
|
1914
|
+
const config: PostgresConfig = {
|
|
1915
|
+
database: 'test',
|
|
1916
|
+
plugins: { vector: true },
|
|
1917
|
+
migrations: {
|
|
1918
|
+
migrations: [{ id: '1', name: 'test', version: 1, sql: 'SELECT 1' }],
|
|
1919
|
+
},
|
|
1920
|
+
}
|
|
1921
|
+
const doo = new PostgresDO(state as any, env, config)
|
|
1922
|
+
|
|
1923
|
+
const executionOrder: string[] = []
|
|
1924
|
+
const mockPGlite = createMockPGlite()
|
|
1925
|
+
|
|
1926
|
+
// Track when PGLite is created
|
|
1927
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockImplementation(async () => {
|
|
1928
|
+
executionOrder.push('pglite_created')
|
|
1929
|
+
return mockPGlite
|
|
1930
|
+
})
|
|
1931
|
+
|
|
1932
|
+
// Track when plugins are loaded
|
|
1933
|
+
vi.spyOn(doo as any, 'runPluginAutoCreate').mockImplementation(async () => {
|
|
1934
|
+
executionOrder.push('plugins_loaded')
|
|
1935
|
+
})
|
|
1936
|
+
|
|
1937
|
+
await doo.initPGlite()
|
|
1938
|
+
|
|
1939
|
+
// PGLite should be created before plugins
|
|
1940
|
+
expect(executionOrder.indexOf('pglite_created')).toBeLessThan(
|
|
1941
|
+
executionOrder.indexOf('plugins_loaded')
|
|
1942
|
+
)
|
|
1943
|
+
})
|
|
1944
|
+
|
|
1945
|
+
it('should handle concurrent initialization calls safely', async () => {
|
|
1946
|
+
const state = createMockState()
|
|
1947
|
+
const env = createMockEnv()
|
|
1948
|
+
const doo = new PostgresDO(state as any, env)
|
|
1949
|
+
|
|
1950
|
+
const mockPGlite = createMockPGlite()
|
|
1951
|
+
let initCount = 0
|
|
1952
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockImplementation(async () => {
|
|
1953
|
+
initCount++
|
|
1954
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
1955
|
+
return mockPGlite
|
|
1956
|
+
})
|
|
1957
|
+
|
|
1958
|
+
// Start multiple concurrent initializations
|
|
1959
|
+
const promises = [
|
|
1960
|
+
doo.initPGlite(),
|
|
1961
|
+
doo.initPGlite(),
|
|
1962
|
+
doo.initPGlite(),
|
|
1963
|
+
]
|
|
1964
|
+
|
|
1965
|
+
await Promise.all(promises)
|
|
1966
|
+
|
|
1967
|
+
// Should only actually initialize once
|
|
1968
|
+
expect(initCount).toBe(1)
|
|
1969
|
+
})
|
|
1970
|
+
})
|
|
1971
|
+
|
|
1972
|
+
describe('Dependency injection for testability', () => {
|
|
1973
|
+
it('should allow injecting mock PGLite via manager', async () => {
|
|
1974
|
+
const state = createMockState()
|
|
1975
|
+
const env = createMockEnv()
|
|
1976
|
+
const doo = new PostgresDO(state as any, env)
|
|
1977
|
+
|
|
1978
|
+
const mockPGlite = createMockPGlite()
|
|
1979
|
+
mockPGlite.query.mockResolvedValue({
|
|
1980
|
+
rows: [{ result: 42 }],
|
|
1981
|
+
fields: [{ name: 'result', dataTypeID: 23 }],
|
|
1982
|
+
affectedRows: 1,
|
|
1983
|
+
})
|
|
1984
|
+
|
|
1985
|
+
// Inject mock directly into manager
|
|
1986
|
+
doo.getPGLiteManager().injectInstance(mockPGlite)
|
|
1987
|
+
|
|
1988
|
+
// Query should use the injected mock
|
|
1989
|
+
const result = await doo.executeQuery({ sql: 'SELECT 42 as result' })
|
|
1990
|
+
|
|
1991
|
+
expect(result.rows).toEqual([{ result: 42 }])
|
|
1992
|
+
expect(mockPGlite.query).toHaveBeenCalled()
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
it('should allow setting factory function on manager', async () => {
|
|
1996
|
+
const state = createMockState()
|
|
1997
|
+
const env = createMockEnv()
|
|
1998
|
+
const doo = new PostgresDO(state as any, env)
|
|
1999
|
+
|
|
2000
|
+
const mockPGlite = createMockPGlite()
|
|
2001
|
+
let factoryCalled = false
|
|
2002
|
+
|
|
2003
|
+
doo.getPGLiteManager().setCreatePGLite(async () => {
|
|
2004
|
+
factoryCalled = true
|
|
2005
|
+
return mockPGlite
|
|
2006
|
+
})
|
|
2007
|
+
|
|
2008
|
+
await doo.initPGlite()
|
|
2009
|
+
|
|
2010
|
+
expect(factoryCalled).toBe(true)
|
|
2011
|
+
expect(doo.isInitialized()).toBe(true)
|
|
2012
|
+
})
|
|
2013
|
+
|
|
2014
|
+
it('should override default WASM loading with custom factory', async () => {
|
|
2015
|
+
const state = createMockState()
|
|
2016
|
+
const env = createMockEnv()
|
|
2017
|
+
const doo = new PostgresDO(state as any, env)
|
|
2018
|
+
|
|
2019
|
+
const mockPGlite = createMockPGlite()
|
|
2020
|
+
const customFactory = vi.fn().mockResolvedValue(mockPGlite)
|
|
2021
|
+
|
|
2022
|
+
doo.getPGLiteManager().setCreatePGLite(customFactory)
|
|
2023
|
+
await doo.initPGlite()
|
|
2024
|
+
|
|
2025
|
+
// Custom factory should be used instead of default WASM loading
|
|
2026
|
+
expect(customFactory).toHaveBeenCalled()
|
|
2027
|
+
})
|
|
2028
|
+
})
|
|
2029
|
+
|
|
2030
|
+
describe('Error handling when dependencies missing', () => {
|
|
2031
|
+
it('should throw descriptive error when trying to execute query without initialization and factory fails', async () => {
|
|
2032
|
+
const state = createMockState()
|
|
2033
|
+
const env = createMockEnv()
|
|
2034
|
+
const doo = new PostgresDO(state as any, env)
|
|
2035
|
+
|
|
2036
|
+
vi.spyOn(doo as any, 'createPGliteInstance').mockRejectedValue(
|
|
2037
|
+
new Error('WASM module not found')
|
|
2038
|
+
)
|
|
2039
|
+
|
|
2040
|
+
await expect(doo.executeQuery({ sql: 'SELECT 1' })).rejects.toThrow(
|
|
2041
|
+
'WASM module not found'
|
|
2042
|
+
)
|
|
2043
|
+
})
|
|
2044
|
+
|
|
2045
|
+
it('should provide clear error when getInstance called before init', async () => {
|
|
2046
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2047
|
+
const manager = new PGLiteManager({ database: 'test' })
|
|
2048
|
+
|
|
2049
|
+
expect(() => manager.getInstance()).toThrow('PGLite not initialized')
|
|
2050
|
+
})
|
|
2051
|
+
|
|
2052
|
+
it('should handle factory returning null gracefully', async () => {
|
|
2053
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2054
|
+
const manager = new PGLiteManager({
|
|
2055
|
+
database: 'test',
|
|
2056
|
+
createPGLite: () => Promise.resolve(null as any),
|
|
2057
|
+
})
|
|
2058
|
+
|
|
2059
|
+
// Should throw during initialization when factory returns null
|
|
2060
|
+
await expect(manager.initialize()).rejects.toThrow()
|
|
2061
|
+
})
|
|
2062
|
+
|
|
2063
|
+
it('should propagate factory errors with context', async () => {
|
|
2064
|
+
const state = createMockState()
|
|
2065
|
+
const env = createMockEnv()
|
|
2066
|
+
const doo = new PostgresDO(state as any, env)
|
|
2067
|
+
|
|
2068
|
+
const originalError = new Error('Custom factory failed')
|
|
2069
|
+
doo.getPGLiteManager().setCreatePGLite(() => Promise.reject(originalError))
|
|
2070
|
+
|
|
2071
|
+
try {
|
|
2072
|
+
await doo.initPGlite()
|
|
2073
|
+
expect.fail('Should have thrown')
|
|
2074
|
+
} catch (error) {
|
|
2075
|
+
expect(error).toBe(originalError)
|
|
2076
|
+
}
|
|
2077
|
+
})
|
|
2078
|
+
})
|
|
2079
|
+
|
|
2080
|
+
describe('Manager reset and cleanup', () => {
|
|
2081
|
+
it('should allow resetting manager state', async () => {
|
|
2082
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2083
|
+
const mockPGlite = createMockPGlite()
|
|
2084
|
+
|
|
2085
|
+
const manager = new PGLiteManager({
|
|
2086
|
+
database: 'test',
|
|
2087
|
+
createPGLite: () => Promise.resolve(mockPGlite),
|
|
2088
|
+
})
|
|
2089
|
+
|
|
2090
|
+
await manager.initialize()
|
|
2091
|
+
expect(manager.isInitialized()).toBe(true)
|
|
2092
|
+
|
|
2093
|
+
manager.reset()
|
|
2094
|
+
expect(manager.isInitialized()).toBe(false)
|
|
2095
|
+
expect(manager.getInstanceOrNull()).toBeNull()
|
|
2096
|
+
})
|
|
2097
|
+
|
|
2098
|
+
it('should allow re-initialization after reset', async () => {
|
|
2099
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2100
|
+
const mockPGlite1 = createMockPGlite()
|
|
2101
|
+
const mockPGlite2 = createMockPGlite()
|
|
2102
|
+
let callCount = 0
|
|
2103
|
+
|
|
2104
|
+
const manager = new PGLiteManager({
|
|
2105
|
+
database: 'test',
|
|
2106
|
+
createPGLite: () => Promise.resolve(callCount++ === 0 ? mockPGlite1 : mockPGlite2),
|
|
2107
|
+
})
|
|
2108
|
+
|
|
2109
|
+
// First initialization
|
|
2110
|
+
await manager.initialize()
|
|
2111
|
+
expect(manager.getInstance()).toBe(mockPGlite1)
|
|
2112
|
+
|
|
2113
|
+
// Reset
|
|
2114
|
+
manager.reset()
|
|
2115
|
+
|
|
2116
|
+
// Re-initialize
|
|
2117
|
+
await manager.initialize()
|
|
2118
|
+
expect(manager.getInstance()).toBe(mockPGlite2)
|
|
2119
|
+
})
|
|
2120
|
+
|
|
2121
|
+
it('should close PGLite instance on manager close', async () => {
|
|
2122
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2123
|
+
const mockPGlite = createMockPGlite()
|
|
2124
|
+
|
|
2125
|
+
const manager = new PGLiteManager({
|
|
2126
|
+
database: 'test',
|
|
2127
|
+
createPGLite: () => Promise.resolve(mockPGlite),
|
|
2128
|
+
})
|
|
2129
|
+
|
|
2130
|
+
await manager.initialize()
|
|
2131
|
+
await manager.close()
|
|
2132
|
+
|
|
2133
|
+
expect(mockPGlite.close).toHaveBeenCalled()
|
|
2134
|
+
expect(manager.isInitialized()).toBe(false)
|
|
2135
|
+
})
|
|
2136
|
+
})
|
|
2137
|
+
|
|
2138
|
+
describe('Liveness check without full initialization', () => {
|
|
2139
|
+
it('should return false for liveness check before initialization', async () => {
|
|
2140
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2141
|
+
const manager = new PGLiteManager({ database: 'test' })
|
|
2142
|
+
|
|
2143
|
+
const isAlive = await manager.checkLiveness()
|
|
2144
|
+
expect(isAlive).toBe(false)
|
|
2145
|
+
})
|
|
2146
|
+
|
|
2147
|
+
it('should return true for liveness check after successful initialization', async () => {
|
|
2148
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2149
|
+
const mockPGlite = createMockPGlite()
|
|
2150
|
+
mockPGlite.query.mockResolvedValue({
|
|
2151
|
+
rows: [{ alive: 1 }],
|
|
2152
|
+
fields: [],
|
|
2153
|
+
affectedRows: 0,
|
|
2154
|
+
})
|
|
2155
|
+
|
|
2156
|
+
const manager = new PGLiteManager({
|
|
2157
|
+
database: 'test',
|
|
2158
|
+
createPGLite: () => Promise.resolve(mockPGlite),
|
|
2159
|
+
})
|
|
2160
|
+
|
|
2161
|
+
await manager.initialize()
|
|
2162
|
+
const isAlive = await manager.checkLiveness()
|
|
2163
|
+
expect(isAlive).toBe(true)
|
|
2164
|
+
})
|
|
2165
|
+
|
|
2166
|
+
it('should return false for liveness check if query fails', async () => {
|
|
2167
|
+
const { PGLiteManager } = await import('./do-pglite-manager')
|
|
2168
|
+
const mockPGlite = createMockPGlite()
|
|
2169
|
+
mockPGlite.query.mockRejectedValue(new Error('Query failed'))
|
|
2170
|
+
|
|
2171
|
+
const manager = new PGLiteManager({
|
|
2172
|
+
database: 'test',
|
|
2173
|
+
createPGLite: () => Promise.resolve(mockPGlite),
|
|
2174
|
+
})
|
|
2175
|
+
|
|
2176
|
+
await manager.initialize()
|
|
2177
|
+
const isAlive = await manager.checkLiveness()
|
|
2178
|
+
expect(isAlive).toBe(false)
|
|
2179
|
+
})
|
|
2180
|
+
})
|
|
2181
|
+
})
|
|
2182
|
+
})
|
|
2183
|
+
|
|
2184
|
+
// MOCK REDUCTION EXAMPLES (postgres-6qei)
|
|
2185
|
+
// These tests demonstrate the recommended pattern using helper functions.
|
|
2186
|
+
describe('PostgresDO with Mock Reduction Helpers', () => {
|
|
2187
|
+
describe('Query execution (using createTestDO)', () => {
|
|
2188
|
+
it('should execute queries using injected mock', async () => {
|
|
2189
|
+
const { doo } = createTestDO({
|
|
2190
|
+
mockPGliteSetup: (mock) => {
|
|
2191
|
+
mock.query.mockResolvedValue({
|
|
2192
|
+
rows: [{ id: 1, name: 'Alice' }],
|
|
2193
|
+
fields: [{ name: 'id', dataTypeID: 23 }],
|
|
2194
|
+
affectedRows: 1,
|
|
2195
|
+
})
|
|
2196
|
+
},
|
|
2197
|
+
})
|
|
2198
|
+
const result = await doo.executeQuery({ sql: 'SELECT * FROM users' })
|
|
2199
|
+
expect(result.rows).toEqual([{ id: 1, name: 'Alice' }])
|
|
2200
|
+
})
|
|
2201
|
+
|
|
2202
|
+
it('should pass parameters correctly', async () => {
|
|
2203
|
+
const { doo, mockPGlite } = createTestDO()
|
|
2204
|
+
await doo.executeQuery({
|
|
2205
|
+
sql: 'SELECT * FROM users WHERE id = $1',
|
|
2206
|
+
params: [42],
|
|
2207
|
+
})
|
|
2208
|
+
expect(mockPGlite.query).toHaveBeenCalledWith(
|
|
2209
|
+
'SELECT * FROM users WHERE id = $1',
|
|
2210
|
+
[42]
|
|
2211
|
+
)
|
|
2212
|
+
})
|
|
2213
|
+
})
|
|
2214
|
+
|
|
2215
|
+
describe('Initialization (using createTestDOWithFactory)', () => {
|
|
2216
|
+
it('should initialize PGlite on first call', async () => {
|
|
2217
|
+
const { doo } = createTestDOWithFactory()
|
|
2218
|
+
expect(doo.isInitialized()).toBe(false)
|
|
2219
|
+
await doo.initPGlite()
|
|
2220
|
+
expect(doo.isInitialized()).toBe(true)
|
|
2221
|
+
})
|
|
2222
|
+
|
|
2223
|
+
it('should prevent double initialization', async () => {
|
|
2224
|
+
let factoryCallCount = 0
|
|
2225
|
+
const state = createMockState()
|
|
2226
|
+
const env = createMockEnv()
|
|
2227
|
+
const doo = new PostgresDO(state as any, env)
|
|
2228
|
+
const mockPGlite = createMockPGlite()
|
|
2229
|
+
doo.getPGLiteManager().setCreatePGLite(async () => {
|
|
2230
|
+
factoryCallCount++
|
|
2231
|
+
return mockPGlite
|
|
2232
|
+
})
|
|
2233
|
+
await doo.initPGlite()
|
|
2234
|
+
await doo.initPGlite()
|
|
2235
|
+
expect(factoryCallCount).toBe(1)
|
|
2236
|
+
})
|
|
2237
|
+
})
|
|
2238
|
+
|
|
2239
|
+
describe('Shutdown (using createTestDO)', () => {
|
|
2240
|
+
it('should close PGlite during shutdown', async () => {
|
|
2241
|
+
const { doo, mockPGlite } = createTestDO()
|
|
2242
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
2243
|
+
expect(mockPGlite.close).toHaveBeenCalled()
|
|
2244
|
+
expect(doo.getShutdownStatus()).toBe('shutdown')
|
|
2245
|
+
})
|
|
2246
|
+
|
|
2247
|
+
it('should reject queries after shutdown', async () => {
|
|
2248
|
+
const { doo } = createTestDO()
|
|
2249
|
+
await doo.shutdown({ gracePeriodMs: 100 })
|
|
2250
|
+
await expect(doo.executeQuery({ sql: 'SELECT 1' }))
|
|
2251
|
+
.rejects.toThrow('PostgresDO is shut down')
|
|
2252
|
+
})
|
|
2253
|
+
})
|
|
2254
|
+
})
|