effect 4.0.6 → 4.0.7
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/dist/Arbitrary.d.ts +10 -2
- package/dist/Arbitrary.d.ts.map +1 -1
- package/dist/Arbitrary.js +2 -1
- package/dist/Arbitrary.js.map +1 -1
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +21 -6
- package/dist/Channel.js.map +1 -1
- package/dist/Data.d.ts +54 -235
- package/dist/Data.d.ts.map +1 -1
- package/dist/Data.js.map +1 -1
- package/dist/Effect.d.ts +1 -1
- package/dist/Effect.js +1 -1
- package/dist/Equal.d.ts.map +1 -1
- package/dist/Equal.js +8 -1
- package/dist/Equal.js.map +1 -1
- package/dist/Exit.d.ts +3 -3
- package/dist/Exit.d.ts.map +1 -1
- package/dist/Exit.js.map +1 -1
- package/dist/Formatter.d.ts.map +1 -1
- package/dist/Formatter.js +27 -13
- package/dist/Formatter.js.map +1 -1
- package/dist/Hash.d.ts.map +1 -1
- package/dist/Hash.js +4 -1
- package/dist/Hash.js.map +1 -1
- package/dist/JsonPatch.js +4 -0
- package/dist/JsonPatch.js.map +1 -1
- package/dist/JsonSchema.d.ts.map +1 -1
- package/dist/JsonSchema.js +13 -40
- package/dist/JsonSchema.js.map +1 -1
- package/dist/Metric.d.ts +3 -3
- package/dist/MutableList.d.ts +5 -2
- package/dist/MutableList.d.ts.map +1 -1
- package/dist/MutableList.js +7 -5
- package/dist/MutableList.js.map +1 -1
- package/dist/MutableRef.d.ts.map +1 -1
- package/dist/MutableRef.js +7 -0
- package/dist/MutableRef.js.map +1 -1
- package/dist/PartitionedSemaphore.d.ts +2 -1
- package/dist/PartitionedSemaphore.d.ts.map +1 -1
- package/dist/PartitionedSemaphore.js +22 -12
- package/dist/PartitionedSemaphore.js.map +1 -1
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +77 -27
- package/dist/Pool.js.map +1 -1
- package/dist/PubSub.d.ts.map +1 -1
- package/dist/PubSub.js +5 -6
- package/dist/PubSub.js.map +1 -1
- package/dist/Ref.d.ts.map +1 -1
- package/dist/Ref.js +8 -0
- package/dist/Ref.js.map +1 -1
- package/dist/Schedule.js +1 -1
- package/dist/Schedule.js.map +1 -1
- package/dist/Schema.d.ts +24 -8
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +37 -28
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts +4 -2
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +14 -17
- package/dist/SchemaAST.js.map +1 -1
- package/dist/SchemaParser.d.ts.map +1 -1
- package/dist/SchemaParser.js +48 -22
- package/dist/SchemaParser.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +19 -10
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +23 -13
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/SchemaTransformation.d.ts +4 -1
- package/dist/SchemaTransformation.d.ts.map +1 -1
- package/dist/SchemaTransformation.js +9 -2
- package/dist/SchemaTransformation.js.map +1 -1
- package/dist/Semaphore.d.ts.map +1 -1
- package/dist/Semaphore.js +54 -35
- package/dist/Semaphore.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +7 -2
- package/dist/Sink.js.map +1 -1
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +22 -10
- package/dist/Stream.js.map +1 -1
- package/dist/String.d.ts +3 -0
- package/dist/String.d.ts.map +1 -1
- package/dist/String.js +3 -0
- package/dist/String.js.map +1 -1
- package/dist/Tracer.d.ts +11 -1
- package/dist/Tracer.d.ts.map +1 -1
- package/dist/Tracer.js +6 -1
- package/dist/Tracer.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +9 -0
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +110 -110
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +21 -15
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts +52 -0
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +38 -0
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +16 -3
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js +4 -0
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/Tool.d.ts +24 -0
- package/dist/ai/Tool.d.ts.map +1 -1
- package/dist/ai/Tool.js +5 -2
- package/dist/ai/Tool.js.map +1 -1
- package/dist/ai/Toolkit.d.ts.map +1 -1
- package/dist/ai/Toolkit.js +8 -1
- package/dist/ai/Toolkit.js.map +1 -1
- package/dist/cli/Argument.d.ts +15 -0
- package/dist/cli/Argument.d.ts.map +1 -1
- package/dist/cli/Argument.js +5 -0
- package/dist/cli/Argument.js.map +1 -1
- package/dist/cli/CliError.d.ts +78 -0
- package/dist/cli/CliError.d.ts.map +1 -1
- package/dist/cli/CliError.js +65 -3
- package/dist/cli/CliError.js.map +1 -1
- package/dist/cli/CliOutput.d.ts +155 -6
- package/dist/cli/CliOutput.d.ts.map +1 -1
- package/dist/cli/CliOutput.js +62 -6
- package/dist/cli/CliOutput.js.map +1 -1
- package/dist/cli/Command.d.ts +24 -16
- package/dist/cli/Command.d.ts.map +1 -1
- package/dist/cli/Command.js +59 -29
- package/dist/cli/Command.js.map +1 -1
- package/dist/cli/Completions.d.ts +58 -0
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js +49 -9
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/Flag.d.ts +15 -0
- package/dist/cli/Flag.d.ts.map +1 -1
- package/dist/cli/Flag.js +5 -0
- package/dist/cli/Flag.js.map +1 -1
- package/dist/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/cli/GlobalFlag.js +15 -7
- package/dist/cli/GlobalFlag.js.map +1 -1
- package/dist/cli/Param.d.ts +9 -0
- package/dist/cli/Param.d.ts.map +1 -1
- package/dist/cli/Param.js +21 -2
- package/dist/cli/Param.js.map +1 -1
- package/dist/cli/TerminalCapabilities.d.ts +3 -2
- package/dist/cli/TerminalCapabilities.d.ts.map +1 -1
- package/dist/cli/TerminalCapabilities.js +3 -1
- package/dist/cli/TerminalCapabilities.js.map +1 -1
- package/dist/cli/internal/completions/bash.js +73 -27
- package/dist/cli/internal/completions/bash.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +45 -10
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +23 -45
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +6 -7
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +61 -21
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/ShardingConfig.d.ts +10 -0
- package/dist/cluster/ShardingConfig.d.ts.map +1 -1
- package/dist/cluster/ShardingConfig.js.map +1 -1
- package/dist/cluster/Snowflake.d.ts.map +1 -1
- package/dist/cluster/Snowflake.js +3 -7
- package/dist/cluster/Snowflake.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +87 -39
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +54 -20
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/devtools/DevToolsClient.d.ts.map +1 -1
- package/dist/devtools/DevToolsClient.js +2 -0
- package/dist/devtools/DevToolsClient.js.map +1 -1
- package/dist/encoding/Base64.js +36 -0
- package/dist/encoding/Base64.js.map +1 -1
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +3 -3
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/encoding/Toml.d.ts.map +1 -1
- package/dist/encoding/Toml.js +21 -16
- package/dist/encoding/Toml.js.map +1 -1
- package/dist/eventlog/SqlEventJournal.d.ts.map +1 -1
- package/dist/eventlog/SqlEventJournal.js +47 -27
- package/dist/eventlog/SqlEventJournal.js.map +1 -1
- package/dist/eventlog/internal/conflicts.d.ts +0 -1
- package/dist/eventlog/internal/conflicts.d.ts.map +1 -1
- package/dist/eventlog/internal/conflicts.js +0 -3
- package/dist/eventlog/internal/conflicts.js.map +1 -1
- package/dist/http/Cookies.d.ts.map +1 -1
- package/dist/http/Cookies.js +10 -9
- package/dist/http/Cookies.js.map +1 -1
- package/dist/http/HttpClient.d.ts +423 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +231 -7
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpClientError.d.ts +8 -1
- package/dist/http/HttpClientError.d.ts.map +1 -1
- package/dist/http/HttpClientError.js +37 -1
- package/dist/http/HttpClientError.js.map +1 -1
- package/dist/http/HttpClientRequest.d.ts.map +1 -1
- package/dist/http/HttpClientRequest.js +1 -1
- package/dist/http/HttpClientRequest.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +14 -16
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +2 -1
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerError.js +2 -2
- package/dist/http/HttpServerError.js.map +1 -1
- package/dist/http/HttpServerRequest.d.ts +6 -1
- package/dist/http/HttpServerRequest.d.ts.map +1 -1
- package/dist/http/HttpServerRequest.js +73 -10
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +48 -21
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/MultipartParser/internal/contentType.js +3 -3
- package/dist/http/UrlParams.d.ts +3 -1
- package/dist/http/UrlParams.d.ts.map +1 -1
- package/dist/http/UrlParams.js +4 -2
- package/dist/http/UrlParams.js.map +1 -1
- package/dist/http/internal/compression.js +4 -5
- package/dist/http/internal/compression.js.map +1 -1
- package/dist/http/internal/decompression.js +3 -0
- package/dist/http/internal/decompression.js.map +1 -1
- package/dist/http/internal/httpBody.js +8 -1
- package/dist/http/internal/httpBody.js.map +1 -1
- package/dist/http/internal/urlParams.js +25 -0
- package/dist/http/internal/urlParams.js.map +1 -1
- package/dist/http-api/HttpApi.d.ts +3 -6
- package/dist/http-api/HttpApi.d.ts.map +1 -1
- package/dist/http-api/HttpApi.js +3 -6
- package/dist/http-api/HttpApi.js.map +1 -1
- package/dist/http-api/HttpApiBuilder.d.ts.map +1 -1
- package/dist/http-api/HttpApiBuilder.js +27 -11
- package/dist/http-api/HttpApiBuilder.js.map +1 -1
- package/dist/http-api/HttpApiClient.d.ts +77 -7
- package/dist/http-api/HttpApiClient.d.ts.map +1 -1
- package/dist/http-api/HttpApiClient.js +142 -44
- package/dist/http-api/HttpApiClient.js.map +1 -1
- package/dist/http-api/HttpApiEndpoint.d.ts +24 -7
- package/dist/http-api/HttpApiEndpoint.d.ts.map +1 -1
- package/dist/http-api/HttpApiEndpoint.js +109 -27
- package/dist/http-api/HttpApiEndpoint.js.map +1 -1
- package/dist/http-api/HttpApiSchema.d.ts +76 -29
- package/dist/http-api/HttpApiSchema.d.ts.map +1 -1
- package/dist/http-api/HttpApiSchema.js +70 -59
- package/dist/http-api/HttpApiSchema.js.map +1 -1
- package/dist/http-api/HttpApiTest.d.ts.map +1 -1
- package/dist/http-api/HttpApiTest.js +3 -2
- package/dist/http-api/HttpApiTest.js.map +1 -1
- package/dist/http-api/OpenApi.d.ts.map +1 -1
- package/dist/http-api/OpenApi.js +25 -16
- package/dist/http-api/OpenApi.js.map +1 -1
- package/dist/http-api/internal/toCodecHttpProperties.d.ts +2 -0
- package/dist/http-api/internal/toCodecHttpProperties.d.ts.map +1 -0
- package/dist/http-api/internal/toCodecHttpProperties.js +38 -0
- package/dist/http-api/internal/toCodecHttpProperties.js.map +1 -0
- package/dist/internal/arbitrary/runner.d.ts.map +1 -1
- package/dist/internal/arbitrary/runner.js +120 -44
- package/dist/internal/arbitrary/runner.js.map +1 -1
- package/dist/internal/core.js +2 -0
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/effect.js +21 -13
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/equal.js +14 -0
- package/dist/internal/equal.js.map +1 -1
- package/dist/internal/jsonSchema.d.ts +2 -0
- package/dist/internal/jsonSchema.d.ts.map +1 -0
- package/dist/internal/jsonSchema.js +28 -0
- package/dist/internal/jsonSchema.js.map +1 -0
- package/dist/internal/matcher.js +1 -1
- package/dist/internal/matcher.js.map +1 -1
- package/dist/internal/schema/compilerRegistry.js +4 -1
- package/dist/internal/schema/compilerRegistry.js.map +1 -1
- package/dist/internal/schema/content.d.ts +2 -0
- package/dist/internal/schema/content.d.ts.map +1 -0
- package/dist/internal/schema/content.js +13 -0
- package/dist/internal/schema/content.js.map +1 -0
- package/dist/internal/schema/fromJsonSchemaDocument.js +173 -185
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/schema/fromRepresentation.js +2 -1
- package/dist/internal/schema/fromRepresentation.js.map +1 -1
- package/dist/internal/schema/standardSchema.js +8 -2
- package/dist/internal/schema/standardSchema.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +3 -3
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toCodec.js +21 -24
- package/dist/internal/schema/toCodec.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +205 -140
- package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
- package/dist/internal/schema/toRepresentation.js +107 -129
- package/dist/internal/schema/toRepresentation.js.map +1 -1
- package/dist/internal/sql.d.ts +2 -0
- package/dist/internal/sql.d.ts.map +1 -0
- package/dist/internal/sql.js +13 -0
- package/dist/internal/sql.js.map +1 -0
- package/dist/internal/version.js +1 -1
- package/dist/observability/OtlpProxy.d.ts +154 -0
- package/dist/observability/OtlpProxy.d.ts.map +1 -0
- package/dist/observability/OtlpProxy.js +235 -0
- package/dist/observability/OtlpProxy.js.map +1 -0
- package/dist/observability/OtlpTracer.d.ts.map +1 -1
- package/dist/observability/OtlpTracer.js +4 -0
- package/dist/observability/OtlpTracer.js.map +1 -1
- package/dist/observability/index.d.ts +5 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +5 -0
- package/dist/observability/index.js.map +1 -1
- package/dist/persistence/CircuitBreaker.d.ts +897 -0
- package/dist/persistence/CircuitBreaker.d.ts.map +1 -0
- package/dist/persistence/CircuitBreaker.js +1165 -0
- package/dist/persistence/CircuitBreaker.js.map +1 -0
- package/dist/persistence/Redis.d.ts +78 -2
- package/dist/persistence/Redis.d.ts.map +1 -1
- package/dist/persistence/Redis.js +62 -7
- package/dist/persistence/Redis.js.map +1 -1
- package/dist/persistence/index.d.ts +5 -0
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +5 -0
- package/dist/persistence/index.js.map +1 -1
- package/dist/process/ChildProcess.d.ts +30 -4
- package/dist/process/ChildProcess.d.ts.map +1 -1
- package/dist/process/ChildProcess.js.map +1 -1
- package/dist/process/ChildProcessSpawner.d.ts +30 -0
- package/dist/process/ChildProcessSpawner.d.ts.map +1 -1
- package/dist/process/ChildProcessSpawner.js +5 -2
- package/dist/process/ChildProcessSpawner.js.map +1 -1
- package/dist/reactivity/AsyncResult.d.ts +1 -1
- package/dist/reactivity/AsyncResult.d.ts.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +9 -8
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcSerialization.d.ts +14 -0
- package/dist/rpc/RpcSerialization.d.ts.map +1 -1
- package/dist/rpc/RpcSerialization.js +20 -8
- package/dist/rpc/RpcSerialization.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +11 -1
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/rpc/RpcTest.d.ts +1 -0
- package/dist/rpc/RpcTest.d.ts.map +1 -1
- package/dist/rpc/RpcTest.js +1 -0
- package/dist/rpc/RpcTest.js.map +1 -1
- package/dist/sql/Migrator.d.ts +2 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +11 -8
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +9 -4
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +8 -1
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/SqlResolver.d.ts +4 -0
- package/dist/sql/SqlResolver.d.ts.map +1 -1
- package/dist/sql/SqlResolver.js +18 -4
- package/dist/sql/SqlResolver.js.map +1 -1
- package/dist/sql/Statement.d.ts +7 -0
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +21 -2
- package/dist/sql/Statement.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +21 -2
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/package.json +1 -1
- package/src/Arbitrary.ts +10 -2
- package/src/Channel.ts +47 -18
- package/src/Data.ts +60 -265
- package/src/Effect.ts +1 -1
- package/src/Equal.ts +8 -1
- package/src/Exit.ts +3 -3
- package/src/Formatter.ts +28 -13
- package/src/Hash.ts +4 -1
- package/src/JsonPatch.ts +4 -0
- package/src/JsonSchema.ts +12 -40
- package/src/Metric.ts +3 -3
- package/src/MutableList.ts +7 -5
- package/src/MutableRef.ts +8 -1
- package/src/PartitionedSemaphore.ts +24 -13
- package/src/Pool.ts +109 -37
- package/src/PubSub.ts +5 -6
- package/src/Ref.ts +8 -0
- package/src/Schedule.ts +1 -1
- package/src/Schema.ts +48 -25
- package/src/SchemaAST.ts +20 -20
- package/src/SchemaParser.ts +51 -18
- package/src/SchemaRepresentation.ts +31 -34
- package/src/SchemaTransformation.ts +9 -2
- package/src/Semaphore.ts +67 -40
- package/src/Sink.ts +15 -2
- package/src/Stream.ts +33 -16
- package/src/String.ts +3 -0
- package/src/Tracer.ts +15 -1
- package/src/ai/LanguageModel.ts +9 -0
- package/src/ai/McpServer.ts +14 -13
- package/src/ai/Prompt.ts +65 -0
- package/src/ai/Response.ts +34 -9
- package/src/ai/Tool.ts +38 -2
- package/src/ai/Toolkit.ts +4 -1
- package/src/cli/Argument.ts +15 -0
- package/src/cli/CliError.ts +90 -3
- package/src/cli/CliOutput.ts +183 -6
- package/src/cli/Command.ts +53 -41
- package/src/cli/Completions.ts +51 -0
- package/src/cli/Flag.ts +15 -0
- package/src/cli/GlobalFlag.ts +6 -7
- package/src/cli/Param.ts +29 -6
- package/src/cli/TerminalCapabilities.ts +6 -3
- package/src/cli/internal/completions/bash.ts +79 -29
- package/src/cluster/ClusterWorkflowEngine.ts +46 -16
- package/src/cluster/MessageStorage.ts +19 -14
- package/src/cluster/Sharding.ts +83 -21
- package/src/cluster/ShardingConfig.ts +5 -0
- package/src/cluster/Snowflake.ts +3 -7
- package/src/cluster/SqlRunnerStorage.ts +110 -47
- package/src/cluster/internal/entityManager.ts +75 -22
- package/src/devtools/DevToolsClient.ts +2 -0
- package/src/encoding/Base64.ts +38 -0
- package/src/encoding/Sse.ts +4 -3
- package/src/encoding/Toml.ts +33 -16
- package/src/eventlog/SqlEventJournal.ts +68 -45
- package/src/eventlog/internal/conflicts.ts +0 -4
- package/src/http/Cookies.ts +10 -11
- package/src/http/HttpClient.ts +547 -7
- package/src/http/HttpClientError.ts +35 -1
- package/src/http/HttpClientRequest.ts +9 -1
- package/src/http/HttpMiddleware.ts +15 -18
- package/src/http/HttpRouter.ts +2 -1
- package/src/http/HttpServerError.ts +2 -2
- package/src/http/HttpServerRequest.ts +102 -16
- package/src/http/HttpStaticServer.ts +51 -22
- package/src/http/MultipartParser/internal/contentType.ts +3 -3
- package/src/http/UrlParams.ts +6 -1
- package/src/http/internal/compression.ts +4 -5
- package/src/http/internal/decompression.ts +6 -0
- package/src/http/internal/httpBody.ts +10 -2
- package/src/http/internal/urlParams.ts +29 -0
- package/src/http-api/HttpApi.ts +3 -6
- package/src/http-api/HttpApiBuilder.ts +45 -15
- package/src/http-api/HttpApiClient.ts +218 -72
- package/src/http-api/HttpApiEndpoint.ts +121 -30
- package/src/http-api/HttpApiSchema.ts +141 -48
- package/src/http-api/HttpApiTest.ts +3 -2
- package/src/http-api/OpenApi.ts +20 -17
- package/src/http-api/internal/toCodecHttpProperties.ts +56 -0
- package/src/internal/arbitrary/runner.ts +133 -36
- package/src/internal/core.ts +2 -0
- package/src/internal/effect.ts +24 -16
- package/src/internal/equal.ts +17 -0
- package/src/internal/jsonSchema.ts +27 -0
- package/src/internal/matcher.ts +1 -1
- package/src/internal/schema/compilerRegistry.ts +6 -1
- package/src/internal/schema/content.ts +18 -0
- package/src/internal/schema/fromJsonSchemaDocument.ts +228 -190
- package/src/internal/schema/fromRepresentation.ts +2 -1
- package/src/internal/schema/standardSchema.ts +2 -2
- package/src/internal/schema/toCodeDocument.ts +4 -4
- package/src/internal/schema/toCodec.ts +26 -22
- package/src/internal/schema/toJsonSchemaDocument.ts +189 -167
- package/src/internal/schema/toRepresentation.ts +122 -140
- package/src/internal/sql.ts +14 -0
- package/src/internal/version.ts +1 -1
- package/src/observability/OtlpProxy.ts +314 -0
- package/src/observability/OtlpTracer.ts +4 -0
- package/src/observability/index.ts +6 -0
- package/src/persistence/CircuitBreaker.ts +1678 -0
- package/src/persistence/Redis.ts +129 -8
- package/src/persistence/index.ts +6 -0
- package/src/process/ChildProcess.ts +30 -4
- package/src/process/ChildProcessSpawner.ts +41 -2
- package/src/reactivity/AsyncResult.ts +1 -1
- package/src/rpc/RpcClient.ts +13 -10
- package/src/rpc/RpcSerialization.ts +32 -9
- package/src/rpc/RpcServer.ts +16 -1
- package/src/rpc/RpcTest.ts +3 -0
- package/src/sql/Migrator.ts +12 -7
- package/src/sql/SqlClient.ts +11 -4
- package/src/sql/SqlResolver.ts +23 -9
- package/src/sql/Statement.ts +30 -2
- package/src/workflow/DurableDeferred.ts +23 -6
package/src/http/HttpClient.ts
CHANGED
|
@@ -25,6 +25,7 @@ import * as Inspectable from "../Inspectable.ts"
|
|
|
25
25
|
import { getStackTraceLimit, setStackTraceLimit } from "../internal/stackTraceLimit.ts"
|
|
26
26
|
import * as Layer from "../Layer.ts"
|
|
27
27
|
import * as Option from "../Option.ts"
|
|
28
|
+
import type * as CircuitBreaker from "../persistence/CircuitBreaker.ts"
|
|
28
29
|
import type * as RateLimiter from "../persistence/RateLimiter.ts"
|
|
29
30
|
import { type Pipeable, pipeArguments } from "../Pipeable.ts"
|
|
30
31
|
import * as Predicate from "../Predicate.ts"
|
|
@@ -836,12 +837,11 @@ export const make = (
|
|
|
836
837
|
fiber.getRef(TracerDisabledWhen)(request)
|
|
837
838
|
if (tracerDisabled) {
|
|
838
839
|
const effect = f(request, url, controller.signal, fiber as any)
|
|
839
|
-
if (scopedController) return effect
|
|
840
|
+
if (scopedController) return Effect.map(effect, (response) => trackResponse(response, controller, true))
|
|
840
841
|
return Effect.uninterruptibleMask((restore) =>
|
|
841
842
|
Effect.matchCauseEffect(restore(effect), {
|
|
842
843
|
onSuccess(response) {
|
|
843
|
-
|
|
844
|
-
return Effect.succeed(new InterruptibleResponse(response, controller))
|
|
844
|
+
return Effect.succeed(trackResponse(response, controller, false))
|
|
845
845
|
},
|
|
846
846
|
onFailure(cause) {
|
|
847
847
|
if (Cause.hasInterrupts(cause)) {
|
|
@@ -882,9 +882,7 @@ export const make = (
|
|
|
882
882
|
endSpanWithStatusError(fiber, span, request, response)
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
-
|
|
886
|
-
responseRegistry.register(response, controller)
|
|
887
|
-
return Effect.succeed(new InterruptibleResponse(response, controller))
|
|
885
|
+
return Effect.succeed(trackResponse(response, controller, scopedController !== undefined))
|
|
888
886
|
},
|
|
889
887
|
onFailure(cause) {
|
|
890
888
|
if (!scopedController && Cause.hasInterrupts(cause)) {
|
|
@@ -1678,6 +1676,515 @@ export const withRateLimiter: {
|
|
|
1678
1676
|
})
|
|
1679
1677
|
})
|
|
1680
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* Namespace containing configuration types for `withCircuitBreaker`.
|
|
1681
|
+
*
|
|
1682
|
+
* @stability experimental
|
|
1683
|
+
* @since 4.0.6
|
|
1684
|
+
*/
|
|
1685
|
+
export declare namespace WithCircuitBreaker {
|
|
1686
|
+
/**
|
|
1687
|
+
* Options used to configure `withCircuitBreaker`.
|
|
1688
|
+
*
|
|
1689
|
+
* @stability experimental
|
|
1690
|
+
* @category circuit breaking
|
|
1691
|
+
* @since 4.0.6
|
|
1692
|
+
*/
|
|
1693
|
+
export interface Options<E, R2 = never> {
|
|
1694
|
+
/**
|
|
1695
|
+
* The `CircuitBreaker` service that guards each request.
|
|
1696
|
+
*/
|
|
1697
|
+
readonly breaker: CircuitBreaker.CircuitBreaker
|
|
1698
|
+
/**
|
|
1699
|
+
* The circuit key for a request. Requests with the same key share one
|
|
1700
|
+
* circuit. Defaults to the request origin, falling back to the raw request
|
|
1701
|
+
* URL when it cannot be parsed.
|
|
1702
|
+
*/
|
|
1703
|
+
readonly key?: string | ((request: HttpClientRequest.HttpClientRequest) => string) | undefined
|
|
1704
|
+
/**
|
|
1705
|
+
* Classifies the `Exit` of each request that the breaker admitted.
|
|
1706
|
+
* Defaults to `circuitBreakerClassifier`.
|
|
1707
|
+
*/
|
|
1708
|
+
readonly classify?:
|
|
1709
|
+
| ((
|
|
1710
|
+
exit: Exit.Exit<HttpClientResponse.HttpClientResponse, E>
|
|
1711
|
+
) => CircuitBreaker.Outcome | Effect.Effect<CircuitBreaker.Outcome, never, R2>)
|
|
1712
|
+
| undefined
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* Guards requests with a `CircuitBreaker`.
|
|
1718
|
+
*
|
|
1719
|
+
* **Details**
|
|
1720
|
+
*
|
|
1721
|
+
* Requests are keyed by origin unless `key` is set, and classified with
|
|
1722
|
+
* `circuitBreakerClassifier` unless `classify` is set. A rejected request
|
|
1723
|
+
* fails with `CircuitBreakerError` without reaching the wrapped client.
|
|
1724
|
+
*
|
|
1725
|
+
* Apply it after `withRateLimiter` and `retryTransient`, so the breaker sees
|
|
1726
|
+
* one outcome per call and an open circuit skips the retry loop.
|
|
1727
|
+
* `retryTransient` never retries a `CircuitBreakerError`. A custom `classify`
|
|
1728
|
+
* can delegate the cases it doesn't handle to `circuitBreakerClassifier`.
|
|
1729
|
+
*
|
|
1730
|
+
* **Gotchas**
|
|
1731
|
+
*
|
|
1732
|
+
* - The breaker only sees the response headers. To count an error in the
|
|
1733
|
+
* body, lift it into the error channel below the breaker with
|
|
1734
|
+
* `transformResponse` and count it in `classify`.
|
|
1735
|
+
* - `withRateLimiter` retries `429` without limit by default, which can hold
|
|
1736
|
+
* the probe slot for minutes. Set its `times`.
|
|
1737
|
+
* - A rejected request has no client span. The rejection fails the caller's
|
|
1738
|
+
* span instead.
|
|
1739
|
+
*
|
|
1740
|
+
* **Example** (Guarding an API in the recommended order)
|
|
1741
|
+
*
|
|
1742
|
+
* ```ts import.meta.vitest
|
|
1743
|
+
* import { Effect, Layer, Ref, Schedule } from "effect"
|
|
1744
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
1745
|
+
* import { CircuitBreaker, RateLimiter } from "effect/persistence"
|
|
1746
|
+
*
|
|
1747
|
+
* const program = Effect.gen(function*() {
|
|
1748
|
+
* const requests = yield* Ref.make(0)
|
|
1749
|
+
* // A dependency that always answers 503.
|
|
1750
|
+
* const unavailable = HttpClient.make((request) =>
|
|
1751
|
+
* Effect.as(Ref.update(requests, (n) => n + 1), HttpClientResponse.fromWeb(request, new Response(null, { status: 503 })))
|
|
1752
|
+
* )
|
|
1753
|
+
*
|
|
1754
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
1755
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
1756
|
+
* openDuration: "30 seconds"
|
|
1757
|
+
* })
|
|
1758
|
+
* const client = unavailable.pipe(
|
|
1759
|
+
* HttpClient.withRateLimiter({
|
|
1760
|
+
* limiter: yield* RateLimiter.RateLimiter,
|
|
1761
|
+
* key: "inventory",
|
|
1762
|
+
* limit: 100,
|
|
1763
|
+
* window: "1 second",
|
|
1764
|
+
* times: 3
|
|
1765
|
+
* }),
|
|
1766
|
+
* HttpClient.retryTransient({ times: 2, schedule: Schedule.exponential("1 millis") }),
|
|
1767
|
+
* HttpClient.withCircuitBreaker({ breaker })
|
|
1768
|
+
* )
|
|
1769
|
+
*
|
|
1770
|
+
* // Three attempts, then the breaker records one failure and opens.
|
|
1771
|
+
* yield* client.execute(HttpClientRequest.get("http://inventory.test/items"))
|
|
1772
|
+
* // Rejected before the retry loop starts, so no further request is sent.
|
|
1773
|
+
* const rejected = yield* Effect.flip(client.execute(HttpClientRequest.get("http://inventory.test/items")))
|
|
1774
|
+
*
|
|
1775
|
+
* return [yield* Ref.get(requests), rejected._tag]
|
|
1776
|
+
* }).pipe(
|
|
1777
|
+
* Effect.scoped,
|
|
1778
|
+
* Effect.provide(Layer.merge(
|
|
1779
|
+
* CircuitBreaker.layerStoreMemory(),
|
|
1780
|
+
* RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))
|
|
1781
|
+
* ))
|
|
1782
|
+
* )
|
|
1783
|
+
*
|
|
1784
|
+
* await Effect.runPromise(program) // => [3, "CircuitBreakerError"]
|
|
1785
|
+
* ```
|
|
1786
|
+
*
|
|
1787
|
+
* **Example** (Counting an error in the body as a failure)
|
|
1788
|
+
*
|
|
1789
|
+
* This API answers `200` with `{ "status": "degraded" }` when it is failing.
|
|
1790
|
+
*
|
|
1791
|
+
* ```ts import.meta.vitest
|
|
1792
|
+
* import { Data, Effect, Exit, Option } from "effect"
|
|
1793
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
1794
|
+
* import { CircuitBreaker } from "effect/persistence"
|
|
1795
|
+
*
|
|
1796
|
+
* class Degraded extends Data.TaggedError("Degraded") {}
|
|
1797
|
+
*
|
|
1798
|
+
* const degraded = HttpClient.make((request) =>
|
|
1799
|
+
* Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "degraded" })))
|
|
1800
|
+
* )
|
|
1801
|
+
*
|
|
1802
|
+
* const program = Effect.gen(function*() {
|
|
1803
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
1804
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
1805
|
+
* openDuration: "30 seconds"
|
|
1806
|
+
* })
|
|
1807
|
+
* const client = degraded.pipe(
|
|
1808
|
+
* HttpClient.transformResponse(Effect.flatMap((response) =>
|
|
1809
|
+
* Effect.flatMap(response.json, (body) =>
|
|
1810
|
+
* typeof body === "object" && body !== null && "status" in body && body.status === "degraded"
|
|
1811
|
+
* ? Effect.fail(new Degraded())
|
|
1812
|
+
* : Effect.succeed(response))
|
|
1813
|
+
* )),
|
|
1814
|
+
* HttpClient.withCircuitBreaker({
|
|
1815
|
+
* breaker,
|
|
1816
|
+
* classify: (exit) =>
|
|
1817
|
+
* Option.exists(Exit.findErrorOption(exit), (error) => error._tag === "Degraded")
|
|
1818
|
+
* ? CircuitBreaker.failure()
|
|
1819
|
+
* : HttpClient.circuitBreakerClassifier(exit)
|
|
1820
|
+
* })
|
|
1821
|
+
* )
|
|
1822
|
+
*
|
|
1823
|
+
* yield* Effect.flip(client.get("http://status.test/"))
|
|
1824
|
+
* const state = yield* breaker.state("http://status.test")
|
|
1825
|
+
* return state._tag
|
|
1826
|
+
* }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
|
|
1827
|
+
*
|
|
1828
|
+
* await Effect.runPromise(program) // => "Open"
|
|
1829
|
+
* ```
|
|
1830
|
+
*
|
|
1831
|
+
* @stability experimental
|
|
1832
|
+
* @category circuit breaking
|
|
1833
|
+
* @since 4.0.6
|
|
1834
|
+
*/
|
|
1835
|
+
export const withCircuitBreaker: {
|
|
1836
|
+
/**
|
|
1837
|
+
* Guards requests with a `CircuitBreaker`.
|
|
1838
|
+
*
|
|
1839
|
+
* **Details**
|
|
1840
|
+
*
|
|
1841
|
+
* Requests are keyed by origin unless `key` is set, and classified with
|
|
1842
|
+
* `circuitBreakerClassifier` unless `classify` is set. A rejected request
|
|
1843
|
+
* fails with `CircuitBreakerError` without reaching the wrapped client.
|
|
1844
|
+
*
|
|
1845
|
+
* Apply it after `withRateLimiter` and `retryTransient`, so the breaker sees
|
|
1846
|
+
* one outcome per call and an open circuit skips the retry loop.
|
|
1847
|
+
* `retryTransient` never retries a `CircuitBreakerError`. A custom `classify`
|
|
1848
|
+
* can delegate the cases it doesn't handle to `circuitBreakerClassifier`.
|
|
1849
|
+
*
|
|
1850
|
+
* **Gotchas**
|
|
1851
|
+
*
|
|
1852
|
+
* - The breaker only sees the response headers. To count an error in the
|
|
1853
|
+
* body, lift it into the error channel below the breaker with
|
|
1854
|
+
* `transformResponse` and count it in `classify`.
|
|
1855
|
+
* - `withRateLimiter` retries `429` without limit by default, which can hold
|
|
1856
|
+
* the probe slot for minutes. Set its `times`.
|
|
1857
|
+
* - A rejected request has no client span. The rejection fails the caller's
|
|
1858
|
+
* span instead.
|
|
1859
|
+
*
|
|
1860
|
+
* **Example** (Guarding an API in the recommended order)
|
|
1861
|
+
*
|
|
1862
|
+
* ```ts import.meta.vitest
|
|
1863
|
+
* import { Effect, Layer, Ref, Schedule } from "effect"
|
|
1864
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
1865
|
+
* import { CircuitBreaker, RateLimiter } from "effect/persistence"
|
|
1866
|
+
*
|
|
1867
|
+
* const program = Effect.gen(function*() {
|
|
1868
|
+
* const requests = yield* Ref.make(0)
|
|
1869
|
+
* // A dependency that always answers 503.
|
|
1870
|
+
* const unavailable = HttpClient.make((request) =>
|
|
1871
|
+
* Effect.as(Ref.update(requests, (n) => n + 1), HttpClientResponse.fromWeb(request, new Response(null, { status: 503 })))
|
|
1872
|
+
* )
|
|
1873
|
+
*
|
|
1874
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
1875
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
1876
|
+
* openDuration: "30 seconds"
|
|
1877
|
+
* })
|
|
1878
|
+
* const client = unavailable.pipe(
|
|
1879
|
+
* HttpClient.withRateLimiter({
|
|
1880
|
+
* limiter: yield* RateLimiter.RateLimiter,
|
|
1881
|
+
* key: "inventory",
|
|
1882
|
+
* limit: 100,
|
|
1883
|
+
* window: "1 second",
|
|
1884
|
+
* times: 3
|
|
1885
|
+
* }),
|
|
1886
|
+
* HttpClient.retryTransient({ times: 2, schedule: Schedule.exponential("1 millis") }),
|
|
1887
|
+
* HttpClient.withCircuitBreaker({ breaker })
|
|
1888
|
+
* )
|
|
1889
|
+
*
|
|
1890
|
+
* // Three attempts, then the breaker records one failure and opens.
|
|
1891
|
+
* yield* client.execute(HttpClientRequest.get("http://inventory.test/items"))
|
|
1892
|
+
* // Rejected before the retry loop starts, so no further request is sent.
|
|
1893
|
+
* const rejected = yield* Effect.flip(client.execute(HttpClientRequest.get("http://inventory.test/items")))
|
|
1894
|
+
*
|
|
1895
|
+
* return [yield* Ref.get(requests), rejected._tag]
|
|
1896
|
+
* }).pipe(
|
|
1897
|
+
* Effect.scoped,
|
|
1898
|
+
* Effect.provide(Layer.merge(
|
|
1899
|
+
* CircuitBreaker.layerStoreMemory(),
|
|
1900
|
+
* RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))
|
|
1901
|
+
* ))
|
|
1902
|
+
* )
|
|
1903
|
+
*
|
|
1904
|
+
* await Effect.runPromise(program) // => [3, "CircuitBreakerError"]
|
|
1905
|
+
* ```
|
|
1906
|
+
*
|
|
1907
|
+
* **Example** (Counting an error in the body as a failure)
|
|
1908
|
+
*
|
|
1909
|
+
* This API answers `200` with `{ "status": "degraded" }` when it is failing.
|
|
1910
|
+
*
|
|
1911
|
+
* ```ts import.meta.vitest
|
|
1912
|
+
* import { Data, Effect, Exit, Option } from "effect"
|
|
1913
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
1914
|
+
* import { CircuitBreaker } from "effect/persistence"
|
|
1915
|
+
*
|
|
1916
|
+
* class Degraded extends Data.TaggedError("Degraded") {}
|
|
1917
|
+
*
|
|
1918
|
+
* const degraded = HttpClient.make((request) =>
|
|
1919
|
+
* Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "degraded" })))
|
|
1920
|
+
* )
|
|
1921
|
+
*
|
|
1922
|
+
* const program = Effect.gen(function*() {
|
|
1923
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
1924
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
1925
|
+
* openDuration: "30 seconds"
|
|
1926
|
+
* })
|
|
1927
|
+
* const client = degraded.pipe(
|
|
1928
|
+
* HttpClient.transformResponse(Effect.flatMap((response) =>
|
|
1929
|
+
* Effect.flatMap(response.json, (body) =>
|
|
1930
|
+
* typeof body === "object" && body !== null && "status" in body && body.status === "degraded"
|
|
1931
|
+
* ? Effect.fail(new Degraded())
|
|
1932
|
+
* : Effect.succeed(response))
|
|
1933
|
+
* )),
|
|
1934
|
+
* HttpClient.withCircuitBreaker({
|
|
1935
|
+
* breaker,
|
|
1936
|
+
* classify: (exit) =>
|
|
1937
|
+
* Option.exists(Exit.findErrorOption(exit), (error) => error._tag === "Degraded")
|
|
1938
|
+
* ? CircuitBreaker.failure()
|
|
1939
|
+
* : HttpClient.circuitBreakerClassifier(exit)
|
|
1940
|
+
* })
|
|
1941
|
+
* )
|
|
1942
|
+
*
|
|
1943
|
+
* yield* Effect.flip(client.get("http://status.test/"))
|
|
1944
|
+
* const state = yield* breaker.state("http://status.test")
|
|
1945
|
+
* return state._tag
|
|
1946
|
+
* }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
|
|
1947
|
+
*
|
|
1948
|
+
* await Effect.runPromise(program) // => "Open"
|
|
1949
|
+
* ```
|
|
1950
|
+
*
|
|
1951
|
+
* @stability experimental
|
|
1952
|
+
* @category circuit breaking
|
|
1953
|
+
* @since 4.0.6
|
|
1954
|
+
*/
|
|
1955
|
+
<E, R2 = never>(options: WithCircuitBreaker.Options<E, R2>): <R>(
|
|
1956
|
+
self: HttpClient.With<E, R>
|
|
1957
|
+
) => HttpClient.With<E | CircuitBreaker.CircuitBreakerError, R | R2>
|
|
1958
|
+
/**
|
|
1959
|
+
* Guards requests with a `CircuitBreaker`.
|
|
1960
|
+
*
|
|
1961
|
+
* **Details**
|
|
1962
|
+
*
|
|
1963
|
+
* Requests are keyed by origin unless `key` is set, and classified with
|
|
1964
|
+
* `circuitBreakerClassifier` unless `classify` is set. A rejected request
|
|
1965
|
+
* fails with `CircuitBreakerError` without reaching the wrapped client.
|
|
1966
|
+
*
|
|
1967
|
+
* Apply it after `withRateLimiter` and `retryTransient`, so the breaker sees
|
|
1968
|
+
* one outcome per call and an open circuit skips the retry loop.
|
|
1969
|
+
* `retryTransient` never retries a `CircuitBreakerError`. A custom `classify`
|
|
1970
|
+
* can delegate the cases it doesn't handle to `circuitBreakerClassifier`.
|
|
1971
|
+
*
|
|
1972
|
+
* **Gotchas**
|
|
1973
|
+
*
|
|
1974
|
+
* - The breaker only sees the response headers. To count an error in the
|
|
1975
|
+
* body, lift it into the error channel below the breaker with
|
|
1976
|
+
* `transformResponse` and count it in `classify`.
|
|
1977
|
+
* - `withRateLimiter` retries `429` without limit by default, which can hold
|
|
1978
|
+
* the probe slot for minutes. Set its `times`.
|
|
1979
|
+
* - A rejected request has no client span. The rejection fails the caller's
|
|
1980
|
+
* span instead.
|
|
1981
|
+
*
|
|
1982
|
+
* **Example** (Guarding an API in the recommended order)
|
|
1983
|
+
*
|
|
1984
|
+
* ```ts import.meta.vitest
|
|
1985
|
+
* import { Effect, Layer, Ref, Schedule } from "effect"
|
|
1986
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
1987
|
+
* import { CircuitBreaker, RateLimiter } from "effect/persistence"
|
|
1988
|
+
*
|
|
1989
|
+
* const program = Effect.gen(function*() {
|
|
1990
|
+
* const requests = yield* Ref.make(0)
|
|
1991
|
+
* // A dependency that always answers 503.
|
|
1992
|
+
* const unavailable = HttpClient.make((request) =>
|
|
1993
|
+
* Effect.as(Ref.update(requests, (n) => n + 1), HttpClientResponse.fromWeb(request, new Response(null, { status: 503 })))
|
|
1994
|
+
* )
|
|
1995
|
+
*
|
|
1996
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
1997
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
1998
|
+
* openDuration: "30 seconds"
|
|
1999
|
+
* })
|
|
2000
|
+
* const client = unavailable.pipe(
|
|
2001
|
+
* HttpClient.withRateLimiter({
|
|
2002
|
+
* limiter: yield* RateLimiter.RateLimiter,
|
|
2003
|
+
* key: "inventory",
|
|
2004
|
+
* limit: 100,
|
|
2005
|
+
* window: "1 second",
|
|
2006
|
+
* times: 3
|
|
2007
|
+
* }),
|
|
2008
|
+
* HttpClient.retryTransient({ times: 2, schedule: Schedule.exponential("1 millis") }),
|
|
2009
|
+
* HttpClient.withCircuitBreaker({ breaker })
|
|
2010
|
+
* )
|
|
2011
|
+
*
|
|
2012
|
+
* // Three attempts, then the breaker records one failure and opens.
|
|
2013
|
+
* yield* client.execute(HttpClientRequest.get("http://inventory.test/items"))
|
|
2014
|
+
* // Rejected before the retry loop starts, so no further request is sent.
|
|
2015
|
+
* const rejected = yield* Effect.flip(client.execute(HttpClientRequest.get("http://inventory.test/items")))
|
|
2016
|
+
*
|
|
2017
|
+
* return [yield* Ref.get(requests), rejected._tag]
|
|
2018
|
+
* }).pipe(
|
|
2019
|
+
* Effect.scoped,
|
|
2020
|
+
* Effect.provide(Layer.merge(
|
|
2021
|
+
* CircuitBreaker.layerStoreMemory(),
|
|
2022
|
+
* RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))
|
|
2023
|
+
* ))
|
|
2024
|
+
* )
|
|
2025
|
+
*
|
|
2026
|
+
* await Effect.runPromise(program) // => [3, "CircuitBreakerError"]
|
|
2027
|
+
* ```
|
|
2028
|
+
*
|
|
2029
|
+
* **Example** (Counting an error in the body as a failure)
|
|
2030
|
+
*
|
|
2031
|
+
* This API answers `200` with `{ "status": "degraded" }` when it is failing.
|
|
2032
|
+
*
|
|
2033
|
+
* ```ts import.meta.vitest
|
|
2034
|
+
* import { Data, Effect, Exit, Option } from "effect"
|
|
2035
|
+
* import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
|
|
2036
|
+
* import { CircuitBreaker } from "effect/persistence"
|
|
2037
|
+
*
|
|
2038
|
+
* class Degraded extends Data.TaggedError("Degraded") {}
|
|
2039
|
+
*
|
|
2040
|
+
* const degraded = HttpClient.make((request) =>
|
|
2041
|
+
* Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "degraded" })))
|
|
2042
|
+
* )
|
|
2043
|
+
*
|
|
2044
|
+
* const program = Effect.gen(function*() {
|
|
2045
|
+
* const breaker = yield* CircuitBreaker.make({
|
|
2046
|
+
* tripPolicy: CircuitBreaker.consecutive(1),
|
|
2047
|
+
* openDuration: "30 seconds"
|
|
2048
|
+
* })
|
|
2049
|
+
* const client = degraded.pipe(
|
|
2050
|
+
* HttpClient.transformResponse(Effect.flatMap((response) =>
|
|
2051
|
+
* Effect.flatMap(response.json, (body) =>
|
|
2052
|
+
* typeof body === "object" && body !== null && "status" in body && body.status === "degraded"
|
|
2053
|
+
* ? Effect.fail(new Degraded())
|
|
2054
|
+
* : Effect.succeed(response))
|
|
2055
|
+
* )),
|
|
2056
|
+
* HttpClient.withCircuitBreaker({
|
|
2057
|
+
* breaker,
|
|
2058
|
+
* classify: (exit) =>
|
|
2059
|
+
* Option.exists(Exit.findErrorOption(exit), (error) => error._tag === "Degraded")
|
|
2060
|
+
* ? CircuitBreaker.failure()
|
|
2061
|
+
* : HttpClient.circuitBreakerClassifier(exit)
|
|
2062
|
+
* })
|
|
2063
|
+
* )
|
|
2064
|
+
*
|
|
2065
|
+
* yield* Effect.flip(client.get("http://status.test/"))
|
|
2066
|
+
* const state = yield* breaker.state("http://status.test")
|
|
2067
|
+
* return state._tag
|
|
2068
|
+
* }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
|
|
2069
|
+
*
|
|
2070
|
+
* await Effect.runPromise(program) // => "Open"
|
|
2071
|
+
* ```
|
|
2072
|
+
*
|
|
2073
|
+
* @stability experimental
|
|
2074
|
+
* @category circuit breaking
|
|
2075
|
+
* @since 4.0.6
|
|
2076
|
+
*/
|
|
2077
|
+
<E, R, R2 = never>(self: HttpClient.With<E, R>, options: WithCircuitBreaker.Options<E, R2>): HttpClient.With<E | CircuitBreaker.CircuitBreakerError, R | R2>
|
|
2078
|
+
} = dual(2, <E, R, R2>(
|
|
2079
|
+
self: HttpClient.With<E, R>,
|
|
2080
|
+
options: WithCircuitBreaker.Options<E, R2>
|
|
2081
|
+
): HttpClient.With<E | CircuitBreaker.CircuitBreakerError, R | R2> => {
|
|
2082
|
+
const keyOption = options.key
|
|
2083
|
+
const resolveKey: (request: HttpClientRequest.HttpClientRequest) => string = typeof keyOption === "function"
|
|
2084
|
+
? keyOption
|
|
2085
|
+
: keyOption === undefined
|
|
2086
|
+
? requestOrigin
|
|
2087
|
+
: constant(keyOption)
|
|
2088
|
+
const classify = options.classify ?? circuitBreakerClassifier
|
|
2089
|
+
return transform(self, (effect, request) => options.breaker.execute(effect, { key: resolveKey(request), classify }))
|
|
2090
|
+
})
|
|
2091
|
+
|
|
2092
|
+
const requestOrigin = (request: HttpClientRequest.HttpClientRequest): string =>
|
|
2093
|
+
Option.match(HttpClientRequest.toUrl(request), {
|
|
2094
|
+
onNone: () => request.url,
|
|
2095
|
+
onSome: (url) => url.origin
|
|
2096
|
+
})
|
|
2097
|
+
|
|
2098
|
+
/**
|
|
2099
|
+
* The default `withCircuitBreaker` classifier.
|
|
2100
|
+
*
|
|
2101
|
+
* **Details**
|
|
2102
|
+
*
|
|
2103
|
+
* - Failure: status 408, 500, 502, 503 or 504, returned or wrapped in a
|
|
2104
|
+
* `StatusCodeError`, and any `TransportError` or `Cause.TimeoutError`.
|
|
2105
|
+
* - Success: any other response, including other 4xx, since the dependency
|
|
2106
|
+
* answered.
|
|
2107
|
+
* - Ignored: status 429 (left to `withRateLimiter`), `EncodeError`,
|
|
2108
|
+
* `InvalidUrlError`, `DecodeError`, `EmptyBodyError`, unknown typed errors
|
|
2109
|
+
* and defects. HttpClient errors are typed, so a defect here is a bug in
|
|
2110
|
+
* the caller.
|
|
2111
|
+
*
|
|
2112
|
+
* When a cause holds several errors, a failure beats a success and a success
|
|
2113
|
+
* beats an ignored outcome. A failure carries the longest `Retry-After` it
|
|
2114
|
+
* saw. The result is an `Effect` only because parsing an HTTP-date
|
|
2115
|
+
* `Retry-After` reads `Clock`.
|
|
2116
|
+
*
|
|
2117
|
+
* **Gotchas**
|
|
2118
|
+
*
|
|
2119
|
+
* `TransportError` also covers client-side refusals, which count as failures.
|
|
2120
|
+
*
|
|
2121
|
+
* @stability experimental
|
|
2122
|
+
* @category circuit breaking
|
|
2123
|
+
* @since 4.0.6
|
|
2124
|
+
*/
|
|
2125
|
+
export const circuitBreakerClassifier = <E>(
|
|
2126
|
+
exit: Exit.Exit<HttpClientResponse.HttpClientResponse, E>
|
|
2127
|
+
): Effect.Effect<CircuitBreaker.Outcome> =>
|
|
2128
|
+
Effect.clockWith((clock) => Effect.succeed(classifyCircuitBreakerExit(exit, clock.currentTimeMillisUnsafe())))
|
|
2129
|
+
|
|
2130
|
+
const classifyCircuitBreakerExit = <E>(
|
|
2131
|
+
exit: Exit.Exit<HttpClientResponse.HttpClientResponse, E>,
|
|
2132
|
+
nowMillis: number
|
|
2133
|
+
): CircuitBreaker.Outcome => {
|
|
2134
|
+
if (Exit.isSuccess(exit)) {
|
|
2135
|
+
const outcome = circuitBreakerStatusOutcome(exit.value.status)
|
|
2136
|
+
return outcome === "Failure" ? circuitBreakerFailure(retryAfterOf(exit.value, nowMillis)) : { _tag: outcome }
|
|
2137
|
+
}
|
|
2138
|
+
let failed = false
|
|
2139
|
+
let succeeded = false
|
|
2140
|
+
let retryAfter: Duration.Duration | undefined
|
|
2141
|
+
for (const reason of exit.cause.reasons) {
|
|
2142
|
+
if (!Cause.isFailReason(reason)) {
|
|
2143
|
+
continue
|
|
2144
|
+
}
|
|
2145
|
+
const outcome = circuitBreakerErrorOutcome(reason.error)
|
|
2146
|
+
if (outcome === "Failure") {
|
|
2147
|
+
failed = true
|
|
2148
|
+
const hint = retryAfterOf(reason.error, nowMillis)
|
|
2149
|
+
if (hint !== undefined) {
|
|
2150
|
+
retryAfter = retryAfter === undefined ? hint : Duration.max(retryAfter, hint)
|
|
2151
|
+
}
|
|
2152
|
+
} else if (outcome === "Success") {
|
|
2153
|
+
succeeded = true
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return failed ? circuitBreakerFailure(retryAfter) : { _tag: succeeded ? "Success" : "Ignored" }
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
const circuitBreakerFailure = (retryAfter: Duration.Duration | undefined): CircuitBreaker.Outcome => ({
|
|
2160
|
+
_tag: "Failure",
|
|
2161
|
+
retryAfter
|
|
2162
|
+
})
|
|
2163
|
+
|
|
2164
|
+
const circuitBreakerErrorOutcome = (error: unknown): CircuitBreaker.Outcome["_tag"] => {
|
|
2165
|
+
if (Cause.isTimeoutError(error)) {
|
|
2166
|
+
return "Failure"
|
|
2167
|
+
}
|
|
2168
|
+
if (!Error.isHttpClientError(error)) {
|
|
2169
|
+
return "Ignored"
|
|
2170
|
+
}
|
|
2171
|
+
switch (error.reason._tag) {
|
|
2172
|
+
case "TransportError":
|
|
2173
|
+
return "Failure"
|
|
2174
|
+
case "StatusCodeError":
|
|
2175
|
+
return circuitBreakerStatusOutcome(error.reason.response.status)
|
|
2176
|
+
default:
|
|
2177
|
+
return "Ignored"
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
const circuitBreakerStatusOutcome = (status: number): CircuitBreaker.Outcome["_tag"] =>
|
|
2182
|
+
status === 429
|
|
2183
|
+
? "Ignored"
|
|
2184
|
+
: status === 408 || status === 500 || status === 502 || status === 503 || status === 504
|
|
2185
|
+
? "Failure"
|
|
2186
|
+
: "Success"
|
|
2187
|
+
|
|
1681
2188
|
interface RateLimiterHeaderNames {
|
|
1682
2189
|
readonly limit: ReadonlyArray<string>
|
|
1683
2190
|
readonly remaining: ReadonlyArray<string>
|
|
@@ -2187,13 +2694,31 @@ export const followRedirects: {
|
|
|
2187
2694
|
) {
|
|
2188
2695
|
nextRequest = HttpClientRequest.setMethod(nextRequest, "GET")
|
|
2189
2696
|
nextRequest = HttpClientRequest.setBody(nextRequest, HttpBody.empty)
|
|
2697
|
+
nextRequest = HttpClientRequest.removeHeader(nextRequest, "content-encoding")
|
|
2698
|
+
nextRequest = HttpClientRequest.removeHeader(nextRequest, "content-language")
|
|
2699
|
+
nextRequest = HttpClientRequest.removeHeader(nextRequest, "content-location")
|
|
2190
2700
|
}
|
|
2191
2701
|
if (url.origin !== new URL(response.request.url).origin) {
|
|
2192
2702
|
nextRequest = HttpClientRequest.removeHeader(nextRequest, "authorization")
|
|
2193
2703
|
nextRequest = HttpClientRequest.removeHeader(nextRequest, "proxy-authorization")
|
|
2194
2704
|
nextRequest = HttpClientRequest.removeHeader(nextRequest, "cookie")
|
|
2195
2705
|
}
|
|
2196
|
-
|
|
2706
|
+
// Request the Location as sent: setUrl with a URL object would re-encode its query
|
|
2707
|
+
const hash = url.hash === "" ? Option.none() : Option.some(url.hash.slice(1))
|
|
2708
|
+
url.hash = ""
|
|
2709
|
+
nextRequest = HttpClientRequest.makeWith(
|
|
2710
|
+
nextRequest.method,
|
|
2711
|
+
url.href,
|
|
2712
|
+
UrlParams.empty,
|
|
2713
|
+
hash,
|
|
2714
|
+
nextRequest.headers,
|
|
2715
|
+
nextRequest.body
|
|
2716
|
+
)
|
|
2717
|
+
// Abort the discarded response to release its connection without reading the body
|
|
2718
|
+
return Effect.andThen(
|
|
2719
|
+
Effect.sync(() => responseControllers.get(response)?.abort()),
|
|
2720
|
+
loop(Effect.succeed(nextRequest), redirects + 1)
|
|
2721
|
+
)
|
|
2197
2722
|
}
|
|
2198
2723
|
)
|
|
2199
2724
|
})
|
|
@@ -2332,6 +2857,21 @@ const responseRegistry = (() => {
|
|
|
2332
2857
|
|
|
2333
2858
|
const scopedRequests = new WeakMap<HttpClientRequest.HttpClientRequest, AbortController>()
|
|
2334
2859
|
|
|
2860
|
+
const responseControllers = new WeakMap<HttpClientResponse.HttpClientResponse, AbortController>()
|
|
2861
|
+
|
|
2862
|
+
const trackResponse = (
|
|
2863
|
+
response: HttpClientResponse.HttpClientResponse,
|
|
2864
|
+
controller: AbortController,
|
|
2865
|
+
scoped: boolean
|
|
2866
|
+
): HttpClientResponse.HttpClientResponse => {
|
|
2867
|
+
if (!scoped) {
|
|
2868
|
+
responseRegistry.register(response, controller)
|
|
2869
|
+
response = new InterruptibleResponse(response, controller)
|
|
2870
|
+
}
|
|
2871
|
+
responseControllers.set(response, controller)
|
|
2872
|
+
return response
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2335
2875
|
class InterruptibleResponse implements HttpClientResponse.HttpClientResponse, Pipeable {
|
|
2336
2876
|
readonly original: HttpClientResponse.HttpClientResponse
|
|
2337
2877
|
readonly controller: AbortController
|
|
@@ -92,6 +92,15 @@ const formatMessage = (reason: string, description: string | undefined, info: st
|
|
|
92
92
|
const formatMethodAndUrl = (request: HttpClientRequest.HttpClientRequest) =>
|
|
93
93
|
`${request.method} ${UrlParams.redactUrl(request.url)}`
|
|
94
94
|
|
|
95
|
+
// Serialized reasons carry the same request and response information as the
|
|
96
|
+
// message, so headers, URL parameters and bodies stay out of logs. The full
|
|
97
|
+
// objects remain available through the `request` and `response` properties.
|
|
98
|
+
const reasonToJSON = (reason: HttpClientErrorReason, json: unknown) => ({
|
|
99
|
+
...(json as object),
|
|
100
|
+
request: { _id: "HttpClientRequest", method: reason.request.method, url: UrlParams.redactUrl(reason.request.url) },
|
|
101
|
+
...("response" in reason ? { response: { _id: "HttpClientResponse", status: reason.response.status } } : undefined)
|
|
102
|
+
})
|
|
103
|
+
|
|
95
104
|
/**
|
|
96
105
|
* Error describing transport-level failures that occur while sending an HTTP request.
|
|
97
106
|
*
|
|
@@ -123,6 +132,10 @@ export class TransportError extends Data.TaggedError("TransportError")<{
|
|
|
123
132
|
override get message() {
|
|
124
133
|
return formatMessage(formatReason(this._tag), this.description, this.methodAndUrl)
|
|
125
134
|
}
|
|
135
|
+
|
|
136
|
+
override toJSON(): unknown {
|
|
137
|
+
return reasonToJSON(this, super.toJSON())
|
|
138
|
+
}
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
/**
|
|
@@ -156,6 +169,10 @@ export class EncodeError extends Data.TaggedError("EncodeError")<{
|
|
|
156
169
|
override get message() {
|
|
157
170
|
return formatMessage(formatReason(this._tag), this.description, this.methodAndUrl)
|
|
158
171
|
}
|
|
172
|
+
|
|
173
|
+
override toJSON(): unknown {
|
|
174
|
+
return reasonToJSON(this, super.toJSON())
|
|
175
|
+
}
|
|
159
176
|
}
|
|
160
177
|
|
|
161
178
|
/**
|
|
@@ -189,6 +206,10 @@ export class InvalidUrlError extends Data.TaggedError("InvalidUrlError")<{
|
|
|
189
206
|
override get message() {
|
|
190
207
|
return formatMessage(formatReason(this._tag), this.description, this.methodAndUrl)
|
|
191
208
|
}
|
|
209
|
+
|
|
210
|
+
override toJSON(): unknown {
|
|
211
|
+
return reasonToJSON(this, super.toJSON())
|
|
212
|
+
}
|
|
192
213
|
}
|
|
193
214
|
|
|
194
215
|
/**
|
|
@@ -224,10 +245,15 @@ export class StatusCodeError extends Data.TaggedError("StatusCodeError")<{
|
|
|
224
245
|
const info = `${this.response.status} ${this.methodAndUrl}`
|
|
225
246
|
return formatMessage(formatReason(this._tag), this.description, info)
|
|
226
247
|
}
|
|
248
|
+
|
|
249
|
+
override toJSON(): unknown {
|
|
250
|
+
return reasonToJSON(this, super.toJSON())
|
|
251
|
+
}
|
|
227
252
|
}
|
|
228
253
|
|
|
229
254
|
/**
|
|
230
|
-
* Response error for failures while decoding an HTTP response
|
|
255
|
+
* Response error for failures while decoding an HTTP response, including a
|
|
256
|
+
* response with an undeclared status.
|
|
231
257
|
*
|
|
232
258
|
* @stability unstable
|
|
233
259
|
* @category errors
|
|
@@ -259,6 +285,10 @@ export class DecodeError extends Data.TaggedError("DecodeError")<{
|
|
|
259
285
|
const info = `${this.response.status} ${this.methodAndUrl}`
|
|
260
286
|
return formatMessage(formatReason(this._tag), this.description, info)
|
|
261
287
|
}
|
|
288
|
+
|
|
289
|
+
override toJSON(): unknown {
|
|
290
|
+
return reasonToJSON(this, super.toJSON())
|
|
291
|
+
}
|
|
262
292
|
}
|
|
263
293
|
|
|
264
294
|
/**
|
|
@@ -294,6 +324,10 @@ export class EmptyBodyError extends Data.TaggedError("EmptyBodyError")<{
|
|
|
294
324
|
const info = `${this.response.status} ${this.methodAndUrl}`
|
|
295
325
|
return formatMessage(formatReason(this._tag), this.description, info)
|
|
296
326
|
}
|
|
327
|
+
|
|
328
|
+
override toJSON(): unknown {
|
|
329
|
+
return reasonToJSON(this, super.toJSON())
|
|
330
|
+
}
|
|
297
331
|
}
|
|
298
332
|
|
|
299
333
|
/**
|