@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,1505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Circuit Breaker Pattern
|
|
3
|
+
*
|
|
4
|
+
* Task: postgres-rai
|
|
5
|
+
*
|
|
6
|
+
* Tests all state transitions and behaviors of the circuit breaker
|
|
7
|
+
* pattern implementation for Durable Object resilience.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
11
|
+
import {
|
|
12
|
+
CircuitBreaker,
|
|
13
|
+
CircuitOpenError,
|
|
14
|
+
DOCircuitBreakerWrapper,
|
|
15
|
+
CircuitState,
|
|
16
|
+
CircuitBreakerEvent,
|
|
17
|
+
CircuitBreakerConfig,
|
|
18
|
+
CircuitBreakerStats,
|
|
19
|
+
DOStub,
|
|
20
|
+
DefaultMetricsCollector,
|
|
21
|
+
CircuitBreakerMetricsCollector,
|
|
22
|
+
CircuitBreakerMetricsSummary,
|
|
23
|
+
} from './circuit-breaker'
|
|
24
|
+
|
|
25
|
+
describe('CircuitBreaker', () => {
|
|
26
|
+
describe('initial state', () => {
|
|
27
|
+
it('should start in CLOSED state', () => {
|
|
28
|
+
const cb = new CircuitBreaker()
|
|
29
|
+
expect(cb.getState('test-instance')).toBe('CLOSED')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should allow execution in initial CLOSED state', () => {
|
|
33
|
+
const cb = new CircuitBreaker()
|
|
34
|
+
expect(cb.canExecute('test-instance')).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should initialize with default configuration', () => {
|
|
38
|
+
const cb = new CircuitBreaker()
|
|
39
|
+
const config = cb.getConfig()
|
|
40
|
+
|
|
41
|
+
expect(config.failureThreshold).toBe(5)
|
|
42
|
+
expect(config.resetTimeoutMs).toBe(30000)
|
|
43
|
+
expect(config.halfOpenSuccessThreshold).toBe(3)
|
|
44
|
+
expect(config.failureWindowMs).toBe(60000)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should accept custom configuration', () => {
|
|
48
|
+
const cb = new CircuitBreaker({
|
|
49
|
+
failureThreshold: 3,
|
|
50
|
+
resetTimeoutMs: 10000,
|
|
51
|
+
halfOpenSuccessThreshold: 2,
|
|
52
|
+
failureWindowMs: 30000,
|
|
53
|
+
})
|
|
54
|
+
const config = cb.getConfig()
|
|
55
|
+
|
|
56
|
+
expect(config.failureThreshold).toBe(3)
|
|
57
|
+
expect(config.resetTimeoutMs).toBe(10000)
|
|
58
|
+
expect(config.halfOpenSuccessThreshold).toBe(2)
|
|
59
|
+
expect(config.failureWindowMs).toBe(30000)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('CLOSED -> OPEN transition', () => {
|
|
64
|
+
it('should open circuit after consecutive failures exceed threshold', () => {
|
|
65
|
+
const cb = new CircuitBreaker({ failureThreshold: 3 })
|
|
66
|
+
const instanceId = 'test-instance'
|
|
67
|
+
|
|
68
|
+
cb.recordFailure(instanceId)
|
|
69
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
70
|
+
|
|
71
|
+
cb.recordFailure(instanceId)
|
|
72
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
73
|
+
|
|
74
|
+
cb.recordFailure(instanceId)
|
|
75
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should not allow execution when OPEN', () => {
|
|
79
|
+
const cb = new CircuitBreaker({ failureThreshold: 2 })
|
|
80
|
+
const instanceId = 'test-instance'
|
|
81
|
+
|
|
82
|
+
cb.recordFailure(instanceId)
|
|
83
|
+
cb.recordFailure(instanceId)
|
|
84
|
+
|
|
85
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
86
|
+
expect(cb.canExecute(instanceId)).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should emit STATE_CHANGE event when opening', () => {
|
|
90
|
+
const events: CircuitBreakerEvent[] = []
|
|
91
|
+
const cb = new CircuitBreaker({
|
|
92
|
+
failureThreshold: 2,
|
|
93
|
+
onEvent: (event) => events.push(event),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
cb.recordFailure('test-instance')
|
|
97
|
+
cb.recordFailure('test-instance')
|
|
98
|
+
|
|
99
|
+
const stateChangeEvent = events.find(e => e.type === 'STATE_CHANGE')
|
|
100
|
+
expect(stateChangeEvent).toBeDefined()
|
|
101
|
+
expect(stateChangeEvent!.type).toBe('STATE_CHANGE')
|
|
102
|
+
if (stateChangeEvent!.type === 'STATE_CHANGE') {
|
|
103
|
+
expect(stateChangeEvent!.from).toBe('CLOSED')
|
|
104
|
+
expect(stateChangeEvent!.to).toBe('OPEN')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should emit FAILURE_RECORDED events', () => {
|
|
109
|
+
const events: CircuitBreakerEvent[] = []
|
|
110
|
+
const cb = new CircuitBreaker({
|
|
111
|
+
failureThreshold: 5,
|
|
112
|
+
onEvent: (event) => events.push(event),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
cb.recordFailure('test-instance')
|
|
116
|
+
|
|
117
|
+
const failureEvent = events.find(e => e.type === 'FAILURE_RECORDED')
|
|
118
|
+
expect(failureEvent).toBeDefined()
|
|
119
|
+
if (failureEvent?.type === 'FAILURE_RECORDED') {
|
|
120
|
+
expect(failureEvent.failureCount).toBe(1)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('OPEN -> HALF_OPEN transition', () => {
|
|
126
|
+
it('should transition to HALF_OPEN after reset timeout', async () => {
|
|
127
|
+
vi.useFakeTimers()
|
|
128
|
+
|
|
129
|
+
const cb = new CircuitBreaker({
|
|
130
|
+
failureThreshold: 2,
|
|
131
|
+
resetTimeoutMs: 1000,
|
|
132
|
+
})
|
|
133
|
+
const instanceId = 'test-instance'
|
|
134
|
+
|
|
135
|
+
// Open the circuit
|
|
136
|
+
cb.recordFailure(instanceId)
|
|
137
|
+
cb.recordFailure(instanceId)
|
|
138
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
139
|
+
|
|
140
|
+
// Advance time past reset timeout
|
|
141
|
+
vi.advanceTimersByTime(1001)
|
|
142
|
+
|
|
143
|
+
// getState should transition to HALF_OPEN
|
|
144
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
145
|
+
|
|
146
|
+
vi.useRealTimers()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should allow execution in HALF_OPEN state', async () => {
|
|
150
|
+
vi.useFakeTimers()
|
|
151
|
+
|
|
152
|
+
const cb = new CircuitBreaker({
|
|
153
|
+
failureThreshold: 2,
|
|
154
|
+
resetTimeoutMs: 1000,
|
|
155
|
+
})
|
|
156
|
+
const instanceId = 'test-instance'
|
|
157
|
+
|
|
158
|
+
cb.recordFailure(instanceId)
|
|
159
|
+
cb.recordFailure(instanceId)
|
|
160
|
+
|
|
161
|
+
vi.advanceTimersByTime(1001)
|
|
162
|
+
|
|
163
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
164
|
+
expect(cb.canExecute(instanceId)).toBe(true)
|
|
165
|
+
|
|
166
|
+
vi.useRealTimers()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should emit STATE_CHANGE event when transitioning to HALF_OPEN', async () => {
|
|
170
|
+
vi.useFakeTimers()
|
|
171
|
+
|
|
172
|
+
const events: CircuitBreakerEvent[] = []
|
|
173
|
+
const cb = new CircuitBreaker({
|
|
174
|
+
failureThreshold: 2,
|
|
175
|
+
resetTimeoutMs: 1000,
|
|
176
|
+
onEvent: (event) => events.push(event),
|
|
177
|
+
})
|
|
178
|
+
const instanceId = 'test-instance'
|
|
179
|
+
|
|
180
|
+
cb.recordFailure(instanceId)
|
|
181
|
+
cb.recordFailure(instanceId)
|
|
182
|
+
|
|
183
|
+
vi.advanceTimersByTime(1001)
|
|
184
|
+
cb.getState(instanceId) // Trigger transition
|
|
185
|
+
|
|
186
|
+
const halfOpenEvent = events.find(
|
|
187
|
+
e => e.type === 'STATE_CHANGE' && e.type === 'STATE_CHANGE' && e.to === 'HALF_OPEN'
|
|
188
|
+
)
|
|
189
|
+
expect(halfOpenEvent).toBeDefined()
|
|
190
|
+
|
|
191
|
+
vi.useRealTimers()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('HALF_OPEN -> CLOSED transition', () => {
|
|
196
|
+
it('should close circuit after successful requests in HALF_OPEN', async () => {
|
|
197
|
+
vi.useFakeTimers()
|
|
198
|
+
|
|
199
|
+
const cb = new CircuitBreaker({
|
|
200
|
+
failureThreshold: 2,
|
|
201
|
+
resetTimeoutMs: 1000,
|
|
202
|
+
halfOpenSuccessThreshold: 3,
|
|
203
|
+
})
|
|
204
|
+
const instanceId = 'test-instance'
|
|
205
|
+
|
|
206
|
+
// Open the circuit
|
|
207
|
+
cb.recordFailure(instanceId)
|
|
208
|
+
cb.recordFailure(instanceId)
|
|
209
|
+
|
|
210
|
+
// Move to HALF_OPEN
|
|
211
|
+
vi.advanceTimersByTime(1001)
|
|
212
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
213
|
+
|
|
214
|
+
// Record successful requests
|
|
215
|
+
cb.recordSuccess(instanceId)
|
|
216
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
217
|
+
|
|
218
|
+
cb.recordSuccess(instanceId)
|
|
219
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
220
|
+
|
|
221
|
+
cb.recordSuccess(instanceId)
|
|
222
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
223
|
+
|
|
224
|
+
vi.useRealTimers()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('should emit HALF_OPEN_TEST events on success', async () => {
|
|
228
|
+
vi.useFakeTimers()
|
|
229
|
+
|
|
230
|
+
const events: CircuitBreakerEvent[] = []
|
|
231
|
+
const cb = new CircuitBreaker({
|
|
232
|
+
failureThreshold: 2,
|
|
233
|
+
resetTimeoutMs: 1000,
|
|
234
|
+
halfOpenSuccessThreshold: 1,
|
|
235
|
+
onEvent: (event) => events.push(event),
|
|
236
|
+
})
|
|
237
|
+
const instanceId = 'test-instance'
|
|
238
|
+
|
|
239
|
+
cb.recordFailure(instanceId)
|
|
240
|
+
cb.recordFailure(instanceId)
|
|
241
|
+
vi.advanceTimersByTime(1001)
|
|
242
|
+
cb.getState(instanceId) // Trigger HALF_OPEN
|
|
243
|
+
|
|
244
|
+
cb.recordSuccess(instanceId)
|
|
245
|
+
|
|
246
|
+
const halfOpenTest = events.find(
|
|
247
|
+
e => e.type === 'HALF_OPEN_TEST' && e.success === true
|
|
248
|
+
)
|
|
249
|
+
expect(halfOpenTest).toBeDefined()
|
|
250
|
+
|
|
251
|
+
vi.useRealTimers()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('should reset failure count when closing', async () => {
|
|
255
|
+
vi.useFakeTimers()
|
|
256
|
+
|
|
257
|
+
const cb = new CircuitBreaker({
|
|
258
|
+
failureThreshold: 2,
|
|
259
|
+
resetTimeoutMs: 1000,
|
|
260
|
+
halfOpenSuccessThreshold: 1,
|
|
261
|
+
})
|
|
262
|
+
const instanceId = 'test-instance'
|
|
263
|
+
|
|
264
|
+
cb.recordFailure(instanceId)
|
|
265
|
+
cb.recordFailure(instanceId)
|
|
266
|
+
vi.advanceTimersByTime(1001)
|
|
267
|
+
cb.getState(instanceId)
|
|
268
|
+
cb.recordSuccess(instanceId)
|
|
269
|
+
|
|
270
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
271
|
+
|
|
272
|
+
// Should need full failure threshold again to open
|
|
273
|
+
cb.recordFailure(instanceId)
|
|
274
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
275
|
+
|
|
276
|
+
cb.recordFailure(instanceId)
|
|
277
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
278
|
+
|
|
279
|
+
vi.useRealTimers()
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe('HALF_OPEN -> OPEN transition', () => {
|
|
284
|
+
it('should reopen circuit on failure in HALF_OPEN state', async () => {
|
|
285
|
+
vi.useFakeTimers()
|
|
286
|
+
|
|
287
|
+
const cb = new CircuitBreaker({
|
|
288
|
+
failureThreshold: 2,
|
|
289
|
+
resetTimeoutMs: 1000,
|
|
290
|
+
})
|
|
291
|
+
const instanceId = 'test-instance'
|
|
292
|
+
|
|
293
|
+
cb.recordFailure(instanceId)
|
|
294
|
+
cb.recordFailure(instanceId)
|
|
295
|
+
vi.advanceTimersByTime(1001)
|
|
296
|
+
|
|
297
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
298
|
+
|
|
299
|
+
cb.recordFailure(instanceId)
|
|
300
|
+
|
|
301
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
302
|
+
|
|
303
|
+
vi.useRealTimers()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should emit HALF_OPEN_TEST event with success=false on failure', async () => {
|
|
307
|
+
vi.useFakeTimers()
|
|
308
|
+
|
|
309
|
+
const events: CircuitBreakerEvent[] = []
|
|
310
|
+
const cb = new CircuitBreaker({
|
|
311
|
+
failureThreshold: 2,
|
|
312
|
+
resetTimeoutMs: 1000,
|
|
313
|
+
onEvent: (event) => events.push(event),
|
|
314
|
+
})
|
|
315
|
+
const instanceId = 'test-instance'
|
|
316
|
+
|
|
317
|
+
cb.recordFailure(instanceId)
|
|
318
|
+
cb.recordFailure(instanceId)
|
|
319
|
+
vi.advanceTimersByTime(1001)
|
|
320
|
+
cb.getState(instanceId)
|
|
321
|
+
cb.recordFailure(instanceId)
|
|
322
|
+
|
|
323
|
+
const halfOpenTest = events.find(
|
|
324
|
+
e => e.type === 'HALF_OPEN_TEST' && e.success === false
|
|
325
|
+
)
|
|
326
|
+
expect(halfOpenTest).toBeDefined()
|
|
327
|
+
|
|
328
|
+
vi.useRealTimers()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('should reset timeout on reopen', async () => {
|
|
332
|
+
vi.useFakeTimers()
|
|
333
|
+
|
|
334
|
+
const cb = new CircuitBreaker({
|
|
335
|
+
failureThreshold: 2,
|
|
336
|
+
resetTimeoutMs: 1000,
|
|
337
|
+
enableExponentialBackoff: false, // Disable backoff for this test
|
|
338
|
+
})
|
|
339
|
+
const instanceId = 'test-instance'
|
|
340
|
+
|
|
341
|
+
cb.recordFailure(instanceId)
|
|
342
|
+
cb.recordFailure(instanceId)
|
|
343
|
+
vi.advanceTimersByTime(1001)
|
|
344
|
+
cb.getState(instanceId) // HALF_OPEN
|
|
345
|
+
|
|
346
|
+
cb.recordFailure(instanceId) // Back to OPEN
|
|
347
|
+
|
|
348
|
+
// Should not transition immediately
|
|
349
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
350
|
+
|
|
351
|
+
// Should need full timeout again
|
|
352
|
+
vi.advanceTimersByTime(500)
|
|
353
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
354
|
+
|
|
355
|
+
vi.advanceTimersByTime(501)
|
|
356
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
357
|
+
|
|
358
|
+
vi.useRealTimers()
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
describe('success in CLOSED state', () => {
|
|
363
|
+
it('should reset failure count on success', () => {
|
|
364
|
+
const cb = new CircuitBreaker({ failureThreshold: 3 })
|
|
365
|
+
const instanceId = 'test-instance'
|
|
366
|
+
|
|
367
|
+
cb.recordFailure(instanceId)
|
|
368
|
+
cb.recordFailure(instanceId)
|
|
369
|
+
|
|
370
|
+
const statsBefore = cb.getStats(instanceId)
|
|
371
|
+
expect(statsBefore.failureCount).toBe(2)
|
|
372
|
+
|
|
373
|
+
cb.recordSuccess(instanceId)
|
|
374
|
+
|
|
375
|
+
const statsAfter = cb.getStats(instanceId)
|
|
376
|
+
expect(statsAfter.failureCount).toBe(0)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should emit SUCCESS_RECORDED event', () => {
|
|
380
|
+
const events: CircuitBreakerEvent[] = []
|
|
381
|
+
const cb = new CircuitBreaker({
|
|
382
|
+
onEvent: (event) => events.push(event),
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
cb.recordSuccess('test-instance')
|
|
386
|
+
|
|
387
|
+
const successEvent = events.find(e => e.type === 'SUCCESS_RECORDED')
|
|
388
|
+
expect(successEvent).toBeDefined()
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
describe('failure window', () => {
|
|
393
|
+
it('should only count failures within the window', async () => {
|
|
394
|
+
vi.useFakeTimers()
|
|
395
|
+
|
|
396
|
+
const cb = new CircuitBreaker({
|
|
397
|
+
failureThreshold: 3,
|
|
398
|
+
failureWindowMs: 1000,
|
|
399
|
+
})
|
|
400
|
+
const instanceId = 'test-instance'
|
|
401
|
+
|
|
402
|
+
cb.recordFailure(instanceId)
|
|
403
|
+
cb.recordFailure(instanceId)
|
|
404
|
+
|
|
405
|
+
// Advance past window
|
|
406
|
+
vi.advanceTimersByTime(1001)
|
|
407
|
+
|
|
408
|
+
// This failure should only count as 1 (old failures expired)
|
|
409
|
+
cb.recordFailure(instanceId)
|
|
410
|
+
|
|
411
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
412
|
+
|
|
413
|
+
// Need 2 more failures
|
|
414
|
+
cb.recordFailure(instanceId)
|
|
415
|
+
cb.recordFailure(instanceId)
|
|
416
|
+
|
|
417
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
418
|
+
|
|
419
|
+
vi.useRealTimers()
|
|
420
|
+
})
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
describe('execute()', () => {
|
|
424
|
+
it('should execute function when circuit is CLOSED', async () => {
|
|
425
|
+
const cb = new CircuitBreaker()
|
|
426
|
+
const fn = vi.fn().mockResolvedValue('success')
|
|
427
|
+
|
|
428
|
+
const result = await cb.execute('test-instance', fn)
|
|
429
|
+
|
|
430
|
+
expect(fn).toHaveBeenCalled()
|
|
431
|
+
expect(result).toBe('success')
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('should record success on successful execution', async () => {
|
|
435
|
+
const cb = new CircuitBreaker()
|
|
436
|
+
const fn = vi.fn().mockResolvedValue('success')
|
|
437
|
+
|
|
438
|
+
await cb.execute('test-instance', fn)
|
|
439
|
+
|
|
440
|
+
const stats = cb.getStats('test-instance')
|
|
441
|
+
expect(stats.totalSuccesses).toBe(1)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('should record failure on failed execution', async () => {
|
|
445
|
+
const cb = new CircuitBreaker()
|
|
446
|
+
const fn = vi.fn().mockRejectedValue(new Error('fail'))
|
|
447
|
+
|
|
448
|
+
await expect(cb.execute('test-instance', fn)).rejects.toThrow('fail')
|
|
449
|
+
|
|
450
|
+
const stats = cb.getStats('test-instance')
|
|
451
|
+
expect(stats.totalFailures).toBe(1)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('should throw CircuitOpenError when circuit is OPEN', async () => {
|
|
455
|
+
const cb = new CircuitBreaker({ failureThreshold: 1 })
|
|
456
|
+
const instanceId = 'test-instance'
|
|
457
|
+
|
|
458
|
+
cb.recordFailure(instanceId)
|
|
459
|
+
|
|
460
|
+
const fn = vi.fn()
|
|
461
|
+
|
|
462
|
+
await expect(cb.execute(instanceId, fn)).rejects.toThrow(CircuitOpenError)
|
|
463
|
+
expect(fn).not.toHaveBeenCalled()
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('should include retry information in CircuitOpenError', async () => {
|
|
467
|
+
vi.useFakeTimers()
|
|
468
|
+
|
|
469
|
+
const cb = new CircuitBreaker({
|
|
470
|
+
failureThreshold: 1,
|
|
471
|
+
resetTimeoutMs: 5000,
|
|
472
|
+
})
|
|
473
|
+
const instanceId = 'test-instance'
|
|
474
|
+
|
|
475
|
+
cb.recordFailure(instanceId)
|
|
476
|
+
|
|
477
|
+
vi.advanceTimersByTime(1000)
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
await cb.execute(instanceId, vi.fn())
|
|
481
|
+
expect.fail('Should have thrown')
|
|
482
|
+
} catch (error) {
|
|
483
|
+
expect(error).toBeInstanceOf(CircuitOpenError)
|
|
484
|
+
if (error instanceof CircuitOpenError) {
|
|
485
|
+
expect(error.instanceId).toBe(instanceId)
|
|
486
|
+
expect(error.state).toBe('OPEN')
|
|
487
|
+
expect(error.retryAfterMs).toBeCloseTo(4000, -2)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
vi.useRealTimers()
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
it('should emit REQUEST_REJECTED event when circuit is OPEN', async () => {
|
|
495
|
+
const events: CircuitBreakerEvent[] = []
|
|
496
|
+
const cb = new CircuitBreaker({
|
|
497
|
+
failureThreshold: 1,
|
|
498
|
+
onEvent: (event) => events.push(event),
|
|
499
|
+
})
|
|
500
|
+
const instanceId = 'test-instance'
|
|
501
|
+
|
|
502
|
+
cb.recordFailure(instanceId)
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
await cb.execute(instanceId, vi.fn())
|
|
506
|
+
} catch {
|
|
507
|
+
// Expected
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const rejectedEvent = events.find(e => e.type === 'REQUEST_REJECTED')
|
|
511
|
+
expect(rejectedEvent).toBeDefined()
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('should use fallback when circuit is OPEN and fallback provided', async () => {
|
|
515
|
+
const cb = new CircuitBreaker({
|
|
516
|
+
failureThreshold: 1,
|
|
517
|
+
fallback: async <T>() => 'fallback-value' as T,
|
|
518
|
+
})
|
|
519
|
+
const instanceId = 'test-instance'
|
|
520
|
+
|
|
521
|
+
cb.recordFailure(instanceId)
|
|
522
|
+
|
|
523
|
+
const result = await cb.execute(instanceId, vi.fn())
|
|
524
|
+
expect(result).toBe('fallback-value')
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it('should track rejected requests in stats', async () => {
|
|
528
|
+
const cb = new CircuitBreaker({ failureThreshold: 1 })
|
|
529
|
+
const instanceId = 'test-instance'
|
|
530
|
+
|
|
531
|
+
cb.recordFailure(instanceId)
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
await cb.execute(instanceId, vi.fn())
|
|
535
|
+
} catch {
|
|
536
|
+
// Expected
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const stats = cb.getStats(instanceId)
|
|
540
|
+
expect(stats.totalRejected).toBe(1)
|
|
541
|
+
})
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
describe('getStats()', () => {
|
|
545
|
+
it('should return correct statistics', () => {
|
|
546
|
+
const cb = new CircuitBreaker({ failureThreshold: 3 })
|
|
547
|
+
const instanceId = 'test-instance'
|
|
548
|
+
|
|
549
|
+
cb.recordSuccess(instanceId)
|
|
550
|
+
cb.recordSuccess(instanceId)
|
|
551
|
+
cb.recordFailure(instanceId)
|
|
552
|
+
cb.recordFailure(instanceId)
|
|
553
|
+
|
|
554
|
+
const stats = cb.getStats(instanceId)
|
|
555
|
+
|
|
556
|
+
expect(stats.state).toBe('CLOSED')
|
|
557
|
+
expect(stats.totalSuccesses).toBe(2)
|
|
558
|
+
expect(stats.totalFailures).toBe(2)
|
|
559
|
+
expect(stats.totalRequests).toBe(4)
|
|
560
|
+
expect(stats.failureCount).toBe(2)
|
|
561
|
+
expect(stats.lastSuccessTime).not.toBeNull()
|
|
562
|
+
expect(stats.lastFailureTime).not.toBeNull()
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
describe('getAllStats()', () => {
|
|
567
|
+
it('should return stats for all instances', () => {
|
|
568
|
+
const cb = new CircuitBreaker()
|
|
569
|
+
|
|
570
|
+
cb.recordSuccess('instance-1')
|
|
571
|
+
cb.recordFailure('instance-2')
|
|
572
|
+
cb.recordSuccess('instance-3')
|
|
573
|
+
|
|
574
|
+
const allStats = cb.getAllStats()
|
|
575
|
+
|
|
576
|
+
expect(allStats.size).toBe(3)
|
|
577
|
+
expect(allStats.has('instance-1')).toBe(true)
|
|
578
|
+
expect(allStats.has('instance-2')).toBe(true)
|
|
579
|
+
expect(allStats.has('instance-3')).toBe(true)
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
describe('reset()', () => {
|
|
584
|
+
it('should reset circuit to CLOSED state', () => {
|
|
585
|
+
const cb = new CircuitBreaker({ failureThreshold: 2 })
|
|
586
|
+
const instanceId = 'test-instance'
|
|
587
|
+
|
|
588
|
+
cb.recordFailure(instanceId)
|
|
589
|
+
cb.recordFailure(instanceId)
|
|
590
|
+
|
|
591
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
592
|
+
|
|
593
|
+
cb.reset(instanceId)
|
|
594
|
+
|
|
595
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
it('should clear failure count on reset', () => {
|
|
599
|
+
const cb = new CircuitBreaker({ failureThreshold: 5 })
|
|
600
|
+
const instanceId = 'test-instance'
|
|
601
|
+
|
|
602
|
+
cb.recordFailure(instanceId)
|
|
603
|
+
cb.recordFailure(instanceId)
|
|
604
|
+
cb.recordFailure(instanceId)
|
|
605
|
+
|
|
606
|
+
cb.reset(instanceId)
|
|
607
|
+
|
|
608
|
+
const stats = cb.getStats(instanceId)
|
|
609
|
+
expect(stats.failureCount).toBe(0)
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
it('should emit STATE_CHANGE event on reset', () => {
|
|
613
|
+
const events: CircuitBreakerEvent[] = []
|
|
614
|
+
const cb = new CircuitBreaker({
|
|
615
|
+
failureThreshold: 1,
|
|
616
|
+
onEvent: (event) => events.push(event),
|
|
617
|
+
})
|
|
618
|
+
const instanceId = 'test-instance'
|
|
619
|
+
|
|
620
|
+
cb.recordFailure(instanceId)
|
|
621
|
+
cb.reset(instanceId)
|
|
622
|
+
|
|
623
|
+
const stateChange = events.filter(e => e.type === 'STATE_CHANGE').pop()
|
|
624
|
+
expect(stateChange).toBeDefined()
|
|
625
|
+
if (stateChange?.type === 'STATE_CHANGE') {
|
|
626
|
+
expect(stateChange.from).toBe('OPEN')
|
|
627
|
+
expect(stateChange.to).toBe('CLOSED')
|
|
628
|
+
}
|
|
629
|
+
})
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
describe('forceOpen()', () => {
|
|
633
|
+
it('should force circuit to OPEN state', () => {
|
|
634
|
+
const cb = new CircuitBreaker()
|
|
635
|
+
const instanceId = 'test-instance'
|
|
636
|
+
|
|
637
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
638
|
+
|
|
639
|
+
cb.forceOpen(instanceId)
|
|
640
|
+
|
|
641
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it('should emit STATE_CHANGE event', () => {
|
|
645
|
+
const events: CircuitBreakerEvent[] = []
|
|
646
|
+
const cb = new CircuitBreaker({
|
|
647
|
+
onEvent: (event) => events.push(event),
|
|
648
|
+
})
|
|
649
|
+
const instanceId = 'test-instance'
|
|
650
|
+
|
|
651
|
+
cb.forceOpen(instanceId)
|
|
652
|
+
|
|
653
|
+
const stateChange = events.find(e => e.type === 'STATE_CHANGE')
|
|
654
|
+
expect(stateChange).toBeDefined()
|
|
655
|
+
if (stateChange?.type === 'STATE_CHANGE') {
|
|
656
|
+
expect(stateChange.from).toBe('CLOSED')
|
|
657
|
+
expect(stateChange.to).toBe('OPEN')
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
describe('remove()', () => {
|
|
663
|
+
it('should remove instance tracking', () => {
|
|
664
|
+
const cb = new CircuitBreaker()
|
|
665
|
+
const instanceId = 'test-instance'
|
|
666
|
+
|
|
667
|
+
cb.recordSuccess(instanceId)
|
|
668
|
+
expect(cb.getAllStats().has(instanceId)).toBe(true)
|
|
669
|
+
|
|
670
|
+
const removed = cb.remove(instanceId)
|
|
671
|
+
|
|
672
|
+
expect(removed).toBe(true)
|
|
673
|
+
expect(cb.getAllStats().has(instanceId)).toBe(false)
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
it('should return false for non-existent instance', () => {
|
|
677
|
+
const cb = new CircuitBreaker()
|
|
678
|
+
const removed = cb.remove('non-existent')
|
|
679
|
+
expect(removed).toBe(false)
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
describe('clear()', () => {
|
|
684
|
+
it('should clear all instance tracking', () => {
|
|
685
|
+
const cb = new CircuitBreaker()
|
|
686
|
+
|
|
687
|
+
cb.recordSuccess('instance-1')
|
|
688
|
+
cb.recordSuccess('instance-2')
|
|
689
|
+
cb.recordSuccess('instance-3')
|
|
690
|
+
|
|
691
|
+
expect(cb.getAllStats().size).toBe(3)
|
|
692
|
+
|
|
693
|
+
cb.clear()
|
|
694
|
+
|
|
695
|
+
expect(cb.getAllStats().size).toBe(0)
|
|
696
|
+
})
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
describe('multiple instances', () => {
|
|
700
|
+
it('should track state independently per instance', () => {
|
|
701
|
+
const cb = new CircuitBreaker({ failureThreshold: 2 })
|
|
702
|
+
|
|
703
|
+
cb.recordFailure('instance-1')
|
|
704
|
+
cb.recordFailure('instance-1')
|
|
705
|
+
|
|
706
|
+
cb.recordSuccess('instance-2')
|
|
707
|
+
|
|
708
|
+
expect(cb.getState('instance-1')).toBe('OPEN')
|
|
709
|
+
expect(cb.getState('instance-2')).toBe('CLOSED')
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('should allow execution on healthy instances when others are open', async () => {
|
|
713
|
+
const cb = new CircuitBreaker({ failureThreshold: 1 })
|
|
714
|
+
|
|
715
|
+
cb.recordFailure('unhealthy')
|
|
716
|
+
|
|
717
|
+
const fn = vi.fn().mockResolvedValue('success')
|
|
718
|
+
|
|
719
|
+
// Should throw for unhealthy instance
|
|
720
|
+
await expect(cb.execute('unhealthy', fn)).rejects.toThrow(CircuitOpenError)
|
|
721
|
+
expect(fn).not.toHaveBeenCalled()
|
|
722
|
+
|
|
723
|
+
// Should succeed for healthy instance
|
|
724
|
+
const result = await cb.execute('healthy', fn)
|
|
725
|
+
expect(fn).toHaveBeenCalled()
|
|
726
|
+
expect(result).toBe('success')
|
|
727
|
+
})
|
|
728
|
+
})
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
describe('DOCircuitBreakerWrapper', () => {
|
|
732
|
+
const createMockStub = (handler: (request: Request) => Promise<Response>): DOStub => ({
|
|
733
|
+
fetch: handler,
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
describe('wrap()', () => {
|
|
737
|
+
it('should pass requests through when circuit is CLOSED', async () => {
|
|
738
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
739
|
+
const mockResponse = new Response('OK', { status: 200 })
|
|
740
|
+
const stub = createMockStub(async () => mockResponse)
|
|
741
|
+
|
|
742
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
743
|
+
const request = new Request('http://localhost/test')
|
|
744
|
+
|
|
745
|
+
const response = await wrappedStub.fetch(request)
|
|
746
|
+
|
|
747
|
+
expect(response.status).toBe(200)
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
it('should record success for 2xx responses', async () => {
|
|
751
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
752
|
+
const stub = createMockStub(async () => new Response('OK', { status: 200 }))
|
|
753
|
+
|
|
754
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
755
|
+
const request = new Request('http://localhost/test')
|
|
756
|
+
|
|
757
|
+
await wrappedStub.fetch(request)
|
|
758
|
+
|
|
759
|
+
const stats = wrapper.getStats('test-do')
|
|
760
|
+
expect(stats.totalSuccesses).toBe(1)
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
it('should record failure for 5xx responses', async () => {
|
|
764
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
765
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
766
|
+
|
|
767
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
768
|
+
const request = new Request('http://localhost/test')
|
|
769
|
+
|
|
770
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
771
|
+
|
|
772
|
+
const stats = wrapper.getStats('test-do')
|
|
773
|
+
expect(stats.totalFailures).toBe(1)
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
it('should not treat 4xx as failures', async () => {
|
|
777
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
778
|
+
const stub = createMockStub(async () => new Response('Not Found', { status: 404 }))
|
|
779
|
+
|
|
780
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
781
|
+
const request = new Request('http://localhost/test')
|
|
782
|
+
|
|
783
|
+
const response = await wrappedStub.fetch(request)
|
|
784
|
+
|
|
785
|
+
expect(response.status).toBe(404)
|
|
786
|
+
|
|
787
|
+
const stats = wrapper.getStats('test-do')
|
|
788
|
+
expect(stats.totalSuccesses).toBe(1)
|
|
789
|
+
expect(stats.totalFailures).toBe(0)
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
it('should open circuit after failure threshold', async () => {
|
|
793
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
794
|
+
failureThreshold: 2,
|
|
795
|
+
})
|
|
796
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
797
|
+
|
|
798
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
799
|
+
const request = new Request('http://localhost/test')
|
|
800
|
+
|
|
801
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
802
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
803
|
+
|
|
804
|
+
expect(wrapper.getState('test-do')).toBe('OPEN')
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
it('should throw CircuitOpenError when circuit is OPEN', async () => {
|
|
808
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
809
|
+
failureThreshold: 1,
|
|
810
|
+
})
|
|
811
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
812
|
+
|
|
813
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
814
|
+
const request = new Request('http://localhost/test')
|
|
815
|
+
|
|
816
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
817
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow(CircuitOpenError)
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
it('should use fallbackResponse when circuit is OPEN', async () => {
|
|
821
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
822
|
+
failureThreshold: 1,
|
|
823
|
+
fallbackResponse: () => new Response('Fallback', { status: 503 }),
|
|
824
|
+
})
|
|
825
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
826
|
+
|
|
827
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
828
|
+
const request = new Request('http://localhost/test')
|
|
829
|
+
|
|
830
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
831
|
+
|
|
832
|
+
const response = await wrappedStub.fetch(request)
|
|
833
|
+
expect(response.status).toBe(503)
|
|
834
|
+
expect(await response.text()).toBe('Fallback')
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
it('should use custom isFailure function', async () => {
|
|
838
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
839
|
+
failureThreshold: 1,
|
|
840
|
+
isFailure: (response) => response.status === 429, // Rate limited
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
const calls: number[] = []
|
|
844
|
+
const stub = createMockStub(async () => {
|
|
845
|
+
calls.push(1)
|
|
846
|
+
return new Response('Rate Limited', { status: 429 })
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
850
|
+
const request = new Request('http://localhost/test')
|
|
851
|
+
|
|
852
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
853
|
+
|
|
854
|
+
expect(wrapper.getState('test-do')).toBe('OPEN')
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
it('should use custom getInstanceId function', async () => {
|
|
858
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
859
|
+
getInstanceId: (req) => new URL(req.url).searchParams.get('db') || 'default',
|
|
860
|
+
})
|
|
861
|
+
const stub = createMockStub(async () => new Response('OK', { status: 200 }))
|
|
862
|
+
|
|
863
|
+
const wrappedStub = wrapper.wrap(stub)
|
|
864
|
+
const request1 = new Request('http://localhost/test?db=primary')
|
|
865
|
+
const request2 = new Request('http://localhost/test?db=replica')
|
|
866
|
+
|
|
867
|
+
await wrappedStub.fetch(request1)
|
|
868
|
+
await wrappedStub.fetch(request2)
|
|
869
|
+
|
|
870
|
+
expect(wrapper.getStats('primary').totalRequests).toBe(1)
|
|
871
|
+
expect(wrapper.getStats('replica').totalRequests).toBe(1)
|
|
872
|
+
})
|
|
873
|
+
})
|
|
874
|
+
|
|
875
|
+
describe('fetch()', () => {
|
|
876
|
+
it('should execute fetch with circuit breaker protection', async () => {
|
|
877
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
878
|
+
const stub = createMockStub(async () => new Response('OK', { status: 200 }))
|
|
879
|
+
const request = new Request('http://localhost/test')
|
|
880
|
+
|
|
881
|
+
const response = await wrapper.fetch(stub, request, 'test-do')
|
|
882
|
+
|
|
883
|
+
expect(response.status).toBe(200)
|
|
884
|
+
expect(wrapper.getStats('test-do').totalSuccesses).toBe(1)
|
|
885
|
+
})
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
describe('getCircuitBreaker()', () => {
|
|
889
|
+
it('should return underlying circuit breaker', () => {
|
|
890
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
891
|
+
const cb = wrapper.getCircuitBreaker()
|
|
892
|
+
|
|
893
|
+
expect(cb).toBeInstanceOf(CircuitBreaker)
|
|
894
|
+
})
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
describe('reset()', () => {
|
|
898
|
+
it('should reset circuit for instance', async () => {
|
|
899
|
+
const wrapper = new DOCircuitBreakerWrapper({ failureThreshold: 1 })
|
|
900
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
901
|
+
|
|
902
|
+
const wrappedStub = wrapper.wrap(stub, 'test-do')
|
|
903
|
+
const request = new Request('http://localhost/test')
|
|
904
|
+
|
|
905
|
+
await expect(wrappedStub.fetch(request)).rejects.toThrow()
|
|
906
|
+
expect(wrapper.getState('test-do')).toBe('OPEN')
|
|
907
|
+
|
|
908
|
+
wrapper.reset('test-do')
|
|
909
|
+
expect(wrapper.getState('test-do')).toBe('CLOSED')
|
|
910
|
+
})
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
describe('forceOpen()', () => {
|
|
914
|
+
it('should force circuit open for instance', () => {
|
|
915
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
916
|
+
|
|
917
|
+
wrapper.forceOpen('test-do')
|
|
918
|
+
|
|
919
|
+
expect(wrapper.getState('test-do')).toBe('OPEN')
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
describe('getAllStats()', () => {
|
|
924
|
+
it('should return stats for all tracked DO instances', async () => {
|
|
925
|
+
const wrapper = new DOCircuitBreakerWrapper()
|
|
926
|
+
const stub = createMockStub(async () => new Response('OK', { status: 200 }))
|
|
927
|
+
|
|
928
|
+
await wrapper.fetch(stub, new Request('http://localhost/test'), 'do-1')
|
|
929
|
+
await wrapper.fetch(stub, new Request('http://localhost/test'), 'do-2')
|
|
930
|
+
await wrapper.fetch(stub, new Request('http://localhost/test'), 'do-3')
|
|
931
|
+
|
|
932
|
+
const stats = wrapper.getAllStats()
|
|
933
|
+
|
|
934
|
+
expect(stats.size).toBe(3)
|
|
935
|
+
})
|
|
936
|
+
})
|
|
937
|
+
|
|
938
|
+
describe('event emission', () => {
|
|
939
|
+
it('should emit events through configuration', async () => {
|
|
940
|
+
const events: CircuitBreakerEvent[] = []
|
|
941
|
+
const wrapper = new DOCircuitBreakerWrapper({
|
|
942
|
+
failureThreshold: 1,
|
|
943
|
+
onEvent: (event) => events.push(event),
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
const stub = createMockStub(async () => new Response('Error', { status: 500 }))
|
|
947
|
+
const request = new Request('http://localhost/test')
|
|
948
|
+
|
|
949
|
+
try {
|
|
950
|
+
await wrapper.fetch(stub, request, 'test-do')
|
|
951
|
+
} catch {
|
|
952
|
+
// Expected
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
expect(events.some(e => e.type === 'FAILURE_RECORDED')).toBe(true)
|
|
956
|
+
expect(events.some(e => e.type === 'STATE_CHANGE')).toBe(true)
|
|
957
|
+
})
|
|
958
|
+
})
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
describe('CircuitOpenError', () => {
|
|
962
|
+
it('should have correct properties', () => {
|
|
963
|
+
const error = new CircuitOpenError('test-instance', 'OPEN', 5000)
|
|
964
|
+
|
|
965
|
+
expect(error.name).toBe('CircuitOpenError')
|
|
966
|
+
expect(error.instanceId).toBe('test-instance')
|
|
967
|
+
expect(error.state).toBe('OPEN')
|
|
968
|
+
expect(error.retryAfterMs).toBe(5000)
|
|
969
|
+
expect(error.message).toContain('test-instance')
|
|
970
|
+
expect(error.message).toContain('5000ms')
|
|
971
|
+
})
|
|
972
|
+
})
|
|
973
|
+
|
|
974
|
+
describe('Exponential Backoff', () => {
|
|
975
|
+
describe('configuration', () => {
|
|
976
|
+
it('should enable exponential backoff by default', () => {
|
|
977
|
+
const cb = new CircuitBreaker()
|
|
978
|
+
const config = cb.getConfig()
|
|
979
|
+
|
|
980
|
+
expect(config.enableExponentialBackoff).toBe(true)
|
|
981
|
+
expect(config.backoffMultiplier).toBe(2)
|
|
982
|
+
expect(config.maxResetTimeoutMs).toBe(300000)
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
it('should accept custom backoff configuration', () => {
|
|
986
|
+
const cb = new CircuitBreaker({
|
|
987
|
+
enableExponentialBackoff: true,
|
|
988
|
+
backoffMultiplier: 3,
|
|
989
|
+
maxResetTimeoutMs: 60000,
|
|
990
|
+
})
|
|
991
|
+
const config = cb.getConfig()
|
|
992
|
+
|
|
993
|
+
expect(config.enableExponentialBackoff).toBe(true)
|
|
994
|
+
expect(config.backoffMultiplier).toBe(3)
|
|
995
|
+
expect(config.maxResetTimeoutMs).toBe(60000)
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
it('should allow disabling exponential backoff', () => {
|
|
999
|
+
const cb = new CircuitBreaker({
|
|
1000
|
+
enableExponentialBackoff: false,
|
|
1001
|
+
})
|
|
1002
|
+
const config = cb.getConfig()
|
|
1003
|
+
|
|
1004
|
+
expect(config.enableExponentialBackoff).toBe(false)
|
|
1005
|
+
})
|
|
1006
|
+
})
|
|
1007
|
+
|
|
1008
|
+
describe('backoff timeout calculation', () => {
|
|
1009
|
+
it('should use base timeout on first circuit open', () => {
|
|
1010
|
+
const cb = new CircuitBreaker({
|
|
1011
|
+
failureThreshold: 2,
|
|
1012
|
+
resetTimeoutMs: 1000,
|
|
1013
|
+
enableExponentialBackoff: true,
|
|
1014
|
+
})
|
|
1015
|
+
const instanceId = 'test-instance'
|
|
1016
|
+
|
|
1017
|
+
cb.recordFailure(instanceId)
|
|
1018
|
+
cb.recordFailure(instanceId)
|
|
1019
|
+
|
|
1020
|
+
const stats = cb.getStats(instanceId)
|
|
1021
|
+
expect(stats.consecutiveOpens).toBe(1)
|
|
1022
|
+
expect(stats.currentResetTimeoutMs).toBe(1000)
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
it('should double timeout on second consecutive circuit open', async () => {
|
|
1026
|
+
vi.useFakeTimers()
|
|
1027
|
+
|
|
1028
|
+
const cb = new CircuitBreaker({
|
|
1029
|
+
failureThreshold: 1,
|
|
1030
|
+
resetTimeoutMs: 1000,
|
|
1031
|
+
halfOpenSuccessThreshold: 1,
|
|
1032
|
+
enableExponentialBackoff: true,
|
|
1033
|
+
backoffMultiplier: 2,
|
|
1034
|
+
})
|
|
1035
|
+
const instanceId = 'test-instance'
|
|
1036
|
+
|
|
1037
|
+
// First open
|
|
1038
|
+
cb.recordFailure(instanceId)
|
|
1039
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
1040
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1041
|
+
|
|
1042
|
+
// Move to HALF_OPEN
|
|
1043
|
+
vi.advanceTimersByTime(1001)
|
|
1044
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
1045
|
+
|
|
1046
|
+
// Fail again -> second open with backoff
|
|
1047
|
+
cb.recordFailure(instanceId)
|
|
1048
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
1049
|
+
expect(cb.getStats(instanceId).consecutiveOpens).toBe(2)
|
|
1050
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(2000) // 1000 * 2^1
|
|
1051
|
+
|
|
1052
|
+
vi.useRealTimers()
|
|
1053
|
+
})
|
|
1054
|
+
|
|
1055
|
+
it('should apply exponential backoff correctly over multiple reopens', async () => {
|
|
1056
|
+
vi.useFakeTimers()
|
|
1057
|
+
|
|
1058
|
+
const cb = new CircuitBreaker({
|
|
1059
|
+
failureThreshold: 1,
|
|
1060
|
+
resetTimeoutMs: 1000,
|
|
1061
|
+
halfOpenSuccessThreshold: 1,
|
|
1062
|
+
enableExponentialBackoff: true,
|
|
1063
|
+
backoffMultiplier: 2,
|
|
1064
|
+
maxResetTimeoutMs: 100000,
|
|
1065
|
+
})
|
|
1066
|
+
const instanceId = 'test-instance'
|
|
1067
|
+
|
|
1068
|
+
// First open: timeout = 1000ms
|
|
1069
|
+
cb.recordFailure(instanceId)
|
|
1070
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1071
|
+
vi.advanceTimersByTime(1001)
|
|
1072
|
+
cb.getState(instanceId)
|
|
1073
|
+
|
|
1074
|
+
// Second open: timeout = 2000ms (1000 * 2^1)
|
|
1075
|
+
cb.recordFailure(instanceId)
|
|
1076
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(2000)
|
|
1077
|
+
vi.advanceTimersByTime(2001)
|
|
1078
|
+
cb.getState(instanceId)
|
|
1079
|
+
|
|
1080
|
+
// Third open: timeout = 4000ms (1000 * 2^2)
|
|
1081
|
+
cb.recordFailure(instanceId)
|
|
1082
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(4000)
|
|
1083
|
+
vi.advanceTimersByTime(4001)
|
|
1084
|
+
cb.getState(instanceId)
|
|
1085
|
+
|
|
1086
|
+
// Fourth open: timeout = 8000ms (1000 * 2^3)
|
|
1087
|
+
cb.recordFailure(instanceId)
|
|
1088
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(8000)
|
|
1089
|
+
|
|
1090
|
+
vi.useRealTimers()
|
|
1091
|
+
})
|
|
1092
|
+
|
|
1093
|
+
it('should cap timeout at maxResetTimeoutMs', async () => {
|
|
1094
|
+
vi.useFakeTimers()
|
|
1095
|
+
|
|
1096
|
+
const cb = new CircuitBreaker({
|
|
1097
|
+
failureThreshold: 1,
|
|
1098
|
+
resetTimeoutMs: 1000,
|
|
1099
|
+
halfOpenSuccessThreshold: 1,
|
|
1100
|
+
enableExponentialBackoff: true,
|
|
1101
|
+
backoffMultiplier: 10,
|
|
1102
|
+
maxResetTimeoutMs: 5000,
|
|
1103
|
+
})
|
|
1104
|
+
const instanceId = 'test-instance'
|
|
1105
|
+
|
|
1106
|
+
// First open: 1000ms
|
|
1107
|
+
cb.recordFailure(instanceId)
|
|
1108
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1109
|
+
vi.advanceTimersByTime(1001)
|
|
1110
|
+
cb.getState(instanceId)
|
|
1111
|
+
|
|
1112
|
+
// Second open: would be 10000ms, but capped at 5000ms
|
|
1113
|
+
cb.recordFailure(instanceId)
|
|
1114
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(5000)
|
|
1115
|
+
|
|
1116
|
+
vi.useRealTimers()
|
|
1117
|
+
})
|
|
1118
|
+
|
|
1119
|
+
it('should not apply backoff when disabled', async () => {
|
|
1120
|
+
vi.useFakeTimers()
|
|
1121
|
+
|
|
1122
|
+
const cb = new CircuitBreaker({
|
|
1123
|
+
failureThreshold: 1,
|
|
1124
|
+
resetTimeoutMs: 1000,
|
|
1125
|
+
halfOpenSuccessThreshold: 1,
|
|
1126
|
+
enableExponentialBackoff: false,
|
|
1127
|
+
})
|
|
1128
|
+
const instanceId = 'test-instance'
|
|
1129
|
+
|
|
1130
|
+
// First open
|
|
1131
|
+
cb.recordFailure(instanceId)
|
|
1132
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1133
|
+
vi.advanceTimersByTime(1001)
|
|
1134
|
+
cb.getState(instanceId)
|
|
1135
|
+
|
|
1136
|
+
// Second open - should still be 1000ms (no backoff)
|
|
1137
|
+
cb.recordFailure(instanceId)
|
|
1138
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1139
|
+
|
|
1140
|
+
vi.useRealTimers()
|
|
1141
|
+
})
|
|
1142
|
+
})
|
|
1143
|
+
|
|
1144
|
+
describe('backoff reset on successful recovery', () => {
|
|
1145
|
+
it('should reset consecutive opens and timeout on successful recovery', async () => {
|
|
1146
|
+
vi.useFakeTimers()
|
|
1147
|
+
|
|
1148
|
+
const cb = new CircuitBreaker({
|
|
1149
|
+
failureThreshold: 1,
|
|
1150
|
+
resetTimeoutMs: 1000,
|
|
1151
|
+
halfOpenSuccessThreshold: 1,
|
|
1152
|
+
enableExponentialBackoff: true,
|
|
1153
|
+
backoffMultiplier: 2,
|
|
1154
|
+
})
|
|
1155
|
+
const instanceId = 'test-instance'
|
|
1156
|
+
|
|
1157
|
+
// Open circuit multiple times to increase backoff
|
|
1158
|
+
cb.recordFailure(instanceId)
|
|
1159
|
+
vi.advanceTimersByTime(1001)
|
|
1160
|
+
cb.getState(instanceId)
|
|
1161
|
+
cb.recordFailure(instanceId)
|
|
1162
|
+
vi.advanceTimersByTime(2001)
|
|
1163
|
+
cb.getState(instanceId)
|
|
1164
|
+
cb.recordFailure(instanceId)
|
|
1165
|
+
|
|
1166
|
+
expect(cb.getStats(instanceId).consecutiveOpens).toBe(3)
|
|
1167
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(4000)
|
|
1168
|
+
|
|
1169
|
+
// Move to HALF_OPEN
|
|
1170
|
+
vi.advanceTimersByTime(4001)
|
|
1171
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
1172
|
+
|
|
1173
|
+
// Successfully recover
|
|
1174
|
+
cb.recordSuccess(instanceId)
|
|
1175
|
+
expect(cb.getState(instanceId)).toBe('CLOSED')
|
|
1176
|
+
|
|
1177
|
+
// Consecutive opens and timeout should be reset
|
|
1178
|
+
expect(cb.getStats(instanceId).consecutiveOpens).toBe(0)
|
|
1179
|
+
expect(cb.getStats(instanceId).currentResetTimeoutMs).toBe(1000)
|
|
1180
|
+
|
|
1181
|
+
vi.useRealTimers()
|
|
1182
|
+
})
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
describe('retry after calculation with backoff', () => {
|
|
1186
|
+
it('should include backoff timeout in CircuitOpenError retryAfterMs', async () => {
|
|
1187
|
+
vi.useFakeTimers()
|
|
1188
|
+
|
|
1189
|
+
const cb = new CircuitBreaker({
|
|
1190
|
+
failureThreshold: 1,
|
|
1191
|
+
resetTimeoutMs: 1000,
|
|
1192
|
+
halfOpenSuccessThreshold: 1,
|
|
1193
|
+
enableExponentialBackoff: true,
|
|
1194
|
+
backoffMultiplier: 2,
|
|
1195
|
+
})
|
|
1196
|
+
const instanceId = 'test-instance'
|
|
1197
|
+
|
|
1198
|
+
// First open
|
|
1199
|
+
cb.recordFailure(instanceId)
|
|
1200
|
+
vi.advanceTimersByTime(1001)
|
|
1201
|
+
cb.getState(instanceId)
|
|
1202
|
+
|
|
1203
|
+
// Second open with 2000ms timeout
|
|
1204
|
+
cb.recordFailure(instanceId)
|
|
1205
|
+
|
|
1206
|
+
// Advance 500ms into the open period
|
|
1207
|
+
vi.advanceTimersByTime(500)
|
|
1208
|
+
|
|
1209
|
+
try {
|
|
1210
|
+
await cb.execute(instanceId, vi.fn())
|
|
1211
|
+
expect.fail('Should have thrown')
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
expect(error).toBeInstanceOf(CircuitOpenError)
|
|
1214
|
+
if (error instanceof CircuitOpenError) {
|
|
1215
|
+
// Should be approximately 1500ms remaining (2000ms - 500ms)
|
|
1216
|
+
expect(error.retryAfterMs).toBeCloseTo(1500, -2)
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
vi.useRealTimers()
|
|
1221
|
+
})
|
|
1222
|
+
})
|
|
1223
|
+
|
|
1224
|
+
describe('HALF_OPEN timeout respects backoff', () => {
|
|
1225
|
+
it('should wait for backoff timeout before transitioning to HALF_OPEN', async () => {
|
|
1226
|
+
vi.useFakeTimers()
|
|
1227
|
+
|
|
1228
|
+
const cb = new CircuitBreaker({
|
|
1229
|
+
failureThreshold: 1,
|
|
1230
|
+
resetTimeoutMs: 1000,
|
|
1231
|
+
halfOpenSuccessThreshold: 1,
|
|
1232
|
+
enableExponentialBackoff: true,
|
|
1233
|
+
backoffMultiplier: 2,
|
|
1234
|
+
})
|
|
1235
|
+
const instanceId = 'test-instance'
|
|
1236
|
+
|
|
1237
|
+
// First open
|
|
1238
|
+
cb.recordFailure(instanceId)
|
|
1239
|
+
vi.advanceTimersByTime(1001)
|
|
1240
|
+
cb.getState(instanceId) // HALF_OPEN
|
|
1241
|
+
|
|
1242
|
+
// Second open with 2000ms timeout
|
|
1243
|
+
cb.recordFailure(instanceId)
|
|
1244
|
+
|
|
1245
|
+
// Should still be OPEN after 1000ms
|
|
1246
|
+
vi.advanceTimersByTime(1000)
|
|
1247
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
1248
|
+
|
|
1249
|
+
// Should still be OPEN at 1999ms
|
|
1250
|
+
vi.advanceTimersByTime(999)
|
|
1251
|
+
expect(cb.getState(instanceId)).toBe('OPEN')
|
|
1252
|
+
|
|
1253
|
+
// Should transition to HALF_OPEN after 2000ms
|
|
1254
|
+
vi.advanceTimersByTime(2)
|
|
1255
|
+
expect(cb.getState(instanceId)).toBe('HALF_OPEN')
|
|
1256
|
+
|
|
1257
|
+
vi.useRealTimers()
|
|
1258
|
+
})
|
|
1259
|
+
})
|
|
1260
|
+
})
|
|
1261
|
+
|
|
1262
|
+
describe('DefaultMetricsCollector', () => {
|
|
1263
|
+
describe('basic operations', () => {
|
|
1264
|
+
it('should record state transitions', () => {
|
|
1265
|
+
const collector = new DefaultMetricsCollector()
|
|
1266
|
+
|
|
1267
|
+
collector.recordStateTransition('instance-1', 'CLOSED', 'OPEN')
|
|
1268
|
+
collector.recordStateTransition('instance-2', 'CLOSED', 'OPEN')
|
|
1269
|
+
collector.recordStateTransition('instance-1', 'OPEN', 'HALF_OPEN')
|
|
1270
|
+
|
|
1271
|
+
const summary = collector.getSummary()
|
|
1272
|
+
expect(summary.totalInstances).toBe(2)
|
|
1273
|
+
expect(summary.openCircuits).toBe(1)
|
|
1274
|
+
expect(summary.halfOpenCircuits).toBe(1)
|
|
1275
|
+
})
|
|
1276
|
+
|
|
1277
|
+
it('should record failures', () => {
|
|
1278
|
+
const collector = new DefaultMetricsCollector()
|
|
1279
|
+
|
|
1280
|
+
collector.recordFailure('instance-1', 1)
|
|
1281
|
+
collector.recordFailure('instance-1', 2)
|
|
1282
|
+
collector.recordFailure('instance-2', 1)
|
|
1283
|
+
|
|
1284
|
+
const summary = collector.getSummary()
|
|
1285
|
+
expect(summary.totalFailures).toBe(3)
|
|
1286
|
+
})
|
|
1287
|
+
|
|
1288
|
+
it('should record successes', () => {
|
|
1289
|
+
const collector = new DefaultMetricsCollector()
|
|
1290
|
+
|
|
1291
|
+
collector.recordSuccess('instance-1')
|
|
1292
|
+
collector.recordSuccess('instance-1')
|
|
1293
|
+
collector.recordSuccess('instance-2')
|
|
1294
|
+
|
|
1295
|
+
const summary = collector.getSummary()
|
|
1296
|
+
expect(summary.totalSuccesses).toBe(3)
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
it('should record rejections', () => {
|
|
1300
|
+
const collector = new DefaultMetricsCollector()
|
|
1301
|
+
|
|
1302
|
+
collector.recordRejection('instance-1')
|
|
1303
|
+
collector.recordRejection('instance-1')
|
|
1304
|
+
|
|
1305
|
+
const summary = collector.getSummary()
|
|
1306
|
+
expect(summary.totalRejections).toBe(2)
|
|
1307
|
+
})
|
|
1308
|
+
|
|
1309
|
+
it('should record backoff timeouts', () => {
|
|
1310
|
+
const collector = new DefaultMetricsCollector()
|
|
1311
|
+
|
|
1312
|
+
collector.recordBackoffTimeout('instance-1', 1000)
|
|
1313
|
+
collector.recordBackoffTimeout('instance-2', 2000)
|
|
1314
|
+
collector.recordBackoffTimeout('instance-3', 3000)
|
|
1315
|
+
|
|
1316
|
+
const summary = collector.getSummary()
|
|
1317
|
+
expect(summary.averageBackoffMs).toBe(2000)
|
|
1318
|
+
})
|
|
1319
|
+
|
|
1320
|
+
it('should track circuit state counts correctly', () => {
|
|
1321
|
+
const collector = new DefaultMetricsCollector()
|
|
1322
|
+
|
|
1323
|
+
// Create various states
|
|
1324
|
+
collector.recordStateTransition('instance-1', 'CLOSED', 'OPEN')
|
|
1325
|
+
collector.recordStateTransition('instance-2', 'CLOSED', 'OPEN')
|
|
1326
|
+
collector.recordStateTransition('instance-3', 'CLOSED', 'HALF_OPEN')
|
|
1327
|
+
collector.recordStateTransition('instance-4', 'OPEN', 'CLOSED')
|
|
1328
|
+
|
|
1329
|
+
const summary = collector.getSummary()
|
|
1330
|
+
expect(summary.openCircuits).toBe(2)
|
|
1331
|
+
expect(summary.halfOpenCircuits).toBe(1)
|
|
1332
|
+
expect(summary.closedCircuits).toBe(1)
|
|
1333
|
+
})
|
|
1334
|
+
})
|
|
1335
|
+
|
|
1336
|
+
describe('getRecentTransitions()', () => {
|
|
1337
|
+
it('should return recent state transitions', () => {
|
|
1338
|
+
const collector = new DefaultMetricsCollector()
|
|
1339
|
+
|
|
1340
|
+
collector.recordStateTransition('instance-1', 'CLOSED', 'OPEN')
|
|
1341
|
+
collector.recordStateTransition('instance-2', 'CLOSED', 'OPEN')
|
|
1342
|
+
collector.recordStateTransition('instance-1', 'OPEN', 'HALF_OPEN')
|
|
1343
|
+
|
|
1344
|
+
const transitions = collector.getRecentTransitions()
|
|
1345
|
+
expect(transitions).toHaveLength(3)
|
|
1346
|
+
expect(transitions[0]!.from).toBe('CLOSED')
|
|
1347
|
+
expect(transitions[0]!.to).toBe('OPEN')
|
|
1348
|
+
})
|
|
1349
|
+
|
|
1350
|
+
it('should limit returned transitions', () => {
|
|
1351
|
+
const collector = new DefaultMetricsCollector()
|
|
1352
|
+
|
|
1353
|
+
for (let i = 0; i < 10; i++) {
|
|
1354
|
+
collector.recordStateTransition(`instance-${i}`, 'CLOSED', 'OPEN')
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
const transitions = collector.getRecentTransitions(5)
|
|
1358
|
+
expect(transitions).toHaveLength(5)
|
|
1359
|
+
})
|
|
1360
|
+
})
|
|
1361
|
+
|
|
1362
|
+
describe('reset()', () => {
|
|
1363
|
+
it('should clear all collected metrics', () => {
|
|
1364
|
+
const collector = new DefaultMetricsCollector()
|
|
1365
|
+
|
|
1366
|
+
collector.recordStateTransition('instance-1', 'CLOSED', 'OPEN')
|
|
1367
|
+
collector.recordFailure('instance-1', 1)
|
|
1368
|
+
collector.recordSuccess('instance-2')
|
|
1369
|
+
collector.recordRejection('instance-3')
|
|
1370
|
+
collector.recordBackoffTimeout('instance-1', 1000)
|
|
1371
|
+
|
|
1372
|
+
collector.reset()
|
|
1373
|
+
|
|
1374
|
+
const summary = collector.getSummary()
|
|
1375
|
+
expect(summary.totalInstances).toBe(0)
|
|
1376
|
+
expect(summary.totalFailures).toBe(0)
|
|
1377
|
+
expect(summary.totalSuccesses).toBe(0)
|
|
1378
|
+
expect(summary.totalRejections).toBe(0)
|
|
1379
|
+
expect(summary.averageBackoffMs).toBe(0)
|
|
1380
|
+
})
|
|
1381
|
+
})
|
|
1382
|
+
})
|
|
1383
|
+
|
|
1384
|
+
describe('Metrics Integration', () => {
|
|
1385
|
+
it('should report state transitions to metrics collector', () => {
|
|
1386
|
+
const collector = new DefaultMetricsCollector()
|
|
1387
|
+
const cb = new CircuitBreaker({
|
|
1388
|
+
failureThreshold: 2,
|
|
1389
|
+
metricsCollector: collector,
|
|
1390
|
+
})
|
|
1391
|
+
|
|
1392
|
+
cb.recordFailure('test-instance')
|
|
1393
|
+
cb.recordFailure('test-instance')
|
|
1394
|
+
|
|
1395
|
+
const summary = collector.getSummary()
|
|
1396
|
+
expect(summary.openCircuits).toBe(1)
|
|
1397
|
+
})
|
|
1398
|
+
|
|
1399
|
+
it('should report failures to metrics collector', () => {
|
|
1400
|
+
const collector = new DefaultMetricsCollector()
|
|
1401
|
+
const cb = new CircuitBreaker({
|
|
1402
|
+
failureThreshold: 5,
|
|
1403
|
+
metricsCollector: collector,
|
|
1404
|
+
})
|
|
1405
|
+
|
|
1406
|
+
cb.recordFailure('test-instance')
|
|
1407
|
+
cb.recordFailure('test-instance')
|
|
1408
|
+
|
|
1409
|
+
const summary = collector.getSummary()
|
|
1410
|
+
expect(summary.totalFailures).toBe(2)
|
|
1411
|
+
})
|
|
1412
|
+
|
|
1413
|
+
it('should report successes to metrics collector', () => {
|
|
1414
|
+
const collector = new DefaultMetricsCollector()
|
|
1415
|
+
const cb = new CircuitBreaker({
|
|
1416
|
+
metricsCollector: collector,
|
|
1417
|
+
})
|
|
1418
|
+
|
|
1419
|
+
cb.recordSuccess('test-instance')
|
|
1420
|
+
cb.recordSuccess('test-instance')
|
|
1421
|
+
|
|
1422
|
+
const summary = collector.getSummary()
|
|
1423
|
+
expect(summary.totalSuccesses).toBe(2)
|
|
1424
|
+
})
|
|
1425
|
+
|
|
1426
|
+
it('should report rejections to metrics collector', async () => {
|
|
1427
|
+
const collector = new DefaultMetricsCollector()
|
|
1428
|
+
const cb = new CircuitBreaker({
|
|
1429
|
+
failureThreshold: 1,
|
|
1430
|
+
metricsCollector: collector,
|
|
1431
|
+
})
|
|
1432
|
+
|
|
1433
|
+
cb.recordFailure('test-instance')
|
|
1434
|
+
|
|
1435
|
+
try {
|
|
1436
|
+
await cb.execute('test-instance', vi.fn())
|
|
1437
|
+
} catch {
|
|
1438
|
+
// Expected
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const summary = collector.getSummary()
|
|
1442
|
+
expect(summary.totalRejections).toBe(1)
|
|
1443
|
+
})
|
|
1444
|
+
|
|
1445
|
+
it('should report backoff timeouts to metrics collector', async () => {
|
|
1446
|
+
vi.useFakeTimers()
|
|
1447
|
+
|
|
1448
|
+
const collector = new DefaultMetricsCollector()
|
|
1449
|
+
const cb = new CircuitBreaker({
|
|
1450
|
+
failureThreshold: 1,
|
|
1451
|
+
resetTimeoutMs: 1000,
|
|
1452
|
+
halfOpenSuccessThreshold: 1,
|
|
1453
|
+
enableExponentialBackoff: true,
|
|
1454
|
+
backoffMultiplier: 2,
|
|
1455
|
+
metricsCollector: collector,
|
|
1456
|
+
})
|
|
1457
|
+
|
|
1458
|
+
// First open: 1000ms timeout
|
|
1459
|
+
cb.recordFailure('test-instance')
|
|
1460
|
+
vi.advanceTimersByTime(1001)
|
|
1461
|
+
cb.getState('test-instance')
|
|
1462
|
+
|
|
1463
|
+
// Second open: 2000ms timeout
|
|
1464
|
+
cb.recordFailure('test-instance')
|
|
1465
|
+
|
|
1466
|
+
const summary = collector.getSummary()
|
|
1467
|
+
expect(summary.averageBackoffMs).toBe(2000)
|
|
1468
|
+
|
|
1469
|
+
vi.useRealTimers()
|
|
1470
|
+
})
|
|
1471
|
+
|
|
1472
|
+
it('should provide complete metrics through getAllStats and collector summary', async () => {
|
|
1473
|
+
vi.useFakeTimers()
|
|
1474
|
+
|
|
1475
|
+
const collector = new DefaultMetricsCollector()
|
|
1476
|
+
const cb = new CircuitBreaker({
|
|
1477
|
+
failureThreshold: 2,
|
|
1478
|
+
resetTimeoutMs: 1000,
|
|
1479
|
+
halfOpenSuccessThreshold: 1,
|
|
1480
|
+
metricsCollector: collector,
|
|
1481
|
+
})
|
|
1482
|
+
|
|
1483
|
+
// Create various states across instances
|
|
1484
|
+
cb.recordFailure('instance-1')
|
|
1485
|
+
cb.recordFailure('instance-1') // Opens (CLOSED -> OPEN)
|
|
1486
|
+
cb.recordFailure('instance-2')
|
|
1487
|
+
cb.recordSuccess('instance-3')
|
|
1488
|
+
|
|
1489
|
+
vi.advanceTimersByTime(1001)
|
|
1490
|
+
cb.getState('instance-1') // Moves to HALF_OPEN (OPEN -> HALF_OPEN)
|
|
1491
|
+
|
|
1492
|
+
const allStats = cb.getAllStats()
|
|
1493
|
+
const collectorSummary = collector.getSummary()
|
|
1494
|
+
|
|
1495
|
+
// getAllStats tracks all instances that have been touched
|
|
1496
|
+
expect(allStats.size).toBe(3)
|
|
1497
|
+
// Metrics collector only tracks instances that had state changes
|
|
1498
|
+
// instance-1 had state changes: CLOSED -> OPEN, OPEN -> HALF_OPEN
|
|
1499
|
+
// instance-2 and instance-3 had no state changes (still CLOSED)
|
|
1500
|
+
expect(collectorSummary.totalInstances).toBe(1) // Only instance-1 had state transitions
|
|
1501
|
+
expect(collectorSummary.halfOpenCircuits).toBe(1)
|
|
1502
|
+
|
|
1503
|
+
vi.useRealTimers()
|
|
1504
|
+
})
|
|
1505
|
+
})
|