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.
Files changed (308) hide show
  1. package/ai-docs/src/52_graphql/10_basics.ts +19 -13
  2. package/ai-docs/src/52_graphql/30_batching.ts +6 -4
  3. package/ai-docs/src/52_graphql/40_aliased-batching.ts +6 -4
  4. package/dist/Array.d.ts +90 -6
  5. package/dist/Array.d.ts.map +1 -1
  6. package/dist/Array.js +4 -2
  7. package/dist/Array.js.map +1 -1
  8. package/dist/Channel.d.ts +6 -1
  9. package/dist/Channel.d.ts.map +1 -1
  10. package/dist/Channel.js +17 -3
  11. package/dist/Channel.js.map +1 -1
  12. package/dist/ConfigProvider.d.ts.map +1 -1
  13. package/dist/ConfigProvider.js +33 -32
  14. package/dist/ConfigProvider.js.map +1 -1
  15. package/dist/Duration.d.ts.map +1 -1
  16. package/dist/Duration.js +2 -5
  17. package/dist/Duration.js.map +1 -1
  18. package/dist/Effect.d.ts +61 -19
  19. package/dist/Effect.d.ts.map +1 -1
  20. package/dist/Effect.js +58 -39
  21. package/dist/Effect.js.map +1 -1
  22. package/dist/FileSystem.d.ts +23 -9
  23. package/dist/FileSystem.d.ts.map +1 -1
  24. package/dist/FileSystem.js.map +1 -1
  25. package/dist/Match.d.ts +20 -6
  26. package/dist/Match.d.ts.map +1 -1
  27. package/dist/Match.js +2 -2
  28. package/dist/Match.js.map +1 -1
  29. package/dist/Path.d.ts +37 -5
  30. package/dist/Path.d.ts.map +1 -1
  31. package/dist/Path.js +36 -5
  32. package/dist/Path.js.map +1 -1
  33. package/dist/Schema.d.ts +7 -2
  34. package/dist/Schema.d.ts.map +1 -1
  35. package/dist/Schema.js +9 -2
  36. package/dist/Schema.js.map +1 -1
  37. package/dist/String.d.ts +12 -0
  38. package/dist/String.d.ts.map +1 -1
  39. package/dist/String.js +17 -4
  40. package/dist/String.js.map +1 -1
  41. package/dist/TxRef.d.ts.map +1 -1
  42. package/dist/TxRef.js +3 -2
  43. package/dist/TxRef.js.map +1 -1
  44. package/dist/ai/AiError.d.ts +10 -10
  45. package/dist/ai/AiError.d.ts.map +1 -1
  46. package/dist/ai/AiError.js +1 -1
  47. package/dist/ai/AiError.js.map +1 -1
  48. package/dist/ai/Decision.d.ts +70 -19
  49. package/dist/ai/Decision.d.ts.map +1 -1
  50. package/dist/ai/Decision.js +37 -3
  51. package/dist/ai/Decision.js.map +1 -1
  52. package/dist/ai/DecisionModel.js +1 -1
  53. package/dist/ai/DecisionModel.js.map +1 -1
  54. package/dist/ai/LanguageModel.d.ts.map +1 -1
  55. package/dist/ai/LanguageModel.js +47 -35
  56. package/dist/ai/LanguageModel.js.map +1 -1
  57. package/dist/ai/McpServer.d.ts +6 -52
  58. package/dist/ai/McpServer.d.ts.map +1 -1
  59. package/dist/ai/McpServer.js +2 -28
  60. package/dist/ai/McpServer.js.map +1 -1
  61. package/dist/ai/Response.d.ts +1 -1
  62. package/dist/cli/Completions.d.ts +5 -0
  63. package/dist/cli/Completions.d.ts.map +1 -1
  64. package/dist/cli/Completions.js.map +1 -1
  65. package/dist/cli/Flag.d.ts +100 -0
  66. package/dist/cli/Flag.d.ts.map +1 -1
  67. package/dist/cli/Flag.js +33 -0
  68. package/dist/cli/Flag.js.map +1 -1
  69. package/dist/cli/Param.d.ts +87 -0
  70. package/dist/cli/Param.d.ts.map +1 -1
  71. package/dist/cli/Param.js +30 -0
  72. package/dist/cli/Param.js.map +1 -1
  73. package/dist/cli/internal/command.d.ts.map +1 -1
  74. package/dist/cli/internal/command.js +2 -1
  75. package/dist/cli/internal/command.js.map +1 -1
  76. package/dist/cli/internal/completions/bash.js +4 -3
  77. package/dist/cli/internal/completions/bash.js.map +1 -1
  78. package/dist/cli/internal/completions/descriptor.js +8 -1
  79. package/dist/cli/internal/completions/descriptor.js.map +1 -1
  80. package/dist/cli/internal/completions/fish.js +4 -4
  81. package/dist/cli/internal/completions/fish.js.map +1 -1
  82. package/dist/cli/internal/completions/powershell.js +3 -2
  83. package/dist/cli/internal/completions/powershell.js.map +1 -1
  84. package/dist/cli/internal/completions/zsh.js +7 -3
  85. package/dist/cli/internal/completions/zsh.js.map +1 -1
  86. package/dist/cli/internal/parser.js +11 -3
  87. package/dist/cli/internal/parser.js.map +1 -1
  88. package/dist/cli/internal/wizard.d.ts.map +1 -1
  89. package/dist/cli/internal/wizard.js +2 -1
  90. package/dist/cli/internal/wizard.js.map +1 -1
  91. package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
  92. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  93. package/dist/cluster/ClusterWorkflowEngine.js +67 -13
  94. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  95. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  96. package/dist/cluster/MessageStorage.js +20 -12
  97. package/dist/cluster/MessageStorage.js.map +1 -1
  98. package/dist/cluster/Runners.d.ts.map +1 -1
  99. package/dist/cluster/Runners.js +54 -34
  100. package/dist/cluster/Runners.js.map +1 -1
  101. package/dist/cluster/internal/entityManager.js +1 -1
  102. package/dist/cluster/internal/entityManager.js.map +1 -1
  103. package/dist/encoding/Sse.js +1 -1
  104. package/dist/encoding/Sse.js.map +1 -1
  105. package/dist/graphql/GraphQLClient.d.ts.map +1 -1
  106. package/dist/graphql/GraphQLClient.js +12 -5
  107. package/dist/graphql/GraphQLClient.js.map +1 -1
  108. package/dist/graphql/GraphQLProtocol.d.ts +249 -11
  109. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
  110. package/dist/graphql/GraphQLProtocol.js +98 -8
  111. package/dist/graphql/GraphQLProtocol.js.map +1 -1
  112. package/dist/graphql/GraphQLResolver.d.ts +1 -1
  113. package/dist/graphql/GraphQLResolver.d.ts.map +1 -1
  114. package/dist/graphql/GraphQLResolver.js +46 -16
  115. package/dist/graphql/GraphQLResolver.js.map +1 -1
  116. package/dist/http/FetchHttpClient.d.ts +6 -3
  117. package/dist/http/FetchHttpClient.d.ts.map +1 -1
  118. package/dist/http/FetchHttpClient.js +6 -3
  119. package/dist/http/FetchHttpClient.js.map +1 -1
  120. package/dist/http/FindMyWay/internal/router.js +2 -1
  121. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  122. package/dist/http/Headers.d.ts +3 -3
  123. package/dist/http/Headers.d.ts.map +1 -1
  124. package/dist/http/Headers.js +6 -5
  125. package/dist/http/Headers.js.map +1 -1
  126. package/dist/http/HttpClient.d.ts +12 -0
  127. package/dist/http/HttpClient.d.ts.map +1 -1
  128. package/dist/http/HttpClient.js +14 -1
  129. package/dist/http/HttpClient.js.map +1 -1
  130. package/dist/http/HttpClientError.d.ts.map +1 -1
  131. package/dist/http/HttpClientError.js +8 -6
  132. package/dist/http/HttpClientError.js.map +1 -1
  133. package/dist/http/HttpClientRequest.d.ts +147 -4
  134. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  135. package/dist/http/HttpClientRequest.js +47 -1
  136. package/dist/http/HttpClientRequest.js.map +1 -1
  137. package/dist/http/HttpMiddleware.d.ts +60 -0
  138. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  139. package/dist/http/HttpMiddleware.js +67 -5
  140. package/dist/http/HttpMiddleware.js.map +1 -1
  141. package/dist/http/HttpRouter.d.ts.map +1 -1
  142. package/dist/http/HttpRouter.js +52 -13
  143. package/dist/http/HttpRouter.js.map +1 -1
  144. package/dist/http/HttpServerError.d.ts.map +1 -1
  145. package/dist/http/HttpServerError.js +6 -4
  146. package/dist/http/HttpServerError.js.map +1 -1
  147. package/dist/http/HttpServerRequest.js +2 -2
  148. package/dist/http/HttpServerRequest.js.map +1 -1
  149. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  150. package/dist/http/HttpStaticServer.js +4 -10
  151. package/dist/http/HttpStaticServer.js.map +1 -1
  152. package/dist/http/HttpTraceContext.js +1 -1
  153. package/dist/http/HttpTraceContext.js.map +1 -1
  154. package/dist/http/Url.d.ts.map +1 -1
  155. package/dist/http/Url.js +2 -1
  156. package/dist/http/Url.js.map +1 -1
  157. package/dist/http/UrlParams.d.ts +112 -8
  158. package/dist/http/UrlParams.d.ts.map +1 -1
  159. package/dist/http/UrlParams.js +141 -14
  160. package/dist/http/UrlParams.js.map +1 -1
  161. package/dist/http/internal/compression.d.ts +1 -1
  162. package/dist/http/internal/compression.d.ts.map +1 -1
  163. package/dist/http/internal/compression.js +2 -10
  164. package/dist/http/internal/compression.js.map +1 -1
  165. package/dist/http/internal/compressionBody.d.ts +2 -0
  166. package/dist/http/internal/compressionBody.d.ts.map +1 -0
  167. package/dist/http/internal/compressionBody.js +36 -0
  168. package/dist/http/internal/compressionBody.js.map +1 -0
  169. package/dist/http/internal/decompression.d.ts +2 -0
  170. package/dist/http/internal/decompression.d.ts.map +1 -0
  171. package/dist/http/internal/decompression.js +162 -0
  172. package/dist/http/internal/decompression.js.map +1 -0
  173. package/dist/http/internal/tracing.js +14 -4
  174. package/dist/http/internal/tracing.js.map +1 -1
  175. package/dist/http/internal/urlParams.d.ts +2 -0
  176. package/dist/http/internal/urlParams.d.ts.map +1 -0
  177. package/dist/http/internal/urlParams.js +4 -0
  178. package/dist/http/internal/urlParams.js.map +1 -0
  179. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  180. package/dist/http-api/HttpApiClient.js +2 -1
  181. package/dist/http-api/HttpApiClient.js.map +1 -1
  182. package/dist/internal/core.js +17 -1
  183. package/dist/internal/core.js.map +1 -1
  184. package/dist/internal/effect.js +5 -3
  185. package/dist/internal/effect.js.map +1 -1
  186. package/dist/internal/graphql.js +12 -2
  187. package/dist/internal/graphql.js.map +1 -1
  188. package/dist/internal/version.js +1 -1
  189. package/dist/observability/Otlp.d.ts +15 -0
  190. package/dist/observability/Otlp.d.ts.map +1 -1
  191. package/dist/observability/Otlp.js +3 -0
  192. package/dist/observability/Otlp.js.map +1 -1
  193. package/dist/observability/OtlpExporter.d.ts +8 -0
  194. package/dist/observability/OtlpExporter.d.ts.map +1 -1
  195. package/dist/observability/OtlpExporter.js +10 -1
  196. package/dist/observability/OtlpExporter.js.map +1 -1
  197. package/dist/observability/OtlpLogger.d.ts +17 -0
  198. package/dist/observability/OtlpLogger.d.ts.map +1 -1
  199. package/dist/observability/OtlpLogger.js +14 -1
  200. package/dist/observability/OtlpLogger.js.map +1 -1
  201. package/dist/observability/OtlpMetrics.d.ts +17 -0
  202. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  203. package/dist/observability/OtlpMetrics.js +13 -0
  204. package/dist/observability/OtlpMetrics.js.map +1 -1
  205. package/dist/observability/OtlpTracer.d.ts +17 -0
  206. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  207. package/dist/observability/OtlpTracer.js +15 -2
  208. package/dist/observability/OtlpTracer.js.map +1 -1
  209. package/dist/reactivity/AsyncResult.d.ts +6 -0
  210. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  211. package/dist/reactivity/AsyncResult.js +27 -0
  212. package/dist/reactivity/AsyncResult.js.map +1 -1
  213. package/dist/reactivity/AtomRegistry.js +3 -1
  214. package/dist/reactivity/AtomRegistry.js.map +1 -1
  215. package/dist/rpc/RpcClient.d.ts.map +1 -1
  216. package/dist/rpc/RpcClient.js +6 -1
  217. package/dist/rpc/RpcClient.js.map +1 -1
  218. package/dist/sql/SqlClient.d.ts +8 -7
  219. package/dist/sql/SqlClient.d.ts.map +1 -1
  220. package/dist/sql/SqlClient.js +8 -7
  221. package/dist/sql/SqlClient.js.map +1 -1
  222. package/dist/testing/TestClock.d.ts +16 -3
  223. package/dist/testing/TestClock.d.ts.map +1 -1
  224. package/dist/testing/TestClock.js +112 -22
  225. package/dist/testing/TestClock.js.map +1 -1
  226. package/dist/workflow/Activity.js +1 -0
  227. package/dist/workflow/Activity.js.map +1 -1
  228. package/dist/workflow/Workflow.d.ts +22 -0
  229. package/dist/workflow/Workflow.d.ts.map +1 -1
  230. package/dist/workflow/Workflow.js +15 -3
  231. package/dist/workflow/Workflow.js.map +1 -1
  232. package/dist/workflow/WorkflowEngine.d.ts +20 -1
  233. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  234. package/dist/workflow/WorkflowEngine.js +35 -21
  235. package/dist/workflow/WorkflowEngine.js.map +1 -1
  236. package/package.json +1 -1
  237. package/src/Array.ts +95 -6
  238. package/src/Channel.ts +24 -3
  239. package/src/ConfigProvider.ts +40 -44
  240. package/src/Duration.ts +4 -5
  241. package/src/Effect.ts +140 -53
  242. package/src/FileSystem.ts +23 -9
  243. package/src/Match.ts +65 -12
  244. package/src/Path.ts +52 -7
  245. package/src/Schema.ts +17 -4
  246. package/src/String.ts +17 -4
  247. package/src/TxRef.ts +4 -2
  248. package/src/ai/AiError.ts +3 -1
  249. package/src/ai/Decision.ts +70 -21
  250. package/src/ai/DecisionModel.ts +1 -1
  251. package/src/ai/LanguageModel.ts +57 -51
  252. package/src/ai/McpServer.ts +15 -55
  253. package/src/cli/Completions.ts +5 -0
  254. package/src/cli/Flag.ts +101 -0
  255. package/src/cli/Param.ts +96 -0
  256. package/src/cli/internal/command.ts +2 -1
  257. package/src/cli/internal/completions/bash.ts +4 -3
  258. package/src/cli/internal/completions/descriptor.ts +10 -1
  259. package/src/cli/internal/completions/fish.ts +4 -4
  260. package/src/cli/internal/completions/powershell.ts +5 -2
  261. package/src/cli/internal/completions/zsh.ts +7 -3
  262. package/src/cli/internal/parser.ts +12 -3
  263. package/src/cli/internal/wizard.ts +2 -1
  264. package/src/cluster/ClusterWorkflowEngine.ts +92 -24
  265. package/src/cluster/MessageStorage.ts +14 -3
  266. package/src/cluster/Runners.ts +88 -66
  267. package/src/cluster/internal/entityManager.ts +1 -1
  268. package/src/encoding/Sse.ts +1 -1
  269. package/src/graphql/GraphQLClient.ts +17 -5
  270. package/src/graphql/GraphQLProtocol.ts +274 -20
  271. package/src/graphql/GraphQLResolver.ts +45 -17
  272. package/src/http/FetchHttpClient.ts +6 -3
  273. package/src/http/FindMyWay/internal/router.ts +2 -1
  274. package/src/http/Headers.ts +6 -9
  275. package/src/http/HttpClient.ts +18 -1
  276. package/src/http/HttpClientError.ts +10 -6
  277. package/src/http/HttpClientRequest.ts +173 -7
  278. package/src/http/HttpMiddleware.ts +85 -5
  279. package/src/http/HttpRouter.ts +60 -13
  280. package/src/http/HttpServerError.ts +8 -4
  281. package/src/http/HttpServerRequest.ts +2 -2
  282. package/src/http/HttpStaticServer.ts +4 -10
  283. package/src/http/HttpTraceContext.ts +1 -1
  284. package/src/http/Url.ts +2 -1
  285. package/src/http/UrlParams.ts +225 -26
  286. package/src/http/internal/compression.ts +3 -17
  287. package/src/http/internal/compressionBody.ts +56 -0
  288. package/src/http/internal/decompression.ts +226 -0
  289. package/src/http/internal/tracing.ts +21 -4
  290. package/src/http/internal/urlParams.ts +5 -0
  291. package/src/http-api/HttpApiClient.ts +2 -1
  292. package/src/internal/core.ts +21 -1
  293. package/src/internal/effect.ts +5 -4
  294. package/src/internal/graphql.ts +30 -7
  295. package/src/internal/version.ts +1 -1
  296. package/src/observability/Otlp.ts +18 -0
  297. package/src/observability/OtlpExporter.ts +14 -3
  298. package/src/observability/OtlpLogger.ts +31 -2
  299. package/src/observability/OtlpMetrics.ts +30 -1
  300. package/src/observability/OtlpTracer.ts +32 -3
  301. package/src/reactivity/AsyncResult.ts +31 -0
  302. package/src/reactivity/AtomRegistry.ts +3 -1
  303. package/src/rpc/RpcClient.ts +6 -1
  304. package/src/sql/SqlClient.ts +9 -7
  305. package/src/testing/TestClock.ts +144 -29
  306. package/src/workflow/Activity.ts +1 -0
  307. package/src/workflow/Workflow.ts +31 -5
  308. package/src/workflow/WorkflowEngine.ts +60 -22
