effect 4.0.4 → 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/ai-docs/src/52_graphql/10_basics.ts +19 -13
- package/ai-docs/src/52_graphql/30_batching.ts +6 -4
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +6 -4
- 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/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/Schema.d.ts +7 -2
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +9 -2
- package/dist/Schema.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/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/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.map +1 -1
- package/dist/ai/LanguageModel.js +47 -35
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +6 -52
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +2 -28
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Response.d.ts +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 +67 -13
- 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/internal/entityManager.js +1 -1
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/encoding/Sse.js +1 -1
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQLClient.d.ts.map +1 -1
- package/dist/graphql/GraphQLClient.js +12 -5
- package/dist/graphql/GraphQLClient.js.map +1 -1
- package/dist/graphql/GraphQLProtocol.d.ts +249 -11
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
- package/dist/graphql/GraphQLProtocol.js +98 -8
- package/dist/graphql/GraphQLProtocol.js.map +1 -1
- package/dist/graphql/GraphQLResolver.d.ts +1 -1
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -1
- package/dist/graphql/GraphQLResolver.js +46 -16
- package/dist/graphql/GraphQLResolver.js.map +1 -1
- 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 +12 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +14 -1
- 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/HttpMiddleware.d.ts +60 -0
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +67 -5
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +52 -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/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/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/internal/core.js +17 -1
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/effect.js +5 -3
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.js +12 -2
- package/dist/internal/graphql.js.map +1 -1
- package/dist/internal/version.js +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/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/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 +20 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +35 -21
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Array.ts +95 -6
- package/src/Channel.ts +24 -3
- package/src/ConfigProvider.ts +40 -44
- 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/Schema.ts +17 -4
- package/src/String.ts +17 -4
- package/src/TxRef.ts +4 -2
- package/src/ai/AiError.ts +3 -1
- package/src/ai/Decision.ts +70 -21
- package/src/ai/DecisionModel.ts +1 -1
- package/src/ai/LanguageModel.ts +57 -51
- package/src/ai/McpServer.ts +15 -55
- 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 +92 -24
- package/src/cluster/MessageStorage.ts +14 -3
- package/src/cluster/Runners.ts +88 -66
- package/src/cluster/internal/entityManager.ts +1 -1
- package/src/encoding/Sse.ts +1 -1
- package/src/graphql/GraphQLClient.ts +17 -5
- package/src/graphql/GraphQLProtocol.ts +274 -20
- package/src/graphql/GraphQLResolver.ts +45 -17
- 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 +18 -1
- package/src/http/HttpClientError.ts +10 -6
- package/src/http/HttpClientRequest.ts +173 -7
- package/src/http/HttpMiddleware.ts +85 -5
- package/src/http/HttpRouter.ts +60 -13
- package/src/http/HttpServerError.ts +8 -4
- package/src/http/HttpServerRequest.ts +2 -2
- package/src/http/HttpStaticServer.ts +4 -10
- package/src/http/HttpTraceContext.ts +1 -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/internal/core.ts +21 -1
- package/src/internal/effect.ts +5 -4
- package/src/internal/graphql.ts +30 -7
- package/src/internal/version.ts +1 -1
- 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/sql/SqlClient.ts +9 -7
- package/src/testing/TestClock.ts +144 -29
- package/src/workflow/Activity.ts +1 -0
- package/src/workflow/Workflow.ts +31 -5
- package/src/workflow/WorkflowEngine.ts +60 -22
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
9
|
import type * as Completions from "../../Completions.ts"
|
|
10
|
+
import { takesSeparateValue } from "./descriptor.ts"
|
|
10
11
|
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// Helpers
|
|
@@ -88,7 +89,7 @@ const flagCompletionArgs = (flag: Completions.FlagDescriptor): Array<string> =>
|
|
|
88
89
|
if (flag.description) {
|
|
89
90
|
args.push(`-d '${escapeFishString(flag.description)}'`)
|
|
90
91
|
}
|
|
91
|
-
const valueArgs = flagValueArgs(flag.type)
|
|
92
|
+
const valueArgs = takesSeparateValue(flag) ? flagValueArgs(flag.type) : undefined
|
|
92
93
|
if (valueArgs) {
|
|
93
94
|
args.push(valueArgs)
|
|
94
95
|
}
|
|
@@ -159,8 +160,7 @@ const generateCompletions = (
|
|
|
159
160
|
|
|
160
161
|
// Flag completions
|
|
161
162
|
for (const flag of descriptor.flags) {
|
|
162
|
-
const
|
|
163
|
-
const dedup = isBoolean ? flagContainsOptCondition(flag) : valueFlagDedupCondition(flag)
|
|
163
|
+
const dedup = takesSeparateValue(flag) ? valueFlagDedupCondition(flag) : flagContainsOptCondition(flag)
|
|
164
164
|
const flagCondition = condition ? `${condition}; and ${dedup}` : dedup
|
|
165
165
|
const flagCondArg = flagCondition ? `-n '${flagCondition}'` : ``
|
|
166
166
|
|
|
@@ -170,7 +170,7 @@ const generateCompletions = (
|
|
|
170
170
|
lines.push(parts.join(" "))
|
|
171
171
|
|
|
172
172
|
// Boolean negation
|
|
173
|
-
if (
|
|
173
|
+
if (flag.type._tag === "Boolean") {
|
|
174
174
|
const negParts = [`complete -c ${executableName}`]
|
|
175
175
|
if (flagCondArg) negParts.push(flagCondArg)
|
|
176
176
|
negParts.push(`-l no-${flag.name}`)
|
|
@@ -48,7 +48,9 @@ const valueFields = (type: Completions.FlagType | Completions.ArgumentType): str
|
|
|
48
48
|
|
|
49
49
|
const flagEntry = (flag: Completions.FlagDescriptor): string =>
|
|
50
50
|
`@{ name = ${quotePs(flag.name)}; forms = ${psArray(flagForms(flag))}; ` +
|
|
51
|
-
`takesValue = $${flag.type._tag !== "Boolean"};
|
|
51
|
+
`takesValue = $${flag.type._tag !== "Boolean"}; ` +
|
|
52
|
+
(flag.optionalValue === true ? `optionalValue = $true; ` : "") +
|
|
53
|
+
`${valueFields(flag.type)}; ` +
|
|
52
54
|
`description = ${psOptional(flag.description)} }`
|
|
53
55
|
|
|
54
56
|
const argumentEntry = (argument: Completions.ArgumentDescriptor): string =>
|
|
@@ -235,7 +237,8 @@ const completer = (dataName: string): string =>
|
|
|
235
237
|
continue
|
|
236
238
|
}
|
|
237
239
|
$used[$flag.name] = $true
|
|
238
|
-
|
|
240
|
+
# A flag with an optional value only takes a value attached with '='.
|
|
241
|
+
if (-not $hasValue -and -not $flag.optionalValue) { $pending = $flag }
|
|
239
242
|
}
|
|
240
243
|
continue
|
|
241
244
|
}
|
|
@@ -72,16 +72,20 @@ const argAction = (type: Completions.ArgumentType): string => {
|
|
|
72
72
|
const flagSpecs = (flag: Completions.FlagDescriptor): Array<string> => {
|
|
73
73
|
const specs: Array<string> = []
|
|
74
74
|
const desc = flag.description ? `[${escapeZshExplanation(flag.description)}]` : ""
|
|
75
|
-
|
|
75
|
+
// An optional value must follow `=` in the same word (`--name=-`), and `::`
|
|
76
|
+
// marks it optional.
|
|
77
|
+
const optional = flag.optionalValue === true && flag.type._tag !== "Boolean"
|
|
78
|
+
const valueSuffix = optional ? "=-" : ""
|
|
79
|
+
const action = optional ? `:${valueAction(flag.type)}` : valueAction(flag.type)
|
|
76
80
|
const excl = `(${allForms(flag).join(" ")})`
|
|
77
81
|
|
|
78
82
|
// Primary long name
|
|
79
|
-
specs.push(`'${excl}--${flag.name}${desc}${action}'`)
|
|
83
|
+
specs.push(`'${excl}--${flag.name}${valueSuffix}${desc}${action}'`)
|
|
80
84
|
|
|
81
85
|
// Aliases
|
|
82
86
|
for (const alias of flag.aliases) {
|
|
83
87
|
const prefix = alias.length === 1 ? "-" : "--"
|
|
84
|
-
specs.push(`'${excl}${prefix}${alias}${desc}${action}'`)
|
|
88
|
+
specs.push(`'${excl}${prefix}${alias}${valueSuffix}${desc}${action}'`)
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
// Boolean negation
|
|
@@ -425,8 +425,9 @@ const asBooleanLiteral = (token: Token | undefined): string | undefined =>
|
|
|
425
425
|
*
|
|
426
426
|
* Value resolution order:
|
|
427
427
|
* 1. Inline value: --flag=value or -f=value
|
|
428
|
-
* 2.
|
|
429
|
-
* 3.
|
|
428
|
+
* 2. Optional value: the flag's configured value, without consuming a token
|
|
429
|
+
* 3. Boolean special case: implicit "true" or explicit boolean literal
|
|
430
|
+
* 4. Next token: consume following Value token if present
|
|
430
431
|
*/
|
|
431
432
|
const consumeFlagValue = (
|
|
432
433
|
cursor: TokenCursor,
|
|
@@ -465,7 +466,7 @@ const consumeFlagValueWithTokens = (
|
|
|
465
466
|
}
|
|
466
467
|
}
|
|
467
468
|
|
|
468
|
-
const literal = asBooleanLiteral(cursor.peek())
|
|
469
|
+
const literal = spec.optionalValue === undefined ? asBooleanLiteral(cursor.peek()) : undefined
|
|
469
470
|
if (literal !== undefined) {
|
|
470
471
|
const literalToken = cursor.take()
|
|
471
472
|
return {
|
|
@@ -490,6 +491,14 @@ const consumeFlagValueWithTokens = (
|
|
|
490
491
|
}
|
|
491
492
|
}
|
|
492
493
|
|
|
494
|
+
if (spec.optionalValue !== undefined) {
|
|
495
|
+
return {
|
|
496
|
+
_tag: "Value",
|
|
497
|
+
value: spec.optionalValue,
|
|
498
|
+
tokens: []
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
493
502
|
// Boolean flags: check for explicit literal or default to "true"
|
|
494
503
|
if (Primitive.isBoolean(spec.primitiveType)) {
|
|
495
504
|
const literal = asBooleanLiteral(cursor.peek())
|
|
@@ -171,8 +171,9 @@ const promptParam = Effect.fnUntraced(
|
|
|
171
171
|
return values
|
|
172
172
|
}
|
|
173
173
|
// Inline option-looking values so the lexer cannot interpret them as flags or the -- delimiter.
|
|
174
|
+
// Flags with an optional value only accept an inline value.
|
|
174
175
|
return values.flatMap((value) =>
|
|
175
|
-
value.value.startsWith("-") && value.value.length > 1
|
|
176
|
+
single.optionalValue !== undefined || (value.value.startsWith("-") && value.value.length > 1)
|
|
176
177
|
? [commandLineArg(`--${single.name}=${value.value}`, `--${single.name}=${value.displayValue}`)]
|
|
177
178
|
: [commandLineArg(`--${single.name}`), value]
|
|
178
179
|
)
|
|
@@ -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}`,
|
|
@@ -294,6 +294,8 @@ export const make = Effect.gen(function*() {
|
|
|
294
294
|
const maybeSuspended = Option.filter(maybeReply, isSuspendedReply)
|
|
295
295
|
|
|
296
296
|
if (Option.isNone(maybeSuspended)) return
|
|
297
|
+
// Replies without `failed` retain automatic wakes for compatibility.
|
|
298
|
+
if (mode === "automatic" && isFailedReply(maybeSuspended.value)) return
|
|
297
299
|
if (
|
|
298
300
|
!(yield* sharding.reset(
|
|
299
301
|
Snowflake.Snowflake(maybeSuspended.value.requestId),
|
|
@@ -305,6 +307,15 @@ export const make = Effect.gen(function*() {
|
|
|
305
307
|
yield* sharding.pollStorage
|
|
306
308
|
})
|
|
307
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
|
+
|
|
308
319
|
const sendResumeParent = Effect.fnUntraced(function*(options: {
|
|
309
320
|
readonly workflowName: string
|
|
310
321
|
readonly executionId: string
|
|
@@ -436,7 +447,25 @@ export const make = Effect.gen(function*() {
|
|
|
436
447
|
const activation = yield* CurrentActivationScope
|
|
437
448
|
let pending = pendingByActivation.get(activation)
|
|
438
449
|
if (!pending) {
|
|
439
|
-
|
|
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 }
|
|
440
469
|
pendingByActivation.set(activation, pending)
|
|
441
470
|
}
|
|
442
471
|
// Retain awaited names through reply persistence, including suspension.
|
|
@@ -444,25 +473,30 @@ export const make = Effect.gen(function*() {
|
|
|
444
473
|
let currentRun: {
|
|
445
474
|
readonly request: Entity.Request<any>
|
|
446
475
|
readonly instance: WorkflowEngine.WorkflowInstance["Service"]
|
|
476
|
+
// Activity awaits must wake the run after suspension without preempting it.
|
|
477
|
+
readonly activityAwaits: Map<string, number>
|
|
447
478
|
} | undefined
|
|
448
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.
|
|
449
482
|
const resumeGate = Semaphore.makeUnsafe(1)
|
|
450
|
-
const resumeCurrentRun =
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
453
493
|
)
|
|
454
|
-
).pipe(
|
|
455
|
-
// Release the gate before reset can start another run, so later
|
|
456
|
-
// completions can wake that replay.
|
|
457
|
-
Effect.flatMap((waited) => Option.isSome(waited) ? resume(workflow, executionId) : Effect.void),
|
|
458
|
-
ensureSuccess
|
|
459
|
-
)
|
|
460
494
|
return {
|
|
461
495
|
run: (request: Entity.Request<any>) => {
|
|
462
496
|
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId)
|
|
463
|
-
currentRun = { request, instance }
|
|
497
|
+
currentRun = { request, instance, activityAwaits: new Map() }
|
|
464
498
|
const parent = (request.payload as any)[payloadParentKey] as
|
|
465
|
-
| { workflowName: string; executionId: string }
|
|
499
|
+
| { workflowName: string; executionId: string; detached?: boolean }
|
|
466
500
|
| undefined
|
|
467
501
|
return execute(workflow.payloadSchema.make(request.payload) as object, executionId).pipe(
|
|
468
502
|
Effect.onExit((exit) => {
|
|
@@ -479,7 +513,9 @@ export const make = Effect.gen(function*() {
|
|
|
479
513
|
!instance.abandoned &&
|
|
480
514
|
!(suspendOnFailure && exit._tag === "Failure")
|
|
481
515
|
) {
|
|
482
|
-
return parent
|
|
516
|
+
return parent && !parent.detached
|
|
517
|
+
? ensureSuccess(sendResumeParent(parent, executionId))
|
|
518
|
+
: Effect.void
|
|
483
519
|
}
|
|
484
520
|
return engine.deferredResult(InterruptSignal).pipe(
|
|
485
521
|
Effect.flatMap((maybeExit) => {
|
|
@@ -506,7 +542,9 @@ export const make = Effect.gen(function*() {
|
|
|
506
542
|
activity(request: Entity.Request<any>) {
|
|
507
543
|
const payload = request.payload as { name: string; attempt: number }
|
|
508
544
|
const activityId = `${executionId}/${payload.name}`
|
|
509
|
-
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId
|
|
545
|
+
const instance = WorkflowEngine.WorkflowInstance.initial(workflow, executionId, {
|
|
546
|
+
awaitedDeferreds: currentRun?.activityAwaits
|
|
547
|
+
})
|
|
510
548
|
interruptedActivities.delete(activityId)
|
|
511
549
|
const entry = activities.get(activityId)
|
|
512
550
|
if (!entry) {
|
|
@@ -546,18 +584,24 @@ export const make = Effect.gen(function*() {
|
|
|
546
584
|
deferred: (request: Entity.Request<any>) => {
|
|
547
585
|
const payload = request.payload as any
|
|
548
586
|
pending.results.set(payload.name, payload.exit)
|
|
587
|
+
const mode: WakeMode = payload.name === InterruptSignal.name ? "explicit" : "automatic"
|
|
549
588
|
// Await registers before reading the cache. If this run has not
|
|
550
589
|
// registered yet, a later await will see the result just stored.
|
|
551
590
|
// Check when the wake runs and skip preemption too: interrupting
|
|
552
591
|
// a later await would require waiting for its reply before reset.
|
|
553
592
|
// A run completing its own deferred also reads it from the cache
|
|
554
|
-
// when it awaits later,
|
|
593
|
+
// when it awaits later, unless an activity is already parked on it.
|
|
555
594
|
const wake = Effect.suspend(() =>
|
|
556
595
|
currentRun && !currentRun.instance.awaitedDeferreds.has(payload.name)
|
|
557
|
-
? 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)
|
|
558
600
|
? Effect.void
|
|
559
|
-
: ensureSuccess(resume(workflow, executionId))
|
|
560
|
-
: 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
|
+
)
|
|
561
605
|
)
|
|
562
606
|
// An asynchronous reply releases the RPC concurrency permit while
|
|
563
607
|
// the wake waits; the pending request still occupies mailbox capacity.
|
|
@@ -572,7 +616,7 @@ export const make = Effect.gen(function*() {
|
|
|
572
616
|
)
|
|
573
617
|
},
|
|
574
618
|
|
|
575
|
-
resume: () => resumeCurrentRun.pipe(Rpc.wrap({ fork: true, uninterruptible: true }))
|
|
619
|
+
resume: () => resumeCurrentRun("automatic").pipe(Rpc.wrap({ fork: true, uninterruptible: true }))
|
|
576
620
|
}
|
|
577
621
|
}),
|
|
578
622
|
// Alongside the run, let deferred handlers briefly acquire a permit to
|
|
@@ -589,7 +633,7 @@ export const make = Effect.gen(function*() {
|
|
|
589
633
|
).pipe(Effect.andThen(registration))
|
|
590
634
|
}),
|
|
591
635
|
|
|
592
|
-
execute: (workflow, { discard, executionId, parent, payload }) => {
|
|
636
|
+
execute: (workflow, { detached, discard, executionId, parent, payload }) => {
|
|
593
637
|
ensureEntity(workflow)
|
|
594
638
|
return RcMap.get(clients, workflow._tag).pipe(
|
|
595
639
|
Effect.flatMap((make) =>
|
|
@@ -597,7 +641,10 @@ export const make = Effect.gen(function*() {
|
|
|
597
641
|
parent ?
|
|
598
642
|
{
|
|
599
643
|
...payload,
|
|
600
|
-
|
|
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 }
|
|
601
648
|
} :
|
|
602
649
|
payload,
|
|
603
650
|
{ discard }
|
|
@@ -649,7 +696,7 @@ export const make = Effect.gen(function*() {
|
|
|
649
696
|
))
|
|
650
697
|
}),
|
|
651
698
|
|
|
652
|
-
resume: (workflow, executionId) => ensureSuccess(resume(workflow, executionId)),
|
|
699
|
+
resume: (workflow, executionId) => ensureSuccess(resume(workflow, executionId, "explicit")),
|
|
653
700
|
|
|
654
701
|
awaitComplete,
|
|
655
702
|
|
|
@@ -659,6 +706,21 @@ export const make = Effect.gen(function*() {
|
|
|
659
706
|
const instance = Context.get(services, WorkflowEngine.WorkflowInstance)
|
|
660
707
|
yield* Effect.annotateCurrentSpan("executionId", instance.executionId)
|
|
661
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
|
+
}
|
|
662
724
|
const client = (yield* RcMap.get(clientsPartial, instance.workflow._tag))(instance.executionId)
|
|
663
725
|
while (true) {
|
|
664
726
|
if (!activities.has(activityId)) {
|
|
@@ -845,9 +907,14 @@ const DeferredRpc = Rpc.make("deferred", {
|
|
|
845
907
|
.annotate(ClusterSchema.Persisted, true)
|
|
846
908
|
.annotate(ClusterSchema.Uninterruptible, true)
|
|
847
909
|
|
|
910
|
+
type WakeMode = "explicit" | "automatic"
|
|
911
|
+
|
|
848
912
|
const isSuspendedReply = (reply: WithExitEncoded<Workflow.ResultEncoded<any, any>>) =>
|
|
849
913
|
reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended"
|
|
850
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
|
+
|
|
851
918
|
const decodeDeferredWithExit = Schema.decodeSync(Schema.toCodecJson(Reply.WithExit.schema(DeferredRpc)))
|
|
852
919
|
|
|
853
920
|
const ResumeRpc = Rpc.make("resume", {
|
|
@@ -875,7 +942,8 @@ const makeWorkflowEntity = (workflow: Workflow.Any) =>
|
|
|
875
942
|
...workflow.payloadSchema.fields,
|
|
876
943
|
[payloadParentKey]: Schema.optional(Schema.Struct({
|
|
877
944
|
workflowName: Schema.String,
|
|
878
|
-
executionId: Schema.String
|
|
945
|
+
executionId: Schema.String,
|
|
946
|
+
detached: Schema.optional(Schema.Boolean)
|
|
879
947
|
}))
|
|
880
948
|
},
|
|
881
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
|
}
|
|
@@ -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,
|
package/src/encoding/Sse.ts
CHANGED
|
@@ -683,7 +683,7 @@ export const encoder: Encoder = {
|
|
|
683
683
|
if (event.event !== "message") {
|
|
684
684
|
data += `event: ${event.event}\n`
|
|
685
685
|
}
|
|
686
|
-
data += `data: ${event.data.replace(/\n/g, "\ndata: ")}\n`
|
|
686
|
+
data += `data: ${event.data.replace(/\r\n|\r|\n/g, "\ndata: ")}\n`
|
|
687
687
|
return data + "\n"
|
|
688
688
|
}
|
|
689
689
|
case "Retry": {
|