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
package/src/Pool.ts CHANGED
@@ -19,7 +19,6 @@ import * as Fiber from "./Fiber.ts"
19
19
  import { constant, dual, identity } from "./Function.ts"
20
20
  import * as core from "./internal/core.ts"
21
21
  import * as internal from "./internal/effect.ts"
22
- import * as Iterable from "./Iterable.ts"
23
22
  import { type Pipeable, pipeArguments } from "./Pipeable.ts"
24
23
  import { hasProperty } from "./Predicate.ts"
25
24
  import * as Queue from "./Queue.ts"
@@ -31,10 +30,12 @@ const TypeId = "~effect/Pool"
31
30
 
32
31
  const Acquire = Symbol()
33
32
  const AcquireContext = Symbol()
33
+ const PoolClock = Symbol()
34
34
 
35
35
  interface PoolImpl<A, E> extends Pool<A, E> {
36
36
  readonly [Acquire]: Effect.Effect<A, E, Scope.Scope>
37
37
  readonly [AcquireContext]: Context.Context<Scope.Scope>
38
+ readonly [PoolClock]: Clock
38
39
  }
39
40
 
40
41
  /**
@@ -119,7 +120,6 @@ export interface State<A, E> {
119
120
  isShuttingDown: boolean
120
121
  usage: number
121
122
  readonly resizeSemaphore: Semaphore.Semaphore
122
- // Insertion order determines usage-TTL retirement order; reclaimed items move to the back.
123
123
  readonly items: Set<PoolItem<A, E>>
124
124
  availableHead: PoolItem<A, E> | undefined
125
125
  availableTail: PoolItem<A, E> | undefined
@@ -139,8 +139,8 @@ export interface State<A, E> {
139
139
  * **Details**
140
140
  *
141
141
  * Each item stores the acquisition `Exit`, its finalizer, the current
142
- * reference count, and whether automatic reclaiming has been disabled because
143
- * the item was invalidated.
142
+ * reference count, when it last became idle, and whether automatic reclaiming
143
+ * has been disabled because the item was invalidated.
144
144
  *
145
145
  * @see {@link Strategy} for the custom strategy callbacks that receive and return pool items
146
146
  * @see {@link State} for the runtime sets that store active, available, and invalidated pool items
@@ -152,6 +152,8 @@ export interface PoolItem<A, E> {
152
152
  readonly exit: Exit.Exit<A, E>
153
153
  finalizer: Effect.Effect<void>
154
154
  refCount: number
155
+ /** Clock time at which the item was acquired or its last borrower released it. */
156
+ idleSince: number
155
157
  disableReclaim: boolean
156
158
  isAvailable: boolean
157
159
  availablePrevious: PoolItem<A, E> | undefined
