effect 4.0.3 → 4.0.5
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/AGENTS.md +24 -0
- package/CLAUDE.md +24 -0
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/52_graphql/10_basics.ts +117 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +118 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +124 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/dist/Array.d.ts +90 -6
- package/dist/Array.d.ts.map +1 -1
- package/dist/Array.js +4 -2
- package/dist/Array.js.map +1 -1
- package/dist/Channel.d.ts +6 -1
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +17 -3
- package/dist/Channel.js.map +1 -1
- package/dist/ConfigProvider.d.ts.map +1 -1
- package/dist/ConfigProvider.js +33 -32
- package/dist/ConfigProvider.js.map +1 -1
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +9 -0
- package/dist/Context.js.map +1 -1
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +2 -5
- package/dist/Duration.js.map +1 -1
- package/dist/Effect.d.ts +61 -19
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +58 -39
- package/dist/Effect.js.map +1 -1
- package/dist/FileSystem.d.ts +23 -9
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js.map +1 -1
- package/dist/Match.d.ts +20 -6
- package/dist/Match.d.ts.map +1 -1
- package/dist/Match.js +2 -2
- package/dist/Match.js.map +1 -1
- package/dist/Path.d.ts +37 -5
- package/dist/Path.d.ts.map +1 -1
- package/dist/Path.js +36 -5
- package/dist/Path.js.map +1 -1
- package/dist/RequestResolver.d.ts.map +1 -1
- package/dist/RequestResolver.js +4 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +9 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +15 -2
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +16 -2
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +4 -0
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +52 -33
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +38 -17
- package/dist/Sink.js.map +1 -1
- package/dist/Stream.d.ts +11 -0
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +16 -1
- package/dist/Stream.js.map +1 -1
- package/dist/String.d.ts +12 -0
- package/dist/String.d.ts.map +1 -1
- package/dist/String.js +17 -4
- package/dist/String.js.map +1 -1
- package/dist/TxRef.d.ts.map +1 -1
- package/dist/TxRef.js +3 -2
- package/dist/TxRef.js.map +1 -1
- package/dist/Utils.js +8 -3
- package/dist/Utils.js.map +1 -1
- package/dist/ai/AiError.d.ts +10 -10
- package/dist/ai/AiError.d.ts.map +1 -1
- package/dist/ai/AiError.js +1 -1
- package/dist/ai/AiError.js.map +1 -1
- package/dist/ai/AnthropicStructuredOutput.js +0 -2
- package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
- package/dist/ai/Chat.d.ts +2 -2
- package/dist/ai/Chat.d.ts.map +1 -1
- package/dist/ai/Decision.d.ts +70 -19
- package/dist/ai/Decision.d.ts.map +1 -1
- package/dist/ai/Decision.js +37 -3
- package/dist/ai/Decision.js.map +1 -1
- package/dist/ai/DecisionModel.js +1 -1
- package/dist/ai/DecisionModel.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts +38 -7
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +70 -40
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +61 -52
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +134 -50
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +28 -5
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +5 -4
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js.map +1 -1
- package/dist/cli/Completions.d.ts +5 -0
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/Flag.d.ts +100 -0
- package/dist/cli/Flag.d.ts.map +1 -1
- package/dist/cli/Flag.js +33 -0
- package/dist/cli/Flag.js.map +1 -1
- package/dist/cli/Param.d.ts +87 -0
- package/dist/cli/Param.d.ts.map +1 -1
- package/dist/cli/Param.js +30 -0
- package/dist/cli/Param.js.map +1 -1
- package/dist/cli/internal/command.d.ts.map +1 -1
- package/dist/cli/internal/command.js +2 -1
- package/dist/cli/internal/command.js.map +1 -1
- package/dist/cli/internal/completions/bash.js +4 -3
- package/dist/cli/internal/completions/bash.js.map +1 -1
- package/dist/cli/internal/completions/descriptor.js +8 -1
- package/dist/cli/internal/completions/descriptor.js.map +1 -1
- package/dist/cli/internal/completions/fish.js +4 -4
- package/dist/cli/internal/completions/fish.js.map +1 -1
- package/dist/cli/internal/completions/powershell.js +3 -2
- package/dist/cli/internal/completions/powershell.js.map +1 -1
- package/dist/cli/internal/completions/zsh.js +7 -3
- package/dist/cli/internal/completions/zsh.js.map +1 -1
- package/dist/cli/internal/parser.js +11 -3
- package/dist/cli/internal/parser.js.map +1 -1
- package/dist/cli/internal/wizard.d.ts.map +1 -1
- package/dist/cli/internal/wizard.js +2 -1
- package/dist/cli/internal/wizard.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +113 -21
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +20 -12
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +54 -34
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +6 -4
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +1 -1
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/encoding/Sse.d.ts +6 -0
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +63 -71
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQL.d.ts +349 -0
- package/dist/graphql/GraphQL.d.ts.map +1 -0
- package/dist/graphql/GraphQL.js +207 -0
- package/dist/graphql/GraphQL.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +562 -0
- package/dist/graphql/GraphQLClient.d.ts.map +1 -0
- package/dist/graphql/GraphQLClient.js +505 -0
- package/dist/graphql/GraphQLClient.js.map +1 -0
- package/dist/graphql/GraphQLClientError.d.ts +445 -0
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
- package/dist/graphql/GraphQLClientError.js +479 -0
- package/dist/graphql/GraphQLClientError.js.map +1 -0
- package/dist/graphql/GraphQLGroup.d.ts +118 -0
- package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
- package/dist/graphql/GraphQLGroup.js +109 -0
- package/dist/graphql/GraphQLGroup.js.map +1 -0
- package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
- package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
- package/dist/graphql/GraphQLMiddleware.js +130 -0
- package/dist/graphql/GraphQLMiddleware.js.map +1 -0
- package/dist/graphql/GraphQLProtocol.d.ts +969 -0
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
- package/dist/graphql/GraphQLProtocol.js +809 -0
- package/dist/graphql/GraphQLProtocol.js.map +1 -0
- package/dist/graphql/GraphQLResolver.d.ts +453 -0
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
- package/dist/graphql/GraphQLResolver.js +582 -0
- package/dist/graphql/GraphQLResolver.js.map +1 -0
- package/dist/graphql/index.d.ts +40 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +41 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/http/FetchHttpClient.d.ts +6 -3
- package/dist/http/FetchHttpClient.d.ts.map +1 -1
- package/dist/http/FetchHttpClient.js +6 -3
- package/dist/http/FetchHttpClient.js.map +1 -1
- package/dist/http/FindMyWay/internal/router.js +2 -1
- package/dist/http/FindMyWay/internal/router.js.map +1 -1
- package/dist/http/Headers.d.ts +3 -3
- package/dist/http/Headers.d.ts.map +1 -1
- package/dist/http/Headers.js +6 -5
- package/dist/http/Headers.js.map +1 -1
- package/dist/http/HttpClient.d.ts +27 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +38 -11
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpClientError.d.ts.map +1 -1
- package/dist/http/HttpClientError.js +8 -6
- package/dist/http/HttpClientError.js.map +1 -1
- package/dist/http/HttpClientRequest.d.ts +147 -4
- package/dist/http/HttpClientRequest.d.ts.map +1 -1
- package/dist/http/HttpClientRequest.js +47 -1
- package/dist/http/HttpClientRequest.js.map +1 -1
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +14 -7
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts +60 -0
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +93 -32
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts +35 -0
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +87 -13
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerError.d.ts.map +1 -1
- package/dist/http/HttpServerError.js +6 -4
- package/dist/http/HttpServerError.js.map +1 -1
- package/dist/http/HttpServerRequest.js +2 -2
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts +8 -6
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +27 -16
- package/dist/http/HttpServerResponse.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +4 -10
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/HttpTraceContext.js +1 -1
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http/Multipart.d.ts.map +1 -1
- package/dist/http/Multipart.js +15 -1
- package/dist/http/Multipart.js.map +1 -1
- package/dist/http/Url.d.ts.map +1 -1
- package/dist/http/Url.js +2 -1
- package/dist/http/Url.js.map +1 -1
- package/dist/http/UrlParams.d.ts +112 -8
- package/dist/http/UrlParams.d.ts.map +1 -1
- package/dist/http/UrlParams.js +141 -14
- package/dist/http/UrlParams.js.map +1 -1
- package/dist/http/internal/compression.d.ts +1 -1
- package/dist/http/internal/compression.d.ts.map +1 -1
- package/dist/http/internal/compression.js +2 -10
- package/dist/http/internal/compression.js.map +1 -1
- package/dist/http/internal/compressionBody.d.ts +2 -0
- package/dist/http/internal/compressionBody.d.ts.map +1 -0
- package/dist/http/internal/compressionBody.js +36 -0
- package/dist/http/internal/compressionBody.js.map +1 -0
- package/dist/http/internal/decompression.d.ts +2 -0
- package/dist/http/internal/decompression.d.ts.map +1 -0
- package/dist/http/internal/decompression.js +162 -0
- package/dist/http/internal/decompression.js.map +1 -0
- package/dist/http/internal/tracing.js +14 -4
- package/dist/http/internal/tracing.js.map +1 -1
- package/dist/http/internal/urlParams.d.ts +2 -0
- package/dist/http/internal/urlParams.d.ts.map +1 -0
- package/dist/http/internal/urlParams.js +4 -0
- package/dist/http/internal/urlParams.js.map +1 -0
- package/dist/http-api/HttpApiClient.d.ts.map +1 -1
- package/dist/http-api/HttpApiClient.js +2 -1
- package/dist/http-api/HttpApiClient.js.map +1 -1
- package/dist/http-api/HttpApiScalar.d.ts +5 -0
- package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
- package/dist/http-api/HttpApiScalar.js.map +1 -1
- package/dist/internal/core.js +17 -1
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/dateTime.js +40 -35
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +13 -6
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.d.ts +2 -0
- package/dist/internal/graphql.d.ts.map +1 -0
- package/dist/internal/graphql.js +205 -0
- package/dist/internal/graphql.js.map +1 -0
- package/dist/internal/graphqlHeaders.d.ts +4 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -0
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -0
- package/dist/internal/request.js +20 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +4 -1
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
- package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/Host.d.ts +49 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +55 -0
- package/dist/net/Host.js.map +1 -1
- package/dist/net/NetAddress.d.ts +8 -0
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +8 -0
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/observability/Otlp.d.ts +15 -0
- package/dist/observability/Otlp.d.ts.map +1 -1
- package/dist/observability/Otlp.js +3 -0
- package/dist/observability/Otlp.js.map +1 -1
- package/dist/observability/OtlpExporter.d.ts +8 -0
- package/dist/observability/OtlpExporter.d.ts.map +1 -1
- package/dist/observability/OtlpExporter.js +10 -1
- package/dist/observability/OtlpExporter.js.map +1 -1
- package/dist/observability/OtlpLogger.d.ts +17 -0
- package/dist/observability/OtlpLogger.d.ts.map +1 -1
- package/dist/observability/OtlpLogger.js +14 -1
- package/dist/observability/OtlpLogger.js.map +1 -1
- package/dist/observability/OtlpMetrics.d.ts +17 -0
- package/dist/observability/OtlpMetrics.d.ts.map +1 -1
- package/dist/observability/OtlpMetrics.js +13 -0
- package/dist/observability/OtlpMetrics.js.map +1 -1
- package/dist/observability/OtlpTracer.d.ts +17 -0
- package/dist/observability/OtlpTracer.d.ts.map +1 -1
- package/dist/observability/OtlpTracer.js +15 -2
- package/dist/observability/OtlpTracer.js.map +1 -1
- package/dist/reactivity/AsyncResult.d.ts +6 -0
- package/dist/reactivity/AsyncResult.d.ts.map +1 -1
- package/dist/reactivity/AsyncResult.js +27 -0
- package/dist/reactivity/AsyncResult.js.map +1 -1
- package/dist/reactivity/AtomRegistry.js +3 -1
- package/dist/reactivity/AtomRegistry.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +6 -1
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +36 -9
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/Socket.d.ts +6 -0
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +8 -7
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +8 -7
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/testing/TestClock.d.ts +16 -3
- package/dist/testing/TestClock.d.ts.map +1 -1
- package/dist/testing/TestClock.js +112 -22
- package/dist/testing/TestClock.js.map +1 -1
- package/dist/workflow/Activity.js +1 -0
- package/dist/workflow/Activity.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +32 -3
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/Workflow.d.ts +22 -0
- package/dist/workflow/Workflow.d.ts.map +1 -1
- package/dist/workflow/Workflow.js +15 -3
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +44 -3
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +45 -22
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +6 -3
- package/src/Array.ts +95 -6
- package/src/Channel.ts +24 -3
- package/src/ConfigProvider.ts +40 -44
- package/src/Context.ts +9 -0
- package/src/Duration.ts +4 -5
- package/src/Effect.ts +140 -53
- package/src/FileSystem.ts +23 -9
- package/src/Match.ts +65 -12
- package/src/Path.ts +52 -7
- package/src/RequestResolver.ts +4 -10
- package/src/Scheduler.ts +9 -0
- package/src/Schema.ts +25 -4
- package/src/SchemaGetter.ts +47 -35
- package/src/Sink.ts +45 -18
- package/src/Stream.ts +33 -4
- package/src/String.ts +17 -4
- package/src/TxRef.ts +4 -2
- package/src/Utils.ts +8 -5
- package/src/ai/AiError.ts +3 -1
- package/src/ai/AnthropicStructuredOutput.ts +2 -2
- package/src/ai/Chat.ts +2 -2
- package/src/ai/Decision.ts +70 -21
- package/src/ai/DecisionModel.ts +1 -1
- package/src/ai/LanguageModel.ts +141 -63
- package/src/ai/McpServer.ts +259 -145
- package/src/ai/Prompt.ts +24 -10
- package/src/ai/Response.ts +6 -3
- package/src/cli/Completions.ts +5 -0
- package/src/cli/Flag.ts +101 -0
- package/src/cli/Param.ts +96 -0
- package/src/cli/internal/command.ts +2 -1
- package/src/cli/internal/completions/bash.ts +4 -3
- package/src/cli/internal/completions/descriptor.ts +10 -1
- package/src/cli/internal/completions/fish.ts +4 -4
- package/src/cli/internal/completions/powershell.ts +5 -2
- package/src/cli/internal/completions/zsh.ts +7 -3
- package/src/cli/internal/parser.ts +12 -3
- package/src/cli/internal/wizard.ts +2 -1
- package/src/cluster/ClusterWorkflowEngine.ts +143 -36
- package/src/cluster/MessageStorage.ts +14 -3
- package/src/cluster/Runners.ts +88 -66
- package/src/cluster/Sharding.ts +12 -9
- package/src/cluster/internal/entityManager.ts +1 -1
- package/src/encoding/Sse.ts +70 -80
- package/src/graphql/GraphQL.ts +494 -0
- package/src/graphql/GraphQLClient.ts +924 -0
- package/src/graphql/GraphQLClientError.ts +542 -0
- package/src/graphql/GraphQLGroup.ts +167 -0
- package/src/graphql/GraphQLMiddleware.ts +253 -0
- package/src/graphql/GraphQLProtocol.ts +1540 -0
- package/src/graphql/GraphQLResolver.ts +1038 -0
- package/src/graphql/index.ts +48 -0
- package/src/http/FetchHttpClient.ts +6 -3
- package/src/http/FindMyWay/internal/router.ts +2 -1
- package/src/http/Headers.ts +6 -9
- package/src/http/HttpClient.ts +72 -11
- package/src/http/HttpClientError.ts +10 -6
- package/src/http/HttpClientRequest.ts +173 -7
- package/src/http/HttpEffect.ts +11 -5
- package/src/http/HttpMiddleware.ts +113 -34
- package/src/http/HttpRouter.ts +95 -13
- package/src/http/HttpServerError.ts +8 -4
- package/src/http/HttpServerRequest.ts +2 -2
- package/src/http/HttpServerResponse.ts +28 -16
- package/src/http/HttpStaticServer.ts +4 -10
- package/src/http/HttpTraceContext.ts +1 -1
- package/src/http/Multipart.ts +17 -1
- package/src/http/Url.ts +2 -1
- package/src/http/UrlParams.ts +225 -26
- package/src/http/internal/compression.ts +3 -17
- package/src/http/internal/compressionBody.ts +56 -0
- package/src/http/internal/decompression.ts +226 -0
- package/src/http/internal/tracing.ts +21 -4
- package/src/http/internal/urlParams.ts +5 -0
- package/src/http-api/HttpApiClient.ts +2 -1
- package/src/http-api/HttpApiScalar.ts +5 -0
- package/src/internal/core.ts +21 -1
- package/src/internal/dateTime.ts +13 -10
- package/src/internal/effect.ts +13 -7
- package/src/internal/graphql.ts +286 -0
- package/src/internal/graphqlHeaders.ts +11 -0
- package/src/internal/request.ts +31 -3
- package/src/internal/schema/toCodeDocument.ts +7 -1
- package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
- package/src/internal/version.ts +1 -1
- package/src/net/Host.ts +72 -0
- package/src/net/NetAddress.ts +9 -0
- package/src/observability/Otlp.ts +18 -0
- package/src/observability/OtlpExporter.ts +14 -3
- package/src/observability/OtlpLogger.ts +31 -2
- package/src/observability/OtlpMetrics.ts +30 -1
- package/src/observability/OtlpTracer.ts +32 -3
- package/src/reactivity/AsyncResult.ts +31 -0
- package/src/reactivity/AtomRegistry.ts +3 -1
- package/src/rpc/RpcClient.ts +6 -1
- package/src/rpc/RpcServer.ts +55 -15
- package/src/socket/Socket.ts +6 -0
- package/src/sql/SqlClient.ts +9 -7
- package/src/testing/TestClock.ts +144 -29
- package/src/workflow/Activity.ts +1 -0
- package/src/workflow/DurableDeferred.ts +43 -5
- package/src/workflow/Workflow.ts +31 -5
- package/src/workflow/WorkflowEngine.ts +107 -25
|
@@ -282,7 +282,7 @@ export const make = Effect.gen(function*() {
|
|
|
282
282
|
yield* storage.clearAddress(clockAddress)
|
|
283
283
|
})
|
|
284
284
|
|
|
285
|
-
const resume = Effect.fnUntraced(function*(workflow: Workflow.Any, executionId: string) {
|
|
285
|
+
const resume = Effect.fnUntraced(function*(workflow: Workflow.Any, executionId: string, mode: WakeMode) {
|
|
286
286
|
const maybeReply = yield* requestReply({
|
|
287
287
|
workflow,
|
|
288
288
|
entityType: `Workflow/${workflow._tag}`,
|
|
@@ -291,12 +291,11 @@ export const make = Effect.gen(function*() {
|
|
|
291
291
|
id: ""
|
|
292
292
|
})
|
|
293
293
|
|
|
294
|
-
const maybeSuspended = Option.filter(
|
|
295
|
-
maybeReply,
|
|
296
|
-
(reply) => reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended"
|
|
297
|
-
)
|
|
294
|
+
const maybeSuspended = Option.filter(maybeReply, isSuspendedReply)
|
|
298
295
|
|
|
299
296
|
if (Option.isNone(maybeSuspended)) return
|
|
297
|
+
// Replies without `failed` retain automatic wakes for compatibility.
|
|
298
|
+
if (mode === "automatic" && isFailedReply(maybeSuspended.value)) return
|
|
300
299
|
if (
|
|
301
300
|
!(yield* sharding.reset(
|
|
302
301
|
Snowflake.Snowflake(maybeSuspended.value.requestId),
|
|
@@ -308,6 +307,15 @@ export const make = Effect.gen(function*() {
|
|
|
308
307
|
yield* sharding.pollStorage
|
|
309
308
|
})
|
|
310
309
|
|
|
310
|
+
// Activations load a persisted interrupt when they start, so the per-call
|
|
311
|
+
// check reads only memory.
|
|
312
|
+
const interruptPending = (instance: WorkflowEngine.WorkflowInstance["Service"]) =>
|
|
313
|
+
Effect.map(Effect.serviceOption(CurrentActivationScope), (activation) => {
|
|
314
|
+
const pending = Option.isSome(activation) ? pendingByActivation.get(activation.value) : undefined
|
|
315
|
+
return pending?.workflowName === instance.workflow._tag && pending.executionId === instance.executionId &&
|
|
316
|
+
pending.results.has(InterruptSignal.name)
|
|
317
|
+
})
|
|
318
|
+
|
|
311
319
|
const sendResumeParent = Effect.fnUntraced(function*(options: {
|
|
312
320
|
readonly workflowName: string
|
|
313
321
|
readonly executionId: string
|
|
@@ -328,10 +336,14 @@ export const make = Effect.gen(function*() {
|
|
|
328
336
|
yield* sharding.reset(requestId.value)
|
|
329
337
|
}, Effect.scoped)
|
|
330
338
|
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// the
|
|
334
|
-
const
|
|
339
|
+
// Subscribe to the run's next reply before reading storage, so a reply
|
|
340
|
+
// cannot fall between the read and the subscription. Returns `true` when the
|
|
341
|
+
// stored reply already settles the wait, `false` when a new reply arrived.
|
|
342
|
+
const awaitRunReply = Effect.fnUntraced(function*(
|
|
343
|
+
workflow: Workflow.Any,
|
|
344
|
+
request: Envelope.Request<any>,
|
|
345
|
+
isSettled: (reply: WithExitEncoded<Workflow.ResultEncoded<any, any>>) => boolean
|
|
346
|
+
) {
|
|
335
347
|
const waiter = yield* storage.registerReplyHandler(
|
|
336
348
|
new Message.OutgoingRequest<any>({
|
|
337
349
|
envelope: request,
|
|
@@ -343,12 +355,45 @@ export const make = Effect.gen(function*() {
|
|
|
343
355
|
})
|
|
344
356
|
).pipe(Effect.forkScoped({ startImmediately: true }))
|
|
345
357
|
const reply = yield* replyForRequestId(request.requestId)
|
|
346
|
-
if (Option.isSome(reply)) return
|
|
347
|
-
yield* Fiber.join(waiter)
|
|
358
|
+
if (Option.isSome(reply) && isSettled(reply.value)) return true
|
|
359
|
+
yield* Fiber.join(waiter)
|
|
360
|
+
return false
|
|
361
|
+
}, Effect.scoped)
|
|
362
|
+
|
|
363
|
+
// A child or deferred may complete while a run is still unwinding. Wait for
|
|
364
|
+
// the run's reply so the wake cannot fall between the run's last read and
|
|
365
|
+
// its suspended reply.
|
|
366
|
+
const waitForRunReply = (workflow: Workflow.Any, request: Entity.Request<any>) =>
|
|
367
|
+
awaitRunReply(workflow, request, () => true).pipe(
|
|
368
|
+
Effect.asVoid,
|
|
348
369
|
Effect.catchTag("EntityNotAssignedToRunner", () => ClusterAbandon.interrupt),
|
|
349
370
|
Effect.catchCause((cause) => ClusterAbandon.isCause(cause) ? ClusterAbandon.interrupt : Effect.failCause(cause))
|
|
350
371
|
)
|
|
351
|
-
|
|
372
|
+
|
|
373
|
+
// Wake an external caller once its run publishes a reply other than
|
|
374
|
+
// `Suspended`. Replies are only observed on the runner that saves them, so
|
|
375
|
+
// callers on other runners, and any failure here, leave delivery to the
|
|
376
|
+
// caller's retry schedule.
|
|
377
|
+
const awaitComplete = (workflow: Workflow.Any, executionId: string) =>
|
|
378
|
+
Effect.gen(function*() {
|
|
379
|
+
const address = entityAddressFor({ workflow, entityType: `Workflow/${workflow._tag}`, executionId })
|
|
380
|
+
if (!sharding.hasShardId(address.shardId)) return yield* Effect.never
|
|
381
|
+
const requestId = yield* storage.requestIdForPrimaryKey({ address, tag: "run", id: "" })
|
|
382
|
+
if (Option.isNone(requestId)) return yield* Effect.never
|
|
383
|
+
const request = Envelope.makeRequest<any>({
|
|
384
|
+
requestId: requestId.value,
|
|
385
|
+
address,
|
|
386
|
+
tag: "run",
|
|
387
|
+
payload: {},
|
|
388
|
+
headers: Headers.empty
|
|
389
|
+
})
|
|
390
|
+
// A replay that suspends again re-arms the wait, so it does not spend a
|
|
391
|
+
// step of the caller's retry schedule.
|
|
392
|
+
let settled = false
|
|
393
|
+
while (!settled) {
|
|
394
|
+
settled = yield* awaitRunReply(workflow, request, (reply) => !isSuspendedReply(reply))
|
|
395
|
+
}
|
|
396
|
+
}).pipe(Effect.catchCause(() => Effect.never))
|
|
352
397
|
|
|
353
398
|
const interrupt = Effect.fnUntraced(
|
|
354
399
|
function*(workflow: Workflow.Any, executionId: string) {
|
|
@@ -367,7 +412,7 @@ export const make = Effect.gen(function*() {
|
|
|
367
412
|
|
|
368
413
|
const nonSuspendedReply = Option.filter(
|
|
369
414
|
reply,
|
|
370
|
-
(reply) => reply
|
|
415
|
+
(reply) => !isSuspendedReply(reply)
|
|
371
416
|
)
|
|
372
417
|
if (Option.isSome(nonSuspendedReply)) {
|
|
373
418
|
return Option.none()
|
|
@@ -402,7 +447,25 @@ export const make = Effect.gen(function*() {
|
|
|
402
447
|
const activation = yield* CurrentActivationScope
|
|
403
448
|
let pending = pendingByActivation.get(activation)
|
|
404
449
|
if (!pending) {
|
|
405
|
-
|
|
450
|
+
const results = new Map<string, Exit.Exit<unknown, unknown>>()
|
|
451
|
+
// A new owner must see an interrupt persisted before it took over.
|
|
452
|
+
// Publish the cache only once loaded, so a retried build repeats
|
|
453
|
+
// a failed lookup.
|
|
454
|
+
const interrupt = yield* Effect.orDie(requestReply({
|
|
455
|
+
workflow,
|
|
456
|
+
entityType: `Workflow/${workflow._tag}`,
|
|
457
|
+
executionId,
|
|
458
|
+
tag: "deferred",
|
|
459
|
+
id: InterruptSignal.name
|
|
460
|
+
}))
|
|
461
|
+
if (Option.isSome(interrupt)) {
|
|
462
|
+
const decoded = decodeDeferredWithExit(interrupt.value as any)
|
|
463
|
+
results.set(
|
|
464
|
+
InterruptSignal.name,
|
|
465
|
+
(decoded.exit._tag === "Success" ? decoded.exit.value : decoded.exit) as Exit.Exit<unknown, unknown>
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
pending = { workflowName: workflow._tag, executionId, results }
|
|
406
469
|
pendingByActivation.set(activation, pending)
|
|
407
470
|
}
|
|
408
471
|
// Retain awaited names through reply persistence, including suspension.
|
|
@@ -410,25 +473,30 @@ export const make = Effect.gen(function*() {
|
|
|
410
473
|
let currentRun: {
|
|
411
474
|
readonly request: Entity.Request<any>
|
|
412
475
|
readonly instance: WorkflowEngine.WorkflowInstance["Service"]
|
|
476
|
+
// Activity awaits must wake the run after suspension without preempting it.
|
|
477
|
+
readonly activityAwaits: Map<string, number>
|
|
413
478
|
} | undefined
|
|
414
479
|
// Concurrent wakes share one wait for the current run to publish its reply.
|
|
480
|
+
// Explicit wakes cannot be dropped: operator resume bypasses the gate;
|
|
481
|
+
// InterruptSignal enters it only on a fresh entity, with no run to wait for.
|
|
415
482
|
const resumeGate = Semaphore.makeUnsafe(1)
|
|
416
|
-
const resumeCurrentRun =
|
|
417
|
-
|
|
418
|
-
|
|
483
|
+
const resumeCurrentRun = (mode: WakeMode) =>
|
|
484
|
+
Effect.suspend(() =>
|
|
485
|
+
resumeGate.withPermitsIfAvailable(1)(
|
|
486
|
+
currentRun ? waitForRunReply(workflow, currentRun.request) : Effect.void
|
|
487
|
+
)
|
|
488
|
+
).pipe(
|
|
489
|
+
// Release the gate before reset can start another run, so later
|
|
490
|
+
// completions can wake that replay.
|
|
491
|
+
Effect.flatMap((waited) => Option.isSome(waited) ? resume(workflow, executionId, mode) : Effect.void),
|
|
492
|
+
ensureSuccess
|
|
419
493
|
)
|
|
420
|
-
).pipe(
|
|
421
|
-
// Release the gate before reset can start another run, so later
|
|
422
|
-
// completions can wake that replay.
|
|
423
|
-
Effect.flatMap((waited) => Option.isSome(waited) ? resume(workflow, executionId) : Effect.void),
|
|
424
|
-
ensureSuccess
|
|
425
|
-
)
|
|
426
494
|
return {
|
|
427
495
|
run: (request: Entity.Request<any>) => {
|
|
428
496
|
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId)
|
|
429
|
-
currentRun = { request, instance }
|
|
497
|
+
currentRun = { request, instance, activityAwaits: new Map() }
|
|
430
498
|
const parent = (request.payload as any)[payloadParentKey] as
|
|
431
|
-
| { workflowName: string; executionId: string }
|
|
499
|
+
| { workflowName: string; executionId: string; detached?: boolean }
|
|
432
500
|
| undefined
|
|
433
501
|
return execute(workflow.payloadSchema.make(request.payload) as object, executionId).pipe(
|
|
434
502
|
Effect.onExit((exit) => {
|
|
@@ -445,7 +513,9 @@ export const make = Effect.gen(function*() {
|
|
|
445
513
|
!instance.abandoned &&
|
|
446
514
|
!(suspendOnFailure && exit._tag === "Failure")
|
|
447
515
|
) {
|
|
448
|
-
return parent
|
|
516
|
+
return parent && !parent.detached
|
|
517
|
+
? ensureSuccess(sendResumeParent(parent, executionId))
|
|
518
|
+
: Effect.void
|
|
449
519
|
}
|
|
450
520
|
return engine.deferredResult(InterruptSignal).pipe(
|
|
451
521
|
Effect.flatMap((maybeExit) => {
|
|
@@ -472,7 +542,9 @@ export const make = Effect.gen(function*() {
|
|
|
472
542
|
activity(request: Entity.Request<any>) {
|
|
473
543
|
const payload = request.payload as { name: string; attempt: number }
|
|
474
544
|
const activityId = `${executionId}/${payload.name}`
|
|
475
|
-
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId
|
|
545
|
+
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId, {
|
|
546
|
+
awaitedDeferreds: currentRun?.activityAwaits
|
|
547
|
+
})
|
|
476
548
|
interruptedActivities.delete(activityId)
|
|
477
549
|
const entry = activities.get(activityId)
|
|
478
550
|
if (!entry) {
|
|
@@ -512,18 +584,24 @@ export const make = Effect.gen(function*() {
|
|
|
512
584
|
deferred: (request: Entity.Request<any>) => {
|
|
513
585
|
const payload = request.payload as any
|
|
514
586
|
pending.results.set(payload.name, payload.exit)
|
|
587
|
+
const mode: WakeMode = payload.name === InterruptSignal.name ? "explicit" : "automatic"
|
|
515
588
|
// Await registers before reading the cache. If this run has not
|
|
516
589
|
// registered yet, a later await will see the result just stored.
|
|
517
590
|
// Check when the wake runs and skip preemption too: interrupting
|
|
518
591
|
// a later await would require waiting for its reply before reset.
|
|
519
592
|
// A run completing its own deferred also reads it from the cache
|
|
520
|
-
// when it awaits later,
|
|
593
|
+
// when it awaits later, unless an activity is already parked on it.
|
|
521
594
|
const wake = Effect.suspend(() =>
|
|
522
595
|
currentRun && !currentRun.instance.awaitedDeferreds.has(payload.name)
|
|
523
|
-
? currentRun.
|
|
596
|
+
? currentRun.activityAwaits.has(payload.name)
|
|
597
|
+
// Hold an activity wake until the run has published its reply.
|
|
598
|
+
? resumeCurrentRun(mode)
|
|
599
|
+
: currentRun.instance.completedDeferreds.has(payload.name)
|
|
524
600
|
? Effect.void
|
|
525
|
-
: ensureSuccess(resume(workflow, executionId))
|
|
526
|
-
: deferredState.deferredDone(executionId, payload.name).pipe(
|
|
601
|
+
: ensureSuccess(resume(workflow, executionId, mode))
|
|
602
|
+
: deferredState.deferredDone(executionId, payload.name).pipe(
|
|
603
|
+
Effect.andThen(resumeCurrentRun(mode))
|
|
604
|
+
)
|
|
527
605
|
)
|
|
528
606
|
// An asynchronous reply releases the RPC concurrency permit while
|
|
529
607
|
// the wake waits; the pending request still occupies mailbox capacity.
|
|
@@ -538,7 +616,7 @@ export const make = Effect.gen(function*() {
|
|
|
538
616
|
)
|
|
539
617
|
},
|
|
540
618
|
|
|
541
|
-
resume: () => resumeCurrentRun.pipe(Rpc.wrap({ fork: true, uninterruptible: true }))
|
|
619
|
+
resume: () => resumeCurrentRun("automatic").pipe(Rpc.wrap({ fork: true, uninterruptible: true }))
|
|
542
620
|
}
|
|
543
621
|
}),
|
|
544
622
|
// Alongside the run, let deferred handlers briefly acquire a permit to
|
|
@@ -555,7 +633,7 @@ export const make = Effect.gen(function*() {
|
|
|
555
633
|
).pipe(Effect.andThen(registration))
|
|
556
634
|
}),
|
|
557
635
|
|
|
558
|
-
execute: (workflow, { discard, executionId, parent, payload }) => {
|
|
636
|
+
execute: (workflow, { detached, discard, executionId, parent, payload }) => {
|
|
559
637
|
ensureEntity(workflow)
|
|
560
638
|
return RcMap.get(clients, workflow._tag).pipe(
|
|
561
639
|
Effect.flatMap((make) =>
|
|
@@ -563,7 +641,10 @@ export const make = Effect.gen(function*() {
|
|
|
563
641
|
parent ?
|
|
564
642
|
{
|
|
565
643
|
...payload,
|
|
566
|
-
|
|
644
|
+
// A detached child keeps its parent for observability only.
|
|
645
|
+
[payloadParentKey]: detached
|
|
646
|
+
? { workflowName: parent.workflow._tag, executionId: parent.executionId, detached: true }
|
|
647
|
+
: { workflowName: parent.workflow._tag, executionId: parent.executionId }
|
|
567
648
|
} :
|
|
568
649
|
payload,
|
|
569
650
|
{ discard }
|
|
@@ -615,7 +696,9 @@ export const make = Effect.gen(function*() {
|
|
|
615
696
|
))
|
|
616
697
|
}),
|
|
617
698
|
|
|
618
|
-
resume: (workflow, executionId) => ensureSuccess(resume(workflow, executionId)),
|
|
699
|
+
resume: (workflow, executionId) => ensureSuccess(resume(workflow, executionId, "explicit")),
|
|
700
|
+
|
|
701
|
+
awaitComplete,
|
|
619
702
|
|
|
620
703
|
activityExecute: Effect.fnUntraced(
|
|
621
704
|
function*(activity, attempt) {
|
|
@@ -623,6 +706,21 @@ export const make = Effect.gen(function*() {
|
|
|
623
706
|
const instance = Context.get(services, WorkflowEngine.WorkflowInstance)
|
|
624
707
|
yield* Effect.annotateCurrentSpan("executionId", instance.executionId)
|
|
625
708
|
const activityId = `${instance.executionId}/${activity.name}`
|
|
709
|
+
// A pending interrupt replays only to rebuild compensations, so it must
|
|
710
|
+
// not run new activities. Finalizers run once `interrupted` is set.
|
|
711
|
+
if (!instance.interrupted && (yield* interruptPending(instance))) {
|
|
712
|
+
const reply = yield* Effect.orDie(requestReply({
|
|
713
|
+
workflow: instance.workflow,
|
|
714
|
+
entityType: `Workflow/${instance.workflow._tag}`,
|
|
715
|
+
executionId: instance.executionId,
|
|
716
|
+
tag: "activity",
|
|
717
|
+
id: activityPrimaryKey(activity.name, attempt)
|
|
718
|
+
}))
|
|
719
|
+
// Suspend so `onExit` applies the interrupt to the run.
|
|
720
|
+
if (Option.isNone(reply) || isSuspendedReply(reply.value)) {
|
|
721
|
+
return new Workflow.Suspended({})
|
|
722
|
+
}
|
|
723
|
+
}
|
|
626
724
|
const client = (yield* RcMap.get(clientsPartial, instance.workflow._tag))(instance.executionId)
|
|
627
725
|
while (true) {
|
|
628
726
|
if (!activities.has(activityId)) {
|
|
@@ -809,6 +907,14 @@ const DeferredRpc = Rpc.make("deferred", {
|
|
|
809
907
|
.annotate(ClusterSchema.Persisted, true)
|
|
810
908
|
.annotate(ClusterSchema.Uninterruptible, true)
|
|
811
909
|
|
|
910
|
+
type WakeMode = "explicit" | "automatic"
|
|
911
|
+
|
|
912
|
+
const isSuspendedReply = (reply: WithExitEncoded<Workflow.ResultEncoded<any, any>>) =>
|
|
913
|
+
reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended"
|
|
914
|
+
|
|
915
|
+
const isFailedReply = (reply: WithExitEncoded<Workflow.ResultEncoded<any, any>>) =>
|
|
916
|
+
reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended" && reply.exit.value.failed === true
|
|
917
|
+
|
|
812
918
|
const decodeDeferredWithExit = Schema.decodeSync(Schema.toCodecJson(Reply.WithExit.schema(DeferredRpc)))
|
|
813
919
|
|
|
814
920
|
const ResumeRpc = Rpc.make("resume", {
|
|
@@ -836,7 +942,8 @@ const makeWorkflowEntity = (workflow: Workflow.Any) =>
|
|
|
836
942
|
...workflow.payloadSchema.fields,
|
|
837
943
|
[payloadParentKey]: Schema.optional(Schema.Struct({
|
|
838
944
|
workflowName: Schema.String,
|
|
839
|
-
executionId: Schema.String
|
|
945
|
+
executionId: Schema.String,
|
|
946
|
+
detached: Schema.optional(Schema.Boolean)
|
|
840
947
|
}))
|
|
841
948
|
},
|
|
842
949
|
primaryKey: () => "",
|
|
@@ -1086,8 +1086,8 @@ export class MemoryDriver extends Context.Service<MemoryDriver>()("effect/cluste
|
|
|
1086
1086
|
journal.push(envelope)
|
|
1087
1087
|
return SaveResultEncoded.Success()
|
|
1088
1088
|
}),
|
|
1089
|
-
saveReply: (reply_) =>
|
|
1090
|
-
Effect.sync(() => {
|
|
1089
|
+
saveReply: (reply_) => {
|
|
1090
|
+
const save = Effect.sync(() => {
|
|
1091
1091
|
const reply = JSON.parse(JSON.stringify(reply_)) as Reply.Encoded
|
|
1092
1092
|
const entry = requests.get(reply.requestId)
|
|
1093
1093
|
if (!entry || replyIds.has(reply.id)) return
|
|
@@ -1098,7 +1098,18 @@ export class MemoryDriver extends Context.Service<MemoryDriver>()("effect/cluste
|
|
|
1098
1098
|
entry.replies.push(reply)
|
|
1099
1099
|
replyIds.add(reply.id)
|
|
1100
1100
|
replyLatch.openUnsafe()
|
|
1101
|
-
})
|
|
1101
|
+
})
|
|
1102
|
+
// Memory has no transaction isolation. Publish terminal replies at the
|
|
1103
|
+
// outer commit boundary, before their queued handler notifications, so
|
|
1104
|
+
// reply readers cannot deliver a success that may still be rolled back.
|
|
1105
|
+
return TransactionNotifications.use((notifications) =>
|
|
1106
|
+
reply_._tag === "WithExit" && notifications
|
|
1107
|
+
? Effect.sync(() => {
|
|
1108
|
+
notifications.push(save)
|
|
1109
|
+
})
|
|
1110
|
+
: save
|
|
1111
|
+
)
|
|
1112
|
+
},
|
|
1102
1113
|
clearReplies: (id, options) =>
|
|
1103
1114
|
Effect.sync(() => {
|
|
1104
1115
|
const entry = requests.get(String(id))
|
package/src/cluster/Runners.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as Context from "../Context.ts"
|
|
15
15
|
import * as Effect from "../Effect.ts"
|
|
16
16
|
import * as Exit from "../Exit.ts"
|
|
17
|
+
import * as Fiber from "../Fiber.ts"
|
|
17
18
|
import * as Latch from "../Latch.ts"
|
|
18
19
|
import * as Layer from "../Layer.ts"
|
|
19
20
|
import * as Option from "../Option.ts"
|
|
@@ -247,78 +248,93 @@ export const make: (
|
|
|
247
248
|
|
|
248
249
|
type StorageRequestEntry = {
|
|
249
250
|
readonly latch: Latch.Latch
|
|
250
|
-
doneLatch: Latch.Latch | undefined
|
|
251
251
|
readonly messages: Set<Message.OutgoingRequest<any>>
|
|
252
|
+
readonly pump: Fiber.Fiber<void>
|
|
252
253
|
replies: Array<Reply.Reply<any>>
|
|
253
254
|
}
|
|
254
255
|
const storageRequests = new Map<Snowflake.Snowflake, StorageRequestEntry>()
|
|
255
256
|
const waitingStorageRequests = new Map<Snowflake.Snowflake, Message.OutgoingRequest<any>>()
|
|
256
|
-
const replyFromStorage = Effect.fnUntraced(
|
|
257
|
-
function*(message: Message.OutgoingRequest<any>) {
|
|
258
|
-
let entry = storageRequests.get(message.envelope.requestId)
|
|
259
|
-
if (entry) {
|
|
260
|
-
entry.messages.add(message)
|
|
261
|
-
entry.doneLatch ??= Latch.makeUnsafe(false)
|
|
262
|
-
return yield* entry.doneLatch.await
|
|
263
|
-
} else {
|
|
264
|
-
entry = {
|
|
265
|
-
latch: Latch.makeUnsafe(false),
|
|
266
|
-
doneLatch: undefined,
|
|
267
|
-
replies: [],
|
|
268
|
-
messages: new Set([message])
|
|
269
|
-
}
|
|
270
|
-
storageRequests.set(message.envelope.requestId, entry)
|
|
271
|
-
}
|
|
272
257
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// send the replies back
|
|
281
|
-
for (let i = 0; i < entry.replies.length; i++) {
|
|
282
|
-
const reply = entry.replies[i]
|
|
283
|
-
// we have reached the end
|
|
284
|
-
if (reply._tag === "WithExit") {
|
|
285
|
-
// A reply can immediately reset and resend the same request. Do not
|
|
286
|
-
// let that sender attach to this completed reply loop.
|
|
287
|
-
storageRequests.delete(message.envelope.requestId)
|
|
288
|
-
waitingStorageRequests.delete(message.envelope.requestId)
|
|
289
|
-
for (const message of entry.messages) {
|
|
290
|
-
yield* message.respond(reply)
|
|
291
|
-
}
|
|
292
|
-
entry.doneLatch?.openUnsafe()
|
|
293
|
-
return
|
|
294
|
-
}
|
|
258
|
+
// A reset request can register a replacement entry under the same id, which
|
|
259
|
+
// must survive the old entry's cleanup.
|
|
260
|
+
const removeStorageRequest = (requestId: Snowflake.Snowflake, entry: Omit<StorageRequestEntry, "pump">) => {
|
|
261
|
+
if (storageRequests.get(requestId) !== entry) return
|
|
262
|
+
storageRequests.delete(requestId)
|
|
263
|
+
waitingStorageRequests.delete(requestId)
|
|
264
|
+
}
|
|
295
265
|
|
|
296
|
-
|
|
266
|
+
// The pump is owned by the shared entry rather than the first caller, so any
|
|
267
|
+
// waiter can be interrupted without stopping delivery to the others.
|
|
268
|
+
const pumpReplies = Effect.fnUntraced(function*(
|
|
269
|
+
requestId: Snowflake.Snowflake,
|
|
270
|
+
entry: Omit<StorageRequestEntry, "pump">
|
|
271
|
+
) {
|
|
272
|
+
while (true) {
|
|
273
|
+
// wait for the storage loop to notify us
|
|
274
|
+
entry.latch.closeUnsafe()
|
|
275
|
+
const message = entry.messages.values().next().value
|
|
276
|
+
if (!message) return
|
|
277
|
+
waitingStorageRequests.set(requestId, message)
|
|
278
|
+
storageLatch.openUnsafe()
|
|
279
|
+
yield* entry.latch.await
|
|
280
|
+
|
|
281
|
+
// send the replies back
|
|
282
|
+
for (let i = 0; i < entry.replies.length; i++) {
|
|
283
|
+
const reply = entry.replies[i]
|
|
284
|
+
// we have reached the end
|
|
285
|
+
if (reply._tag === "WithExit") {
|
|
286
|
+
// A reply can immediately reset and resend the same request. Do not
|
|
287
|
+
// let that sender attach to this completed entry.
|
|
288
|
+
removeStorageRequest(requestId, entry)
|
|
297
289
|
for (const message of entry.messages) {
|
|
298
290
|
yield* message.respond(reply)
|
|
299
291
|
}
|
|
300
|
-
|
|
301
|
-
yield* entry.latch.await
|
|
292
|
+
return
|
|
302
293
|
}
|
|
303
|
-
|
|
294
|
+
|
|
295
|
+
entry.latch.closeUnsafe()
|
|
296
|
+
for (const message of entry.messages) {
|
|
297
|
+
yield* message.respond(reply)
|
|
298
|
+
}
|
|
299
|
+
// wait for ack
|
|
300
|
+
yield* entry.latch.await
|
|
304
301
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
302
|
+
entry.replies = []
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const replyFromStorage = (message: Message.OutgoingRequest<any>) =>
|
|
307
|
+
Effect.uninterruptibleMask((restore) =>
|
|
308
|
+
Effect.gen(function*() {
|
|
309
|
+
const requestId = message.envelope.requestId
|
|
310
|
+
let entry = storageRequests.get(requestId)
|
|
311
|
+
if (!entry) {
|
|
312
|
+
const state = {
|
|
313
|
+
latch: Latch.makeUnsafe(false),
|
|
314
|
+
messages: new Set<Message.OutgoingRequest<any>>(),
|
|
315
|
+
replies: [] as Array<Reply.Reply<any>>
|
|
316
316
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
entry = Object.assign(state, { pump: yield* Effect.forkIn(pumpReplies(requestId, state), runnersScope) })
|
|
318
|
+
storageRequests.set(requestId, entry)
|
|
319
|
+
}
|
|
320
|
+
const current = entry
|
|
321
|
+
current.messages.add(message)
|
|
322
|
+
return yield* restore(Fiber.join(current.pump)).pipe(
|
|
323
|
+
Effect.ensuring(Effect.suspend(() => {
|
|
324
|
+
current.messages.delete(message)
|
|
325
|
+
if (current.messages.size === 0) {
|
|
326
|
+
removeStorageRequest(requestId, current)
|
|
327
|
+
return Fiber.interrupt(current.pump)
|
|
328
|
+
}
|
|
329
|
+
// The storage loop decodes replies using the waiting message.
|
|
330
|
+
if (waitingStorageRequests.get(requestId) === message) {
|
|
331
|
+
waitingStorageRequests.set(requestId, current.messages.values().next().value!)
|
|
332
|
+
}
|
|
333
|
+
return Effect.void
|
|
334
|
+
}))
|
|
335
|
+
)
|
|
336
|
+
})
|
|
337
|
+
)
|
|
322
338
|
|
|
323
339
|
const storageLatch = Latch.makeUnsafe(false)
|
|
324
340
|
if (storage !== MessageStorage.noop) {
|
|
@@ -415,22 +431,28 @@ export const make: (
|
|
|
415
431
|
})
|
|
416
432
|
},
|
|
417
433
|
notifyLocal(options) {
|
|
418
|
-
return notifyWith(options.message, (message
|
|
434
|
+
return notifyWith(options.message, (message) => {
|
|
419
435
|
if (options.discard || message._tag === "OutgoingEnvelope") {
|
|
420
436
|
return Effect.catchTag(
|
|
421
437
|
options.notify(Message.incomingLocalFromOutgoing(message)),
|
|
422
438
|
"EntityNotAssignedToRunner",
|
|
423
439
|
() => Effect.void
|
|
424
440
|
)
|
|
425
|
-
} else if (!duplicate && options.storageOnly !== true) {
|
|
426
|
-
return options.notify(Message.incomingLocalFromOutgoing(message)).pipe(
|
|
427
|
-
Effect.andThen(storage.registerReplyHandler(message)),
|
|
428
|
-
Effect.catchTag("EntityNotAssignedToRunner", () => replyFromStorage(message))
|
|
429
|
-
)
|
|
430
441
|
}
|
|
442
|
+
// The request can be processed as soon as it is saved, so replies are
|
|
443
|
+
// always read back from storage. The local reply handler only wakes the
|
|
444
|
+
// storage loop, so streams do not have to wait for the next poll.
|
|
445
|
+
const readReplies = options.storageOnly
|
|
446
|
+
? replyFromStorage(message)
|
|
447
|
+
: Effect.raceFirst(
|
|
448
|
+
replyFromStorage(message),
|
|
449
|
+
storage.registerReplyHandler(
|
|
450
|
+
new Message.OutgoingRequest({ ...message, respond: () => storageLatch.open })
|
|
451
|
+
).pipe(Effect.ignore, Effect.andThen(Effect.never))
|
|
452
|
+
)
|
|
431
453
|
return options.notify(Message.incomingLocalFromOutgoing(message)).pipe(
|
|
432
454
|
Effect.catchTag("EntityNotAssignedToRunner", () => Effect.void),
|
|
433
|
-
Effect.andThen(
|
|
455
|
+
Effect.andThen(readReplies)
|
|
434
456
|
)
|
|
435
457
|
})
|
|
436
458
|
}
|
package/src/cluster/Sharding.ts
CHANGED
|
@@ -692,6 +692,12 @@ const make = Effect.gen(function*() {
|
|
|
692
692
|
const storageReadLatch = yield* Latch.make(true)
|
|
693
693
|
const openStorageReadLatch = constant(Effect.asVoid(storageReadLatch.open))
|
|
694
694
|
|
|
695
|
+
type PendingNotification = {
|
|
696
|
+
resume: (_: Effect.Effect<void, EntityNotAssignedToRunner>) => void
|
|
697
|
+
readonly message: Message.IncomingRequest<any>
|
|
698
|
+
}
|
|
699
|
+
const pendingNotifications = new Map<Snowflake.Snowflake, PendingNotification>()
|
|
700
|
+
|
|
695
701
|
const storageReadLock = Semaphore.makeUnsafe(1)
|
|
696
702
|
const withStorageReadLock = storageReadLock.withPermits(1)
|
|
697
703
|
|
|
@@ -725,7 +731,7 @@ const make = Effect.gen(function*() {
|
|
|
725
731
|
if (storageEnabled && initialRunnerAddress) {
|
|
726
732
|
const selfAddress = initialRunnerAddress
|
|
727
733
|
|
|
728
|
-
|
|
734
|
+
const storageReadLoop = Effect.gen(function*() {
|
|
729
735
|
yield* Effect.logDebug("Starting")
|
|
730
736
|
yield* Effect.addFinalizer(() => Effect.logDebug("Shutting down"))
|
|
731
737
|
|
|
@@ -982,8 +988,7 @@ const make = Effect.gen(function*() {
|
|
|
982
988
|
module: "effect/cluster/Sharding",
|
|
983
989
|
fiber: "Storage read loop",
|
|
984
990
|
runner: selfAddress
|
|
985
|
-
})
|
|
986
|
-
Effect.forkIn(shardingScope)
|
|
991
|
+
})
|
|
987
992
|
)
|
|
988
993
|
|
|
989
994
|
// open the storage latch every poll interval
|
|
@@ -1110,6 +1115,9 @@ const make = Effect.gen(function*() {
|
|
|
1110
1115
|
),
|
|
1111
1116
|
Effect.forkIn(shardingScope)
|
|
1112
1117
|
)
|
|
1118
|
+
|
|
1119
|
+
// fork after the state the loop reads has been initialized
|
|
1120
|
+
yield* Effect.forkIn(storageReadLoop, shardingScope)
|
|
1113
1121
|
}
|
|
1114
1122
|
|
|
1115
1123
|
// --- Sending messages ---
|
|
@@ -1150,11 +1158,6 @@ const make = Effect.gen(function*() {
|
|
|
1150
1158
|
return isRequest ? Effect.andThen(awaitShardLock(address.shardId), send) : send
|
|
1151
1159
|
}
|
|
1152
1160
|
|
|
1153
|
-
type PendingNotification = {
|
|
1154
|
-
resume: (_: Effect.Effect<void, EntityNotAssignedToRunner>) => void
|
|
1155
|
-
readonly message: Message.IncomingRequest<any>
|
|
1156
|
-
}
|
|
1157
|
-
const pendingNotifications = new Map<Snowflake.Snowflake, PendingNotification>()
|
|
1158
1161
|
const notifyLocal = <M extends Message.Outgoing<any> | Message.Incoming<any>>(
|
|
1159
1162
|
message: M,
|
|
1160
1163
|
discard: boolean,
|
|
@@ -1172,7 +1175,7 @@ const make = Effect.gen(function*() {
|
|
|
1172
1175
|
const state = entityManagers.get(address.entityType)
|
|
1173
1176
|
if (!state) {
|
|
1174
1177
|
return Effect.flatMap(waitForEntityManager(address.entityType), loop)
|
|
1175
|
-
} else if (state.status === "closed"
|
|
1178
|
+
} else if (state.status === "closed") {
|
|
1176
1179
|
return Effect.fail(new EntityNotAssignedToRunner({ address }))
|
|
1177
1180
|
}
|
|
1178
1181
|
|
|
@@ -250,7 +250,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
250
250
|
) as Effect.Effect<Context.Context<Rpc.ToHandler<Rpcs>>>)
|
|
251
251
|
|
|
252
252
|
const server = yield* RpcServer.makeNoSerialization(entity.protocol, {
|
|
253
|
-
spanPrefix:
|
|
253
|
+
spanPrefix: entity.type,
|
|
254
254
|
spanAttributes: {
|
|
255
255
|
...options.spanAttributes,
|
|
256
256
|
"entity.type": entity.type,
|