effect 4.0.2 → 4.0.4

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 (389) hide show
  1. package/AGENTS.md +24 -0
  2. package/CLAUDE.md +24 -0
  3. package/ai-docs/package.json +2 -2
  4. package/ai-docs/src/52_graphql/10_basics.ts +111 -0
  5. package/ai-docs/src/52_graphql/20_testing.ts +45 -0
  6. package/ai-docs/src/52_graphql/30_batching.ts +116 -0
  7. package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
  8. package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
  9. package/ai-docs/src/52_graphql/index.md +6 -0
  10. package/dist/Context.d.ts.map +1 -1
  11. package/dist/Context.js +12 -3
  12. package/dist/Context.js.map +1 -1
  13. package/dist/Crypto.d.ts +33 -5
  14. package/dist/Crypto.d.ts.map +1 -1
  15. package/dist/Crypto.js +8 -2
  16. package/dist/Crypto.js.map +1 -1
  17. package/dist/DateTime.d.ts +9 -6
  18. package/dist/DateTime.d.ts.map +1 -1
  19. package/dist/DateTime.js +9 -6
  20. package/dist/DateTime.js.map +1 -1
  21. package/dist/Duration.d.ts +6 -6
  22. package/dist/Duration.d.ts.map +1 -1
  23. package/dist/Duration.js +27 -8
  24. package/dist/Duration.js.map +1 -1
  25. package/dist/FileSystem.d.ts +44 -2
  26. package/dist/FileSystem.d.ts.map +1 -1
  27. package/dist/FileSystem.js.map +1 -1
  28. package/dist/RequestResolver.d.ts.map +1 -1
  29. package/dist/RequestResolver.js +4 -10
  30. package/dist/RequestResolver.js.map +1 -1
  31. package/dist/Scheduler.d.ts +9 -0
  32. package/dist/Scheduler.d.ts.map +1 -1
  33. package/dist/Scheduler.js +9 -0
  34. package/dist/Scheduler.js.map +1 -1
  35. package/dist/Schema.d.ts +481 -1
  36. package/dist/Schema.d.ts.map +1 -1
  37. package/dist/Schema.js +362 -4
  38. package/dist/Schema.js.map +1 -1
  39. package/dist/SchemaGetter.d.ts +4 -0
  40. package/dist/SchemaGetter.d.ts.map +1 -1
  41. package/dist/SchemaGetter.js +52 -33
  42. package/dist/SchemaGetter.js.map +1 -1
  43. package/dist/Sink.d.ts.map +1 -1
  44. package/dist/Sink.js +38 -17
  45. package/dist/Sink.js.map +1 -1
  46. package/dist/Stdio.d.ts +7 -1
  47. package/dist/Stdio.d.ts.map +1 -1
  48. package/dist/Stdio.js +1 -0
  49. package/dist/Stdio.js.map +1 -1
  50. package/dist/Stream.d.ts +22 -11
  51. package/dist/Stream.d.ts.map +1 -1
  52. package/dist/Stream.js +21 -6
  53. package/dist/Stream.js.map +1 -1
  54. package/dist/Utils.js +8 -3
  55. package/dist/Utils.js.map +1 -1
  56. package/dist/ai/AnthropicStructuredOutput.js +0 -2
  57. package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
  58. package/dist/ai/Chat.d.ts +2 -2
  59. package/dist/ai/Chat.d.ts.map +1 -1
  60. package/dist/ai/LanguageModel.d.ts +38 -7
  61. package/dist/ai/LanguageModel.d.ts.map +1 -1
  62. package/dist/ai/LanguageModel.js +25 -7
  63. package/dist/ai/LanguageModel.js.map +1 -1
  64. package/dist/ai/McpServer.d.ts +61 -0
  65. package/dist/ai/McpServer.d.ts.map +1 -1
  66. package/dist/ai/McpServer.js +196 -44
  67. package/dist/ai/McpServer.js.map +1 -1
  68. package/dist/ai/Prompt.d.ts.map +1 -1
  69. package/dist/ai/Prompt.js +28 -5
  70. package/dist/ai/Prompt.js.map +1 -1
  71. package/dist/ai/Response.d.ts +4 -3
  72. package/dist/ai/Response.d.ts.map +1 -1
  73. package/dist/ai/Response.js.map +1 -1
  74. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
  75. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  76. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  77. package/dist/ai/internal/mcpRuntime.js +1 -0
  78. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  79. package/dist/cli/CliOutput.d.ts +27 -0
  80. package/dist/cli/CliOutput.d.ts.map +1 -1
  81. package/dist/cli/CliOutput.js +33 -10
  82. package/dist/cli/CliOutput.js.map +1 -1
  83. package/dist/cli/Command.d.ts.map +1 -1
  84. package/dist/cli/Command.js +6 -3
  85. package/dist/cli/Command.js.map +1 -1
  86. package/dist/cli/Completions.d.ts +3 -3
  87. package/dist/cli/Completions.d.ts.map +1 -1
  88. package/dist/cli/Completions.js +8 -5
  89. package/dist/cli/Completions.js.map +1 -1
  90. package/dist/cli/GlobalFlag.d.ts +5 -4
  91. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  92. package/dist/cli/GlobalFlag.js +3 -3
  93. package/dist/cli/GlobalFlag.js.map +1 -1
  94. package/dist/cli/Prompt.d.ts +17 -1
  95. package/dist/cli/Prompt.d.ts.map +1 -1
  96. package/dist/cli/Prompt.js +119 -114
  97. package/dist/cli/Prompt.js.map +1 -1
  98. package/dist/cli/internal/ansi.d.ts +0 -1
  99. package/dist/cli/internal/ansi.d.ts.map +1 -1
  100. package/dist/cli/internal/ansi.js +20 -3
  101. package/dist/cli/internal/ansi.js.map +1 -1
  102. package/dist/cli/internal/completions/powershell.d.ts +2 -0
  103. package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
  104. package/dist/cli/internal/completions/powershell.js +273 -0
  105. package/dist/cli/internal/completions/powershell.js.map +1 -0
  106. package/dist/cli/internal/completions/zsh.js +3 -2
  107. package/dist/cli/internal/completions/zsh.js.map +1 -1
  108. package/dist/cli/internal/wizard.d.ts +3 -3
  109. package/dist/cli/internal/wizard.d.ts.map +1 -1
  110. package/dist/cli/internal/wizard.js +21 -18
  111. package/dist/cli/internal/wizard.js.map +1 -1
  112. package/dist/cluster/ClusterSchema.d.ts +28 -0
  113. package/dist/cluster/ClusterSchema.d.ts.map +1 -1
  114. package/dist/cluster/ClusterSchema.js +30 -0
  115. package/dist/cluster/ClusterSchema.js.map +1 -1
  116. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  117. package/dist/cluster/ClusterWorkflowEngine.js +56 -26
  118. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  119. package/dist/cluster/MessageStorage.d.ts +18 -3
  120. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  121. package/dist/cluster/MessageStorage.js +31 -11
  122. package/dist/cluster/MessageStorage.js.map +1 -1
  123. package/dist/cluster/Runners.d.ts.map +1 -1
  124. package/dist/cluster/Runners.js +8 -2
  125. package/dist/cluster/Runners.js.map +1 -1
  126. package/dist/cluster/Sharding.d.ts.map +1 -1
  127. package/dist/cluster/Sharding.js +187 -66
  128. package/dist/cluster/Sharding.js.map +1 -1
  129. package/dist/cluster/SqlMessageStorage.d.ts +59 -19
  130. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  131. package/dist/cluster/SqlMessageStorage.js +84 -55
  132. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  133. package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
  134. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  135. package/dist/cluster/SqlRunnerStorage.js +191 -113
  136. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  137. package/dist/cluster/internal/entityManager.js +104 -37
  138. package/dist/cluster/internal/entityManager.js.map +1 -1
  139. package/dist/encoding/Sse.d.ts +6 -0
  140. package/dist/encoding/Sse.d.ts.map +1 -1
  141. package/dist/encoding/Sse.js +62 -70
  142. package/dist/encoding/Sse.js.map +1 -1
  143. package/dist/graphql/GraphQL.d.ts +349 -0
  144. package/dist/graphql/GraphQL.d.ts.map +1 -0
  145. package/dist/graphql/GraphQL.js +207 -0
  146. package/dist/graphql/GraphQL.js.map +1 -0
  147. package/dist/graphql/GraphQLClient.d.ts +562 -0
  148. package/dist/graphql/GraphQLClient.d.ts.map +1 -0
  149. package/dist/graphql/GraphQLClient.js +498 -0
  150. package/dist/graphql/GraphQLClient.js.map +1 -0
  151. package/dist/graphql/GraphQLClientError.d.ts +445 -0
  152. package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
  153. package/dist/graphql/GraphQLClientError.js +479 -0
  154. package/dist/graphql/GraphQLClientError.js.map +1 -0
  155. package/dist/graphql/GraphQLGroup.d.ts +118 -0
  156. package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
  157. package/dist/graphql/GraphQLGroup.js +109 -0
  158. package/dist/graphql/GraphQLGroup.js.map +1 -0
  159. package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
  160. package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
  161. package/dist/graphql/GraphQLMiddleware.js +130 -0
  162. package/dist/graphql/GraphQLMiddleware.js.map +1 -0
  163. package/dist/graphql/GraphQLProtocol.d.ts +731 -0
  164. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
  165. package/dist/graphql/GraphQLProtocol.js +719 -0
  166. package/dist/graphql/GraphQLProtocol.js.map +1 -0
  167. package/dist/graphql/GraphQLResolver.d.ts +453 -0
  168. package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
  169. package/dist/graphql/GraphQLResolver.js +552 -0
  170. package/dist/graphql/GraphQLResolver.js.map +1 -0
  171. package/dist/graphql/index.d.ts +40 -0
  172. package/dist/graphql/index.d.ts.map +1 -0
  173. package/dist/graphql/index.js +41 -0
  174. package/dist/graphql/index.js.map +1 -0
  175. package/dist/http/FindMyWay/internal/router.js +3 -6
  176. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  177. package/dist/http/HttpClient.d.ts +21 -0
  178. package/dist/http/HttpClient.d.ts.map +1 -1
  179. package/dist/http/HttpClient.js +63 -12
  180. package/dist/http/HttpClient.js.map +1 -1
  181. package/dist/http/HttpEffect.d.ts.map +1 -1
  182. package/dist/http/HttpEffect.js +14 -7
  183. package/dist/http/HttpEffect.js.map +1 -1
  184. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  185. package/dist/http/HttpMiddleware.js +31 -32
  186. package/dist/http/HttpMiddleware.js.map +1 -1
  187. package/dist/http/HttpRouter.d.ts +35 -0
  188. package/dist/http/HttpRouter.d.ts.map +1 -1
  189. package/dist/http/HttpRouter.js +35 -0
  190. package/dist/http/HttpRouter.js.map +1 -1
  191. package/dist/http/HttpServerResponse.d.ts +8 -6
  192. package/dist/http/HttpServerResponse.d.ts.map +1 -1
  193. package/dist/http/HttpServerResponse.js +27 -16
  194. package/dist/http/HttpServerResponse.js.map +1 -1
  195. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  196. package/dist/http/HttpTraceContext.js +4 -2
  197. package/dist/http/HttpTraceContext.js.map +1 -1
  198. package/dist/http/Multipart.d.ts.map +1 -1
  199. package/dist/http/Multipart.js +15 -1
  200. package/dist/http/Multipart.js.map +1 -1
  201. package/dist/http-api/HttpApiScalar.d.ts +5 -0
  202. package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
  203. package/dist/http-api/HttpApiScalar.js.map +1 -1
  204. package/dist/internal/dateTime.js +40 -35
  205. package/dist/internal/dateTime.js.map +1 -1
  206. package/dist/internal/effect.js +19 -9
  207. package/dist/internal/effect.js.map +1 -1
  208. package/dist/internal/graphql.d.ts +2 -0
  209. package/dist/internal/graphql.d.ts.map +1 -0
  210. package/dist/internal/graphql.js +195 -0
  211. package/dist/internal/graphql.js.map +1 -0
  212. package/dist/internal/graphqlHeaders.d.ts +4 -0
  213. package/dist/internal/graphqlHeaders.d.ts.map +1 -0
  214. package/dist/internal/graphqlHeaders.js +11 -0
  215. package/dist/internal/graphqlHeaders.js.map +1 -0
  216. package/dist/internal/request.js +20 -1
  217. package/dist/internal/request.js.map +1 -1
  218. package/dist/internal/schema/toCodeDocument.js +4 -1
  219. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  220. package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
  221. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  222. package/dist/internal/version.js +1 -1
  223. package/dist/net/AddressResolver.d.ts +145 -0
  224. package/dist/net/AddressResolver.d.ts.map +1 -0
  225. package/dist/net/AddressResolver.js +114 -0
  226. package/dist/net/AddressResolver.js.map +1 -0
  227. package/dist/net/Dns.d.ts +596 -0
  228. package/dist/net/Dns.d.ts.map +1 -0
  229. package/dist/net/Dns.js +549 -0
  230. package/dist/net/Dns.js.map +1 -0
  231. package/dist/net/Host.d.ts +369 -0
  232. package/dist/net/Host.d.ts.map +1 -0
  233. package/dist/net/Host.js +397 -0
  234. package/dist/net/Host.js.map +1 -0
  235. package/dist/net/IpInterface.d.ts +76 -3
  236. package/dist/net/IpInterface.d.ts.map +1 -1
  237. package/dist/net/IpInterface.js +53 -4
  238. package/dist/net/IpInterface.js.map +1 -1
  239. package/dist/net/IpNetwork.d.ts +82 -8
  240. package/dist/net/IpNetwork.d.ts.map +1 -1
  241. package/dist/net/IpNetwork.js +60 -4
  242. package/dist/net/IpNetwork.js.map +1 -1
  243. package/dist/net/NetAddress.d.ts +396 -35
  244. package/dist/net/NetAddress.d.ts.map +1 -1
  245. package/dist/net/NetAddress.js +358 -34
  246. package/dist/net/NetAddress.js.map +1 -1
  247. package/dist/net/index.d.ts +15 -0
  248. package/dist/net/index.d.ts.map +1 -1
  249. package/dist/net/index.js +15 -0
  250. package/dist/net/index.js.map +1 -1
  251. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  252. package/dist/observability/OtlpMetrics.js +15 -21
  253. package/dist/observability/OtlpMetrics.js.map +1 -1
  254. package/dist/observability/PrometheusMetrics.js +1 -0
  255. package/dist/observability/PrometheusMetrics.js.map +1 -1
  256. package/dist/persistence/PersistedQueue.d.ts +32 -3
  257. package/dist/persistence/PersistedQueue.d.ts.map +1 -1
  258. package/dist/persistence/PersistedQueue.js +75 -16
  259. package/dist/persistence/PersistedQueue.js.map +1 -1
  260. package/dist/rpc/RpcClient.d.ts +22 -6
  261. package/dist/rpc/RpcClient.d.ts.map +1 -1
  262. package/dist/rpc/RpcClient.js +12 -5
  263. package/dist/rpc/RpcClient.js.map +1 -1
  264. package/dist/rpc/RpcServer.d.ts +6 -0
  265. package/dist/rpc/RpcServer.d.ts.map +1 -1
  266. package/dist/rpc/RpcServer.js +78 -29
  267. package/dist/rpc/RpcServer.js.map +1 -1
  268. package/dist/socket/DatagramSocket.d.ts +456 -0
  269. package/dist/socket/DatagramSocket.d.ts.map +1 -0
  270. package/dist/socket/DatagramSocket.js +535 -0
  271. package/dist/socket/DatagramSocket.js.map +1 -0
  272. package/dist/socket/Socket.d.ts +20 -1
  273. package/dist/socket/Socket.d.ts.map +1 -1
  274. package/dist/socket/Socket.js +9 -1
  275. package/dist/socket/Socket.js.map +1 -1
  276. package/dist/socket/index.d.ts +5 -0
  277. package/dist/socket/index.d.ts.map +1 -1
  278. package/dist/socket/index.js +5 -0
  279. package/dist/socket/index.js.map +1 -1
  280. package/dist/sql/Migrator.d.ts.map +1 -1
  281. package/dist/sql/Migrator.js +7 -2
  282. package/dist/sql/Migrator.js.map +1 -1
  283. package/dist/sql/SqlClient.d.ts +7 -1
  284. package/dist/sql/SqlClient.d.ts.map +1 -1
  285. package/dist/sql/SqlClient.js +31 -6
  286. package/dist/sql/SqlClient.js.map +1 -1
  287. package/dist/testing/TestCrypto.d.ts +73 -0
  288. package/dist/testing/TestCrypto.d.ts.map +1 -0
  289. package/dist/testing/TestCrypto.js +87 -0
  290. package/dist/testing/TestCrypto.js.map +1 -0
  291. package/dist/testing/index.d.ts +5 -0
  292. package/dist/testing/index.d.ts.map +1 -1
  293. package/dist/testing/index.js +5 -0
  294. package/dist/testing/index.js.map +1 -1
  295. package/dist/workflow/DurableDeferred.d.ts +54 -0
  296. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  297. package/dist/workflow/DurableDeferred.js +108 -50
  298. package/dist/workflow/DurableDeferred.js.map +1 -1
  299. package/dist/workflow/WorkflowEngine.d.ts +24 -2
  300. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  301. package/dist/workflow/WorkflowEngine.js +13 -3
  302. package/dist/workflow/WorkflowEngine.js.map +1 -1
  303. package/package.json +6 -3
  304. package/src/Context.ts +12 -3
  305. package/src/Crypto.ts +43 -9
  306. package/src/DateTime.ts +9 -6
  307. package/src/Duration.ts +36 -16
  308. package/src/FileSystem.ts +48 -2
  309. package/src/RequestResolver.ts +4 -10
  310. package/src/Scheduler.ts +9 -0
  311. package/src/Schema.ts +677 -5
  312. package/src/SchemaGetter.ts +47 -35
  313. package/src/Sink.ts +45 -18
  314. package/src/Stdio.ts +9 -2
  315. package/src/Stream.ts +44 -15
  316. package/src/Utils.ts +8 -5
  317. package/src/ai/AnthropicStructuredOutput.ts +2 -2
  318. package/src/ai/Chat.ts +2 -2
  319. package/src/ai/LanguageModel.ts +96 -24
  320. package/src/ai/McpServer.ts +288 -94
  321. package/src/ai/Prompt.ts +24 -10
  322. package/src/ai/Response.ts +6 -3
  323. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
  324. package/src/ai/internal/mcpProtocol.ts +3 -0
  325. package/src/ai/internal/mcpRuntime.ts +2 -0
  326. package/src/cli/CliOutput.ts +33 -16
  327. package/src/cli/Command.ts +4 -3
  328. package/src/cli/Completions.ts +9 -6
  329. package/src/cli/GlobalFlag.ts +19 -11
  330. package/src/cli/Prompt.ts +159 -149
  331. package/src/cli/internal/ansi.ts +26 -3
  332. package/src/cli/internal/completions/powershell.ts +322 -0
  333. package/src/cli/internal/completions/zsh.ts +4 -2
  334. package/src/cli/internal/wizard.ts +34 -26
  335. package/src/cluster/ClusterSchema.ts +32 -0
  336. package/src/cluster/ClusterWorkflowEngine.ts +64 -36
  337. package/src/cluster/MessageStorage.ts +45 -14
  338. package/src/cluster/Runners.ts +8 -2
  339. package/src/cluster/Sharding.ts +219 -58
  340. package/src/cluster/SqlMessageStorage.ts +118 -84
  341. package/src/cluster/SqlRunnerStorage.ts +224 -128
  342. package/src/cluster/internal/entityManager.ts +151 -55
  343. package/src/encoding/Sse.ts +69 -79
  344. package/src/graphql/GraphQL.ts +494 -0
  345. package/src/graphql/GraphQLClient.ts +912 -0
  346. package/src/graphql/GraphQLClientError.ts +542 -0
  347. package/src/graphql/GraphQLGroup.ts +167 -0
  348. package/src/graphql/GraphQLMiddleware.ts +253 -0
  349. package/src/graphql/GraphQLProtocol.ts +1286 -0
  350. package/src/graphql/GraphQLResolver.ts +1010 -0
  351. package/src/graphql/index.ts +48 -0
  352. package/src/http/FindMyWay/internal/router.ts +4 -7
  353. package/src/http/HttpClient.ts +110 -12
  354. package/src/http/HttpEffect.ts +11 -5
  355. package/src/http/HttpMiddleware.ts +33 -34
  356. package/src/http/HttpRouter.ts +35 -0
  357. package/src/http/HttpServerResponse.ts +28 -16
  358. package/src/http/HttpTraceContext.ts +8 -2
  359. package/src/http/Multipart.ts +17 -1
  360. package/src/http-api/HttpApiScalar.ts +5 -0
  361. package/src/internal/dateTime.ts +13 -10
  362. package/src/internal/effect.ts +22 -15
  363. package/src/internal/graphql.ts +263 -0
  364. package/src/internal/graphqlHeaders.ts +11 -0
  365. package/src/internal/request.ts +31 -3
  366. package/src/internal/schema/toCodeDocument.ts +7 -1
  367. package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
  368. package/src/internal/version.ts +1 -1
  369. package/src/net/AddressResolver.ts +222 -0
  370. package/src/net/Dns.ts +1035 -0
  371. package/src/net/Host.ts +544 -0
  372. package/src/net/IpInterface.ts +117 -7
  373. package/src/net/IpNetwork.ts +116 -17
  374. package/src/net/NetAddress.ts +620 -76
  375. package/src/net/index.ts +18 -0
  376. package/src/observability/OtlpMetrics.ts +12 -23
  377. package/src/observability/PrometheusMetrics.ts +1 -0
  378. package/src/persistence/PersistedQueue.ts +146 -53
  379. package/src/rpc/RpcClient.ts +41 -19
  380. package/src/rpc/RpcServer.ts +115 -40
  381. package/src/socket/DatagramSocket.ts +919 -0
  382. package/src/socket/Socket.ts +28 -2
  383. package/src/socket/index.ts +6 -0
  384. package/src/sql/Migrator.ts +21 -5
  385. package/src/sql/SqlClient.ts +39 -15
  386. package/src/testing/TestCrypto.ts +94 -0
  387. package/src/testing/index.ts +6 -0
  388. package/src/workflow/DurableDeferred.ts +160 -47
  389. package/src/workflow/WorkflowEngine.ts +50 -5
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @stability experimental
3
+ * @since 4.0.0
4
+ */
5
+
6
+ // @barrel: Auto-generated exports. Do not edit manually.
7
+
8
+ /**
9
+ * @stability experimental
10
+ * @since 4.0.0
11
+ */
12
+ export * as GraphQL from "./GraphQL.ts"
13
+
14
+ /**
15
+ * @stability experimental
16
+ * @since 4.0.0
17
+ */
18
+ export * as GraphQLClient from "./GraphQLClient.ts"
19
+
20
+ /**
21
+ * @stability experimental
22
+ * @since 4.0.0
23
+ */
24
+ export * as GraphQLClientError from "./GraphQLClientError.ts"
25
+
26
+ /**
27
+ * @stability experimental
28
+ * @since 4.0.0
29
+ */
30
+ export * as GraphQLGroup from "./GraphQLGroup.ts"
31
+
32
+ /**
33
+ * @stability experimental
34
+ * @since 4.0.0
35
+ */
36
+ export * as GraphQLMiddleware from "./GraphQLMiddleware.ts"
37
+
38
+ /**
39
+ * @stability experimental
40
+ * @since 4.0.0
41
+ */
42
+ export * as GraphQLProtocol from "./GraphQLProtocol.ts"
43
+
44
+ /**
45
+ * @stability experimental
46
+ * @since 4.0.0
47
+ */
48
+ export * as GraphQLResolver from "./GraphQLResolver.ts"
@@ -338,13 +338,6 @@ class RouterImpl<A> implements Router.Router<A> {
338
338
  path = path.replace(FULL_PATH_REGEXP, "/")
339
339
  }