@@ -257,9 +259,11 @@ export const make = <A, E, R>(options: {
257
259
  * pool implementation. A value of `1` waits until existing items are fully
258
260
  * utilized before creating more items.
259
261
  *
260
- * `timeToLiveStrategy` controls when excess items expire: `"creation"` measures
261
- * from item creation, while `"usage"` measures from pool usage. The default is
262
- * `"usage"`.
262
+ * `timeToLiveStrategy` defaults to `"usage"`, which expires idle, unreserved
263
+ * excess items after `timeToLive` since their last borrower released them
264
+ * (or acquisition if never borrowed). It checks once per `timeToLive`, so
265
+ * retirement can take up to twice that duration. `"creation"` measures from
266
+ * item creation instead.
263
267
  *
264
268
  * **Example** (Creating a connection pool)
265
269
  *
@@ -374,6 +378,7 @@ export const makeWithStrategy = <A, E, R>(options: {
374
378
  [TypeId]: TypeId,
375
379
  [Acquire]: options.acquire as Effect.Effect<A, E, Scope.Scope>,
376
380
  [AcquireContext]: services as Context.Context<Scope.Scope>,
381
+ [PoolClock]: yield* Clock,
377
382
  config,
378
383
  state,
379
384
  pipe() {
@@ -676,6 +681,9 @@ const releaseItem = <A, E>(self: Pool<A, E>, item: PoolItem<A, E>): Effect.Effec
676
681
  const state = self.state
677
682
  item.refCount--
678
683
  state.usage--
684
+ if (item.refCount === 0) {
685
+ item.idleSince = (self as PoolImpl<A, E>)[PoolClock].currentTimeMillisUnsafe()
686
+ }
679
687
  if (state.invalidated.has(item)) {
680
688
  return invalidatePoolItem(self, item)
681
689
  }
@@ -1014,6 +1022,7 @@ const allocate = <A, E>(self: Pool<A, E>): Effect.Effect<PoolItem<A, E>> =>
1014
1022
  exit,
1015
1023
  finalizer: Effect.catchCause(Scope.close(scope, exit), reportUnhandledError),
1016
1024
  refCount: 0,
1025
+ idleSince: impl[PoolClock].currentTimeMillisUnsafe(),
1017
1026
  disableReclaim: false,
1018
1027
  isAvailable: false,
1019
1028
  availablePrevious: undefined,
@@ -1110,19 +1119,21 @@ const strategyCreationTTL = Effect.fnUntraced(function*<A, E>(ttl: Duration.Inpu
1110
1119
  })
1111
1120
  })
1112
1121
 
1113
- const strategyUsageTTL = <A, E>(ttl: Duration.Input): Effect.Effect<Strategy<A, E>> =>
1114
- Effect.succeed<Strategy<A, E>>({
1122
+ const strategyUsageTTL = Effect.fnUntraced(function*<A, E>(ttl: Duration.Input) {
1123
+ const clock = yield* Clock
1124
+ const ttlMillis = Duration.toMillis(Duration.fromInputUnsafe(ttl))
1125
+ return identity<Strategy<A, E>>({
1115
1126
  run: (pool) => {
1116
- // `state.items` iterates in insertion order, so its oldest live entry is
1117
- // the next to retire. Using it directly means an item stops being
1118
- // referenced by this strategy as soon as it leaves the pool.
1119
1127
  const process: Effect.Effect<void> = Effect.suspend(() => {
1120
1128
  if (activeSize(pool) <= targetSize(pool)) return Effect.void
1129
+ const expired = clock.currentTimeMillisUnsafe() - ttlMillis
1130
+ let oldest: PoolItem<A, E> | undefined
1121
1131
  for (const item of pool.state.items) {
1122
- if (pool.state.invalidated.has(item)) continue
1123
- return Effect.flatMap(invalidatePoolItem(pool, item), () => process)
1132
+ if (item.refCount > 0 || item.idleSince > expired) continue
1133
+ if (pool.state.invalidated.has(item) || reservations.has(item)) continue
1134
+ if (oldest === undefined || item.idleSince < oldest.idleSince) oldest = item
1124
1135
  }
1125
- return Effect.void
1136
+ return oldest === undefined ? Effect.void : Effect.flatMap(invalidatePoolItem(pool, oldest), () => process)
1126
1137
  })
1127
1138
  return process.pipe(
1128
1139
  Effect.delay(ttl),
@@ -1130,28 +1141,9 @@ const strategyUsageTTL = <A, E>(ttl: Duration.Input): Effect.Effect<Strategy<A,
1130
1141
  )
1131
1142
  },
1132
1143
  onAcquire: (_) => Effect.void,
1133
- reclaim(pool) {
1134
- return Effect.suspend((): Effect.Effect<PoolItem<A, E> | undefined> => {
1135
- if (pool.state.invalidated.size === 0) {
1136
- return Effect.undefined
1137
- }
1138
- const item = Iterable.head(
1139
- Iterable.filter(pool.state.invalidated, (item) => !item.disableReclaim && !reservations.has(item))
1140
- )
1141
- if (item._tag === "None") {
1142
- return Effect.undefined
1143
- }
1144
- pool.state.invalidated.delete(item.value)
1145
- // Re-adding moves the reclaimed item to the back of the retirement order.
1146
- pool.state.items.delete(item.value)
1147
- pool.state.items.add(item.value)
1148
- if (item.value.refCount < pool.config.concurrency) {
1149
- addAvailable(pool, item.value)
1150
- }
1151
- return Effect.succeed(item.value)
1152
- })
1153
- }
1144
+ reclaim: (_) => Effect.undefined
1154
1145
  })
1146
+ })
1155
1147
 
1156
1148
  const reportUnhandledError = <E>(cause: Cause.Cause<E>) =>
1157
1149
  Effect.withFiber<void>((fiber) => {
package/src/Queue.ts CHANGED
@@ -344,13 +344,13 @@ export declare namespace Queue {
344
344
  export type State<A, E> =
345
345
  | {
346
346
  readonly _tag: "Open"
347
- readonly takers: Set<(_: Effect<void, E>) => void>
347
+ readonly takers: Set<Taker<E>>
348
348
  readonly offers: Set<OfferEntry<A>>
349
349
  readonly awaiters: Set<(_: Effect<void, E>) => void>
350
350
  }
351
351
  | {
352
352
  readonly _tag: "Closing"
353
- readonly takers: Set<(_: Effect<void, E>) => void>
353
+ readonly takers: Set<Taker<E>>
354
354
  readonly offers: Set<OfferEntry<A>>
355
355
  readonly awaiters: Set<(_: Effect<void, E>) => void>
356
356
  readonly exit: Failure<never, E>
@@ -384,6 +384,24 @@ export declare namespace Queue {
384
384
  readonly message: A
385
385
  readonly resume: (_: Effect<boolean>) => void
386
386
  }
387
+
388
+ /**
389
+ * Represents a suspended take waiting for the queue to become readable.
390
+ *
391
+ * **Details**
392
+ *
393
+ * `ready` reports whether the take can now complete, so a batch take is
394
+ * only resumed once its minimum is available. `resume` completes the
395
+ * suspended take, with `void` to retry or with a failure exit when the queue
396
+ * is done.
397
+ *
398
+ * @category models
399
+ * @since 4.0.0
400
+ */
401
+ export interface Taker<E> {
402
+ readonly ready: () => boolean
403
+ readonly resume: (_: Effect<void, E>) => void
404
+ }
387
405
  }
388
406
 
389
407
  const variance = {
@@ -2427,8 +2445,9 @@ const releaseTakers = <A, E>(self: Enqueue<A, E>) => {
2427
2445
  return
2428
2446
  }
2429
2447
  for (const taker of self.state.takers) {
2448
+ if (!taker.ready()) continue
2430
2449
  self.state.takers.delete(taker)
2431
- taker(internalEffect.exitVoid)
2450
+ taker.resume(internalEffect.exitVoid)
2432
2451
  if (self.messages.length === 0) {
2433
2452
  break
2434
2453
  }
@@ -2478,9 +2497,10 @@ const awaitTake = <A, E>(self: Dequeue<A, E>, ready: () => boolean) =>
2478
2497
  internalEffect.callback<void, E>((resume) => {
2479
2498
  if (self.state._tag === "Done") return resume(self.state.exit)
2480
2499
  if (ready()) return resume(internalEffect.exitVoid)
2481
- self.state.takers.add(resume)
2500
+ const taker = { ready, resume }
2501
+ self.state.takers.add(taker)
2482
2502
  return internalEffect.sync(() => {
2483
- if (self.state._tag !== "Done") self.state.takers.delete(resume)
2503
+ if (self.state._tag !== "Done") self.state.takers.delete(taker)
2484
2504
  })
2485
2505
  })
2486
2506
 
@@ -2577,7 +2597,7 @@ const finalize = <A, E>(self: Enqueue<A, E> | Dequeue<A, E>, exit: Failure<never
2577
2597
  const openState = self.state
2578
2598
  self.state = { _tag: "Done", exit }
2579
2599
  for (const taker of openState.takers) {
2580
- taker(exit)
2600
+ taker.resume(exit)
2581
2601
  }
2582
2602
  openState.takers.clear()
2583
2603
  for (const awaiter of openState.awaiters) {
package/src/RcMap.ts CHANGED
@@ -566,10 +566,16 @@ export const get: {
566
566
  context.set(key, value)
567
567
  })
568
568
  context.set(Scope.Scope.key, entry.scope)
569
+ const lookupContext: Context.Context<Scope.Scope> = Context.makeUnsafe(context)
569
570
  Effect.suspend(() => self.lookup(key)).pipe(
570
- Effect.runForkWith(Context.makeUnsafe(context)),
571
+ Effect.runForkWith(lookupContext),
571
572
  Fiber.runIn(entry.scope)
572
- ).addObserver((exit) => Deferred.doneUnsafe(entry.deferred, exit))
573
+ ).addObserver((exit) => {
574
+ // Interruption is abandonment, so the entry is dropped and the next
575
+ // get starts a fresh lookup.
576
+ if (Exit.hasInterrupts(exit)) removeInterrupted(self, key, entry, lookupContext)
577
+ Deferred.doneUnsafe(entry.deferred, exit)
578
+ })
573
579
  }
574
580
  const scope = Context.getUnsafe(parent.context, Scope.Scope)
575
581
  return Scope.addFinalizer(scope, entry.finalizer).pipe(
@@ -733,6 +739,24 @@ export const getOption: {
733
739
  })
734
740
  )
735
741
 
742
+ const removeInterrupted = <K, A, E>(
743
+ self: RcMap<K, A, E>,
744
+ key: K,
745
+ entry: State.Entry<A, E>,
746
+ context: Context.Context<Scope.Scope>
747
+ ) => {
748
+ if (self.state._tag === "Closed") return
749
+ const o = MutableHashMap.get(self.state.map, key)
750
+ if (o._tag === "None" || o.value !== entry) return
751
+ MutableHashMap.remove(self.state.map, key)
752
+ // Borrowers close the entry on release; an idle entry has none left.
753
+ if (entry.refCount > 0) return
754
+ closeEntry(entry).pipe(
755
+ Effect.runForkWith(context),
756
+ Fiber.runIn(self.scope)
757
+ )
758
+ }
759
+
736
760
  const closeEntry = <A, E>(entry: State.Entry<A, E>) =>
737
761
  entry.fiber
738
762
  ? Fiber.interrupt(entry.fiber).pipe(Effect.andThen(Scope.close(entry.scope, Exit.void)))
package/src/Record.ts CHANGED
@@ -1372,8 +1372,8 @@ export const getSuccesses = <K extends string, A, E>(
1372
1372
  *
1373
1373
  * **Details**
1374
1374
  *
1375
- * Failure values are collected in the left record, and success values are
1376
- * collected in the right record, preserving the original keys.
1375
+ * Success values are collected in the first record, and failure values are
1376
+ * collected in the second record, preserving the original keys.
1377
1377
  *
1378
1378
  * **Example** (Partitioning with Result)
1379
1379
  *
@@ -1382,7 +1382,7 @@ export const getSuccesses = <K extends string, A, E>(
1382
1382
  *
1383
1383
  * const x = { a: 1, b: 2, c: 3 }
1384
1384
  * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n))
1385
- * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }]
1385
+ * Record.partition(x, f) // => [{ b: 2 }, { a: 1, c: 3 }]
1386
1386
  * ```
1387
1387
  *
1388
1388
  * @category filtering
@@ -1395,8 +1395,8 @@ export const partition: {
1395
1395
  *
1396
1396
  * **Details**
1397
1397
  *
1398
- * Failure values are collected in the left record, and success values are
1399
- * collected in the right record, preserving the original keys.
1398
+ * Success values are collected in the first record, and failure values are
1399
+ * collected in the second record, preserving the original keys.
1400
1400
  *
1401
1401
  * **Example** (Partitioning with Result)
1402
1402
  *
@@ -1405,7 +1405,7 @@ export const partition: {
1405
1405
  *
1406
1406
  * const x = { a: 1, b: 2, c: 3 }
1407
1407
  * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n))
1408
- * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }]
1408
+ * Record.partition(x, f) // => [{ b: 2 }, { a: 1, c: 3 }]
1409
1409
  * ```
1410
1410
  *
1411
1411
  * @category filtering
@@ -1413,15 +1413,15 @@ export const partition: {
1413
1413
  */
1414
1414
  <K extends string, A, B, C>(f: (input: A, key: K) => Result<C, B>): (
1415
1415
  self: ReadonlyRecord<K, A>
1416
- ) => [left: Record<ReadonlyRecord.NonLiteralKey<K>, B>, right: Record<ReadonlyRecord.NonLiteralKey<K>, C>]
1416
+ ) => [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>]
1417
1417
  /**
1418
1418
  * Applies a function to each record entry and partitions the returned `Result`
1419
1419
  * values into two records.
1420
1420
  *
1421
1421
  * **Details**
1422
1422
  *
1423
- * Failure values are collected in the left record, and success values are
1424
- * collected in the right record, preserving the original keys.
1423
+ * Success values are collected in the first record, and failure values are
1424
+ * collected in the second record, preserving the original keys.
1425
1425
  *
1426
1426
  * **Example** (Partitioning with Result)
1427
1427
  *
@@ -1430,43 +1430,43 @@ export const partition: {
1430
1430
  *
1431
1431
  * const x = { a: 1, b: 2, c: 3 }
1432
1432
  * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n))
1433
- * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }]
1433
+ * Record.partition(x, f) // => [{ b: 2 }, { a: 1, c: 3 }]
1434
1434
  * ```
1435
1435
  *
1436
1436
  * @category filtering
1437
1437
  * @since 2.0.0
1438
1438
  */
1439
- <K extends string, A, B, C>(self: ReadonlyRecord<K, A>, f: (input: A, key: K) => Result<C, B>): [left: Record<ReadonlyRecord.NonLiteralKey<K>, B>, right: Record<ReadonlyRecord.NonLiteralKey<K>, C>]
1439
+ <K extends string, A, B, C>(self: ReadonlyRecord<K, A>, f: (input: A, key: K) => Result<C, B>): [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>]
1440
1440
  } = dual(
1441
1441
  2,
1442
1442
  <K extends string, A, B, C>(
1443
1443
  self: ReadonlyRecord<K, A>,
1444
1444
  f: (input: A, key: K) => Result<C, B>
1445
- ): [left: Record<ReadonlyRecord.NonLiteralKey<K>, B>, right: Record<ReadonlyRecord.NonLiteralKey<K>, C>] => {
1446
- const left: Record<string, B> = empty()
1447
- const right: Record<string, C> = empty()
1445
+ ): [passes: Record<ReadonlyRecord.NonLiteralKey<K>, C>, fails: Record<ReadonlyRecord.NonLiteralKey<K>, B>] => {
1446
+ const passes: Record<string, C> = empty()
1447
+ const fails: Record<string, B> = empty()
1448
1448
  for (const key of keys(self)) {
1449
1449
  const e = f(self[key], key)
1450
1450
  if (R.isFailure(e)) {
1451
- InternalRecord.assignProperty(left, key, e.failure)
1451
+ InternalRecord.assignProperty(fails, key, e.failure)
1452
1452
  } else {
1453
- InternalRecord.assignProperty(right, key, e.success)
1453
+ InternalRecord.assignProperty(passes, key, e.success)
1454
1454
  }
1455
1455
  }
1456
- return [left, right]
1456
+ return [passes, fails]
1457
1457
  }
1458
1458
  )
1459
1459
 
1460
1460
  /**
1461
1461
  * Partitions a record of `Result` values into two separate records,
1462
- * one with the `Err` values and one with the `Ok` values.
1462
+ * one with the success values and one with the failure values.
1463
1463
  *
1464
1464
  * **Example** (Separating Result values)
1465
1465
  *
1466
1466
  * ```ts import.meta.vitest
1467
1467
  * import { Record, Result } from "effect"
1468
1468
  *
1469
- * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ a: "e" }, { b: 1 }]
1469
+ * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ b: 1 }, { a: "e" }]
1470
1470
  * ```
1471
1471
  *
1472
1472
  * @category filtering
@@ -1474,7 +1474,7 @@ export const partition: {
1474
1474
  */
1475
1475
  export const separate: <K extends string, A, B>(
1476
1476
  self: ReadonlyRecord<K, Result<B, A>>
1477
- ) => [Record<ReadonlyRecord.NonLiteralKey<K>, A>, Record<ReadonlyRecord.NonLiteralKey<K>, B>] = partition(identity)
1477
+ ) => [Record<ReadonlyRecord.NonLiteralKey<K>, B>, Record<ReadonlyRecord.NonLiteralKey<K>, A>] = partition(identity)
1478
1478
 
1479
1479
  /**
1480
1480
  * Retrieves the keys of a given record as an array.
package/src/Schema.ts CHANGED
@@ -77,7 +77,7 @@ import * as SchemaTransformation from "./SchemaTransformation.ts"
77
77
  import type { StandardJSONSchemaV1, StandardSchemaV1 } from "./StandardSchema.ts"
78
78
  import type { Assign, Lambda, Mutable, Simplify } from "./Struct.ts"
79
79
  import * as Struct_ from "./Struct.ts"
80
- import type { RequiredKeys, UnionToIntersection } from "./Types.ts"
80
+ import type { IsUnion, RequiredKeys } from "./Types.ts"
81
81
  import type { Unify } from "./Unify.ts"
82
82
 
83
83
  const TypeId = InternalMake.TypeId
@@ -5057,14 +5057,19 @@ export function refine<S extends Constraint, T extends S["Type"]>(
5057
5057
  return (schema: S): refine<T, S> =>
5058
5058
  make(SchemaAST.appendChecks(schema.ast, [SchemaAST.makeFilterByGuard(refinement, annotations)]), { schema })
5059
5059
  }
5060
- type DistributeBrands<B> = UnionToIntersection<B extends infer U extends string ? Brand.Brand<U> : never>
5060
+ // A concrete key requires a property; broad and open template keys do not.
5061
+ type EnsureSingleBrandKey<K extends PropertyKey> = IsUnion<K> extends false
5062
+ ? {} extends Record<K, unknown> ? never : unknown
5063
+ : never
5064
+ // Collect all keys, including those not shared by every union member.
5065
+ type FromBrandKeys<A extends Brand.Brand<any>> = A extends unknown ? Brand.Brand.Keys<A> : never
5061
5066
  /**
5062
5067
  * Type-level representation returned by {@link brand}.
5063
5068
  *
5064
5069
  * @category branding
5065
5070
  * @since 3.10.0
5066
5071
  */
5067
- export interface brand<S extends Constraint, B> extends
5072
+ export interface brand<S extends Constraint, B extends string> extends
5068
5073
  BottomLazy<
5069
5074
  S["ast"],
5070
5075
  brand<S, B>,
@@ -5076,19 +5081,19 @@ export interface brand<S extends Constraint, B> extends
5076
5081
  S["~encoded.optionality"]
5077
5082
  >
5078
5083
  {
5079
- readonly "Type": S["Type"] & DistributeBrands<B>
5084
+ readonly "Type": S["Type"] & Brand.Brand<B>
5080
5085
  readonly "Encoded": S["Encoded"]
5081
5086
  readonly "DecodingServices": S["DecodingServices"]
5082
5087
  readonly "EncodingServices": S["EncodingServices"]
5083
5088
  readonly "~type.make.in": S["~type.make.in"]
5084
- readonly "~type.make": S["Type"] & DistributeBrands<B>
5085
- readonly "Iso": S["Type"] & DistributeBrands<B>
5089
+ readonly "~type.make": S["Type"] & Brand.Brand<B>
5090
+ readonly "Iso": S["Type"] & Brand.Brand<B>
5086
5091
  readonly schema: S
5087
5092
  readonly identifier: string
5088
5093
  }
5089
5094
  /**
5090
- * Adds a nominal brand to a schema, intersecting the output type with
5091
- * `Brand.Brand<B>` to prevent accidental mixing of structurally identical types.
5095
+ * Intersects a schema's output type with `Brand.Brand<B>` to prevent accidental
5096
+ * mixing of structurally identical types.
5092
5097
  *
5093
5098
  * **When to use**
5094
5099
  *
@@ -5097,31 +5102,52 @@ export interface brand<S extends Constraint, B> extends
5097
5102
  *
5098
5103
  * **Gotchas**
5099
5104
  *
5100
- * `brand` adds brand metadata and narrows the TypeScript output type, but it
5101
- * does not add runtime checks.
5105
+ * - `identifier` must be a single concrete string literal. Widened strings,
5106
+ * unions, and open template literal types are rejected.
5107
+ * - `brand` only narrows the TypeScript output type. It does not change the
5108
+ * schema's runtime AST or add runtime checks.
5109
+ * - Schema representations and generated schema code omit the brand. Reapply
5110
+ * `brand` after rebuilding or generating a schema when the nominal type is
5111
+ * still required.
5102
5112
  *
5103
- * @see {@link fromBrand} for applying a Brand constructor's checks along with the brand tag
5113
+ * @see {@link fromBrand} for applying a Brand constructor's checks along with its branded type
5104
5114
  *
5105
5115
  * @category branding
5106
5116
  * @since 3.10.0
5107
5117
  */
5108
- export function brand<B extends string>(identifier: B) {
5118
+ export function brand<B extends string>(identifier: B & EnsureSingleBrandKey<B>) {
5109
5119
  return <S extends ConstraintRebuildable>(schema: S): brand<S["Rebuild"], B> =>
5110
- make(SchemaAST.brand(schema.ast, identifier), { schema, identifier })
5120
+ make(schema.ast, { schema, identifier })
5111
5121
  }
5112
5122
  /**
5113
5123
  * Creates a branded schema from a {@link Brand.Constructor}, applying the
5114
- * constructor's checks and brand tag to the underlying schema.
5124
+ * constructor's checks and branded type to the underlying schema.
5125
+ *
5126
+ * **When to use**
5127
+ *
5128
+ * Use to reuse the checks from a constructor with one concrete brand key.
5129
+ *
5130
+ * **Gotchas**
5131
+ *
5132
+ * `identifier` must match the constructor's only brand key. Apply `fromBrand`
5133
+ * repeatedly to compose distinct brands, and use {@link Union} to represent
5134
+ * alternatives.
5135
+ *
5136
+ * @see {@link brand} for adding a brand without constructor checks
5115
5137
  *
5116
5138
  * @category branding
5117
5139
  * @since 3.10.0
5118
5140
  */
5119
- export function fromBrand<A extends Brand.Brand<any>>(identifier: string, ctor: Brand.Constructor<A>) {
5141
+ export function fromBrand<A extends Brand.Brand<any>>(
5142
+ identifier: Brand.Brand.Keys<A> & string,
5143
+ ctor: Brand.Constructor<A> & EnsureSingleBrandKey<FromBrandKeys<A>>
5144
+ ) {
5145
+ type B = Brand.Brand.Keys<A> & string
5120
5146
  return <S extends Top & { readonly "Type": Brand.Brand.Unbranded<A> }>(
5121
5147
  self: S
5122
- ): brand<S["Rebuild"], Brand.Brand.Keys<A>> => {
5123
- return (ctor.checks ? self.check(...ctor.checks) : self).pipe(brand(identifier))
5124
- }
5148
+ ): brand<S["Rebuild"], B> =>
5149
+ // The constructor already guarantees a single concrete brand key.
5150
+ (ctor.checks ? self.check(...ctor.checks) : self).pipe(brand<B>(identifier as B & EnsureSingleBrandKey<B>))
5125
5151
  }
5126
5152
  /**
5127
5153
  * Type-level representation returned by {@link middlewareDecoding}.
@@ -15695,6 +15721,8 @@ export function toEquivalence<T>(schema: Schema<T>): Equivalence.Equivalence<T>
15695
15721
  * Use {@link toType} before this function to represent the type side instead.
15696
15722
  * The optional reference policy controls which candidates are extracted into the document's reference table. By
15697
15723
  * default, only candidates with a resolved identifier become references; recursive candidates always require one.
15724
+ * TypeScript-only distinctions such as those added by {@link brand} are not
15725
+ * part of the schema AST and are therefore omitted.
15698
15726
  *
15699
15727
  * @see {@link SchemaRepresentation.toRepresentation} for converting a `SchemaAST.AST` directly
15700
15728
  *
@@ -16577,8 +16605,8 @@ export declare namespace Annotations {
16577
16605
  }
16578
16606
  /**
16579
16607
  * Base annotations shared by all composite schema nodes. Extends
16580
- * {@link Documentation} with error messages, branding, and arbitrary
16581
- * generation hooks. {@link Declaration} and other annotation
16608
+ * {@link Documentation} with error messages and arbitrary generation hooks.
16609
+ * {@link Declaration} and other annotation
16582
16610
  * interfaces build on top of this.
16583
16611
  *
16584
16612
  * @category models
@@ -16616,10 +16644,6 @@ export declare namespace Annotations {
16616
16644
  * filter/refinement instead.
16617
16645
  */
16618
16646
  readonly identifier?: string | undefined
16619
- /**
16620
- * Accumulated brands when multiple brands are added with `Schema.brand`.
16621
- */
16622
- readonly brands?: ReadonlyArray<string> | undefined
16623
16647
  }
16624
16648
  /**
16625
16649
  * Helpers for projecting declaration type-parameter schemas into decoded or