effect 4.0.0-rc.118 → 4.0.1

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 (254) hide show
  1. package/README.md +17 -4
  2. package/dist/Array.d.ts +25 -25
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +16 -16
  5. package/dist/Array.js.map +1 -1
  6. package/dist/Cache.d.ts.map +1 -1
  7. package/dist/Cache.js +20 -16
  8. package/dist/Cache.js.map +1 -1
  9. package/dist/Cause.d.ts.map +1 -1
  10. package/dist/Cause.js +1 -1
  11. package/dist/Cause.js.map +1 -1
  12. package/dist/Chunk.d.ts +26 -26
  13. package/dist/Chunk.d.ts.map +1 -1
  14. package/dist/Chunk.js +15 -15
  15. package/dist/Chunk.js.map +1 -1
  16. package/dist/Effect.d.ts +167 -39
  17. package/dist/Effect.d.ts.map +1 -1
  18. package/dist/Effect.js +16 -10
  19. package/dist/Effect.js.map +1 -1
  20. package/dist/HashRing.d.ts.map +1 -1
  21. package/dist/HashRing.js +13 -5
  22. package/dist/HashRing.js.map +1 -1
  23. package/dist/MutableHashMap.d.ts.map +1 -1
  24. package/dist/MutableHashMap.js +4 -22
  25. package/dist/MutableHashMap.js.map +1 -1
  26. package/dist/Option.d.ts +17 -17
  27. package/dist/Option.d.ts.map +1 -1
  28. package/dist/Option.js +6 -6
  29. package/dist/Option.js.map +1 -1
  30. package/dist/Pool.d.ts +9 -5
  31. package/dist/Pool.d.ts.map +1 -1
  32. package/dist/Pool.js +34 -41
  33. package/dist/Pool.js.map +1 -1
  34. package/dist/Queue.d.ts +19 -2
  35. package/dist/Queue.d.ts.map +1 -1
  36. package/dist/Queue.js +9 -4
  37. package/dist/Queue.js.map +1 -1
  38. package/dist/RcMap.d.ts.map +1 -1
  39. package/dist/RcMap.js +16 -1
  40. package/dist/RcMap.js.map +1 -1
  41. package/dist/Record.d.ts +14 -14
  42. package/dist/Record.d.ts.map +1 -1
  43. package/dist/Record.js +10 -10
  44. package/dist/Record.js.map +1 -1
  45. package/dist/Schema.d.ts +37 -21
  46. package/dist/Schema.d.ts.map +1 -1
  47. package/dist/Schema.js +29 -10
  48. package/dist/Schema.js.map +1 -1
  49. package/dist/SchemaAST.d.ts.map +1 -1
  50. package/dist/SchemaAST.js +116 -97
  51. package/dist/SchemaAST.js.map +1 -1
  52. package/dist/SchemaRepresentation.d.ts +3 -1
  53. package/dist/SchemaRepresentation.d.ts.map +1 -1
  54. package/dist/SchemaRepresentation.js +3 -1
  55. package/dist/SchemaRepresentation.js.map +1 -1
  56. package/dist/ScopedCache.d.ts.map +1 -1
  57. package/dist/ScopedCache.js +24 -16
  58. package/dist/ScopedCache.js.map +1 -1
  59. package/dist/Stream.d.ts +45 -6
  60. package/dist/Stream.d.ts.map +1 -1
  61. package/dist/Stream.js +55 -44
  62. package/dist/Stream.js.map +1 -1
  63. package/dist/ai/LanguageModel.d.ts +8 -0
  64. package/dist/ai/LanguageModel.d.ts.map +1 -1
  65. package/dist/ai/LanguageModel.js +13 -2
  66. package/dist/ai/LanguageModel.js.map +1 -1
  67. package/dist/ai/McpSchema.d.ts +402 -402
  68. package/dist/ai/McpSchema.d.ts.map +1 -1
  69. package/dist/ai/McpSchema.js +1 -1
  70. package/dist/ai/McpSchema.js.map +1 -1
  71. package/dist/ai/McpServer.d.ts.map +1 -1
  72. package/dist/ai/McpServer.js +11 -3
  73. package/dist/ai/McpServer.js.map +1 -1
  74. package/dist/ai/Response.d.ts.map +1 -1
  75. package/dist/ai/Response.js +22 -12
  76. package/dist/ai/Response.js.map +1 -1
  77. package/dist/ai/internal/mcpProtocol/v2024_11_05.js +1 -0
  78. package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
  79. package/dist/ai/internal/mcpProtocol/v2025_03_26.js +1 -0
  80. package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
  81. package/dist/ai/internal/mcpProtocol/v2025_06_18.js +1 -0
  82. package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
  83. package/dist/ai/internal/mcpProtocol/v2025_11_25.js +1 -0
  84. package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
  85. package/dist/ai/internal/mcpSchema/v2024_11_05.js +3 -1
  86. package/dist/ai/internal/mcpSchema/v2024_11_05.js.map +1 -1
  87. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  88. package/dist/cluster/ClusterWorkflowEngine.js +16 -3
  89. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  90. package/dist/cluster/MessageStorage.d.ts +32 -8
  91. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  92. package/dist/cluster/MessageStorage.js +2 -1
  93. package/dist/cluster/MessageStorage.js.map +1 -1
  94. package/dist/cluster/Sharding.d.ts +5 -2
  95. package/dist/cluster/Sharding.d.ts.map +1 -1
  96. package/dist/cluster/Sharding.js +15 -8
  97. package/dist/cluster/Sharding.js.map +1 -1
  98. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  99. package/dist/cluster/SqlMessageStorage.js +36 -16
  100. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  101. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  102. package/dist/cluster/SqlRunnerStorage.js +30 -44
  103. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  104. package/dist/cluster/internal/entityManager.js +63 -62
  105. package/dist/cluster/internal/entityManager.js.map +1 -1
  106. package/dist/cluster/internal/interruptors.js +8 -0
  107. package/dist/cluster/internal/interruptors.js.map +1 -1
  108. package/dist/cluster/internal/resourceMap.js +39 -14
  109. package/dist/cluster/internal/resourceMap.js.map +1 -1
  110. package/dist/cluster/internal/resourceRef.js +168 -87
  111. package/dist/cluster/internal/resourceRef.js.map +1 -1
  112. package/dist/http/HttpEffect.d.ts +1 -0
  113. package/dist/http/HttpEffect.d.ts.map +1 -1
  114. package/dist/http/HttpEffect.js +29 -22
  115. package/dist/http/HttpEffect.js.map +1 -1
  116. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  117. package/dist/http/HttpMiddleware.js +3 -2
  118. package/dist/http/HttpMiddleware.js.map +1 -1
  119. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  120. package/dist/http/HttpStaticServer.js +14 -0
  121. package/dist/http/HttpStaticServer.js.map +1 -1
  122. package/dist/http/HttpTraceContext.d.ts +1 -1
  123. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  124. package/dist/http/HttpTraceContext.js +4 -4
  125. package/dist/http/HttpTraceContext.js.map +1 -1
  126. package/dist/http-api/HttpApiError.d.ts +1 -0
  127. package/dist/http-api/HttpApiError.d.ts.map +1 -1
  128. package/dist/http-api/HttpApiError.js +1 -0
  129. package/dist/http-api/HttpApiError.js.map +1 -1
  130. package/dist/internal/core.js +43 -2
  131. package/dist/internal/core.js.map +1 -1
  132. package/dist/internal/dateTime.js +1 -1
  133. package/dist/internal/dateTime.js.map +1 -1
  134. package/dist/internal/effect.js +138 -165
  135. package/dist/internal/effect.js.map +1 -1
  136. package/dist/internal/rcRef.js +59 -23
  137. package/dist/internal/rcRef.js.map +1 -1
  138. package/dist/internal/schema/annotations.js +1 -7
  139. package/dist/internal/schema/annotations.js.map +1 -1
  140. package/dist/internal/schema/codegen.js +56 -28
  141. package/dist/internal/schema/codegen.js.map +1 -1
  142. package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
  143. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  144. package/dist/internal/schema/toCodeDocument.js +10 -30
  145. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  146. package/dist/internal/schema/toCodec.js +1 -15
  147. package/dist/internal/schema/toCodec.js.map +1 -1
  148. package/dist/internal/schema/toEquivalence.js +4 -3
  149. package/dist/internal/schema/toEquivalence.js.map +1 -1
  150. package/dist/internal/schema/toFormatter.js +4 -4
  151. package/dist/internal/schema/toFormatter.js.map +1 -1
  152. package/dist/reactivity/Atom.d.ts.map +1 -1
  153. package/dist/reactivity/Atom.js +6 -2
  154. package/dist/reactivity/Atom.js.map +1 -1
  155. package/dist/reactivity/AtomRpc.d.ts +3 -3
  156. package/dist/reactivity/AtomRpc.d.ts.map +1 -1
  157. package/dist/rpc/Rpc.d.ts +2 -2
  158. package/dist/rpc/Rpc.d.ts.map +1 -1
  159. package/dist/rpc/RpcClient.d.ts +7 -7
  160. package/dist/rpc/RpcClient.d.ts.map +1 -1
  161. package/dist/rpc/RpcClient.js +21 -16
  162. package/dist/rpc/RpcClient.js.map +1 -1
  163. package/dist/rpc/RpcMessage.d.ts +1 -1
  164. package/dist/rpc/RpcMessage.d.ts.map +1 -1
  165. package/dist/rpc/RpcServer.d.ts.map +1 -1
  166. package/dist/rpc/RpcServer.js +2 -2
  167. package/dist/rpc/RpcServer.js.map +1 -1
  168. package/dist/rpc/Utils.d.ts +2 -3
  169. package/dist/rpc/Utils.d.ts.map +1 -1
  170. package/dist/rpc/Utils.js +3 -4
  171. package/dist/rpc/Utils.js.map +1 -1
  172. package/dist/schema/SchemaCompiler/runtime.js +6 -1
  173. package/dist/schema/SchemaCompiler/runtime.js.map +1 -1
  174. package/dist/testing/TestSchema.d.ts +234 -6
  175. package/dist/testing/TestSchema.d.ts.map +1 -1
  176. package/dist/testing/TestSchema.js +244 -25
  177. package/dist/testing/TestSchema.js.map +1 -1
  178. package/dist/testing/index.d.ts +1 -0
  179. package/dist/testing/index.d.ts.map +1 -1
  180. package/dist/testing/index.js +1 -0
  181. package/dist/testing/index.js.map +1 -1
  182. package/dist/workflow/DurableDeferred.js +1 -1
  183. package/dist/workflow/DurableDeferred.js.map +1 -1
  184. package/dist/workflow/Workflow.js +1 -1
  185. package/dist/workflow/Workflow.js.map +1 -1
  186. package/dist/workflow/WorkflowEngine.d.ts +2 -0
  187. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  188. package/dist/workflow/WorkflowEngine.js +1 -0
  189. package/dist/workflow/WorkflowEngine.js.map +1 -1
  190. package/package.json +1 -1
  191. package/src/Array.ts +31 -31
  192. package/src/Cache.ts +22 -16
  193. package/src/Cause.ts +1 -1
  194. package/src/Chunk.ts +29 -29
  195. package/src/Effect.ts +170 -39
  196. package/src/HashRing.ts +14 -5
  197. package/src/MutableHashMap.ts +4 -24
  198. package/src/Option.ts +19 -19
  199. package/src/Pool.ts +28 -36
  200. package/src/Queue.ts +26 -6
  201. package/src/RcMap.ts +26 -2
  202. package/src/Record.ts +20 -20
  203. package/src/Schema.ts +48 -24
  204. package/src/SchemaAST.ts +135 -115
  205. package/src/SchemaRepresentation.ts +3 -1
  206. package/src/ScopedCache.ts +27 -16
  207. package/src/Stream.ts +158 -88
  208. package/src/ai/LanguageModel.ts +27 -2
  209. package/src/ai/McpSchema.ts +3 -3
  210. package/src/ai/McpServer.ts +12 -3
  211. package/src/ai/Response.ts +27 -12
  212. package/src/ai/internal/mcpProtocol/v2024_11_05.ts +1 -0
  213. package/src/ai/internal/mcpProtocol/v2025_03_26.ts +1 -0
  214. package/src/ai/internal/mcpProtocol/v2025_06_18.ts +1 -0
  215. package/src/ai/internal/mcpProtocol/v2025_11_25.ts +1 -0
  216. package/src/ai/internal/mcpSchema/v2024_11_05.ts +4 -2
  217. package/src/cluster/ClusterWorkflowEngine.ts +26 -5
  218. package/src/cluster/MessageStorage.ts +22 -5
  219. package/src/cluster/Sharding.ts +32 -28
  220. package/src/cluster/SqlMessageStorage.ts +45 -21
  221. package/src/cluster/SqlRunnerStorage.ts +85 -113
  222. package/src/cluster/internal/entityManager.ts +68 -80
  223. package/src/cluster/internal/interruptors.ts +13 -0
  224. package/src/cluster/internal/resourceMap.ts +55 -17
  225. package/src/cluster/internal/resourceRef.ts +189 -96
  226. package/src/http/HttpEffect.ts +15 -9
  227. package/src/http/HttpMiddleware.ts +8 -2
  228. package/src/http/HttpStaticServer.ts +16 -0
  229. package/src/http/HttpTraceContext.ts +8 -4
  230. package/src/http-api/HttpApiError.ts +1 -0
  231. package/src/internal/core.ts +56 -2
  232. package/src/internal/dateTime.ts +3 -1
  233. package/src/internal/effect.ts +146 -173
  234. package/src/internal/rcRef.ts +83 -33
  235. package/src/internal/schema/annotations.ts +0 -9
  236. package/src/internal/schema/codegen.ts +90 -43
  237. package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
  238. package/src/internal/schema/toCodeDocument.ts +10 -47
  239. package/src/internal/schema/toCodec.ts +3 -16
  240. package/src/internal/schema/toEquivalence.ts +4 -5
  241. package/src/internal/schema/toFormatter.ts +4 -6
  242. package/src/reactivity/Atom.ts +6 -2
  243. package/src/reactivity/AtomRpc.ts +5 -5
  244. package/src/rpc/Rpc.ts +2 -2
  245. package/src/rpc/RpcClient.ts +35 -32
  246. package/src/rpc/RpcMessage.ts +1 -1
  247. package/src/rpc/RpcServer.ts +2 -1
  248. package/src/rpc/Utils.ts +4 -10
  249. package/src/schema/SchemaCompiler/runtime.ts +17 -1
  250. package/src/testing/TestSchema.ts +315 -63
  251. package/src/testing/index.ts +1 -0
  252. package/src/workflow/DurableDeferred.ts +1 -1
  253. package/src/workflow/Workflow.ts +1 -1
  254. package/src/workflow/WorkflowEngine.ts +4 -0