@@ -55,6 +55,11 @@ export const make: (
55
55
  readonly attributes?: Record<string, unknown>
56
56
  } | undefined
57
57
  readonly headers?: Headers.Input | undefined
58
+ /**
59
+ * Request compression. Defaults to "none". Gzip uses
60
+ * HttpClientRequest's default minimum body size of 1KB.
61
+ */
62
+ readonly compression?: "gzip" | "none" | undefined
58
63
  readonly exportInterval?: Duration.Input | undefined
59
64
  readonly maxBatchSize?: number | undefined
60
65
  readonly context?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
@@ -76,6 +81,7 @@ export const make: (
76
81
  label: "OtlpTracer",
77
82
  url: options.url,
78
83
  headers: options.headers,
84
+ compression: options.compression,
79
85
  exportInterval: options.exportInterval ?? Duration.seconds(5),
80
86
  maxBatchSize: options.maxBatchSize ?? 1000,
81
87
  body(spans) {
@@ -128,6 +134,11 @@ export const layer: (options: {
128
134
  readonly attributes?: Record<string, unknown>
129
135
  } | undefined
130
136
  readonly headers?: Headers.Input | undefined
137
+ /**
138
+ * Request compression. Defaults to "none". Gzip uses
139
+ * HttpClientRequest's default minimum body size of 1KB.
140
+ */
141
+ readonly compression?: "gzip" | "none" | undefined
131
142
  readonly exportInterval?: Duration.Input | undefined
132
143
  readonly maxBatchSize?: number | undefined
133
144
  readonly context?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
@@ -141,6 +152,13 @@ export const layer: (options: {
141
152
  /**
142
153
  * Creates an OTLP traces layer from OpenTelemetry configuration.
143
154
  *
155
+ * **Details**
156
+ *
157
+ * Compression accepts "gzip" or "none".
158
+ * `OTEL_EXPORTER_OTLP_TRACES_COMPRESSION` takes precedence over
159
+ * `OTEL_EXPORTER_OTLP_COMPRESSION`, defaulting to "none". Gzip uses
160
+ * HttpClientRequest's default minimum body size of 1KB.
161
+ *
144
162
  * @stability unstable
145
163
  * @category layers
146
164
  * @since 4.0.0
@@ -165,7 +183,17 @@ export const layerFromConfig = (options?: {
165
183
  return Exporter.layerFlusher
166
184
  }
167
185
 
168
- const { baseTimeout, tracesTimeout, exportTimeout, scheduleDelay, maxBatchSize } = yield* Config.all({
186
+ const {
187
+ baseCompression,
188
+ compression,
189
+ baseTimeout,
190
+ tracesTimeout,
191
+ exportTimeout,
192
+ scheduleDelay,
193
+ maxBatchSize
194
+ } = yield* Config.all({
195
+ baseCompression: Config.option(Config.Literals(["gzip", "none"], "OTEL_EXPORTER_OTLP_COMPRESSION")),
196
+ compression: Config.option(Config.Literals(["gzip", "none"], "OTEL_EXPORTER_OTLP_TRACES_COMPRESSION")),
169
197
  baseTimeout: Config.option(Config.Int("OTEL_EXPORTER_OTLP_TIMEOUT")),
170
198
  tracesTimeout: Config.option(Config.Int("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT")),
171
199
  exportTimeout: Config.option(Config.Int("OTEL_BSP_EXPORT_TIMEOUT")),
@@ -185,6 +213,7 @@ export const layerFromConfig = (options?: {
185
213
  url: endpoint.toString(),
186
214
  resource: options?.resource,
187
215
  headers: options?.headers ?? (yield* OtlpEnv.headers("TRACES")),
216
+ compression: Option.getOrElse(Option.firstSomeOf([compression, baseCompression]), () => "none" as const),
188
217
  exportInterval: Option.getOrUndefined(scheduleDelay),
189
218
  maxBatchSize: Option.getOrUndefined(maxBatchSize),
190
219
  context: options?.context,
@@ -342,7 +371,7 @@ const makeOtlpSpan = (self: SpanImpl): OtlpSpan => {
342
371
  for (let i = 0; i < self.links.length; i++) {
343
372
  const link = self.links[i]
344
373
  links.push({
345
- traceId: link.span.traceId,
374
+ traceId: link.span.traceId.padStart(32, "0"),
346
375
  spanId: link.span.spanId,
347
376
  attributes: entriesToAttributes(Object.entries(link.attributes)),
348
377
  droppedAttributesCount: 0
@@ -350,7 +379,7 @@ const makeOtlpSpan = (self: SpanImpl): OtlpSpan => {
350
379
  }
351
380
 
352
381
  return {
353
- traceId: self.traceId,
382
+ traceId: self.traceId.padStart(32, "0"),
354
383
  spanId: self.spanId,
355
384
  parentSpanId: Option.isSome(self.parent) ? self.parent.value.spanId : undefined,
356
385
  name: self.name,
@@ -901,6 +901,12 @@ export const matchWithWaiting: {
901
901
  /**
902
902
  * Combines an iterable or record of `AsyncResult` and plain values into one `AsyncResult`, returning the first non-success result or a success of the collected values marked waiting when any input success is waiting.
903
903
  *
904
+ * **Details**
905
+ *
906
+ * If the first non-success result is a failure, its previous success is
907
+ * rebuilt in the combined shape from each input's current or previous value.
908
+ * It is omitted if any input has no value.
909
+ *
904
910
  * @stability unstable
905
911
  * @category combinators
906
912
  * @since 4.0.0
@@ -932,6 +938,11 @@ export const all = <const Arg extends Iterable<any> | Record<string, any>>(
932
938
  if (!isAsyncResult(result)) {
933
939
  InternalRecord.assignProperty(successes, key, result)
934
940
  continue
941
+ } else if (isFailure(result)) {
942
+ return failure(result.cause, {
943
+ previousSuccess: combinedPreviousSuccess(entries, isIter),
944
+ waiting: result.waiting
945
+ }) as any
935
946
  } else if (!isSuccess(result)) {
936
947
  return result as any
937
948
  }
@@ -943,6 +954,26 @@ export const all = <const Arg extends Iterable<any> | Record<string, any>>(
943
954
  return success(successes, { waiting }) as any
944
955
  }
945
956
 
957
+ const combinedPreviousSuccess = (
958
+ entries: ReadonlyArray<readonly [PropertyKey, unknown]>,
959
+ isIter: boolean
960
+ ): Option.Option<Success<unknown, never>> => {
961
+ const values: any = isIter ? [] : {}
962
+ for (let i = 0; i < entries.length; i++) {
963
+ const [key, entry] = entries[i]
964
+ if (!isAsyncResult(entry)) {
965
+ InternalRecord.assignProperty(values, key, entry)
966
+ continue
967
+ }
968
+ const entryValue = value(entry)
969
+ if (Option.isNone(entryValue)) {
970
+ return Option.none()
971
+ }
972
+ InternalRecord.assignProperty(values, key, entryValue.value)
973
+ }
974
+ return Option.some(success(values))
975
+ }
976
+
946
977
  /**
947
978
  * Creates a typed builder for rendering an `AsyncResult` by handling waiting, initial, success, error, defect, interrupt, and failure cases.
948
979
  *
@@ -967,7 +967,9 @@ class NodeImpl<A> {
967
967
  this._value = value
968
968
  let failure: unknown = noFailure
969
969
  try {
970
- this.invalidateChildren()
970
+ // A first value cannot replace one held by a dependent. Recovery still
971
+ // retries dependents whose reads failed before this node had a value.
972
+ if (initialized || recovered) this.invalidateChildren()
971
973
  } catch (error) {
972
974
  failure = error
973
975
  }
@@ -1249,16 +1249,21 @@ const makePinger = Effect.fnUntraced(function*<A, E, R>(writePing: Effect.Effect
1249
1249
  const interval = options?.pingInterval ?? "5 seconds"
1250
1250
  const timeoutMillis = Duration.toMillis(options?.pingTimeout ?? interval)
1251
1251
  let lastSeen = clock.currentTimeMillisUnsafe()
1252
+ let awaitingResponse = false
1252
1253
  const latch = Latch.makeUnsafe()
1253
1254
  const onFrame = () => {
1254
1255
  lastSeen = clock.currentTimeMillisUnsafe()
1256
+ awaitingResponse = false
1255
1257
  }
1256
1258
  const reset = () => {
1257
1259
  onFrame()
1258
1260
  latch.closeUnsafe()
1259
1261
  }
1260
1262
  yield* Effect.suspend((): Effect.Effect<void, E, R> => {
1261
- if (clock.currentTimeMillisUnsafe() - lastSeen > timeoutMillis) return latch.open
1263
+ // A late tick must send a ping before it can declare the connection silent.
1264
+ // Keep the timeout measured from the last frame, including for custom timeouts.
1265
+ if (awaitingResponse && clock.currentTimeMillisUnsafe() - lastSeen > timeoutMillis) return latch.open
1266
+ awaitingResponse = true
1262
1267
  return writePing
1263
1268
  }).pipe(
1264
1269
  Effect.delay(interval),
@@ -275,22 +275,22 @@ export const make = Effect.fnUntraced(function*(options: SqlClient.MakeOptions)
275
275
  })
276
276
 
277
277
  /**
278
- * Builds the acquirers and failed-COMMIT cleanup for a SQLite client that
278
+ * Builds the acquirers, borrower and failed-COMMIT cleanup for a SQLite client that
279
279
  * shares one connection through a semaphore.
280
280
  *
281
281
  * **Details**
282
282
  *
283
283
  * Both acquirers lease the connection into the current scope and hold the
284
- * semaphore until that scope closes, so a statement or stream keeps the
285
- * connection until it finishes and another fiber cannot begin a transaction
286
- * underneath it. Effects that use the connection directly, such as an export,
287
- * have to run inside `Effect.scoped`.
284
+ * semaphore until that scope closes. The borrower holds the same semaphore
285
+ * until the statement finishes without creating a scope. Another fiber cannot
286
+ * begin a transaction underneath either. Effects that use the connection directly,
287
+ * such as an export, have to run inside `Effect.scoped`.
288
288
  *
289
289
  * SQLite can keep a transaction open after COMMIT fails, for example on a
290
290
  * deferred foreign key violation. `onCommitFailure` rolls it back before the
291
291
  * connection is reused. If that ROLLBACK fails, the connection is rejected, and
292
- * each acquisition retries the ROLLBACK once while holding the semaphore until
293
- * it succeeds. The database is never reopened, and transactions started with a
292
+ * each acquisition or borrow retries the ROLLBACK once while holding the semaphore
293
+ * until it succeeds. The database is never reopened, and transactions started with a
294
294
  * plain `BEGIN` are only touched after a failed cleanup.
295
295
  *
296
296
  * Pass `isTransaction` when the driver can report whether a transaction is
@@ -307,6 +307,7 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
307
307
  readonly isTransaction?: ((conn: C) => boolean) | undefined
308
308
  }): {
309
309
  readonly acquirer: Effect.Effect<C, SqlError, Scope.Scope>
310
+ readonly borrower: Connection.Borrower
310
311
  readonly transactionAcquirer: Effect.Effect<C, SqlError, Scope.Scope>
311
312
  readonly onCommitFailure: (conn: Connection.Connection) => Effect.Effect<void, SqlError>
312
313
  } => {
@@ -352,6 +353,7 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
352
353
 
353
354
  return {
354
355
  acquirer: lease,
356
+ borrower: (f) => semaphore.withPermit(Effect.flatMap(available, f)),
355
357
  transactionAcquirer: lease,
356
358
  onCommitFailure: (conn) => Effect.tapCause(rollback(conn as C), (cause) => Effect.sync(() => poison(cause)))
357
359
  }
@@ -13,14 +13,14 @@
13
13
  */
14
14
  import * as Arr from "../Array.ts"
15
15
  import * as Clock from "../Clock.ts"
16
+ import * as Context from "../Context.ts"
16
17
  import * as Data from "../Data.ts"
17
18
  import * as Duration from "../Duration.ts"
18
19
  import * as Effect from "../Effect.ts"
19
20
  import * as Fiber from "../Fiber.ts"
20
- import { flow } from "../Function.ts"
21
21
  import * as Latch from "../Latch.ts"
22
22
  import * as Layer from "../Layer.ts"
23
- import * as Order from "../Order.ts"
23
+ import * as Scheduler from "../Scheduler.ts"
24
24
  import * as Semaphore from "../Semaphore.ts"
25
25
 
26
26
  /**
@@ -40,6 +40,9 @@ import * as Semaphore from "../Semaphore.ts"
40
40
  * the time is set to on or after the time they are scheduled to run. Fork the
41
41
  * effect being tested, then adjust the clock time, and finally verify that the
42
42
  * expected effects have been performed.
43
+ * The ordered execution of sleeps registered after multiple scheduler hops
44
+ * requires fibers running under {@link layer}; providing a clock from
45
+ * {@link make} by hand does not install the tracking scheduler.
43
46
  *
44
47
  * **Example** (Testing timeouts deterministically)
45
48
  *
@@ -142,7 +145,8 @@ export declare namespace TestClock {
142
145
  /**
143
146
  * Options used when constructing a `TestClock`. `warningDelay` controls how
144
147
  * long the live clock waits before logging a warning when a test uses time
145
- * without advancing the test clock.
148
+ * without advancing the test clock, or when a fiber woken by the test clock
149
+ * keeps running without suspending.
146
150
  *
147
151
  * **Example** (Configuring the warning delay)
148
152
  *
@@ -170,7 +174,8 @@ export declare namespace TestClock {
170
174
  export interface Options {
171
175
  /**
172
176
  * The amount of time to wait before displaying a warning message when a
173
- * test is using time but is not advancing the `TestClock`.
177
+ * test is using time but is not advancing the `TestClock`, or when a fiber
178
+ * woken by `adjust` or `setTime` keeps running without suspending.
174
179
  */
175
180
  readonly warningDelay?: Duration.Input
176
181
  }
@@ -197,15 +202,30 @@ const warningMessage = "A test is using time, but is not advancing the test " +
197
202
  "clock, which may result in the test hanging. Use TestClock.adjust to " +
198
203
  "manually advance the time."
199
204
 
205
+ /**
206
+ * The warning message that will be displayed if a fiber woken by the
207
+ * `TestClock` does not suspend within the warning delay.
208
+ */
209
+ const busyWarningMessage = "A fiber woken by the test clock is still running " +
210
+ "without suspending, so the test clock stopped waiting for it. Sleeps it " +
211
+ "starts later may be scheduled relative to a later time."
212
+
213
+ /**
214
+ * The live clock of each `TestClock`, so a `TestClock` constructed while
215
+ * another one is the current clock uses the outer clock's live clock instead
216
+ * of the outer `TestClock`, which only advances when adjusted.
217
+ */
218
+ const liveClocks = new WeakMap<Clock.Clock, Clock.Clock>()
219
+
220
+ interface WakeTracker {
221
+ pending: number
222
+ onIdle: (() => void) | undefined
223
+ }
224
+
200
225
  const defaultOptions: Required<TestClock.Options> = {
201
226
  warningDelay: "1 second"
202
227
  }
203
228
 
204
- const SleepOrder = Order.flip(Order.Struct({
205
- timestamp: Order.Number,
206
- sequence: Order.Number
207
- }))
208
-
209
229
  const nanosPerMilli = BigInt(1_000_000)
210
230
 
211
231
  const millisToNanos = (millis: number): bigint => {
@@ -217,6 +237,14 @@ const millisToNanos = (millis: number): bigint => {
217
237
  /**
218
238
  * Creates a `TestClock` with optional configuration.
219
239
  *
240
+ * **Gotchas**
241
+ *
242
+ * The guarantee that sleeps registered after multiple scheduler hops run in
243
+ * order applies only to fibers running under {@link layer}, which
244
+ * `@effect/vitest`'s `it.effect` uses. Providing a clock from `make` by hand
245
+ * waits only one scheduler turn after each woken sleep. Prefer `layer` when
246
+ * forked fibers sleep.
247
+ *
220
248
  * **Example** (Creating a test clock)
221
249
  *
222
250
  * ```ts import.meta.vitest
@@ -245,18 +273,21 @@ const millisToNanos = (millis: number): bigint => {
245
273
  * @category constructors
246
274
  * @since 4.0.0
247
275
  */
248
- export const make = Effect.fnUntraced(function*(
276
+ export const make = (options?: TestClock.Options) => Effect.map(makeWithScheduler(options), ([testClock]) => testClock)
277
+
278
+ const makeWithScheduler = Effect.fnUntraced(function*(
249
279
  options?: TestClock.Options
250
280
  ) {
251
281
  const config = { ...defaultOptions, ...options }
252
- let sequence = 0
282
+ // Sorted by descending timestamp, so `pop` wakes the earliest sleep first
253
283
  const sleeps: Array<{
254
- readonly sequence: number
255
284
  readonly timestamp: number
256
285
  readonly latch: Latch.Latch
257
286
  }> = []
258
- const liveClock = yield* Clock.clockWith(Effect.succeed)
287
+ const currentClock = yield* Clock.clockWith(Effect.succeed)
288
+ const liveClock = liveClocks.get(currentClock) ?? currentClock
259
289
  const warningSemaphore = yield* Semaphore.make(1)
290
+ const parentScheduler = yield* Scheduler.Scheduler
260
291
 
261
292
  let currentTimestamp: number = new Date(0).getTime()
262
293
  let currentWallNanos = BigInt(0)
@@ -332,17 +363,97 @@ export const make = Effect.fnUntraced(function*(
332
363
  const millis = Duration.toMillis(duration)
333
364
  const end = currentTimestamp + millis
334
365
  if (end <= currentTimestamp) return
335
- const latch = Latch.makeUnsafe()
336
- sleeps.push({
337
- sequence: sequence++,
338
- timestamp: end,
339
- latch
340
- })
341
- sleeps.sort(SleepOrder)
342
- yield* warningStart
343
- yield* latch.await
366
+ yield* Effect.acquireUseRelease(
367
+ Effect.sync(() => {
368
+ const entry = { timestamp: end, latch: Latch.makeUnsafe() }
369
+ // Insert before entries with an equal timestamp, so they wake in
370
+ // registration order
371
+ let low = 0
372
+ let high = sleeps.length
373
+ while (low < high) {
374
+ const mid = (low + high) >>> 1
375
+ if (sleeps[mid].timestamp > end) low = mid + 1
376
+ else high = mid
377
+ }
378
+ sleeps.splice(low, 0, entry)
379
+ return entry
380
+ }),
381
+ (entry) => Effect.andThen(warningStart, entry.latch.await),
382
+ (entry) =>
383
+ Effect.sync(() => {
384
+ // The entry is already gone if `run` popped it
385
+ const index = sleeps.indexOf(entry)
386
+ if (index !== -1) sleeps.splice(index, 1)
387
+ })
388
+ )
344
389
  })
345
390
 
391
+ /**
392
+ * Tasks scheduled while waking a sleeper, and tasks scheduled by those tasks,
393
+ * are counted by that wake's tracker so `run` can wait for the woken fiber,
394
+ * and any fibers it resumes or forks, to suspend before checking the next
395
+ * sleep.
396
+ */
397
+ let currentTracker: WakeTracker | undefined = undefined
398
+
399
+ function runTracked(tracker: WakeTracker, f: () => void): void {
400
+ const previous = currentTracker
401
+ currentTracker = tracker
402
+ try {
403
+ f()
404
+ } finally {
405
+ currentTracker = previous
406
+ }
407
+ }
408
+
409
+ const scheduler: Scheduler.Scheduler = {
410
+ executionMode: parentScheduler.executionMode,
411
+ shouldYield: (fiber) => parentScheduler.shouldYield(fiber),
412
+ makeDispatcher() {
413
+ const dispatcher = parentScheduler.makeDispatcher()
414
+ return {
415
+ scheduleTask(task, priority) {
416
+ const tracker = currentTracker
417
+ if (tracker === undefined) {
418
+ return dispatcher.scheduleTask(task, priority)
419
+ }
420
+ tracker.pending++
421
+ dispatcher.scheduleTask(() => {
422
+ try {
423
+ runTracked(tracker, task)
424
+ } finally {
425
+ if (--tracker.pending === 0) tracker.onIdle?.()
426
+ }
427
+ }, priority)
428
+ },
429
+ flush() {
430
+ dispatcher.flush()
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ const wake = (latch: Latch.Latch) =>
437
+ Effect.suspend(() => {
438
+ const tracker: WakeTracker = { pending: 0, onIdle: undefined }
439
+ runTracked(tracker, () => latch.openUnsafe())
440
+ if (tracker.pending === 0) return Effect.yieldNow
441
+ return Effect.callback<void>((resume) => {
442
+ if (tracker.pending === 0) return resume(Effect.void)
443
+ tracker.onIdle = () => resume(Effect.void)
444
+ return Effect.sync(() => {
445
+ tracker.onIdle = undefined
446
+ })
447
+ }).pipe(
448
+ Effect.timeoutOrElse({
449
+ duration: config.warningDelay,
450
+ orElse: () => Effect.logWarning(busyWarningMessage)
451
+ }),
452
+ withLive,
453
+ Effect.andThen(Effect.yieldNow)
454
+ )
455
+ })
456
+
346
457
  const runSemaphore = yield* Semaphore.make(1)
347
458
  const run = Effect.fnUntraced(function*(
348
459
  step: (currentTimestamp: number) => number,
@@ -366,8 +477,7 @@ export const make = Effect.fnUntraced(function*(
366
477
  if (Arr.lastNonEmpty(sleeps).timestamp > endTimestamp) break
367
478
  const entry = sleeps.pop()!
368
479
  advanceTo(entry.timestamp)
369
- entry.latch.openUnsafe()
370
- yield* Effect.yieldNow
480
+ yield* wake(entry.latch)
371
481
  }
372
482
  advanceTo(endTimestamp)
373
483
  if (adjustmentNanos !== undefined && Number.isFinite(endTimestamp)) {
@@ -391,7 +501,7 @@ export const make = Effect.fnUntraced(function*(
391
501
 
392
502
  yield* Effect.addFinalizer(() => warningDone)
393
503
 
394
- return {
504
+ const testClock = {
395
505
  currentTimeMillisUnsafe,
396
506
  currentTimeNanosUnsafe,
397
507
  monotonicTimeNanosUnsafe,
@@ -403,6 +513,8 @@ export const make = Effect.fnUntraced(function*(
403
513
  sleep,
404
514
  withLive
405
515
  }
516
+ liveClocks.set(testClock, liveClock)
517
+ return [testClock, scheduler] as const
406
518
  })
407
519
 
408
520
  /**
@@ -438,10 +550,13 @@ export const make = Effect.fnUntraced(function*(
438
550
  * @category layers
439
551
  * @since 4.0.0
440
552
  */
441
- export const layer: (options?: TestClock.Options) => Layer.Layer<TestClock> = flow(
442
- make,
443
- Layer.effect(Clock.Clock)
444
- ) as any
553
+ export const layer = (options?: TestClock.Options): Layer.Layer<TestClock> =>
554
+ Layer.effectContext(
555
+ Effect.map(makeWithScheduler(options), ([testClock, scheduler]) =>
556
+ Context.make(Clock.Clock, testClock).pipe(
557
+ Context.add(Scheduler.Scheduler, scheduler)
558
+ ))
559
+ ) as any
445
560
 
446
561
  /**
447
562
  * Retrieves the `TestClock` service for this test and uses it to run the
@@ -347,6 +347,7 @@ const makeExecute = Effect.fnUntraced(function*<
347
347
  (_) => _._tag === "Suspended"
348
348
  )
349
349
  if (result._tag === "Suspended") {
350
+ if (result.failed) instance.failed = true
350
351
  return yield* Workflow.suspend(instance)
351
352
  }
352
353
  return yield* result.exit
@@ -78,11 +78,26 @@ export interface Workflow<
78
78
 
79
79
  /**
80
80
  * Execute the workflow with the given payload.
81
+ *
82
+ * **Details**
83
+ *
84
+ * `discard: true` returns the execution ID without waiting for the result.
85
+ * The child remains linked to its parent for completion wakes and interruption.
86
+ *
87
+ * With `discard: true, detached: true`, the child neither wakes its parent
88
+ * nor gets interrupted with it. The cluster engine retains the parent for
89
+ * observability.
90
+ *
91
+ * **Gotchas**
92
+ *
93
+ * Do not await a detached execution later. Executing the same payload joins
94
+ * it, but its completion will not wake the awaiting workflow.
81
95
  */
82
96
  readonly execute: <const Discard extends boolean = false>(
83
97
  payload: Payload["~type.make.in"],
84
98
  options?: {
85
99
  readonly discard?: Discard
100
+ readonly detached?: [Discard] extends [true] ? boolean | undefined : undefined
86
101
  }
87
102
  ) => Effect.Effect<
88
103
  Discard extends true ? string : Success["Type"],
@@ -248,7 +263,7 @@ export interface AnyWithProps extends Any {
248
263
  readonly errorSchema: Schema.Top
249
264
  readonly execute: (
250
265
  payload: any,
251
- options?: { readonly discard?: boolean }
266
+ options?: { readonly discard?: boolean; readonly detached?: boolean | undefined }
252
267
  ) => Effect.Effect<any, any, any>
253
268
  readonly resume: (
254
269
  executionId: string
@@ -352,7 +367,7 @@ const Proto = {
352
367
  execute<const Discard extends boolean = false>(
353
368
  this: AnyWithProps,
354
369
  fields: any,
355
- opts?: { readonly discard?: Discard } | undefined
370
+ opts?: { readonly discard?: Discard; readonly detached?: boolean | undefined } | undefined
356
371
  ) {
357
372
  return Effect.suspend(() => {
358
373
  const payload = this.payloadSchema.make(fields)
@@ -366,6 +381,7 @@ const Proto = {
366
381
  executionId,
367
382
  payload,
368
383
  discard: opts?.discard,
384
+ detached: opts?.detached,
369
385
  suspendedRetrySchedule: this.suspendedRetrySchedule
370
386
  })
371
387
  ))
@@ -617,6 +633,11 @@ export class Complete<A, E> extends Data.TaggedClass("Complete")<{
617
633
  * Represents a suspended workflow execution, optionally carrying the cause that
618
634
  * triggered suspension.
619
635
  *
636
+ * **Details**
637
+ *
638
+ * `failed` marks this execution's own `SuspendOnFailure` and requires explicit
639
+ * `resume` or `interrupt`. Inherited child failures do not set it.
640
+ *
620
641
  * @stability unstable
621
642
  * @category schemas
622
643
  * @since 4.0.0
@@ -625,7 +646,8 @@ export class Suspended extends Schema.Class<Suspended>(
625
646
  "effect/workflow/Workflow/Suspended"
626
647
  )({
627
648
  _tag: Schema.tag("Suspended"),
628
- cause: Schema.optional(Schema.Cause(Schema.Never, Schema.Defect()))
649
+ cause: Schema.optional(Schema.Cause(Schema.Never, Schema.Defect())),
650
+ failed: Schema.optional(Schema.Boolean)
629
651
  }) {
630
652
  /**
631
653
  * Marks this value as a workflow result for runtime guards.
@@ -697,11 +719,13 @@ export const intoResult = <A, E, R>(
697
719
  Effect.interruptible,
698
720
  suspendOnFailure
699
721
  ? Effect.catchCause((cause) => {
700
- if (instance.abandoned) {
722
+ if (instance.abandoned || instance.interrupted) {
701
723
  return Effect.failCause(cause)
702
724
  }
703
725
  instance.suspended = true
726
+ // Ordinary suspension is a self-interrupt and also reaches here.
704
727
  if (!Cause.hasInterruptsOnly(cause)) {
728
+ instance.failed = true
705
729
  instance.cause = Cause.die(Cause.squash(cause))
706
730
  }
707
731
  return Effect.interrupt
@@ -720,7 +744,9 @@ export const intoResult = <A, E, R>(
720
744
  const abandoned = instance.abandoned && !instance.interrupted && hasInterruptsOnly
721
745
  instance.abandoned = abandoned
722
746
  if (instance.suspended && hasInterruptsOnly && !abandoned) {
723
- return Effect.succeed(new Suspended({ cause: instance.cause }))
747
+ return Effect.succeed(
748
+ new Suspended(instance.failed ? { cause: instance.cause, failed: true } : { cause: instance.cause })
749
+ )
724
750
  }
725
751
  if (!instance.interrupted && hasInterruptsOnly) {
726
752
  // External interrupts exit without a result. When the cluster has