340
340
 
341
- // This must be run before sanitizeUrl as the resulting function
342
- // .sliceParameter must be constructed with same URL string used
343
- // throughout the rest of this function.
344
- if (this.options.ignoreDuplicateSlashes) {
345
- path = removeDuplicateSlashes(path)
346
- }
347
-
348
341
  let sanitizedUrl
349
342
  try {
350
343
  sanitizedUrl = safeDecodeURI(path)
@@ -355,6 +348,10 @@ class RouterImpl<A> implements Router.Router<A> {
355
348
  return undefined
356
349
  }
357
350
 
351
+ if (this.options.ignoreDuplicateSlashes) {
352
+ path = removeDuplicateSlashes(path)
353
+ }
354
+
358
355
  if (this.options.ignoreTrailingSlash) {
359
356
  path = trimLastSlash(path)
360
357
  }
@@ -1204,6 +1204,9 @@ export const retry: {
1204
1204
  * Specifying a `while` predicate allows you to consider other errors as
1205
1205
  * transient, and is ignored in "response-only" mode.
1206
1206
  *
1207
+ * A `Retry-After` header on a transient response, or on the response of a
1208
+ * status code error, sets the minimum delay before the next attempt.
1209
+ *
1207
1210
  * @stability unstable
1208
1211
  * @category error handling
1209
1212
  * @since 4.0.0
@@ -1221,6 +1224,9 @@ export const retryTransient: {
1221
1224
  * Specifying a `while` predicate allows you to consider other errors as
1222
1225
  * transient, and is ignored in "response-only" mode.
1223
1226
  *
1227
+ * A `Retry-After` header on a transient response, or on the response of a
1228
+ * status code error, sets the minimum delay before the next attempt.
1229
+ *
1224
1230
  * @stability unstable
1225
1231
  * @category error handling
1226
1232
  * @since 4.0.0
@@ -1257,6 +1263,9 @@ export const retryTransient: {
1257
1263
  * Specifying a `while` predicate allows you to consider other errors as
1258
1264
  * transient, and is ignored in "response-only" mode.
1259
1265
  *
1266
+ * A `Retry-After` header on a transient response, or on the response of a
1267
+ * status code error, sets the minimum delay before the next attempt.
1268
+ *
1260
1269
  * @stability unstable
1261
1270
  * @category error handling
1262
1271
  * @since 4.0.0
@@ -1295,6 +1304,9 @@ export const retryTransient: {
1295
1304
  * Specifying a `while` predicate allows you to consider other errors as
1296
1305
  * transient, and is ignored in "response-only" mode.
1297
1306
  *
1307
+ * A `Retry-After` header on a transient response, or on the response of a
1308
+ * status code error, sets the minimum delay before the next attempt.
1309
+ *
1298
1310
  * @stability unstable
1299
1311
  * @category error handling
1300
1312
  * @since 4.0.0
@@ -1314,6 +1326,9 @@ export const retryTransient: {
1314
1326
  * Specifying a `while` predicate allows you to consider other errors as
1315
1327
  * transient, and is ignored in "response-only" mode.
1316
1328
  *
1329
+ * A `Retry-After` header on a transient response, or on the response of a
1330
+ * status code error, sets the minimum delay before the next attempt.
1331
+ *
1317
1332
  * @stability unstable
1318
1333
  * @category error handling
1319
1334
  * @since 4.0.0
@@ -1341,14 +1356,15 @@ export const retryTransient: {
1341
1356
  ): HttpClient.With<E | ES, R1 | R> => {
1342
1357
  const isOnlySchedule = Schedule.isSchedule(options)
1343
1358
  const retryOn = isOnlySchedule ? "errors-and-responses" : options.retryOn ?? "errors-and-responses"
1344
- const schedule = isOnlySchedule ? options : options.schedule
1345
- const passthroughSchedule = schedule && Schedule.passthrough(schedule)
1359
+ const schedule = withRetryAfter(
1360
+ isOnlySchedule ? options : options.schedule ?? Schedule.forever
1361
+ )
1346
1362
  const times = isOnlySchedule ? undefined : options.times
1347
1363
  return transformResponse(
1348
1364
  self,
1349
1365
  flow(
1350
1366
  retryOn === "errors-only" ? identity : Effect.repeat({
1351
- schedule: passthroughSchedule!,
1367
+ schedule,
1352
1368
  times,
1353
1369
  while: isTransientResponse
1354
1370
  }),
@@ -1466,6 +1482,8 @@ export declare namespace WithRateLimiter {
1466
1482
  * It can update limits by inspecting common rate limit response headers and
1467
1483
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1468
1484
  * wrapping a `429` response) by forcing the retry back through the limiter.
1485
+ * When a response reports the remaining budget, requests wait for the
1486
+ * reported reset once that budget is exhausted.
1469
1487
  *
1470
1488
  * **Gotchas**
1471
1489
  *
@@ -1486,6 +1504,8 @@ export const withRateLimiter: {
1486
1504
  * It can update limits by inspecting common rate limit response headers and
1487
1505
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1488
1506
  * wrapping a `429` response) by forcing the retry back through the limiter.
1507
+ * When a response reports the remaining budget, requests wait for the
1508
+ * reported reset once that budget is exhausted.
1489
1509
  *
1490
1510
  * **Gotchas**
1491
1511
  *
@@ -1508,6 +1528,8 @@ export const withRateLimiter: {
1508
1528
  * It can update limits by inspecting common rate limit response headers and
1509
1529
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1510
1530
  * wrapping a `429` response) by forcing the retry back through the limiter.
1531
+ * When a response reports the remaining budget, requests wait for the
1532
+ * reported reset once that budget is exhausted.
1511
1533
  *
1512
1534
  * **Gotchas**
1513
1535
  *
@@ -1551,14 +1573,36 @@ export const withRateLimiter: {
1551
1573
  return initialState
1552
1574
  }
1553
1575
 
1576
+ const budgets = new Map<string, RateLimitBudget>()
1577
+ const getBudget = (key: string): RateLimitBudget => {
1578
+ let budget = budgets.get(key)
1579
+ if (budget === undefined) {
1580
+ budget = { sent: 0, observed: 0, remaining: 0, resetAt: 0 }
1581
+ budgets.set(key, budget)
1582
+ }
1583
+ return budget
1584
+ }
1585
+
1554
1586
  const onResponse = options.disableResponseInspection
1555
1587
  ? undefined
1556
- : (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => {
1588
+ : (clock: Clock, key: string, headers: Headers.Headers, tokens: number, sent: number) => {
1557
1589
  const current = getState(key)
1558
1590
  const next = parseRateLimiterState(current, clock, headers, tokens, headerNames)
1559
1591
  if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) {
1560
1592
  states.set(key, next)
1561
1593
  }
1594
+ const remaining = parseRateLimitRemaining(headers, headerNames)
1595
+ const budget = getBudget(key)
1596
+ // responses to earlier requests carry stale counts
1597
+ if (remaining === undefined || sent <= budget.observed) {
1598
+ return
1599
+ }
1600
+ const resetAfter = parseRateLimitWindow(clock, headers, headerNames)
1601
+ budget.observed = sent
1602
+ budget.remaining = remaining
1603
+ if (resetAfter !== undefined) {
1604
+ budget.resetAt = clock.currentTimeMillisUnsafe() + Duration.toMillis(resetAfter)
1605
+ }
1562
1606
  }
1563
1607
 
1564
1608
  return transform(self, function loop(effect, request, retries = 0): Effect.Effect<
@@ -1570,6 +1614,15 @@ export const withRateLimiter: {
1570
1614
  const clock = fiber.getRef(Clock)
1571
1615
  const key = resolveKey(request)
1572
1616
  const tokens = Math.max(resolveTokens(request), 1)
1617
+ const budget = getBudget(key)
1618
+ const now = clock.currentTimeMillisUnsafe()
1619
+ // requests admitted after the observed one are not reflected in its count
1620
+ if (budget.resetAt > now && budget.remaining - (budget.sent - budget.observed) < tokens) {
1621
+ return Effect.flatMap(Effect.sleep(budget.resetAt - now), () => loop(effect, request, retries))
1622
+ }
1623
+ // counted at admission, so requests already admitted are sent even if a
1624
+ // later response exhausts the budget
1625
+ const sent = budget.sent += tokens
1573
1626
  const current = getState(key)
1574
1627
  const canRetry = options.times === undefined || retries < options.times
1575
1628
  function retry(retryAfter: Duration.Duration | undefined) {
@@ -1581,7 +1634,7 @@ export const withRateLimiter: {
1581
1634
  response: HttpClientResponse.HttpClientResponse,
1582
1635
  adaptive: RateLimiter.AdaptiveConsumeResult | undefined
1583
1636
  ) => {
1584
- onResponse?.(clock, key, response.headers, tokens)
1637
+ onResponse?.(clock, key, response.headers, tokens, sent)
1585
1638
  if (options.disableResponseInspection || response.status !== 429) {
1586
1639
  return Effect.succeed</**
1587
1640
  * Applies request rate limiting using the `RateLimiter` service.
@@ -1591,6 +1644,8 @@ export const withRateLimiter: {
1591
1644
  * It can update limits by inspecting common rate limit response headers and
1592
1645
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1593
1646
  * wrapping a `429` response) by forcing the retry back through the limiter.
1647
+ * When a response reports the remaining budget, requests wait for the
1648
+ * reported reset once that budget is exhausted.
1594
1649
  *
1595
1650
  * **Gotchas**
1596
1651
  *
@@ -1604,7 +1659,10 @@ export const withRateLimiter: {
1604
1659
  */
1605
1660
  Duration.Duration | undefined>(undefined);
1606
1661
  }
1607
- const retryAfter = parseRetryAfter(clock, getHeader(response.headers, ...headerNames.retryAfter))
1662
+ const retryAfter = parseRetryAfter(
1663
+ clock.currentTimeMillisUnsafe(),
1664
+ getHeader(response.headers, ...headerNames.retryAfter)
1665
+ )
1608
1666
  if (retryAfter === undefined) {
1609
1667
  return Effect.succeed</**
1610
1668
  * Applies request rate limiting using the `RateLimiter` service.
@@ -1614,6 +1672,8 @@ export const withRateLimiter: {
1614
1672
  * It can update limits by inspecting common rate limit response headers and
1615
1673
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1616
1674
  * wrapping a `429` response) by forcing the retry back through the limiter.
1675
+ * When a response reports the remaining budget, requests wait for the
1676
+ * reported reset once that budget is exhausted.
1617
1677
  *
1618
1678
  * **Gotchas**
1619
1679
  *
@@ -1637,6 +1697,8 @@ export const withRateLimiter: {
1637
1697
  * It can update limits by inspecting common rate limit response headers and
1638
1698
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1639
1699
  * wrapping a `429` response) by forcing the retry back through the limiter.
1700
+ * When a response reports the remaining budget, requests wait for the
1701
+ * reported reset once that budget is exhausted.
1640
1702
  *
1641
1703
  * **Gotchas**
1642
1704
  *
@@ -1756,6 +1818,17 @@ interface RateLimiterState {
1756
1818
  readonly initial: boolean
1757
1819
  }
1758
1820
 
1821
+ interface RateLimitBudget {
1822
+ /** Total tokens admitted for the key. */
1823
+ sent: number
1824
+ /** The `sent` total of the request whose remaining count was last applied. */
1825
+ observed: number
1826
+ /** The remaining count reported in response to the `observed` request. */
1827
+ remaining: number
1828
+ /** When the server budget resets, in epoch milliseconds. */
1829
+ resetAt: number
1830
+ }
1831
+
1759
1832
  const parseRateLimiterState = (
1760
1833
  state: RateLimiterState,
1761
1834
  clock: Clock,
@@ -1811,21 +1884,24 @@ const parseRateLimitWindow = (
1811
1884
  }
1812
1885
 
1813
1886
  const parseRetryAfter = (
1814
- clock: Clock,
1887
+ nowMillis: number,
1815
1888
  value: string | undefined
1816
1889
  ): Duration.Duration | undefined => {
1817
1890
  if (value === undefined) {
1818
1891
  return undefined
1819
1892
  }
1820
- const numeric = parseNumberHeader(value)
1821
- if (numeric !== undefined) {
1822
- return Duration.max(Duration.seconds(numeric), Duration.millis(1))
1893
+ const trimmed = value.trim()
1894
+ if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
1895
+ const numeric = Number(trimmed)
1896
+ return Number.isFinite(numeric)
1897
+ ? Duration.max(Duration.seconds(numeric), Duration.millis(1))
1898
+ : undefined
1823
1899
  }
1824
- const parsedDate = Date.parse(value)
1900
+ const parsedDate = Date.parse(trimmed)
1825
1901
  if (Number.isNaN(parsedDate)) {
1826
1902
  return undefined
1827
1903
  }
1828
- const millis = parsedDate - clock.currentTimeMillisUnsafe()
1904
+ const millis = parsedDate - nowMillis
1829
1905
  if (millis <= 0) {
1830
1906
  return Duration.millis(1)
1831
1907
  }
@@ -2363,6 +2439,28 @@ const isTransientHttpError = (error: unknown) =>
2363
2439
  (error.reason._tag === "TransportError" ||
2364
2440
  (error.reason._tag === "StatusCodeError" && isTransientResponse(error.reason.response)))
2365
2441
 
2442
+ const retryAfterOf = (input: unknown, nowMillis: number): Duration.Duration | undefined => {
2443
+ const response = Error.isHttpClientError(input)
2444
+ ? input.response
2445
+ : Predicate.hasProperty(input, HttpClientResponse.TypeId)
2446
+ ? input as HttpClientResponse.HttpClientResponse
2447
+ : undefined
2448
+ return parseRetryAfter(nowMillis, response?.headers["retry-after"])
2449
+ }
2450
+
2451
+ const withRetryAfter = <ES, R1>(
2452
+ schedule: Schedule.Schedule<unknown, any, ES, R1>
2453
+ ): Schedule.Schedule<unknown, any, ES, R1> =>
2454
+ Schedule.fromStep(
2455
+ Effect.map(Schedule.toStep(schedule), (step) => (now: number, input: unknown) => {
2456
+ const retryAfter = retryAfterOf(input, now)
2457
+ if (retryAfter === undefined) {
2458
+ return step(now, input)
2459
+ }
2460
+ return Effect.map(step(now, input), ([output, delay]) => [output, Duration.max(delay, retryAfter)])
2461
+ })
2462
+ )
2463
+
2366
2464
  const isTooManyRequestsHttpClientError = (
2367
2465
  error: unknown
2368
2466
  ): error is Error.HttpClientError & { readonly reason: Error.StatusCodeError } =>
@@ -347,10 +347,11 @@ export const toWebHandlerWith = <Provided, R = never, ReqR = Exclude<R, Provided
347
347
  const httpApp = toHandled(self, (request, response) => {
348
348
  const withoutBody = request.method === "HEAD"
349
349
  if (!Response.omitsBody(response, withoutBody)) {
350
+ // The transferred request scope closes when an abort interrupts the body, even if it is no longer read.
350
351
  response = scopeTransferToStream(response)
351
352
  }
352
353
  ;(request as any)[resolveSymbol](
353
- Response.toWeb(response, { withoutBody, context })
354
+ Response.toWeb(response, { withoutBody, context, signal: (request.source as globalThis.Request).signal })
354
355
  )
355
356
  return Effect.void
356
357
  }, middleware)
@@ -366,10 +367,15 @@ export const toWebHandlerWith = <Provided, R = never, ReqR = Exclude<R, Provided
366
367
  reqContext = Context.add(reqContext, HttpServerRequest, httpServerRequest)
367
368
  ;(httpServerRequest as any)[resolveSymbol] = resolve
368
369
  const fiber = Effect.runForkWith(reqContext)(httpApp as any)
369
- if (fiber.pollUnsafe() === undefined) {
370
- request.signal?.addEventListener("abort", () => {
371
- fiber.interruptUnsafe(undefined, ClientAbort.annotation)
372
- }, { once: true })
370
+ const signal = request.signal
371
+ if (fiber.pollUnsafe() === undefined && signal) {
372
+ const onAbort = () => fiber.interruptUnsafe(undefined, ClientAbort.annotation)
373
+ if (signal.aborted) {
374
+ onAbort()
375
+ } else {
376
+ signal.addEventListener("abort", onAbort, { once: true })
377
+ fiber.addObserver(() => signal.removeEventListener("abort", onAbort))
378
+ }
373
379
  }
374
380
  })
375
381
  }
@@ -268,42 +268,41 @@ export const tracer: <E, R>(
268
268
  }
269
269
  const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames)
270
270
  const headerFilter = fiber.getRef(TracerHeaderFilter)
271
- fiber.currentDispatcher.scheduleTask(() => {
272
- let response: HttpServerResponse
273
- let spanExit: Exit.Exit<HttpServerResponse, unknown>
274
- if (Exit.isFailure(exit)) {
275
- const [failureResponse, cause] = causeResponseStripped(exit.cause)
276
- response = failureResponse
277
- // OpenTelemetry only treats 5xx responses as server span errors; interrupts still fail the span.
278
- spanExit = Option.isSome(cause) && (response.status >= 500 || Cause.hasInterrupts(cause.value))
279
- ? Exit.failCause(cause.value)
280
- : responseSpanExit(request, response)
281
- } else {
282
- response = exit.value
283
- spanExit = responseSpanExit(request, response)
284
- }
285
- if (span.sampled) {
286
- span.attribute("http.request.method", request.method)
287
- const url = Request.toURL(request)
288
- if (Option.isSome(url)) {
289
- if (!request.url.startsWith("/") && (url.value.username !== "" || url.value.password !== "")) {
290
- url.value.username = "REDACTED"
291
- url.value.password = "REDACTED"
292
- }
293
- tracing.addUrlAttributes(span, url.value)
294
- }
295
- if (request.headers["user-agent"] !== undefined) {
296
- span.attribute("user_agent.original", request.headers["user-agent"])
297
- }
298
- tracing.addHeaderAttributes(span, "request", request.headers, headerFilter, redactedHeaderNames)
299
- if (Option.isSome(request.remoteAddress)) {
300
- span.attribute("client.address", request.remoteAddress.value)
271
+ // Complete the span before the request fiber exits so shutdown can flush it.
272
+ let response: HttpServerResponse
273
+ let spanExit: Exit.Exit<HttpServerResponse, unknown>
274
+ if (Exit.isFailure(exit)) {
275
+ const [failureResponse, cause] = causeResponseStripped(exit.cause)
276
+ response = failureResponse
277
+ // OpenTelemetry only treats 5xx responses as server span errors; interrupts still fail the span.
278
+ spanExit = Option.isSome(cause) && (response.status >= 500 || Cause.hasInterrupts(cause.value))
279
+ ? Exit.failCause(cause.value)
280
+ : responseSpanExit(request, response)
281
+ } else {
282
+ response = exit.value
283
+ spanExit = responseSpanExit(request, response)
284
+ }
285
+ if (span.sampled) {
286
+ span.attribute("http.request.method", request.method)
287
+ const url = Request.toURL(request)
288
+ if (Option.isSome(url)) {
289
+ if (!request.url.startsWith("/") && (url.value.username !== "" || url.value.password !== "")) {
290
+ url.value.username = "REDACTED"
291
+ url.value.password = "REDACTED"
301
292
  }
302
- span.attribute("http.response.status_code", response.status)
303
- tracing.addHeaderAttributes(span, "response", response.headers, headerFilter, redactedHeaderNames)
293
+ tracing.addUrlAttributes(span, url.value)
304
294
  }
305
- span.end(endTime, spanExit)
306
- }, 0)
295
+ if (request.headers["user-agent"] !== undefined) {
296
+ span.attribute("user_agent.original", request.headers["user-agent"])
297
+ }
298
+ tracing.addHeaderAttributes(span, "request", request.headers, headerFilter, redactedHeaderNames)
299
+ if (Option.isSome(request.remoteAddress)) {
300
+ span.attribute("client.address", request.remoteAddress.value)
301
+ }
302
+ span.attribute("http.response.status_code", response.status)
303
+ tracing.addHeaderAttributes(span, "response", response.headers, headerFilter, redactedHeaderNames)
304
+ }
305
+ span.end(endTime, spanExit)
307
306
  return undefined
308
307
  })
309
308
  return httpApp
@@ -1410,6 +1410,41 @@ export const serve = <A, E, R, HE, HR = Request.Only<"Requires", R> | Request.On
1410
1410
  * request arrives, in which case that request waits for the build to finish.
1411
1411
  * If the build fails, every request rejects with the build error.
1412
1412
  *
1413
+ * **Gotchas**
1414
+ *
1415
+ * On Cloudflare Workers, build the handler at module scope: fibers started
1416
+ * inside `fetch` capture that request's async context. Layer construction
1417
+ * must not require request-time I/O; bindings are available from
1418
+ * `cloudflare:workers` via its `env` export.
1419
+ * Prefer request-local work or a Durable Object's supported lifecycle APIs,
1420
+ * not indefinite background execution. For shared fibers, provide a sync
1421
+ * scheduler to both layers and requests as below; see `Scheduler.MixedScheduler`
1422
+ * for its limitations. Bound waits for shared work with request-local timeouts.
1423
+ *
1424
+ * **Example** (Opting in to the sync scheduler on Workers)
1425
+ *
1426
+ * ```ts
1427
+ * import { Effect, Layer, Scheduler } from "effect"
1428
+ * import { HttpRouter, HttpServer, HttpServerResponse } from "effect/http"
1429
+ *
1430
+ * const SyncScheduler = Layer.succeed(
1431
+ * Scheduler.Scheduler,
1432
+ * new Scheduler.MixedScheduler("sync")
1433
+ * )
1434
+ * const AppLive = HttpRouter.add(
1435
+ * "GET",
1436
+ * "/ping",
1437
+ * Effect.succeed(HttpServerResponse.text("pong"))
1438
+ * ).pipe(Layer.provide(HttpServer.layerServices))
1439
+ *
1440
+ * // Build once at module scope. The scheduler is also retained for requests.
1441
+ * const app = HttpRouter.toWebHandler(
1442
+ * AppLive.pipe(Layer.provideMerge(SyncScheduler))
1443
+ * )
1444
+ *
1445
+ * export default { fetch: (request: Request) => app.handler(request) }
1446
+ * ```
1447
+ *
1413
1448
  * @stability unstable
1414
1449
  * @category converting
1415
1450
  * @since 4.0.0
@@ -1346,23 +1346,35 @@ export const setStatus: {
1346
1346
  export const omitsBody = (response: HttpServerResponse, withoutBody = false): boolean =>
1347
1347
  withoutBody || response.status === 204 || response.status === 205 || response.status === 304
1348
1348
 
1349
+ const mergeHeaders = (target: globalThis.Headers, headers: globalThis.Headers): globalThis.Headers => {
1350
+ for (const [key, value] of headers as any) {
1351
+ if (key === "set-cookie") {
1352
+ target.append(key, value)
1353
+ } else {
1354
+ target.set(key, value)
1355
+ }
1356
+ }
1357
+ return target
1358
+ }
1359
+
1349
1360
  /**
1350
1361
  * Converts an `HttpServerResponse` to a Web `Response`.
1351
1362
  *
1352
1363
  * **Details**
1353
1364
  *
1354
1365
  * Cookies are appended as `Set-Cookie` headers. Stream bodies are converted using
1355
- * the supplied context.
1366
+ * the supplied context, and are interrupted when `signal` aborts.
1356
1367
  *
1357
1368
  * Bodies are omitted for statuses 204, 205, and 304, or when `withoutBody` is true
1358
1369
  * (for example, for HEAD responses). Omitted raw `ReadableStream` bodies are
1359
1370
  * cancelled without awaiting completion, and cancellation errors are ignored.
1360
1371
  *
1361
- * A raw `Response` body is returned as-is after the outer headers are merged
1362
- * into it: outer headers replace native ones, and cookies are appended. When
1363
- * its body is omitted, the bodyless `Response` keeps those merged headers and
1364
- * uses the outer status for 204, 205 and 304, or the raw `Response` status
1365
- * otherwise.
1372
+ * A raw `Response` is returned as-is when there are no outer headers or cookies
1373
+ * to merge. Otherwise, they are merged into a copy without modifying the raw
1374
+ * response: outer headers replace native ones, and cookies are appended. When
1375
+ * its body is omitted, the bodyless `Response` keeps those merged
1376
+ * headers and uses the outer status for 204, 205 and 304, or the raw `Response`
1377
+ * status otherwise.
1366
1378
  *
1367
1379
  * @stability unstable
1368
1380
  * @category converting
@@ -1373,6 +1385,7 @@ export const toWeb = (
1373
1385
  options?: {
1374
1386
  readonly withoutBody?: boolean | undefined
1375
1387
  readonly context?: Context.Context<never> | undefined
1388
+ readonly signal?: AbortSignal | undefined
1376
1389
  }
1377
1390
  ): Response => {
1378
1391
  const headers = new globalThis.Headers(response.headers)
@@ -1387,15 +1400,13 @@ export const toWeb = (
1387
1400
  const withoutBody = outerOmitsBody || options?.withoutBody === true
1388
1401
  if (body._tag === "Raw" && body.body instanceof Response) {
1389
1402
  const raw = body.body
1390
- for (const [key, value] of headers as any) {
1391
- if (key === "set-cookie") {
1392
- raw.headers.append(key, value)
1393
- } else {
1394
- raw.headers.set(key, value)
1395
- }
1396
- }
1397
1403
  if (!withoutBody) {
1398
- return raw
1404
+ if (headers.keys().next().done) {
1405
+ return raw
1406
+ }
1407
+ const copy = new Response(raw.body, raw)
1408
+ mergeHeaders(copy.headers, headers)
1409
+ return copy
1399
1410
  }
1400
1411
  // A bodyless outer status is what the caller chose to send; otherwise the
1401
1412
  // body is only omitted for HEAD and the raw Response describes the resource.
@@ -1403,7 +1414,7 @@ export const toWeb = (
1403
1414
  return new Response(undefined, {
1404
1415
  status: source.status,
1405
1416
  statusText: source.statusText as string,
1406
- headers: raw.headers
1417
+ headers: mergeHeaders(new globalThis.Headers(raw.headers), headers)
1407
1418
  })
1408
1419
  }
1409
1420
  if (withoutBody) {
@@ -1449,7 +1460,8 @@ export const toWeb = (
1449
1460
  return new Response(
1450
1461
  Stream.toReadableStreamWith(
1451
1462
  body.stream,
1452
- options?.context ?? Context.empty()
1463
+ options?.context ?? Context.empty(),
1464
+ { signal: options?.signal }
1453
1465
  ),
1454
1466
  {
1455
1467
  status: response.status,
@@ -93,7 +93,7 @@ export const b3: FromHeaders = (headers) => {
93
93
  return Option.none()
94
94
  }
95
95
  const parts = headers["b3"].split("-")
96
- if (parts.length < 2) {
96
+ if (parts.length < 2 || isB3Ids(parts[0], parts[1]) === false) {
97
97
  return Option.none()
98
98
  }
99
99
  return Option.some(Tracer.externalSpan({
@@ -116,7 +116,10 @@ export const b3: FromHeaders = (headers) => {
116
116
  * @since 4.0.0
117
117
  */
118
118
  export const xb3: FromHeaders = (headers) => {
119
- if (!(headers["x-b3-traceid"]) || !(headers["x-b3-spanid"])) {
119
+ if (
120
+ !(headers["x-b3-traceid"]) || !(headers["x-b3-spanid"]) ||
121
+ isB3Ids(headers["x-b3-traceid"], headers["x-b3-spanid"]) === false
122
+ ) {
120
123
  return Option.none()
121
124
  }
122
125
  return Option.some(Tracer.externalSpan({
@@ -130,6 +133,9 @@ export const xb3: FromHeaders = (headers) => {
130
133
  const w3cTraceId = /^[0-9a-f]{32}$/i
131
134
  const w3cSpanId = /^[0-9a-f]{16}$/i
132
135
 
136
+ const b3TraceId = /^(?:[0-9a-f]{16}|[0-9a-f]{32})$/i
137
+ const isB3Ids = (traceId: string, spanId: string): boolean => b3TraceId.test(traceId) && w3cSpanId.test(spanId)
138
+
133
139
  /**
134
140
  * Decodes an external span safely from the W3C `traceparent` header.
135
141
  *
@@ -689,6 +689,22 @@ const defaultWriteFile = (path: string, file: File) =>
689
689
  (fs) => Effect.mapError(Stream.run(file.content, fs.sink(path)), toMultipartError)
690
690
  )
691
691
 
692
+ const maxPersistedFileNameBytes = 128
693
+ const fileNameEncoder = new TextEncoder()
694
+
695
+ // Keeps the trailing 128 UTF-8 bytes without splitting code points. Empty and dot names fall back to "file".
696
+ const persistedFileName = (baseName: string): string => {
697
+ const codePoints = Array.from(baseName)
698
+ let fileName = ""
699
+ let bytes = 0
700
+ for (let i = codePoints.length - 1; i >= 0; i--) {
701
+ bytes += fileNameEncoder.encode(codePoints[i]).length
702
+ if (bytes > maxPersistedFileNameBytes) break
703
+ fileName = codePoints[i] + fileName
704
+ }
705
+ return fileName === "" || fileName === "." || fileName === ".." ? "file" : fileName
706
+ }
707
+
692
708
  /**
693
709
  * Runs a channel of byte chunks and collects all output into a single
694
710
  * `Uint8Array`.
@@ -748,7 +764,7 @@ export const toPersisted = (
748
764
  return Effect.void
749
765
  }
750
766
  const file = part
751
- const fileName = path_.basename(file.name).slice(-128)
767
+ const fileName = persistedFileName(path_.basename(file.name))
752
768
  let path = path_.join(dir, fileName)
753
769
  while (usedPaths.has(path)) {
754
770
  path = path_.join(dir, `${fileIndex++}-${fileName}`)
@@ -60,6 +60,11 @@ export type ScalarConfig = {
60
60
  layout?: "modern" | "classic"
61
61
  /** URL to a request proxy for the API client */
62
62
  proxyUrl?: string
63
+ /** Authentication preferences for the API client */
64
+ authentication?: {
65
+ /** OpenAPI security scheme names to select by default */
66
+ preferredSecurityScheme?: string | Array<string | Array<string>> | null
67
+ }
63
68
  /** Browser JavaScript function expression used by Scalar for documents and test requests */
64
69
  customFetch?: string
65
70
  /** Whether to show the sidebar */