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
@@ -1,133 +1,226 @@
1
- import type * as Cause from "../../Cause.ts"
1
+ import * as Deferred from "../../Deferred.ts"
2
2
  import * as Effect from "../../Effect.ts"
3
3
  import * as Exit from "../../Exit.ts"
4
- import * as Latch from "../../Latch.ts"
5
- import * as MutableRef from "../../MutableRef.ts"
6
- import * as Option from "../../Option.ts"
4
+ import * as Fiber from "../../Fiber.ts"
7
5
  import * as Scope from "../../Scope.ts"
8
6
  import type { EntityAddress } from "../EntityAddress.ts"
9
- import { acquireEntity, releaseEntity } from "./interruptors.ts"
7
+ import { aroundEntity } from "./interruptors.ts"
10
8
 
11
- /**
12
- * @internal
13
- */
14
- export type State<A, E> = {
15
- readonly _tag: "Closed"
16
- } | {
17
- readonly _tag: "Acquiring"
18
- readonly scope: Scope.Closeable
19
- } | {
20
- readonly _tag: "Acquired"
21
- readonly scope: Scope.Closeable
22
- readonly value: A
23
- } | {
24
- readonly _tag: "Failed"
9
+ type Pending<A> = {
10
+ readonly _tag: "Pending"
25
11
  readonly scope: Scope.Closeable
26
- readonly cause: Cause.Cause<E>
12
+ /** Predecessor scope or ongoing release to await before acquisition. */
13
+ readonly releases: Scope.Closeable | Fiber.Fiber<void> | undefined
14
+ releasing: boolean
15
+ /** Shared with waiters across superseded rebuilds. */
16
+ readonly ready: Deferred.Deferred<A>
17
+ value: A | undefined
18
+ work: Fiber.Fiber<void> | undefined
27
19
  }
28
20
 
21
+ type State<A> =
22
+ | { readonly _tag: "Closed" }
23
+ | { readonly _tag: "Ready"; readonly scope: Scope.Closeable; readonly value: A }
24
+ | Pending<A>
25
+ | {
26
+ readonly _tag: "Failed"
27
+ readonly ready: Deferred.Deferred<A>
28
+ readonly releasing: Fiber.Fiber<void> | undefined
29
+ }
30
+
29
31
  /**
32
+ * A replaceable resource whose acquired value identifies its generation.
33
+ *
30
34
  * @internal
31
35
  */
32
- export class ResourceRef<A, E = never> {
33
- static from = Effect.fnUntraced(function*<A, E>(
36
+ export class ResourceRef<A> {
37
+ static from = Effect.fnUntraced(function*<A>(
34
38
  parentScope: Scope.Scope,
35
- acquire: (scope: Scope.Scope) => Effect.Effect<A, E>,
39
+ acquire: (scope: Scope.Scope) => Effect.Effect<A>,
36
40
  teardownAddress?: EntityAddress
37
41
  ) {
38
- const state = MutableRef.make<State<A, E>>({ _tag: "Closed" })
39
-
40
- yield* Scope.addFinalizerExit(parentScope, (exit) => {
41
- const s = MutableRef.get(state)
42
- if (s._tag === "Closed") {
43
- return Effect.void
44
- }
45
- const scope = s.scope
46
- MutableRef.set(state, { _tag: "Closed" })
47
- return Scope.close(scope, exit)
48
- })
49
-
50
- const scope = yield* Scope.make()
51
- MutableRef.set(state, { _tag: "Acquiring", scope })
52
- const value = yield* acquire(scope)
53
- MutableRef.set(state, { _tag: "Acquired", scope, value })
54
-
55
- return new ResourceRef(state, acquire, teardownAddress)
42
+ // Close the ref before joining releases, so waiters wake even if release stalls.
43
+ const releaseScope = Scope.forkUnsafe(parentScope)
44
+ const ref = new ResourceRef(releaseScope, acquire, teardownAddress)
45
+ yield* Scope.addFinalizerExit(parentScope, (exit) => ref.close(exit))
46
+ const pending = ref.pending(undefined, Deferred.makeUnsafe())
47
+ const value = yield* acquire(pending.scope)
48
+ if (ref.state === pending) ref.publish(pending, value)
49
+ return ref
56
50
  })
57
51
 
58
- readonly state: MutableRef.MutableRef<State<A, E>>
59
- readonly acquire: (scope: Scope.Scope) => Effect.Effect<A, E>
60
- readonly teardownAddress: EntityAddress | undefined
61
- constructor(
62
- state: MutableRef.MutableRef<State<A, E>>,
63
- acquire: (scope: Scope.Scope) => Effect.Effect<A, E>,
64
- teardownAddress?: EntityAddress
52
+ private state: State<A> = { _tag: "Closed" }
53
+ private readonly releaseScope: Scope.Scope
54
+ private readonly acquire: (scope: Scope.Scope) => Effect.Effect<A>
55
+ private readonly teardownAddress: EntityAddress | undefined
56
+ private constructor(
57
+ releaseScope: Scope.Scope,
58
+ acquire: (scope: Scope.Scope) => Effect.Effect<A>,
59
+ teardownAddress: EntityAddress | undefined
65
60
  ) {
66
- this.state = state
61
+ this.releaseScope = releaseScope
67
62
  this.acquire = acquire
68
63
  this.teardownAddress = teardownAddress
69
64
  }
70
65
 
71
- latch = Latch.makeUnsafe(true)
66
+ /** Returns the value only after acquisition and preparation complete. */
67
+ getUnsafe(): A | undefined {
68
+ return this.state._tag === "Ready" ? this.state.value : undefined
69
+ }
70
+
71
+ /** Waits for a ready value; interrupts when the ref closes. */
72
+ readonly await: Effect.Effect<A> = Effect.suspend(() => {
73
+ const s = this.state
74
+ switch (s._tag) {
75
+ case "Closed":
76
+ return Effect.interrupt
77
+ case "Ready":
78
+ return Effect.succeed(s.value)
79
+ case "Pending":
80
+ case "Failed":
81
+ return Deferred.await(s.ready)
82
+ }
83
+ })
72
84
 
73
- getUnsafe(): Option.Option<A> {
74
- if (this.state.current._tag === "Acquired") {
75
- return Option.some(this.state.current.value)
85
+ /**
86
+ * Replaces the generation, holding waiters until acquisition and `prepare` finish.
87
+ * Returns `undefined` if closed, if `from` differs from the ready or preparing
88
+ * value, or if `from` is omitted while a rebuild is pending.
89
+ *
90
+ * Acceptance immediately blocks waiters; run the returned effect to release
91
+ * the predecessor and build its replacement. Closing or superseding the ref
92
+ * interrupts the rebuild. Failure wakes waiters with its cause; the next
93
+ * rebuild must omit `from` and await cleanup. Interruption during predecessor
94
+ * release instead carries waiters forward without awaiting that release again.
95
+ */
96
+ rebuildUnsafe(options?: {
97
+ readonly from?: A | undefined
98
+ readonly prepare?: ((value: A) => Effect.Effect<void>) | undefined
99
+ }): Effect.Effect<void> | undefined {
100
+ const s = this.state
101
+ const from = options?.from
102
+ let pending: Pending<A>
103
+ switch (s._tag) {
104
+ case "Closed":
105
+ return undefined
106
+ case "Ready": {
107
+ if (from !== undefined && from !== s.value) return undefined
108
+ pending = this.pending(s.scope, Deferred.makeUnsafe())
109
+ break
110
+ }
111
+ case "Failed": {
112
+ if (from !== undefined) return undefined
113
+ pending = this.pending(s.releasing, s.ready)
114
+ break
115
+ }
116
+ case "Pending": {
117
+ if (from === undefined || s.value !== from) return undefined
118
+ s.work?.interruptUnsafe()
119
+ pending = this.pending(s.scope, s.ready)
120
+ break
121
+ }
76
122
  }
77
- return Option.none()
123
+ return this.run(pending, options?.prepare)
78
124
  }
79
125
 
80
- rebuildUnsafe(): Effect.Effect<void, E> {
81
- const s = this.state.current
82
- if (s._tag === "Closed") {
83
- return Effect.interrupt
126
+ private pending(
127
+ releases: Scope.Closeable | Fiber.Fiber<void> | undefined,
128
+ ready: Deferred.Deferred<A>
129
+ ): Pending<A> {
130
+ const pending: Pending<A> = {
131
+ _tag: "Pending",
132
+ scope: Scope.makeUnsafe(),
133
+ releases,
134
+ releasing: false,
135
+ ready,
136
+ value: undefined,
137
+ work: undefined
84
138
  }
85
- const prevScope = s.scope
86
- const scope = Scope.makeUnsafe()
87
- this.latch.closeUnsafe()
88
- MutableRef.set(this.state, { _tag: "Acquiring", scope })
89
- const teardownAddress = this.teardownAddress
90
- return Effect.suspend(() => {
91
- const close = Scope.close(prevScope, Exit.void)
92
- if (!teardownAddress) return close
93
- acquireEntity(teardownAddress)
94
- return Effect.ensuring(close, Effect.sync(() => releaseEntity(teardownAddress)))
139
+ this.state = pending
140
+ return pending
141
+ }
142
+
143
+ private publish(pending: Pending<A>, value: A): void {
144
+ this.state = { _tag: "Ready", scope: pending.scope, value }
145
+ Deferred.doneUnsafe(pending.ready, Exit.succeed(value))
146
+ }
147
+
148
+ private run(pending: Pending<A>, prepare: ((value: A) => Effect.Effect<void>) | undefined): Effect.Effect<void> {
149
+ const work = Effect.suspend(() => {
150
+ if (this.state !== pending) return Effect.interrupt
151
+ const releases = pending.releases
152
+ if (!releases) return Effect.void
153
+ pending.releasing = true
154
+ return Effect.andThen(
155
+ Fiber.isFiber(releases) ? Fiber.join(releases) : Effect.flatMap(this.release(releases), Fiber.join),
156
+ Effect.sync(() => {
157
+ pending.releasing = false
158
+ })
159
+ )
95
160
  }).pipe(
96
- Effect.andThen(this.acquire(scope)),
161
+ Effect.andThen(Effect.suspend(() => this.acquire(pending.scope))),
162
+ Effect.tap((value) => {
163
+ if (this.state !== pending) return Effect.interrupt
164
+ pending.value = value
165
+ return prepare ? prepare(value) : Effect.void
166
+ }),
97
167
  Effect.flatMap((value) => {
98
- if (this.state.current._tag === "Closed") {
99
- return Effect.interrupt
100
- }
101
- MutableRef.set(this.state, { _tag: "Acquired", scope, value })
102
- return this.latch.open
103
- })
104
- ).pipe(
168
+ if (this.state !== pending) return Effect.interrupt
169
+ this.publish(pending, value)
170
+ return Effect.void
171
+ }),
105
172
  Effect.onExit((exit) => {
106
- if (Exit.isSuccess(exit)) {
173
+ if (Exit.isSuccess(exit) || this.state !== pending) {
174
+ return Effect.void
175
+ }
176
+ if (pending.releasing) {
177
+ // No resource was acquired. Carry waiters forward without rejoining the stalled release.
178
+ this.state = { _tag: "Failed", ready: pending.ready, releasing: undefined }
107
179
  return Effect.void
108
180
  }
109
- return Scope.close(scope, exit).pipe(
110
- Effect.ensuring(Effect.sync(() => {
111
- const state = this.state.current
112
- if (state._tag === "Acquiring" && state.scope === scope) {
113
- MutableRef.set(this.state, { _tag: "Failed", scope, cause: exit.cause })
114
- this.latch.openUnsafe()
115
- }
116
- }))
117
- )
181
+ return Effect.map(this.release(pending.scope), (releasing) => {
182
+ if (this.state !== pending) return
183
+ this.state = { _tag: "Failed", ready: Deferred.makeUnsafe(), releasing }
184
+ Deferred.doneUnsafe(pending.ready, Exit.failCause(exit.cause))
185
+ })
118
186
  })
119
187
  )
188
+ return Effect.flatMap(Effect.forkChild(work, { startImmediately: true }), (fiber) => {
189
+ pending.work = fiber
190
+ // Replaced or closed before the fiber handle existed.
191
+ if (this.state !== pending) fiber.interruptUnsafe()
192
+ return Fiber.join(fiber)
193
+ })
120
194
  }
121
195
 
122
- await: Effect.Effect<A, E> = Effect.suspend(() => {
123
- const s = this.state.current
124
- if (s._tag === "Closed") {
125
- return Effect.interrupt
126
- } else if (s._tag === "Acquired") {
127
- return Effect.succeed(s.value)
128
- } else if (s._tag === "Failed") {
129
- return Effect.failCause(s.cause)
196
+ // A stalled release must not prevent rebuild interruption.
197
+ private release(scope: Scope.Closeable): Effect.Effect<Fiber.Fiber<void>> {
198
+ const close = Scope.close(scope, Exit.void)
199
+ return Effect.forkIn(
200
+ this.teardownAddress ? aroundEntity(this.teardownAddress, close) : close,
201
+ this.releaseScope,
202
+ { startImmediately: true }
203
+ )
204
+ }
205
+
206
+ private close(exit: Exit.Exit<unknown, unknown>): Effect.Effect<void> {
207
+ const s = this.state
208
+ this.state = { _tag: "Closed" }
209
+ switch (s._tag) {
210
+ case "Closed":
211
+ return Effect.void
212
+ case "Ready":
213
+ return Scope.close(s.scope, exit)
214
+ case "Failed": {
215
+ Deferred.doneUnsafe(s.ready, Effect.interrupt)
216
+ return Effect.void
217
+ }
218
+ case "Pending": {
219
+ Deferred.doneUnsafe(s.ready, Effect.interrupt)
220
+ s.work?.interruptUnsafe()
221
+ const close = Scope.close(s.scope, exit)
222
+ return s.releases && !Fiber.isFiber(s.releases) ? Effect.andThen(Scope.close(s.releases, exit), close) : close
223
+ }
130
224
  }
131
- return Effect.flatMap(this.latch.await, () => this.await)
132
- })
225
+ }
133
226
  }
@@ -32,6 +32,7 @@ import * as preResponseHandler from "./internal/preResponseHandler.ts"
32
32
 
33
33
  /**
34
34
  * Runs an HTTP server effect, sends the produced response with the supplied handler, and converts failures into HTTP responses.
35
+ * On handler failure, the request scope closes with the original cause.
35
36
  *
36
37
  * @stability unstable
37
38
  * @category combinators
@@ -45,8 +46,10 @@ export const toHandled = <E, R, EH, RH>(
45
46
  ) => Effect.Effect<unknown, EH, RH>,
46
47
  middleware?: HttpMiddleware | undefined
47
48
  ): Effect.Effect<void, never, Exclude<R | RH | HttpServerRequest, Scope.Scope>> => {
48
- const handleCause = (request: HttpServerRequest, cause: Cause.Cause<E | EH | HttpServerError>) =>
49
- Effect.flatMapEager(causeResponse(cause), ([response, cause]) => {
49
+ const handleCause = (frame: RequestFrame, cause: Cause.Cause<E | EH | HttpServerError>) => {
50
+ frame.cause = cause
51
+ const request = frame.request
52
+ return Effect.flatMapEager(causeResponse(cause), ([response, cause]) => {
50
53
  const fiber = Fiber.getCurrent()!
51
54
  reportCauseUnsafe(fiber, cause)
52
55
  const handler = preResponseHandler.requestPreResponseHandlers.get(request.source)
@@ -67,6 +70,7 @@ export const toHandled = <E, R, EH, RH>(
67
70
  () => cont
68
71
  )
69
72
  })
73
+ }
70
74
 
71
75
  // Writes the response, applying any registered pre-response handler first.
72
76
  // Returns an `Exit` when the write completes synchronously.
@@ -85,14 +89,15 @@ export const toHandled = <E, R, EH, RH>(
85
89
  })
86
90
  }
87
91
 
88
- const withMiddleware = (request: HttpServerRequest): Effect.Effect<
92
+ const withMiddleware = (frame: RequestFrame): Effect.Effect<
89
93
  unknown,
90
94
  E | EH | HttpServerError,
91
95
  HttpServerRequest | R | RH
92
96
  > => {
97
+ const request = frame.request
93
98
  const responded = Effect.matchCauseEffect(self, {
94
99
  onSuccess: (response) => sendResponse(request, response),
95
- onFailure: (cause) => handleCause(request, cause)
100
+ onFailure: (cause) => handleCause(frame, cause)
96
101
  })
97
102
  return middleware === undefined ?
98
103
  responded :
@@ -116,9 +121,9 @@ export const toHandled = <E, R, EH, RH>(
116
121
  }
117
122
 
118
123
  // Apply tracing lazily so disabled requests can use the single-frame path.
119
- const traced = (request: HttpServerRequest) =>
124
+ const traced = (frame: RequestFrame) =>
120
125
  tracer(
121
- withMiddleware(request) as Effect.Effect<
126
+ withMiddleware(frame) as Effect.Effect<
122
127
  HttpServerResponse,
123
128
  E | EH | HttpServerError,
124
129
  HttpServerRequest | R | RH
@@ -135,13 +140,14 @@ export const toHandled = <E, R, EH, RH>(
135
140
  Effect.onExitPrimitive(effect, (exit) => {
136
141
  fiber.setContext(frame.prev)
137
142
  if (scopeEjected in frame.scope) return undefined
138
- return Scope.closeUnsafe(frame.scope, exit)
143
+ return Scope.closeUnsafe(frame.scope, frame.cause === undefined ? exit : Exit.failCause(frame.cause))
139
144
  }, true)
140
145
 
141
146
  class RequestFrame {
142
147
  readonly scope: Scope.Closeable
143
148
  readonly prev: Context.Context<never>
144
149
  readonly request: HttpServerRequest
150
+ cause: Cause.Cause<E | EH | HttpServerError> | undefined
145
151
  constructor(scope: Scope.Closeable, prev: Context.Context<never>, request: HttpServerRequest) {
146
152
  this.scope = scope
147
153
  this.prev = prev
@@ -159,7 +165,7 @@ export const toHandled = <E, R, EH, RH>(
159
165
  return finishAfter(this, fiber, sent)
160
166
  }
161
167
  [contE](cause: Cause.Cause<E | EH | HttpServerError>, fiber: Fiber.Fiber<unknown, unknown>) {
162
- return finishAfter(this, fiber, handleCause(this.request, cause))
168
+ return finishAfter(this, fiber, handleCause(this, cause))
163
169
  }
164
170
  }
165
171
 
@@ -177,7 +183,7 @@ export const toHandled = <E, R, EH, RH>(
177
183
  ;(fiber as any)._stack.push(frame)
178
184
  return self
179
185
  }
180
- return finishAfter(frame, fiber, middleware === undefined ? traced(frame.request) : withMiddleware(frame.request))
186
+ return finishAfter(frame, fiber, middleware === undefined ? traced(frame) : withMiddleware(frame))
181
187
  }) as any
182
188
  }
183
189
 
@@ -26,7 +26,7 @@ import { nativeTracer, ParentSpan, Tracer } from "../Tracer.ts"
26
26
  import * as Headers from "./Headers.ts"
27
27
  import type { CompressionAlgorithm } from "./HttpPlatform.ts"
28
28
  import { HttpPlatform } from "./HttpPlatform.ts"
29
- import { causeResponseStripped } from "./HttpServerError.ts"
29
+ import { causeResponseStripped, ClientAbort } from "./HttpServerError.ts"
30
30
  import { HttpServerRequest } from "./HttpServerRequest.ts"
31
31
  import * as Request from "./HttpServerRequest.ts"
32
32
  import * as Response from "./HttpServerResponse.ts"
@@ -158,8 +158,14 @@ export const logger: <E, R>(
158
158
  return exit
159
159
  } else if (exit._tag === "Failure") {
160
160
  const [response, cause] = causeResponseStripped(exit.cause)
161
+ const message = Option.isSome(cause) &&
162
+ !cause.value.reasons.every((reason) =>
163
+ reason._tag === "Interrupt" && reason.annotations.has(ClientAbort.key)
164
+ )
165
+ ? cause.value
166
+ : "Sent HTTP response"
161
167
  return Effect.andThen(
162
- Effect.annotateLogs(Effect.log(Option.getOrElse(cause, () => "Sent HTTP Response")), {
168
+ Effect.annotateLogs(Effect.log(message), {
163
169
  "http.method": request.method,
164
170
  "http.url": path,
165
171
  "http.status": response.status
@@ -140,6 +140,14 @@ export const make: (options: {
140
140
  return yield* getFullResponse()
141
141
  }
142
142
 
143
+ const ifRange = request.headers["if-range"]
144
+ if (ifRange !== undefined) {
145
+ fullResponse = yield* getFullResponse()
146
+ if (!matchesIfRange(ifRange, fullResponse.headers["etag"])) {
147
+ return fullResponse
148
+ }
149
+ }
150
+
143
151
  const resolvedFileSize = fileSize ?? (yield* handlePlatformError(request, fileSystem.stat(filePath))).size
144
152
  const parsedRange = parseRange(rangeHeader, resolvedFileSize)
145
153
 
@@ -459,6 +467,14 @@ const isNotModifiedSince = (ifModifiedSince: string, lastModified: string | unde
459
467
  return lastModifiedMs <= ifModifiedSinceMs
460
468
  }
461
469
 
470
+ // Only a strong entity-tag can match (RFC 9110 §13.1.5). Weak tags start
471
+ // with `W/`, and Last-Modified dates never match: filesystem metadata cannot
472
+ // prove the file did not change twice within the same second.
473
+ const matchesIfRange = (ifRange: string, etag: string | undefined): boolean => {
474
+ const value = ifRange.trim()
475
+ return value.startsWith("\"") && value === etag
476
+ }
477
+
462
478
  const notModifiedResponse = (
463
479
  response: HttpServerResponse.HttpServerResponse
464
480
  ): HttpServerResponse.HttpServerResponse => {
@@ -99,7 +99,7 @@ export const b3: FromHeaders = (headers) => {
99
99
  return Option.some(Tracer.externalSpan({
100
100
  traceId: parts[0],
101
101
  spanId: parts[1],
102
- sampled: parts[2] ? parts[2] === "1" : true
102
+ sampled: parts[2] ? parts[2] === "1" || parts[2] === "d" : true
103
103
  }))
104
104
  }
105
105
 
@@ -109,7 +109,7 @@ export const b3: FromHeaders = (headers) => {
109
109
  * **Details**
110
110
  *
111
111
  * The decoder reads `x-b3-traceid`, `x-b3-spanid`, and optional `x-b3-sampled`
112
- * headers.
112
+ * and `x-b3-flags` headers. Debug flags override the sampling decision.
113
113
  *
114
114
  * @stability unstable
115
115
  * @category decoding
@@ -122,7 +122,8 @@ export const xb3: FromHeaders = (headers) => {
122
122
  return Option.some(Tracer.externalSpan({
123
123
  traceId: headers["x-b3-traceid"],
124
124
  spanId: headers["x-b3-spanid"],
125
- sampled: headers["x-b3-sampled"] ? headers["x-b3-sampled"] === "1" : true
125
+ sampled: headers["x-b3-flags"] === "1" ||
126
+ (headers["x-b3-sampled"] ? headers["x-b3-sampled"] === "1" || headers["x-b3-sampled"] === "true" : true)
126
127
  }))
127
128
  }
128
129
 
@@ -151,7 +152,10 @@ export const w3c: FromHeaders = (headers) => {
151
152
  const [version, traceId, spanId, flags] = parts
152
153
  switch (version) {
153
154
  case "00": {
154
- if (w3cTraceId.test(traceId) === false || w3cSpanId.test(spanId) === false) {
155
+ if (
156
+ w3cTraceId.test(traceId) === false || w3cSpanId.test(spanId) === false ||
157
+ traceId === "00000000000000000000000000000000" || spanId === "0000000000000000"
158
+ ) {
155
159
  return Option.none()
156
160
  }
157
161
  return Option.some(Tracer.externalSpan({
@@ -479,6 +479,7 @@ export class HttpApiSchemaError extends Data.TaggedClass("HttpApiSchemaError")<{
479
479
  readonly cause: Schema.SchemaError
480
480
  }> {
481
481
  readonly [HttpApiSchemaErrorTypeId]: HttpApiSchemaErrorTypeId = HttpApiSchemaErrorTypeId
482
+ readonly [ErrorReporter.ignore] = true
482
483
 
483
484
  static is(u: unknown): u is HttpApiSchemaError {
484
485
  return hasProperty(u, HttpApiSchemaErrorTypeId)
@@ -279,6 +279,48 @@ export const causeFromReasons = <E>(
279
279
  reasons: ReadonlyArray<Cause.Reason<E>>
280
280
  ): Cause.Cause<E> => new CauseImpl(reasons)
281
281
 
282
+ const dedupeReasons = <E>(
283
+ self: ReadonlyArray<Cause.Reason<E>>,
284
+ that: ReadonlyArray<Cause.Reason<E>>
285
+ ): Array<Cause.Reason<E>> => {
286
+ // Avoid importing Array.ts into the core bundle.
287
+ // Snapshot both arrays before invoking user-defined hash or equality methods.
288
+ const buckets = new Map<number, Array<Cause.Reason<E>>>()
289
+ const out: Array<Cause.Reason<E>> = []
290
+ for (const reason of self.concat(that)) {
291
+ const hash = Hash.hash(reason)
292
+ const bucket = buckets.get(hash)
293
+ if (bucket === undefined) {
294
+ buckets.set(hash, [reason])
295
+ } else if (bucket.some((previous) => Equal.equals(previous, reason))) {
296
+ continue
297
+ } else {
298
+ bucket.push(reason)
299
+ }
300
+ out.push(reason)
301
+ }
302
+ return out
303
+ }
304
+
305
+ /** @internal */
306
+ export const causeCombine: {
307
+ <E2>(that: Cause.Cause<E2>): <E>(self: Cause.Cause<E>) => Cause.Cause<E | E2>
308
+ <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2>
309
+ } = dual(
310
+ 2,
311
+ <E, E2>(self: Cause.Cause<E>, that: Cause.Cause<E2>): Cause.Cause<E | E2> => {
312
+ if (self.reasons.length === 0) {
313
+ return that as Cause.Cause<E | E2>
314
+ } else if (that.reasons.length === 0) {
315
+ return self as Cause.Cause<E | E2>
316
+ }
317
+ const newCause = new CauseImpl<E | E2>(
318
+ dedupeReasons<E | E2>(self.reasons, that.reasons)
319
+ )
320
+ return Equal.equals(self, newCause) ? self : newCause
321
+ }
322
+ )
323
+
282
324
  /** @internal */
283
325
  export const causeEmpty: Cause.Cause<never> = new CauseImpl([])
284
326
 
@@ -546,8 +588,20 @@ export const exitFailCause: <E>(cause: Cause.Cause<E>) => Exit.Exit<never, E> =
546
588
  annotated = true
547
589
  }
548
590
  let cont = fiber.getCont(contE)
549
- while (fiber.interruptible && fiber._interruptedCause && cont) {
550
- cont = fiber.getCont(contE)
591
+ const interruptedCause = fiber._interruptedCause
592
+ if (interruptedCause && fiber.interruptible) {
593
+ // Drop typed failures only when interruption skips a recovery handler.
594
+ // Interruptibility-restoration continuations have no identifier.
595
+ let skippedHandler = false
596
+ while (cont && fiber.interruptible) {
597
+ skippedHandler ||= identifier in cont
598
+ cont = fiber.getCont(contE)
599
+ }
600
+ if (skippedHandler) {
601
+ cause = causeFromReasons(cause.reasons.filter((reason) => reason._tag !== "Fail"))
602
+ }
603
+ cause = causeCombine(cause, interruptedCause)
604
+ annotated = true
551
605
  }
552
606
  return cont
553
607
  ? cont[contE](cause, fiber, annotated ? undefined : this)
@@ -575,7 +575,9 @@ export const toDate = (self: DateTime.DateTime): Date => {
575
575
  Number(parts[3].value),
576
576
  Number(parts[4].value),
577
577
  Number(parts[5].value),
578
- Number(parts[6].value)
578
+ parts[6]?.type === "fractionalSecond"
579
+ ? Number(parts[6].value)
580
+ : ((self.epochMilliseconds % 1000) + 1000) % 1000
579
581
  )
580
582
  self.adjustedEpochMilliseconds = date.getTime()
581
583
  return date