@@ -50,7 +50,7 @@ import type { Primitive } from "./core.ts"
50
50
  import {
51
51
  args,
52
52
  causeAnnotate,
53
- causeDie,
53
+ causeCombine,
54
54
  causeEmpty,
55
55
  causeFromReasons,
56
56
  CauseImpl,
@@ -238,48 +238,6 @@ export const causeAnnotations = <E>(
238
238
  return Context.makeUnsafe(map)
239
239
  }
240
240
 
241
- const dedupeReasons = <E>(
242
- self: ReadonlyArray<Cause.Reason<E>>,
243
- that: ReadonlyArray<Cause.Reason<E>>
244
- ): Array<Cause.Reason<E>> => {
245
- // Keep deduplication local so causeCombine does not retain Array.ts in the core bundle.
246
- // Snapshot both arrays before invoking user-defined hash or equality methods.
247
- const buckets = new Map<number, Array<Cause.Reason<E>>>()
248
- const out: Array<Cause.Reason<E>> = []
249
- for (const reason of self.concat(that)) {
250
- const hash = Hash.hash(reason)
251
- const bucket = buckets.get(hash)
252
- if (bucket === undefined) {
253
- buckets.set(hash, [reason])
254
- } else if (bucket.some((previous) => Equal.equals(previous, reason))) {
255
- continue
256
- } else {
257
- bucket.push(reason)
258
- }
259
- out.push(reason)
260
- }
261
- return out
262
- }
263
-
264
- /** @internal */
265
- export const causeCombine: {
266
- <E2>(that: Cause.Cause<E2>): <E>(self: Cause.Cause<E>) => Cause.Cause<E | E2>
267
- <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2>
268
- } = dual(
269
- 2,
270
- <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2> => {
271
- if (self.reasons.length === 0) {
272
- return that as Cause.Cause<E | E2>
273
- } else if (that.reasons.length === 0) {
274
- return self as Cause.Cause<E | E2>
275
- }
276
- const newCause = new CauseImpl<E | E2>(
277
- dedupeReasons<E | E2>(self.reasons, that.reasons)
278
- )
279
- return Equal.equals(self, newCause) ? self : newCause
280
- }
281
- )
282
-
283
241
  /** @internal */
284
242
  export const causeMap: {
285
243
  <E, E2>(f: (error: NoInfer<E>) => E2): (self: Cause.Cause<E>) => Cause.Cause<E2>
@@ -504,6 +462,21 @@ const fiberVariance = {
504
462
 
505
463
  const fiberIdStore = { id: 0 }
506
464
 
465
+ interface AsyncContext {
466
+ runInAsyncScope<This, Arg, R>(fn: (this: This, arg: Arg) => R, thisArg: This, arg: Arg): R
467
+ }
468
+
469
+ const AsyncResource: (new(type: string) => AsyncContext) | undefined = (() => {
470
+ try {
471
+ return (globalThis as any).process?.getBuiltinModule?.("node:async_hooks")?.AsyncResource
472
+ } catch {
473
+ return undefined
474
+ }
475
+ })()
476
+
477
+ const captureAsyncContext = (): AsyncContext | undefined =>
478
+ AsyncResource === undefined ? undefined : new AsyncResource("effect/Fiber")
479
+
507
480
  /** @internal */
508
481
  export const getCurrentFiber = (): Fiber.Fiber<any, any> | undefined => (globalThis as any)[currentFiberTypeId]
509
482
 
@@ -526,6 +499,7 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
526
499
  this._running = false
527
500
  this._deferredInterrupt = false
528
501
  this._parent = undefined
502
+ this._asyncContext = undefined
529
503
  this.cache.runtimeMetrics?.recordFiberStart(this.context)
530
504
  }
531
505
 
@@ -545,6 +519,7 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
545
519
  declare _running: boolean
546
520
  declare _deferredInterrupt: boolean
547
521
  declare _parent: FiberImpl<any, any> | undefined
522
+ declare _asyncContext: AsyncContext | undefined
548
523
 
549
524
  // set in setContext
550
525
  declare context: Context.Context<never>
@@ -605,6 +580,10 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
605
580
  evaluate(effect: Primitive): void {
606
581
  if (this._exit) {
607
582
  return
583
+ } else if (this._asyncContext !== undefined) {
584
+ const asyncContext = this._asyncContext
585
+ this._asyncContext = undefined
586
+ return asyncContext.runInAsyncScope(this.evaluate, this, effect)
608
587
  } else if (this._yielded !== undefined) {
609
588
  const yielded = this._yielded as () => void
610
589
  this._yielded = undefined
@@ -612,6 +591,7 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
612
591
  }
613
592
  const exit = this.runLoop(effect)
614
593
  if (exit === Yield) {
594
+ this._asyncContext = captureAsyncContext()
615
595
  return
616
596
  }
617
597
  // the interruptChildren middleware is added in Effect.forkChild, so it can be
@@ -1122,20 +1102,22 @@ export const tryPromise = <A, E = Cause.UnknownError>(
1122
1102
  ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.tryPromise"))
1123
1103
  : options.catch
1124
1104
  return callbackOptions<A, E>(function(resume, signal) {
1125
- const failWithCatch = (cause: unknown) => {
1126
- try {
1127
- resume(fail(internalCall(() => catcher(cause)) as E))
1128
- } catch (err) {
1129
- resume(die(err))
1130
- }
1131
- }
1105
+ // Defer the catcher until the fiber restores its async context.
1106
+ const failWithCatch = (cause: unknown) =>
1107
+ suspend(() => {
1108
+ try {
1109
+ return fail(internalCall(() => catcher(cause)) as E)
1110
+ } catch (err) {
1111
+ return die(err)
1112
+ }
1113
+ })
1132
1114
  try {
1133
1115
  f(signal!).then(
1134
1116
  (a) => resume(succeed(a)),
1135
- failWithCatch
1117
+ (e) => resume(failWithCatch(e))
1136
1118
  )
1137
1119
  } catch (err) {
1138
- failWithCatch(err)
1120
+ resume(failWithCatch(err))
1139
1121
  }
1140
1122
  }, f.length !== 0)
1141
1123
  }
@@ -1156,7 +1138,7 @@ const callbackOptions: <A, E = never, R = never>(
1156
1138
  this: Scheduler.Scheduler,
1157
1139
  resume: (effect: Effect.Effect<A, E, R>) => void,
1158
1140
  signal?: AbortSignal
1159
- ) => void | Effect.Effect<void, never, R>,
1141
+ ) => void | Effect.Effect<void, E, R>,
1160
1142
  withSignal: boolean
1161
1143
  ) => Effect.Effect<A, E, R> = (function() {
1162
1144
  const Proto = makePrimitiveProto({
@@ -1214,7 +1196,7 @@ const asyncFinalizer: (
1214
1196
  },
1215
1197
  [contE](cause, _fiber) {
1216
1198
  return hasInterrupts(cause)
1217
- ? flatMap(this[args](), () => failCause(cause))
1199
+ ? flatMap(combineFinalizerCause(exitFailCause(cause), this[args]()), () => failCause(cause))
1218
1200
  : failCause(cause)
1219
1201
  }
1220
1202
  })
@@ -1225,7 +1207,7 @@ export const callback = <A, E = never, R = never>(
1225
1207
  this: Scheduler.Scheduler,
1226
1208
  resume: (effect: Effect.Effect<A, E, R>) => void,
1227
1209
  signal: AbortSignal
1228
- ) => void | Effect.Effect<void, never, R>
1210
+ ) => void | Effect.Effect<void, E, R>
1229
1211
  ): Effect.Effect<A, E, R> => callbackOptions(register as any, register.length >= 2)
1230
1212
 
1231
1213
  /** @internal */
@@ -1632,13 +1614,15 @@ export const raceAll = <Eff extends Effect.Effect<any, any, any>>(
1632
1614
  Effect.Error<Eff>,
1633
1615
  Effect.Services<Eff>
1634
1616
  > =>
1635
- withFiber((parent) =>
1636
- callback((resume) => {
1617
+ withFiber((parent) => {
1618
+ const fibers = new Set<Fiber.Fiber<any, any>>()
1619
+ // Read fibers on exit to include losers forked after the race settles.
1620
+ onExitUnsafe(parent, () => fibers.size === 0 ? undefined : fiberInterruptAll(fibers))
1621
+ return callback((resume) => {
1637
1622
  const effects = Arr.fromIterable(all)
1638
1623
  const len = effects.length
1639
1624
  let doneCount = 0
1640
1625
  let done = false
1641
- const fibers = new Set<Fiber.Fiber<any, any>>()
1642
1626
  const failures: Array<Cause.Reason<any>> = []
1643
1627
  const onExit = (exit: Exit.Exit<any, any>, fiber: Fiber.Fiber<any, any>, i: number) => {
1644
1628
  doneCount++
@@ -1651,11 +1635,7 @@ export const raceAll = <Eff extends Effect.Effect<any, any, any>>(
1651
1635
  }
1652
1636
  const isWinner = !done
1653
1637
  done = true
1654
- resume(
1655
- fibers.size === 0
1656
- ? exit
1657
- : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit)
1658
- )
1638
+ resume(exit)
1659
1639
  if (isWinner && options?.onWinner) {
1660
1640
  options.onWinner({ fiber, index: i, parentFiber: parent })
1661
1641
  }
@@ -1670,10 +1650,8 @@ export const raceAll = <Eff extends Effect.Effect<any, any, any>>(
1670
1650
  })
1671
1651
  if (done) break
1672
1652
  }
1673
-
1674
- return fiberInterruptAll(fibers)
1675
1653
  })
1676
- )
1654
+ })
1677
1655
 
1678
1656
  /** @internal */
1679
1657
  export const raceAllFirst = <Eff extends Effect.Effect<any, any, any>>(
@@ -1690,19 +1668,11 @@ export const raceAllFirst = <Eff extends Effect.Effect<any, any, any>>(
1690
1668
  Effect.Error<Eff>,
1691
1669
  Effect.Services<Eff>
1692
1670
  > =>
1693
- withFiber((parent) =>
1694
- callback((resume) => {
1671
+ withFiber((parent) => {
1672
+ const fibers = new Set<Fiber.Fiber<any, any>>()
1673
+ onExitUnsafe(parent, () => fibers.size === 0 ? undefined : fiberInterruptAll(fibers))
1674
+ return callback((resume) => {
1695
1675
  let done = false
1696
- const fibers = new Set<Fiber.Fiber<any, any>>()
1697
- const onExit = (exit: Exit.Exit<any, any>) => {
1698
- done = true
1699
- resume(
1700
- fibers.size === 0
1701
- ? exit
1702
- : flatMap(uninterruptible(fiberInterruptAll(fibers)), () => exit)
1703
- )
1704
- }
1705
-
1706
1676
  let i = 0
1707
1677
  for (const effect of all) {
1708
1678
  if (done) break
@@ -1712,16 +1682,15 @@ export const raceAllFirst = <Eff extends Effect.Effect<any, any, any>>(
1712
1682
  fiber.addObserver((exit) => {
1713
1683
  fibers.delete(fiber)
1714
1684
  const isWinner = !done
1715
- onExit(exit)
1685
+ done = true
1686
+ resume(exit)
1716
1687
  if (isWinner && options?.onWinner) {
1717
1688
  options.onWinner({ fiber, index, parentFiber: parent })
1718
1689
  }
1719
1690
  })
1720
1691
  }
1721
-
1722
- return fiberInterruptAll(fibers)
1723
1692
  })
1724
- )
1693
+ })
1725
1694
 
1726
1695
  /** @internal */
1727
1696
  export const race: {
@@ -4470,48 +4439,78 @@ export const cachedInvalidateWithTTL: {
4470
4439
  self: Effect.Effect<A, E, R>,
4471
4440
  ttl: Duration.Input | ((exit: Exit.Exit<A, E>) => Duration.Input)
4472
4441
  ): Effect.Effect<[Effect.Effect<A, E, R>, Effect.Effect<void>]> =>
4473
- sync(() => {
4474
- const ttlMillis = typeof ttl === "function"
4475
- ? (exit: Exit.Exit<A, E>) => Duration.toMillis(Duration.fromInputUnsafe(ttl(exit)))
4476
- : constant(Duration.toMillis(Duration.fromInputUnsafe(ttl)))
4477
- const latch = makeLatchUnsafe(false)
4478
- let expiresAt = 0
4479
- let running = false
4480
- let exit: Exit.Exit<A, E> | undefined
4481
- const wait = flatMap(latch.await, () => exit!)
4482
- return [
4483
- withFiber((fiber) => {
4484
- const clock = fiber.getRef(ClockRef)
4485
- const now = expiresAt === Infinity ? 0 : clock.currentTimeMillisUnsafe()
4486
- if (running || now < expiresAt) return exit ?? wait
4487
- running = true
4488
- latch.closeUnsafe()
4489
- exit = undefined
4490
- onExitUnsafe<A, E>(fiber, (exit_) =>
4491
- sync(() => {
4492
- try {
4493
- const duration = ttlMillis(exit_)
4494
- expiresAt = clock.currentTimeMillisUnsafe() + duration
4495
- exit = exit_
4496
- } catch (error) {
4497
- const cause = causeDie(error)
4498
- // Publish the same combined cause that onExit returns to the owner.
4499
- exit = exitFailCause(exitIsFailure(exit_) ? causeCombine(exit_.cause, cause) : cause)
4500
- throw error
4501
- } finally {
4502
- running = false
4503
- latch.openUnsafe()
4504
- }
4505
- }))
4506
- return self
4507
- }),
4508
- sync(() => {
4509
- expiresAt = 0
4510
- latch.closeUnsafe()
4511
- exit = undefined
4512
- })
4513
- ]
4514
- }))
4442
+ sync(() =>
4443
+ makeCachedUnsafe(
4444
+ self,
4445
+ typeof ttl === "function"
4446
+ ? (exit: Exit.Exit<A, E>) => Duration.toMillis(Duration.fromInputUnsafe(ttl(exit)))
4447
+ : constant(Duration.toMillis(Duration.fromInputUnsafe(ttl)))
4448
+ )
4449
+ ))
4450
+
4451
+ const infiniteTTL = constant(Infinity)
4452
+
4453
+ interface CachedRun<A, E> {
4454
+ fiber: FiberImpl<A, E> | undefined
4455
+ awaiters: number
4456
+ }
4457
+
4458
+ const makeCachedUnsafe = <A, E, R>(
4459
+ self: Effect.Effect<A, E, R>,
4460
+ ttlMillis: (exit: Exit.Exit<A, E>) => number
4461
+ ): [Effect.Effect<A, E, R>, Effect.Effect<void>] => {
4462
+ let expiresAt = 0
4463
+ let exit: Exit.Exit<A, E> | undefined
4464
+ let current: CachedRun<A, E> | undefined
4465
+
4466
+ const join = (fiber: Fiber.Fiber<unknown, unknown>, run: CachedRun<A, E>): Effect.Effect<A, E> => {
4467
+ run.awaiters++
4468
+ onExitUnsafe(fiber, () => {
4469
+ // Abandon the run once every caller has left, unless it already finished.
4470
+ if (--run.awaiters > 0 || current !== run) return
4471
+ // Detach it first so new callers start a fresh run instead of joining
4472
+ // one that is being interrupted.
4473
+ current = undefined
4474
+ return fiberInterrupt(run.fiber!)
4475
+ })
4476
+ return fiberJoin(run.fiber!)
4477
+ }
4478
+
4479
+ return [
4480
+ withFiber((fiber) => {
4481
+ if (current !== undefined) return join(fiber, current)
4482
+ if (
4483
+ exit !== undefined &&
4484
+ (expiresAt === Infinity || fiber.getRef(ClockRef).currentTimeMillisUnsafe() < expiresAt)
4485
+ ) {
4486
+ return exit
4487
+ }
4488
+ exit = undefined
4489
+ const clock = fiber.getRef(ClockRef)
4490
+ const run: CachedRun<A, E> = { fiber: undefined, awaiters: 0 }
4491
+ current = run
4492
+ run.fiber = forkUnsafe(
4493
+ fiber,
4494
+ onExitPrimitive(self, (exit_) => {
4495
+ // An abandoned run must not overwrite or clear a replacement run.
4496
+ if (current !== run) return
4497
+ current = undefined
4498
+ // Interruption is abandonment, so it is never cached.
4499
+ if (exitHasInterrupts(exit_)) return
4500
+ const duration = ttlMillis(exit_)
4501
+ expiresAt = duration === Infinity ? Infinity : clock.currentTimeMillisUnsafe() + duration
4502
+ exit = exit_
4503
+ }),
4504
+ true,
4505
+ true
4506
+ )
4507
+ return run.fiber._exit ?? join(fiber, run)
4508
+ }),
4509
+ sync(() => {
4510
+ exit = undefined
4511
+ })
4512
+ ]
4513
+ }
4515
4514
 
4516
4515
  /** @internal */
4517
4516
  export const cachedWithTTL: {
@@ -4538,23 +4537,7 @@ export const cachedWithTTL: {
4538
4537
 
4539
4538
  /** @internal */
4540
4539
  export const cached = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<Effect.Effect<A, E, R>> =>
4541
- sync(() => {
4542
- const latch = makeLatchUnsafe(false)
4543
- let started = false
4544
- let exit: Exit.Exit<A, E> | undefined
4545
- const wait = flatMap(latch.await, () => exit!)
4546
- return withFiber((fiber) => {
4547
- if (exit !== undefined) return exit
4548
- if (started) return wait
4549
- started = true
4550
- onExitUnsafe<A, E>(fiber, (result) =>
4551
- sync(() => {
4552
- exit = result
4553
- latch.openUnsafe()
4554
- }))
4555
- return self
4556
- })
4557
- })
4540
+ sync(() => makeCachedUnsafe(self, infiniteTTL)[0])
4558
4541
 
4559
4542
  // ----------------------------------------------------------------------------
4560
4543
  // interruption
@@ -4719,19 +4702,19 @@ export const partition: {
4719
4702
  <A, B, E, R>(
4720
4703
  f: (a: A, i: number) => Effect.Effect<B, E, R>,
4721
4704
  options?: { readonly concurrency?: Concurrency | undefined }
4722
- ): (elements: Iterable<A>) => Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R>
4705
+ ): (elements: Iterable<A>) => Effect.Effect<[passes: Array<B>, fails: Array<E>], never, R>
4723
4706
  <A, B, E, R>(
4724
4707
  elements: Iterable<A>,
4725
4708
  f: (a: A, i: number) => Effect.Effect<B, E, R>,
4726
4709
  options?: { readonly concurrency?: Concurrency | undefined }
4727
- ): Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R>
4710
+ ): Effect.Effect<[passes: Array<B>, fails: Array<E>], never, R>
4728
4711
  } = dual(
4729
4712
  (args) => isIterable(args[0]) && !isEffect(args[0]),
4730
4713
  <A, B, E, R>(
4731
4714
  elements: Iterable<A>,
4732
4715
  f: (a: A, i: number) => Effect.Effect<B, E, R>,
4733
4716
  options?: { readonly concurrency?: Concurrency | undefined }
4734
- ): Effect.Effect<[excluded: Array<E>, satisfying: Array<B>], never, R> =>
4717
+ ): Effect.Effect<[passes: Array<B>, fails: Array<E>], never, R> =>
4735
4718
  map(
4736
4719
  forEach(elements, (a, i) => result(f(a, i)), options),
4737
4720
  (results) => Arr.partition(results, identity)
@@ -4820,11 +4803,11 @@ export const validate: {
4820
4803
  ): Effect.Effect<Array<B> | void, Arr.NonEmptyArray<E>, R> =>
4821
4804
  flatMap(
4822
4805
  partition(elements, f, { concurrency: options?.concurrency }),
4823
- ([excluded, satisfying]) => {
4824
- if (Arr.isArrayNonEmpty(excluded)) {
4825
- return fail(excluded)
4806
+ ([passes, fails]) => {
4807
+ if (Arr.isArrayNonEmpty(fails)) {
4808
+ return fail(fails)
4826
4809
  }
4827
- return options?.discard ? void_ : succeed(satisfying)
4810
+ return options?.discard ? void_ : succeed(passes)
4828
4811
  }
4829
4812
  )
4830
4813
  )
@@ -5085,7 +5068,6 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5085
5068
  let parentFiber: Fiber.Fiber<any, any> | undefined
5086
5069
  let fibers: Set<Fiber.Fiber<any, any>> | undefined
5087
5070
  let resume: ((effect: Effect.Effect<void, E | E2, R>) => void) | undefined
5088
- let interrupted = false
5089
5071
  let terminal: Exit.Exit<void, E | E2> | void
5090
5072
  let effect: Effect.Effect<X, E, R> | undefined
5091
5073
 
@@ -5093,9 +5075,8 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5093
5075
  const defect = exitDie(error)
5094
5076
  terminal = defect
5095
5077
  done = true
5096
- interrupted = true
5097
5078
  return fibers && fibers.size > 0
5098
- ? flatMap(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => defect)
5079
+ ? flatMap(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => terminal ?? defect)
5099
5080
  : defect
5100
5081
  }
5101
5082
 
@@ -5105,12 +5086,9 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5105
5086
  const item = items[index]
5106
5087
  const eff = effect ?? onItem(state, item, index)
5107
5088
 
5108
- // fast case (already an exit)
5109
5089
  if (effectIsExit(eff)) {
5110
5090
  terminal = step(state, item, eff, index)
5111
5091
  if (terminal) break
5112
-
5113
- // We have an effect, so enter "async" mode
5114
5092
  } else if (!parentFiber) {
5115
5093
  return callback((cb) => {
5116
5094
  parentFiber = getCurrentFiber()!
@@ -5125,15 +5103,15 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5125
5103
  }
5126
5104
  if (result) return cb(result)
5127
5105
  return suspend(() => {
5128
- terminal = exitVoid
5129
- interrupted = true
5130
- return fibers ? fiberInterruptAll(fibers) : void_
5106
+ terminal ??= exitVoid
5107
+ return flatMap(
5108
+ fibers ? fiberInterruptAll(fibers) : void_,
5109
+ () => terminal?._tag === "Failure" ? terminal : void_
5110
+ )
5131
5111
  })
5132
5112
  })
5133
-
5134
- // Fork the effect with concurrency > 1
5135
5113
  } else {
5136
- // Clear the temporary effect from capturing the parentFiber
5114
+ // Clear the effect cached before the parent fiber was available.
5137
5115
  effect = undefined
5138
5116
 
5139
5117
  const fiber = forkUnsafe(parentFiber, eff, true, true, "inherit")
@@ -5143,7 +5121,6 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5143
5121
  continue
5144
5122
  }
5145
5123
 
5146
- // Add the fiber to the Set
5147
5124
  fibers!.add(fiber)
5148
5125
 
5149
5126
  const currentIndex = index
@@ -5151,22 +5128,17 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5151
5128
  fibers!.delete(fiber)
5152
5129
  try {
5153
5130
  if (terminal) {
5154
- if (!interrupted && exit._tag === "Failure") {
5155
- for (const reason of exit.cause.reasons) {
5156
- if (reason._tag === "Interrupt") continue
5157
- else if (terminal._tag === "Failure") {
5158
- ;(terminal.cause.reasons as Array<any>).push(reason)
5159
- } else {
5160
- terminal = exitFailCause(causeFromReasons([reason]))
5161
- }
5131
+ if (exit._tag === "Failure") {
5132
+ const reasons = exit.cause.reasons.filter((reason) => reason._tag !== "Interrupt")
5133
+ if (reasons.length > 0) {
5134
+ const cause = causeFromReasons(reasons)
5135
+ terminal = exitFailCause(terminal._tag === "Failure" ? causeCombine(terminal.cause, cause) : cause)
5162
5136
  }
5163
5137
  }
5164
5138
  } else {
5165
5139
  const result = step(state, item, exit, currentIndex)
5166
5140
  if (result) {
5167
- terminal = result._tag === "Failure"
5168
- ? exitFailCause(causeFromReasons(result.cause.reasons.slice()))
5169
- : result
5141
+ terminal = result
5170
5142
  go()
5171
5143
  }
5172
5144
  }
@@ -5182,7 +5154,6 @@ const iterateConcurrentImpl = <S, A, X, E, R, E2>(options: IterateOptions<S, A,
5182
5154
  }
5183
5155
  })
5184
5156
 
5185
- // Check if we have reached the concurrency limit
5186
5157
  if (fibers!.size < concurrency) continue
5187
5158
  paused = true
5188
5159
  index++
@@ -5556,6 +5527,8 @@ export const forkUnsafe = <FA, FE, A, E, R>(
5556
5527
  if (immediate) {
5557
5528
  child.evaluate(effect as any)
5558
5529
  } else {
5530
+ // Preserve the fork context rather than the dispatcher's context.
5531
+ child._asyncContext = captureAsyncContext()
5559
5532
  parentRuntime.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0)
5560
5533
  }
5561
5534
  if (!daemon && !child._exit) {