effect 4.0.3 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) 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 +117 -0
  5. package/ai-docs/src/52_graphql/20_testing.ts +45 -0
  6. package/ai-docs/src/52_graphql/30_batching.ts +118 -0
  7. package/ai-docs/src/52_graphql/40_aliased-batching.ts +124 -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/Array.d.ts +90 -6
  11. package/dist/Array.d.ts.map +1 -1
  12. package/dist/Array.js +4 -2
  13. package/dist/Array.js.map +1 -1
  14. package/dist/Channel.d.ts +6 -1
  15. package/dist/Channel.d.ts.map +1 -1
  16. package/dist/Channel.js +17 -3
  17. package/dist/Channel.js.map +1 -1
  18. package/dist/ConfigProvider.d.ts.map +1 -1
  19. package/dist/ConfigProvider.js +33 -32
  20. package/dist/ConfigProvider.js.map +1 -1
  21. package/dist/Context.d.ts.map +1 -1
  22. package/dist/Context.js +9 -0
  23. package/dist/Context.js.map +1 -1
  24. package/dist/Duration.d.ts.map +1 -1
  25. package/dist/Duration.js +2 -5
  26. package/dist/Duration.js.map +1 -1
  27. package/dist/Effect.d.ts +61 -19
  28. package/dist/Effect.d.ts.map +1 -1
  29. package/dist/Effect.js +58 -39
  30. package/dist/Effect.js.map +1 -1
  31. package/dist/FileSystem.d.ts +23 -9
  32. package/dist/FileSystem.d.ts.map +1 -1
  33. package/dist/FileSystem.js.map +1 -1
  34. package/dist/Match.d.ts +20 -6
  35. package/dist/Match.d.ts.map +1 -1
  36. package/dist/Match.js +2 -2
  37. package/dist/Match.js.map +1 -1
  38. package/dist/Path.d.ts +37 -5
  39. package/dist/Path.d.ts.map +1 -1
  40. package/dist/Path.js +36 -5
  41. package/dist/Path.js.map +1 -1
  42. package/dist/RequestResolver.d.ts.map +1 -1
  43. package/dist/RequestResolver.js +4 -10
  44. package/dist/RequestResolver.js.map +1 -1
  45. package/dist/Scheduler.d.ts +9 -0
  46. package/dist/Scheduler.d.ts.map +1 -1
  47. package/dist/Scheduler.js +9 -0
  48. package/dist/Scheduler.js.map +1 -1
  49. package/dist/Schema.d.ts +15 -2
  50. package/dist/Schema.d.ts.map +1 -1
  51. package/dist/Schema.js +16 -2
  52. package/dist/Schema.js.map +1 -1
  53. package/dist/SchemaGetter.d.ts +4 -0
  54. package/dist/SchemaGetter.d.ts.map +1 -1
  55. package/dist/SchemaGetter.js +52 -33
  56. package/dist/SchemaGetter.js.map +1 -1
  57. package/dist/Sink.d.ts.map +1 -1
  58. package/dist/Sink.js +38 -17
  59. package/dist/Sink.js.map +1 -1
  60. package/dist/Stream.d.ts +11 -0
  61. package/dist/Stream.d.ts.map +1 -1
  62. package/dist/Stream.js +16 -1
  63. package/dist/Stream.js.map +1 -1
  64. package/dist/String.d.ts +12 -0
  65. package/dist/String.d.ts.map +1 -1
  66. package/dist/String.js +17 -4
  67. package/dist/String.js.map +1 -1
  68. package/dist/TxRef.d.ts.map +1 -1
  69. package/dist/TxRef.js +3 -2
  70. package/dist/TxRef.js.map +1 -1
  71. package/dist/Utils.js +8 -3
  72. package/dist/Utils.js.map +1 -1
  73. package/dist/ai/AiError.d.ts +10 -10
  74. package/dist/ai/AiError.d.ts.map +1 -1
  75. package/dist/ai/AiError.js +1 -1
  76. package/dist/ai/AiError.js.map +1 -1
  77. package/dist/ai/AnthropicStructuredOutput.js +0 -2
  78. package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
  79. package/dist/ai/Chat.d.ts +2 -2
  80. package/dist/ai/Chat.d.ts.map +1 -1
  81. package/dist/ai/Decision.d.ts +70 -19
  82. package/dist/ai/Decision.d.ts.map +1 -1
  83. package/dist/ai/Decision.js +37 -3
  84. package/dist/ai/Decision.js.map +1 -1
  85. package/dist/ai/DecisionModel.js +1 -1
  86. package/dist/ai/DecisionModel.js.map +1 -1
  87. package/dist/ai/LanguageModel.d.ts +38 -7
  88. package/dist/ai/LanguageModel.d.ts.map +1 -1
  89. package/dist/ai/LanguageModel.js +70 -40
  90. package/dist/ai/LanguageModel.js.map +1 -1
  91. package/dist/ai/McpServer.d.ts +61 -52
  92. package/dist/ai/McpServer.d.ts.map +1 -1
  93. package/dist/ai/McpServer.js +134 -50
  94. package/dist/ai/McpServer.js.map +1 -1
  95. package/dist/ai/Prompt.d.ts.map +1 -1
  96. package/dist/ai/Prompt.js +28 -5
  97. package/dist/ai/Prompt.js.map +1 -1
  98. package/dist/ai/Response.d.ts +5 -4
  99. package/dist/ai/Response.d.ts.map +1 -1
  100. package/dist/ai/Response.js.map +1 -1
  101. package/dist/cli/Completions.d.ts +5 -0
  102. package/dist/cli/Completions.d.ts.map +1 -1
  103. package/dist/cli/Completions.js.map +1 -1
  104. package/dist/cli/Flag.d.ts +100 -0
  105. package/dist/cli/Flag.d.ts.map +1 -1
  106. package/dist/cli/Flag.js +33 -0
  107. package/dist/cli/Flag.js.map +1 -1
  108. package/dist/cli/Param.d.ts +87 -0
  109. package/dist/cli/Param.d.ts.map +1 -1
  110. package/dist/cli/Param.js +30 -0
  111. package/dist/cli/Param.js.map +1 -1
  112. package/dist/cli/internal/command.d.ts.map +1 -1
  113. package/dist/cli/internal/command.js +2 -1
  114. package/dist/cli/internal/command.js.map +1 -1
  115. package/dist/cli/internal/completions/bash.js +4 -3
  116. package/dist/cli/internal/completions/bash.js.map +1 -1
  117. package/dist/cli/internal/completions/descriptor.js +8 -1
  118. package/dist/cli/internal/completions/descriptor.js.map +1 -1
  119. package/dist/cli/internal/completions/fish.js +4 -4
  120. package/dist/cli/internal/completions/fish.js.map +1 -1
  121. package/dist/cli/internal/completions/powershell.js +3 -2
  122. package/dist/cli/internal/completions/powershell.js.map +1 -1
  123. package/dist/cli/internal/completions/zsh.js +7 -3
  124. package/dist/cli/internal/completions/zsh.js.map +1 -1
  125. package/dist/cli/internal/parser.js +11 -3
  126. package/dist/cli/internal/parser.js.map +1 -1
  127. package/dist/cli/internal/wizard.d.ts.map +1 -1
  128. package/dist/cli/internal/wizard.js +2 -1
  129. package/dist/cli/internal/wizard.js.map +1 -1
  130. package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
  131. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  132. package/dist/cluster/ClusterWorkflowEngine.js +113 -21
  133. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  134. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  135. package/dist/cluster/MessageStorage.js +20 -12
  136. package/dist/cluster/MessageStorage.js.map +1 -1
  137. package/dist/cluster/Runners.d.ts.map +1 -1
  138. package/dist/cluster/Runners.js +54 -34
  139. package/dist/cluster/Runners.js.map +1 -1
  140. package/dist/cluster/Sharding.d.ts.map +1 -1
  141. package/dist/cluster/Sharding.js +6 -4
  142. package/dist/cluster/Sharding.js.map +1 -1
  143. package/dist/cluster/internal/entityManager.js +1 -1
  144. package/dist/cluster/internal/entityManager.js.map +1 -1
  145. package/dist/encoding/Sse.d.ts +6 -0
  146. package/dist/encoding/Sse.d.ts.map +1 -1
  147. package/dist/encoding/Sse.js +63 -71
  148. package/dist/encoding/Sse.js.map +1 -1
  149. package/dist/graphql/GraphQL.d.ts +349 -0
  150. package/dist/graphql/GraphQL.d.ts.map +1 -0
  151. package/dist/graphql/GraphQL.js +207 -0
  152. package/dist/graphql/GraphQL.js.map +1 -0
  153. package/dist/graphql/GraphQLClient.d.ts +562 -0
  154. package/dist/graphql/GraphQLClient.d.ts.map +1 -0
  155. package/dist/graphql/GraphQLClient.js +505 -0
  156. package/dist/graphql/GraphQLClient.js.map +1 -0
  157. package/dist/graphql/GraphQLClientError.d.ts +445 -0
  158. package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
  159. package/dist/graphql/GraphQLClientError.js +479 -0
  160. package/dist/graphql/GraphQLClientError.js.map +1 -0
  161. package/dist/graphql/GraphQLGroup.d.ts +118 -0
  162. package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
  163. package/dist/graphql/GraphQLGroup.js +109 -0
  164. package/dist/graphql/GraphQLGroup.js.map +1 -0
  165. package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
  166. package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
  167. package/dist/graphql/GraphQLMiddleware.js +130 -0
  168. package/dist/graphql/GraphQLMiddleware.js.map +1 -0
  169. package/dist/graphql/GraphQLProtocol.d.ts +969 -0
  170. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
  171. package/dist/graphql/GraphQLProtocol.js +809 -0
  172. package/dist/graphql/GraphQLProtocol.js.map +1 -0
  173. package/dist/graphql/GraphQLResolver.d.ts +453 -0
  174. package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
  175. package/dist/graphql/GraphQLResolver.js +582 -0
  176. package/dist/graphql/GraphQLResolver.js.map +1 -0
  177. package/dist/graphql/index.d.ts +40 -0
  178. package/dist/graphql/index.d.ts.map +1 -0
  179. package/dist/graphql/index.js +41 -0
  180. package/dist/graphql/index.js.map +1 -0
  181. package/dist/http/FetchHttpClient.d.ts +6 -3
  182. package/dist/http/FetchHttpClient.d.ts.map +1 -1
  183. package/dist/http/FetchHttpClient.js +6 -3
  184. package/dist/http/FetchHttpClient.js.map +1 -1
  185. package/dist/http/FindMyWay/internal/router.js +2 -1
  186. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  187. package/dist/http/Headers.d.ts +3 -3
  188. package/dist/http/Headers.d.ts.map +1 -1
  189. package/dist/http/Headers.js +6 -5
  190. package/dist/http/Headers.js.map +1 -1
  191. package/dist/http/HttpClient.d.ts +27 -0
  192. package/dist/http/HttpClient.d.ts.map +1 -1
  193. package/dist/http/HttpClient.js +38 -11
  194. package/dist/http/HttpClient.js.map +1 -1
  195. package/dist/http/HttpClientError.d.ts.map +1 -1
  196. package/dist/http/HttpClientError.js +8 -6
  197. package/dist/http/HttpClientError.js.map +1 -1
  198. package/dist/http/HttpClientRequest.d.ts +147 -4
  199. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  200. package/dist/http/HttpClientRequest.js +47 -1
  201. package/dist/http/HttpClientRequest.js.map +1 -1
  202. package/dist/http/HttpEffect.d.ts.map +1 -1
  203. package/dist/http/HttpEffect.js +14 -7
  204. package/dist/http/HttpEffect.js.map +1 -1
  205. package/dist/http/HttpMiddleware.d.ts +60 -0
  206. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  207. package/dist/http/HttpMiddleware.js +93 -32
  208. package/dist/http/HttpMiddleware.js.map +1 -1
  209. package/dist/http/HttpRouter.d.ts +35 -0
  210. package/dist/http/HttpRouter.d.ts.map +1 -1
  211. package/dist/http/HttpRouter.js +87 -13
  212. package/dist/http/HttpRouter.js.map +1 -1
  213. package/dist/http/HttpServerError.d.ts.map +1 -1
  214. package/dist/http/HttpServerError.js +6 -4
  215. package/dist/http/HttpServerError.js.map +1 -1
  216. package/dist/http/HttpServerRequest.js +2 -2
  217. package/dist/http/HttpServerRequest.js.map +1 -1
  218. package/dist/http/HttpServerResponse.d.ts +8 -6
  219. package/dist/http/HttpServerResponse.d.ts.map +1 -1
  220. package/dist/http/HttpServerResponse.js +27 -16
  221. package/dist/http/HttpServerResponse.js.map +1 -1
  222. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  223. package/dist/http/HttpStaticServer.js +4 -10
  224. package/dist/http/HttpStaticServer.js.map +1 -1
  225. package/dist/http/HttpTraceContext.js +1 -1
  226. package/dist/http/HttpTraceContext.js.map +1 -1
  227. package/dist/http/Multipart.d.ts.map +1 -1
  228. package/dist/http/Multipart.js +15 -1
  229. package/dist/http/Multipart.js.map +1 -1
  230. package/dist/http/Url.d.ts.map +1 -1
  231. package/dist/http/Url.js +2 -1
  232. package/dist/http/Url.js.map +1 -1
  233. package/dist/http/UrlParams.d.ts +112 -8
  234. package/dist/http/UrlParams.d.ts.map +1 -1
  235. package/dist/http/UrlParams.js +141 -14
  236. package/dist/http/UrlParams.js.map +1 -1
  237. package/dist/http/internal/compression.d.ts +1 -1
  238. package/dist/http/internal/compression.d.ts.map +1 -1
  239. package/dist/http/internal/compression.js +2 -10
  240. package/dist/http/internal/compression.js.map +1 -1
  241. package/dist/http/internal/compressionBody.d.ts +2 -0
  242. package/dist/http/internal/compressionBody.d.ts.map +1 -0
  243. package/dist/http/internal/compressionBody.js +36 -0
  244. package/dist/http/internal/compressionBody.js.map +1 -0
  245. package/dist/http/internal/decompression.d.ts +2 -0
  246. package/dist/http/internal/decompression.d.ts.map +1 -0
  247. package/dist/http/internal/decompression.js +162 -0
  248. package/dist/http/internal/decompression.js.map +1 -0
  249. package/dist/http/internal/tracing.js +14 -4
  250. package/dist/http/internal/tracing.js.map +1 -1
  251. package/dist/http/internal/urlParams.d.ts +2 -0
  252. package/dist/http/internal/urlParams.d.ts.map +1 -0
  253. package/dist/http/internal/urlParams.js +4 -0
  254. package/dist/http/internal/urlParams.js.map +1 -0
  255. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  256. package/dist/http-api/HttpApiClient.js +2 -1
  257. package/dist/http-api/HttpApiClient.js.map +1 -1
  258. package/dist/http-api/HttpApiScalar.d.ts +5 -0
  259. package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
  260. package/dist/http-api/HttpApiScalar.js.map +1 -1
  261. package/dist/internal/core.js +17 -1
  262. package/dist/internal/core.js.map +1 -1
  263. package/dist/internal/dateTime.js +40 -35
  264. package/dist/internal/dateTime.js.map +1 -1
  265. package/dist/internal/effect.js +13 -6
  266. package/dist/internal/effect.js.map +1 -1
  267. package/dist/internal/graphql.d.ts +2 -0
  268. package/dist/internal/graphql.d.ts.map +1 -0
  269. package/dist/internal/graphql.js +205 -0
  270. package/dist/internal/graphql.js.map +1 -0
  271. package/dist/internal/graphqlHeaders.d.ts +4 -0
  272. package/dist/internal/graphqlHeaders.d.ts.map +1 -0
  273. package/dist/internal/graphqlHeaders.js +11 -0
  274. package/dist/internal/graphqlHeaders.js.map +1 -0
  275. package/dist/internal/request.js +20 -1
  276. package/dist/internal/request.js.map +1 -1
  277. package/dist/internal/schema/toCodeDocument.js +4 -1
  278. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  279. package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
  280. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  281. package/dist/internal/version.js +1 -1
  282. package/dist/net/Host.d.ts +49 -0
  283. package/dist/net/Host.d.ts.map +1 -1
  284. package/dist/net/Host.js +55 -0
  285. package/dist/net/Host.js.map +1 -1
  286. package/dist/net/NetAddress.d.ts +8 -0
  287. package/dist/net/NetAddress.d.ts.map +1 -1
  288. package/dist/net/NetAddress.js +8 -0
  289. package/dist/net/NetAddress.js.map +1 -1
  290. package/dist/observability/Otlp.d.ts +15 -0
  291. package/dist/observability/Otlp.d.ts.map +1 -1
  292. package/dist/observability/Otlp.js +3 -0
  293. package/dist/observability/Otlp.js.map +1 -1
  294. package/dist/observability/OtlpExporter.d.ts +8 -0
  295. package/dist/observability/OtlpExporter.d.ts.map +1 -1
  296. package/dist/observability/OtlpExporter.js +10 -1
  297. package/dist/observability/OtlpExporter.js.map +1 -1
  298. package/dist/observability/OtlpLogger.d.ts +17 -0
  299. package/dist/observability/OtlpLogger.d.ts.map +1 -1
  300. package/dist/observability/OtlpLogger.js +14 -1
  301. package/dist/observability/OtlpLogger.js.map +1 -1
  302. package/dist/observability/OtlpMetrics.d.ts +17 -0
  303. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  304. package/dist/observability/OtlpMetrics.js +13 -0
  305. package/dist/observability/OtlpMetrics.js.map +1 -1
  306. package/dist/observability/OtlpTracer.d.ts +17 -0
  307. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  308. package/dist/observability/OtlpTracer.js +15 -2
  309. package/dist/observability/OtlpTracer.js.map +1 -1
  310. package/dist/reactivity/AsyncResult.d.ts +6 -0
  311. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  312. package/dist/reactivity/AsyncResult.js +27 -0
  313. package/dist/reactivity/AsyncResult.js.map +1 -1
  314. package/dist/reactivity/AtomRegistry.js +3 -1
  315. package/dist/reactivity/AtomRegistry.js.map +1 -1
  316. package/dist/rpc/RpcClient.d.ts.map +1 -1
  317. package/dist/rpc/RpcClient.js +6 -1
  318. package/dist/rpc/RpcClient.js.map +1 -1
  319. package/dist/rpc/RpcServer.d.ts.map +1 -1
  320. package/dist/rpc/RpcServer.js +36 -9
  321. package/dist/rpc/RpcServer.js.map +1 -1
  322. package/dist/socket/Socket.d.ts +6 -0
  323. package/dist/socket/Socket.d.ts.map +1 -1
  324. package/dist/socket/Socket.js.map +1 -1
  325. package/dist/sql/SqlClient.d.ts +8 -7
  326. package/dist/sql/SqlClient.d.ts.map +1 -1
  327. package/dist/sql/SqlClient.js +8 -7
  328. package/dist/sql/SqlClient.js.map +1 -1
  329. package/dist/testing/TestClock.d.ts +16 -3
  330. package/dist/testing/TestClock.d.ts.map +1 -1
  331. package/dist/testing/TestClock.js +112 -22
  332. package/dist/testing/TestClock.js.map +1 -1
  333. package/dist/workflow/Activity.js +1 -0
  334. package/dist/workflow/Activity.js.map +1 -1
  335. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  336. package/dist/workflow/DurableDeferred.js +32 -3
  337. package/dist/workflow/DurableDeferred.js.map +1 -1
  338. package/dist/workflow/Workflow.d.ts +22 -0
  339. package/dist/workflow/Workflow.d.ts.map +1 -1
  340. package/dist/workflow/Workflow.js +15 -3
  341. package/dist/workflow/Workflow.js.map +1 -1
  342. package/dist/workflow/WorkflowEngine.d.ts +44 -3
  343. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  344. package/dist/workflow/WorkflowEngine.js +45 -22
  345. package/dist/workflow/WorkflowEngine.js.map +1 -1
  346. package/package.json +6 -3
  347. package/src/Array.ts +95 -6
  348. package/src/Channel.ts +24 -3
  349. package/src/ConfigProvider.ts +40 -44
  350. package/src/Context.ts +9 -0
  351. package/src/Duration.ts +4 -5
  352. package/src/Effect.ts +140 -53
  353. package/src/FileSystem.ts +23 -9
  354. package/src/Match.ts +65 -12
  355. package/src/Path.ts +52 -7
  356. package/src/RequestResolver.ts +4 -10
  357. package/src/Scheduler.ts +9 -0
  358. package/src/Schema.ts +25 -4
  359. package/src/SchemaGetter.ts +47 -35
  360. package/src/Sink.ts +45 -18
  361. package/src/Stream.ts +33 -4
  362. package/src/String.ts +17 -4
  363. package/src/TxRef.ts +4 -2
  364. package/src/Utils.ts +8 -5
  365. package/src/ai/AiError.ts +3 -1
  366. package/src/ai/AnthropicStructuredOutput.ts +2 -2
  367. package/src/ai/Chat.ts +2 -2
  368. package/src/ai/Decision.ts +70 -21
  369. package/src/ai/DecisionModel.ts +1 -1
  370. package/src/ai/LanguageModel.ts +141 -63
  371. package/src/ai/McpServer.ts +259 -145
  372. package/src/ai/Prompt.ts +24 -10
  373. package/src/ai/Response.ts +6 -3
  374. package/src/cli/Completions.ts +5 -0
  375. package/src/cli/Flag.ts +101 -0
  376. package/src/cli/Param.ts +96 -0
  377. package/src/cli/internal/command.ts +2 -1
  378. package/src/cli/internal/completions/bash.ts +4 -3
  379. package/src/cli/internal/completions/descriptor.ts +10 -1
  380. package/src/cli/internal/completions/fish.ts +4 -4
  381. package/src/cli/internal/completions/powershell.ts +5 -2
  382. package/src/cli/internal/completions/zsh.ts +7 -3
  383. package/src/cli/internal/parser.ts +12 -3
  384. package/src/cli/internal/wizard.ts +2 -1
  385. package/src/cluster/ClusterWorkflowEngine.ts +143 -36
  386. package/src/cluster/MessageStorage.ts +14 -3
  387. package/src/cluster/Runners.ts +88 -66
  388. package/src/cluster/Sharding.ts +12 -9
  389. package/src/cluster/internal/entityManager.ts +1 -1
  390. package/src/encoding/Sse.ts +70 -80
  391. package/src/graphql/GraphQL.ts +494 -0
  392. package/src/graphql/GraphQLClient.ts +924 -0
  393. package/src/graphql/GraphQLClientError.ts +542 -0
  394. package/src/graphql/GraphQLGroup.ts +167 -0
  395. package/src/graphql/GraphQLMiddleware.ts +253 -0
  396. package/src/graphql/GraphQLProtocol.ts +1540 -0
  397. package/src/graphql/GraphQLResolver.ts +1038 -0
  398. package/src/graphql/index.ts +48 -0
  399. package/src/http/FetchHttpClient.ts +6 -3
  400. package/src/http/FindMyWay/internal/router.ts +2 -1
  401. package/src/http/Headers.ts +6 -9
  402. package/src/http/HttpClient.ts +72 -11
  403. package/src/http/HttpClientError.ts +10 -6
  404. package/src/http/HttpClientRequest.ts +173 -7
  405. package/src/http/HttpEffect.ts +11 -5
  406. package/src/http/HttpMiddleware.ts +113 -34
  407. package/src/http/HttpRouter.ts +95 -13
  408. package/src/http/HttpServerError.ts +8 -4
  409. package/src/http/HttpServerRequest.ts +2 -2
  410. package/src/http/HttpServerResponse.ts +28 -16
  411. package/src/http/HttpStaticServer.ts +4 -10
  412. package/src/http/HttpTraceContext.ts +1 -1
  413. package/src/http/Multipart.ts +17 -1
  414. package/src/http/Url.ts +2 -1
  415. package/src/http/UrlParams.ts +225 -26
  416. package/src/http/internal/compression.ts +3 -17
  417. package/src/http/internal/compressionBody.ts +56 -0
  418. package/src/http/internal/decompression.ts +226 -0
  419. package/src/http/internal/tracing.ts +21 -4
  420. package/src/http/internal/urlParams.ts +5 -0
  421. package/src/http-api/HttpApiClient.ts +2 -1
  422. package/src/http-api/HttpApiScalar.ts +5 -0
  423. package/src/internal/core.ts +21 -1
  424. package/src/internal/dateTime.ts +13 -10
  425. package/src/internal/effect.ts +13 -7
  426. package/src/internal/graphql.ts +286 -0
  427. package/src/internal/graphqlHeaders.ts +11 -0
  428. package/src/internal/request.ts +31 -3
  429. package/src/internal/schema/toCodeDocument.ts +7 -1
  430. package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
  431. package/src/internal/version.ts +1 -1
  432. package/src/net/Host.ts +72 -0
  433. package/src/net/NetAddress.ts +9 -0
  434. package/src/observability/Otlp.ts +18 -0
  435. package/src/observability/OtlpExporter.ts +14 -3
  436. package/src/observability/OtlpLogger.ts +31 -2
  437. package/src/observability/OtlpMetrics.ts +30 -1
  438. package/src/observability/OtlpTracer.ts +32 -3
  439. package/src/reactivity/AsyncResult.ts +31 -0
  440. package/src/reactivity/AtomRegistry.ts +3 -1
  441. package/src/rpc/RpcClient.ts +6 -1
  442. package/src/rpc/RpcServer.ts +55 -15
  443. package/src/socket/Socket.ts +6 -0
  444. package/src/sql/SqlClient.ts +9 -7
  445. package/src/testing/TestClock.ts +144 -29
  446. package/src/workflow/Activity.ts +1 -0
  447. package/src/workflow/DurableDeferred.ts +43 -5
  448. package/src/workflow/Workflow.ts +31 -5
  449. package/src/workflow/WorkflowEngine.ts +107 -25
@@ -0,0 +1,286 @@
1
+ import { isReadonlyArrayNonEmpty, type NonEmptyReadonlyArray } from "../Array.ts"
2
+ import * as Context from "../Context.ts"
3
+ import * as Effect from "../Effect.ts"
4
+ import type * as GraphQL from "../graphql/GraphQL.ts"
5
+ import type { DocumentTemplate } from "../graphql/GraphQL.ts"
6
+ import type { PartialResult } from "../graphql/GraphQLClient.ts"
7
+ import {
8
+ DecodeError,
9
+ EncodeError,
10
+ GraphQLClientError,
11
+ type GraphQLError,
12
+ type Reason,
13
+ ResponseError
14
+ } from "../graphql/GraphQLClientError.ts"
15
+ import type { ExecutionResult, GraphQLProtocol, GraphQLRequest } from "../graphql/GraphQLProtocol.ts"
16
+ import * as Schema from "../Schema.ts"
17
+
18
+ /**
19
+ * Builds a response error with raw data and transport metadata.
20
+ *
21
+ * @internal
22
+ */
23
+ export const responseError = (
24
+ result: ExecutionResult | undefined,
25
+ errors: NonEmptyReadonlyArray<GraphQLError>
26
+ ): ResponseError =>
27
+ new ResponseError({
28
+ errors,
29
+ ...(result?.data === undefined ? {} : { data: result.data as Schema.Json }),
30
+ ...(result?.extensions === undefined ? {} : { extensions: result.extensions }),
31
+ ...(result?.status === undefined ? {} : { status: result.status }),
32
+ ...(result?.headers === undefined ? {} : { headers: result.headers })
33
+ })
34
+
35
+ /**
36
+ * Raw responses for partial results, used by resolvers to preserve error
37
+ * metadata. Mock client results have no entry.
38
+ *
39
+ * @internal
40
+ */
41
+ export const partialResponses = new WeakMap<object, ExecutionResult>()
42
+
43
+ /**
44
+ * Joins two printed pieces, adding a space only where two non-punctuators
45
+ * would otherwise touch. This is the generator printer's rule, so joining a
46
+ * generated template reproduces the printed document exactly.
47
+ */
48
+ const concat = (left: string, right: string): string =>
49
+ /[\w"-]$/.test(left) && /^[\w"-]/.test(right) ? `${left} ${right}` : left + right
50
+
51
+ const concatAll = (pieces: ReadonlyArray<string>): string => pieces.reduce(concat, "")
52
+
53
+ /** Renders template parts, prefixing every name (the odd entries). */
54
+ const renderParts = (parts: DocumentTemplate.Parts, prefix: string): string =>
55
+ parts.map((part, i) => i % 2 === 1 ? prefix + part : part).join("")
56
+
57
+ const isShared = (fragment: DocumentTemplate.Fragment): boolean => fragment.body.length <= 1
58
+
59
+ /**
60
+ * Renders the template once per prefix. The empty prefix renders it as
61
+ * written; any other prefix renames variables and per-slice fragments and
62
+ * aliases root fields to the prefixed response key.
63
+ */
64
+ const render = (
65
+ kind: string,
66
+ name: string,
67
+ template: DocumentTemplate,
68
+ prefixes: ReadonlyArray<string>
69
+ ): string =>
70
+ concatAll([
71
+ kind,
72
+ name,
73
+ template.variables.length === 0 ? "" : `(${
74
+ concatAll(
75
+ prefixes.flatMap((prefix) =>
76
+ template.variables.map((variable) => `$${prefix}${variable.name}:${variable.body}`)
77
+ )
78
+ )
79
+ })`,
80
+ template.directives === undefined ? "" : renderParts(template.directives, ""),
81
+ `{${
82
+ concatAll(prefixes.flatMap((prefix) =>
83
+ template.selections.map((selection) => {
84
+ const key = prefix === "" ? selection.alias : `${prefix}${selection.alias ?? selection.name}`
85
+ return `${key === undefined ? "" : `${key}:`}${selection.name ?? ""}${renderParts(selection.body, prefix)}`
86
+ })
87
+ ))
88
+ }}`,
89
+ ...template.fragments.flatMap((fragment) =>
90
+ (isShared(fragment) ? [""] : prefixes).map((prefix) =>
91
+ concatAll(["fragment", prefix + fragment.name, renderParts(fragment.body, prefix)])
92
+ )
93
+ )
94
+ ])
95
+
96
+ /**
97
+ * Joins a template into the document it was split from.
98
+ *
99
+ * @internal
100
+ */
101
+ export const joinTemplate = (kind: string, name: string, template: DocumentTemplate): string =>
102
+ render(kind, name, template, [""])
103
+
104
+ /**
105
+ * Why the template can't be sliced for any prefixes, if it can't: an operation
106
+ * directive references a variable, or a root selection is a fragment spread or
107
+ * inline fragment.
108
+ *
109
+ * @internal
110
+ */
111
+ export const templateUnsliceableReason = (template: DocumentTemplate): string | undefined => {
112
+ if (template.directives !== undefined && template.directives.length > 1) {
113
+ return "its operation directives reference variables"
114
+ }
115
+ if (template.selections.some((selection) => selection.name === undefined)) {
116
+ return "it has a fragment spread or inline fragment at the root"
117
+ }
118
+ return undefined
119
+ }
120
+
121
+ /**
122
+ * Renders one operation that repeats the template once per prefix. Each
123
+ * slice's variables, root response keys and per-slice fragments are renamed
124
+ * with its prefix; operation directives and shared fragments appear once.
125
+ * Prefixes must start a GraphQL name, and none may be a prefix of another.
126
+ *
127
+ * Throws rather than producing an invalid document when the template is
128
+ * unsliceable (see {@link templateUnsliceableReason}) or a renamed fragment
129
+ * would take a shared fragment's name.
130
+ *
131
+ * @internal
132
+ */
133
+ export const renderSlices = (
134
+ kind: string,
135
+ name: string,
136
+ template: DocumentTemplate,
137
+ prefixes: NonEmptyReadonlyArray<string>
138
+ ): string => {
139
+ const shared = new Set(template.fragments.filter(isShared).map((fragment) => fragment.name))
140
+ const collision = template.fragments
141
+ .flatMap((fragment) => isShared(fragment) ? [] : prefixes.map((prefix) => prefix + fragment.name))
142
+ .find((renamed) => shared.has(renamed))
143
+ const reason = templateUnsliceableReason(template) ??
144
+ (collision === undefined ? undefined : `fragment ${collision} would collide with a shared fragment`)
145
+ if (reason !== undefined) throw new Error(`Cannot render slices of operation ${name}: ${reason}`)
146
+ return render(kind, name, template, prefixes)
147
+ }
148
+
149
+ /**
150
+ * Forces POST for alias batches to avoid GET URL length limits.
151
+ *
152
+ * @internal
153
+ */
154
+ export const ForcePost = Context.Reference<boolean>("effect/graphql/ForcePost", { defaultValue: () => false })
155
+
156
+ /**
157
+ * The URL a resolver batch was grouped by, and the protocol `url` that
158
+ * resolved it, so that transport sends the batch there without resolving it
159
+ * again.
160
+ *
161
+ * @internal
162
+ */
163
+ export type ResolvedUrl = {
164
+ readonly resolve: GraphQLProtocol["Service"]["url"]
165
+ readonly url: string
166
+ } | undefined
167
+
168
+ /**
169
+ * Carries a {@link ResolvedUrl} from a resolver batch to the transport. The
170
+ * client clears it while middleware run, so calls they make resolve their
171
+ * own URL.
172
+ *
173
+ * @internal
174
+ */
175
+ export const ResolvedUrl = Context.Reference<ResolvedUrl>("effect/graphql/ResolvedUrl", {
176
+ defaultValue: () => undefined
177
+ })
178
+
179
+ /**
180
+ * JSON with object keys sorted, so equal JSON values give equal strings.
181
+ *
182
+ * @internal
183
+ */
184
+ export const canonicalJson = (value: unknown): string =>
185
+ JSON.stringify(value, (_, value) =>
186
+ value !== null && typeof value === "object" && !Array.isArray(value)
187
+ ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, value[key]]))
188
+ : value)
189
+
190
+ /**
191
+ * Shared variable encoding, result decoding and client error mapping.
192
+ *
193
+ * @internal
194
+ */
195
+ export const makeCodec = <Op extends GraphQL.Any>(operation: Op) => {
196
+ const fail = (reason: Reason) => new GraphQLClientError({ operation: operation.name, reason })
197
+ const schemaFail = (make: (description: string) => Reason) => (error: Schema.SchemaError) =>
198
+ Effect.fail(fail(make(error.message)))
199
+ const encode = Schema.encodeEffect(operation.variables)
200
+ const decode = Schema.decodeUnknownEffect(operation.result)
201
+
202
+ const encodeVariables = (
203
+ variables: GraphQL.Variables<Op> | undefined
204
+ ): Effect.Effect<unknown, GraphQLClientError, Op["variables"]["EncodingServices"]> =>
205
+ Effect.catch(encode(variables ?? {}), schemaFail((description) => new EncodeError({ description })))
206
+
207
+ const decodeData = (
208
+ data: unknown
209
+ ): Effect.Effect<GraphQL.Result<Op>, GraphQLClientError, Op["result"]["DecodingServices"]> =>
210
+ Effect.catch(decode(data), schemaFail((description) => new DecodeError({ description })))
211
+
212
+ // GraphQL requires errors when data is null or missing. Check before decoding,
213
+ // since schemas such as Schema.Json accept null.
214
+ const failResponse = (result: ExecutionResult): Effect.Effect<never, GraphQLClientError> => {
215
+ const errors = result.errors ?? []
216
+ return Effect.fail(fail(
217
+ isReadonlyArrayNonEmpty(errors)
218
+ ? responseError(result, errors)
219
+ : new DecodeError({ description: "data is null or missing without errors" })
220
+ ))
221
+ }
222
+
223
+ const decodeStrict = (
224
+ result: ExecutionResult
225
+ ): Effect.Effect<GraphQL.Result<Op>, GraphQLClientError, Op["result"]["DecodingServices"]> =>
226
+ isReadonlyArrayNonEmpty(result.errors ?? []) || result.data === null || result.data === undefined
227
+ ? failResponse(result)
228
+ : decodeData(result.data)
229
+
230
+ const decodePartial = (
231
+ result: ExecutionResult
232
+ ): Effect.Effect<PartialResult<GraphQL.Result<Op>>, GraphQLClientError, Op["result"]["DecodingServices"]> => {
233
+ if (result.data === null || result.data === undefined) return failResponse(result)
234
+ return Effect.map(decodeData(result.data), (data): PartialResult<GraphQL.Result<Op>> => {
235
+ const partial = { data, errors: result.errors ?? [] }
236
+ partialResponses.set(partial, result)
237
+ return partial
238
+ })
239
+ }
240
+
241
+ return { fail, schemaFail, encodeVariables, failResponse, decodeStrict, decodePartial } as const
242
+ }
243
+
244
+ /**
245
+ * Client batch runners (middleware and protocol, using POST) and URLs for
246
+ * resolver grouping. Mock and custom methods are not registered.
247
+ *
248
+ * @internal
249
+ */
250
+ export const rawExecutors = new WeakMap<object, {
251
+ readonly execute: (request: GraphQLRequest) => Effect.Effect<ExecutionResult, unknown, unknown>
252
+ readonly url: GraphQLProtocol["Service"]["url"]
253
+ }>()
254
+
255
+ /**
256
+ * Splits a batched response, removing prefixes from root keys and error paths.
257
+ * Returns undefined if an error matches no slice or data is null or missing.
258
+ *
259
+ * @internal
260
+ */
261
+ export const splitSlices = (
262
+ result: ExecutionResult,
263
+ template: DocumentTemplate,
264
+ prefixes: ReadonlyArray<string>
265
+ ): ReadonlyArray<ExecutionResult> | undefined => {
266
+ const sliceErrors = prefixes.map((): Array<GraphQLError> => [])
267
+ for (const error of result.errors ?? []) {
268
+ const key = error.path?.[0]
269
+ // No prefix is a prefix of another, so at most one matches.
270
+ const index = typeof key === "string" ? prefixes.findIndex((prefix) => key.startsWith(prefix)) : -1
271
+ if (index === -1) return undefined
272
+ sliceErrors[index].push(
273
+ { ...error, path: [(key as string).slice(prefixes[index].length), ...error.path!.slice(1)] } as GraphQLError
274
+ )
275
+ }
276
+ if (result.data === null || result.data === undefined) return undefined
277
+ const data = result.data as Record<string, unknown>
278
+ return prefixes.map((prefix, i) => {
279
+ const sliceData: Record<string, unknown> = {}
280
+ for (const selection of template.selections) {
281
+ const key = (selection.alias ?? selection.name)!
282
+ if (Object.hasOwn(data, prefix + key)) sliceData[key] = data[prefix + key]
283
+ }
284
+ return { ...result, data: sliceData, errors: sliceErrors[i] }
285
+ })
286
+ }
@@ -0,0 +1,11 @@
1
+ import * as Headers from "../http/Headers.ts"
2
+ import * as Schema from "../Schema.ts"
3
+ import * as SchemaGetter from "../SchemaGetter.ts"
4
+
5
+ // Keep headers branded at runtime while encoding only their string entries.
6
+ export const HeadersSchema = Schema.Record(Schema.String, Schema.String).pipe(
7
+ Schema.decodeTo(Schema.declare<Headers.Headers>(Headers.isHeaders), {
8
+ decode: SchemaGetter.transform(Headers.fromInput),
9
+ encode: SchemaGetter.transform((headers) => ({ ...headers }))
10
+ })
11
+ )
@@ -4,6 +4,8 @@ import type { Effect } from "../Effect.ts"
4
4
  import type { Exit } from "../Exit.ts"
5
5
  import type { Fiber } from "../Fiber.ts"
6
6
  import { dual } from "../Function.ts"
7
+ import * as MutableHashMap from "../MutableHashMap.ts"
8
+ import * as Option from "../Option.ts"
7
9
  import type * as Request from "../Request.ts"
8
10
  import { makeEntry } from "../Request.ts"
9
11
  import type { RequestResolver } from "../RequestResolver.ts"
@@ -67,10 +69,36 @@ export const requestUnsafe = <A extends Request.Any>(
67
69
  return () => removeEntryUnsafe(options.resolver, entry)
68
70
  }
69
71
 
72
+ /**
73
+ * Pending batches of resolvers whose `batchKey` is in this set match keys with
74
+ * `Equal` instead of by reference.
75
+ *
76
+ * @internal
77
+ */
78
+ export const equalBatchKeys = new WeakSet<Function>()
79
+
80
+ interface BatchMap {
81
+ get(key: unknown): Batch | undefined
82
+ has(key: unknown): boolean
83
+ set(key: unknown, batch: Batch): void
84
+ delete(key: unknown): void
85
+ }
86
+
87
+ const makeBatchMap = (resolver: RequestResolver<any>): BatchMap => {
88
+ if (!equalBatchKeys.has(resolver.batchKey)) return new Map()
89
+ const map = MutableHashMap.empty<unknown, Batch>()
90
+ return {
91
+ get: (key) => Option.getOrUndefined(MutableHashMap.get(map, key)),
92
+ has: (key) => MutableHashMap.has(map, key),
93
+ set: (key, batch) => MutableHashMap.set(map, key, batch),
94
+ delete: (key) => MutableHashMap.remove(map, key)
95
+ }
96
+ }
97
+
70
98
  interface Batch {
71
99
  key: unknown
72
100
  resolver: RequestResolver<any>
73
- map: Map<unknown, Batch>
101
+ map: BatchMap
74
102
  readonly entrySet: Set<Request.Entry<any>>
75
103
  readonly entries: Set<Request.Entry<any>>
76
104
  readonly delayEffect: Effect<void>
@@ -79,7 +107,7 @@ interface Batch {
79
107
  }
80
108
 
81
109
  const batchPool: Array<Batch> = []
82
- const pendingBatches = new WeakMap<RequestResolver<any>, Map<unknown, Batch>>()
110
+ const pendingBatches = new WeakMap<RequestResolver<any>, BatchMap>()
83
111
 
84
112
  const addEntry = <A extends Request.Any>(
85
113
  resolver: RequestResolver<A>,
@@ -93,7 +121,7 @@ const addEntry = <A extends Request.Any>(
93
121
  ) => {
94
122
  let batchMap = pendingBatches.get(resolver)
95
123
  if (!batchMap) {
96
- batchMap = new Map<object, Batch>()
124
+ batchMap = makeBatchMap(resolver)
97
125
  pendingBatches.set(resolver, batchMap)
98
126
  }
99
127
  let batch: Batch | undefined
@@ -48,6 +48,8 @@ function renderEmittableAnnotation(input: unknown): string | undefined {
48
48
  return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`
49
49
  }
50
50
 
51
+ const canonicalFiniteCheck = `Schema.isFinite().annotate({ "expected": "a finite number" })`
52
+
51
53
  function renderAnnotations(
52
54
  annotations: Schema.Annotations.Annotations | undefined
53
55
  ): string | undefined {
@@ -341,7 +343,11 @@ export function toCodeDocument(
341
343
  let runtime = base.runtime + runtimeAnnotate(representation.annotations)
342
344
  for (let index = 0; index < representation.checks.length; index++) {
343
345
  const check = representation.checks[index]
344
- runtime += `.check(${compileCheck(check, [...path, "checks", index])})`
346
+ const compiledCheck = compileCheck(check, [...path, "checks", index])
347
+ // Only collapse a bare number's leading canonical check.
348
+ runtime = runtime === "Schema.Number" && compiledCheck === canonicalFiniteCheck
349
+ ? "Schema.Finite"
350
+ : `${runtime}.check(${compiledCheck})`
345
351
  }
346
352
  return makeCode(runtime, base.Type)
347
353
  }
@@ -121,6 +121,37 @@ function hasNoCollisions(left: JsonSchema.JsonSchema, rightKeys: ReadonlyArray<s
121
121
  return typeof left.$ref !== "string" && rightKeys.every((key) => !Object.hasOwn(left, key))
122
122
  }
123
123
 
124
+ // Every bound on one schema object applies, so a repeated bound reduces to its tighter value.
125
+ const lowerBoundKeywords = "|minimum|exclusiveMinimum|minLength|minItems|minProperties|"
126
+ const upperBoundKeywords = "|maximum|exclusiveMaximum|maxLength|maxItems|maxProperties|"
127
+
128
+ // Tightens repeated bounds and drops an inclusive or exclusive bound implied by the other on the same side. Returns
129
+ // `undefined` for a `$ref` schema or a non-bound collision, so the caller falls back to `allOf`.
130
+ function mergeInlineCheck(
131
+ left: JsonSchema.JsonSchema,
132
+ right: JsonSchema.JsonSchema,
133
+ rightKeys: ReadonlyArray<string>
134
+ ): JsonSchema.JsonSchema | undefined {
135
+ if (typeof left.$ref === "string") return undefined
136
+ const out: JsonSchema.JsonSchema = { ...left, ...right }
137
+ for (const key of rightKeys) {
138
+ if (!Object.hasOwn(left, key)) continue
139
+ const a = left[key]
140
+ const b = right[key]
141
+ if (typeof a !== "number" || typeof b !== "number") return undefined
142
+ if (lowerBoundKeywords.includes(`|${key}|`)) out[key] = Math.max(a, b)
143
+ else if (upperBoundKeywords.includes(`|${key}|`)) out[key] = Math.min(a, b)
144
+ else return undefined
145
+ }
146
+ if (typeof out.minimum === "number" && typeof out.exclusiveMinimum === "number") {
147
+ delete out[out.exclusiveMinimum >= out.minimum ? "minimum" : "exclusiveMinimum"]
148
+ }
149
+ if (typeof out.maximum === "number" && typeof out.exclusiveMaximum === "number") {
150
+ delete out[out.exclusiveMaximum <= out.maximum ? "maximum" : "exclusiveMaximum"]
151
+ }
152
+ return out
153
+ }
154
+
124
155
  // `format` and `content*` can affect validation, so they are not treated as pure annotations.
125
156
  const promotableAnnotationKeywords = "|title|description|default|examples|readOnly|writeOnly|"
126
157
  const inlineableAnnotatedCheckKeywords = inlineableCheckKeywords + promotableAnnotationKeywords
@@ -149,8 +180,9 @@ function appendJsonSchema(
149
180
  : appendJsonSchema(base, extracted.schema, inlineCheck)
150
181
  }
151
182
  }
152
- if (inlineCheck && hasNoCollisions(left, rightKeys)) {
153
- return { ...left, ...right }
183
+ if (inlineCheck) {
184
+ const merged = mergeInlineCheck(left, right, rightKeys)
185
+ if (merged !== undefined) return merged
154
186
  }
155
187
  const members = Array.isArray(right.allOf) && rightKeys.length === 1 ? right.allOf : [right]
156
188
  if (Array.isArray(left.allOf)) {
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = "4.0.3"
2
+ export const version = "4.0.5"
package/src/net/Host.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * @stability experimental
10
10
  * @since 4.0.0
11
11
  */
12
+ import type * as Arr from "../Array.ts"
12
13
  import type * as Brand from "../Brand.ts"
13
14
  import * as Equal from "../Equal.ts"
14
15
  import * as Hash from "../Hash.ts"
@@ -321,6 +322,28 @@ export const formatHost = (self: Host): string => typeof self === "string" ? sel
321
322
  */
322
323
  export const isFullyQualified = (self: DomainName): boolean => self.endsWith(".")
323
324
 
325
+ /**
326
+ * Appends the trailing dot to a domain name, unless it is already fully
327
+ * qualified.
328
+ *
329
+ * @stability experimental
330
+ * @category converting
331
+ * @since 4.0.0
332
+ */
333
+ export const toFullyQualified = (self: DomainName): DomainName =>
334
+ isFullyQualified(self) ? self : `${self}.` as DomainName
335
+
336
+ /**
337
+ * Removes the trailing dot of a fully qualified domain name, except from the
338
+ * root name `.`.
339
+ *
340
+ * @stability experimental
341
+ * @category converting
342
+ * @since 4.0.0
343
+ */
344
+ export const toRelative = (self: DomainName): DomainName =>
345
+ self.length > 1 && isFullyQualified(self) ? self.slice(0, -1) as DomainName : self
346
+
324
347
  const HostPortProto = {
325
348
  ...Inspectable.BaseProto,
326
349
  _tag: "HostPort",
@@ -470,3 +493,52 @@ export const formatHostPort = (self: HostPort): string => {
470
493
  const host = formatHost(self.host)
471
494
  return host.includes(":") ? `[${host}]:${self.port}` : `${host}:${self.port}`
472
495
  }
496
+
497
+ /**
498
+ * The addresses of host names, as read from a hosts file.
499
+ *
500
+ * @see {@link parseHostsFile} for reading a hosts file
501
+ * @stability experimental
502
+ * @category models
503
+ * @since 4.0.0
504
+ */
505
+ export type HostsTable = ReadonlyMap<DomainName, Arr.NonEmptyReadonlyArray<NetAddress.IpAddress>>
506
+
507
+ /**
508
+ * Parses a hosts file into the addresses of each host name. Invalid entries
509
+ * are skipped.
510
+ *
511
+ * **Example** (Parsing a hosts file)
512
+ *
513
+ * ```ts import.meta.vitest
514
+ * import { Host, NetAddress } from "effect/net"
515
+ *
516
+ * const hosts = Host.parseHostsFile(`
517
+ * 127.0.0.1 localhost
518
+ * ::1 localhost ip6-localhost # loopback
519
+ * `)
520
+ * hosts.get(Host.domainNameFromStringUnsafe("localhost"))?.map(NetAddress.formatIp) // => ["127.0.0.1", "::1"]
521
+ * ```
522
+ *
523
+ * @stability experimental
524
+ * @category decoding
525
+ * @since 4.0.0
526
+ */
527
+ export const parseHostsFile = (text: string): HostsTable => {
528
+ const hosts = new Map<DomainName, Array<NetAddress.IpAddress>>()
529
+ for (const line of text.split(/\r?\n/)) {
530
+ const comment = line.indexOf("#")
531
+ const [first, ...names] = (comment === -1 ? line : line.slice(0, comment)).trim().split(/\s+/)
532
+ const address = NetAddress.ipFromString(first)
533
+ if (Result.isFailure(address)) continue
534
+ for (const name of names) {
535
+ const domain = domainNameFromString(name)
536
+ if (Result.isFailure(domain)) continue
537
+ const key = toRelative(domain.success)
538
+ const addresses = hosts.get(key)
539
+ if (addresses === undefined) hosts.set(key, [address.success])
540
+ else if (!addresses.some((existing) => Equal.equals(existing, address.success))) addresses.push(address.success)
541
+ }
542
+ }
543
+ return hosts as unknown as HostsTable
544
+ }
@@ -864,6 +864,15 @@ export const ipv4Loopback: Ipv4Address = makeIpv4(0x7f000001)
864
864
  */
865
865
  export const ipv6Loopback: Ipv6Address = makeIpv6(0, 0, 0, 1)
866
866
 
867
+ /**
868
+ * The loopback addresses `127.0.0.1` and `::1`.
869
+ *
870
+ * @stability unstable
871
+ * @category constants
872
+ * @since 4.0.0
873
+ */
874
+ export const loopbackAddresses: readonly [Ipv4Address, Ipv6Address] = [ipv4Loopback, ipv6Loopback]
875
+
867
876
  /**
868
877
  * The unspecified IPv4 address `0.0.0.0`.
869
878
  *
@@ -42,6 +42,11 @@ export const layer = (options: {
42
42
  readonly attributes?: Record<string, unknown>
43
43
  } | undefined
44
44
  readonly headers?: Headers.Input | undefined
45
+ /**
46
+ * Request compression. Defaults to "none". Gzip uses
47
+ * HttpClientRequest's default minimum body size of 1KB.
48
+ */
49
+ readonly compression?: "gzip" | "none" | undefined
45
50
  readonly maxBatchSize?: number | undefined
46
51
  readonly tracerContext?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
47
52
  readonly loggerExportInterval?: Duration.Input | undefined
@@ -59,6 +64,7 @@ export const layer = (options: {
59
64
  url: url("/v1/logs"),
60
65
  resource: options.resource,
61
66
  headers: options.headers,
67
+ compression: options.compression,
62
68
  exportInterval: options.loggerExportInterval,
63
69
  maxBatchSize: options.maxBatchSize,
64
70
  shutdownTimeout: options.shutdownTimeout,
@@ -69,6 +75,7 @@ export const layer = (options: {
69
75
  url: url("/v1/metrics"),
70
76
  resource: options.resource,
71
77
  headers: options.headers,
78
+ compression: options.compression,
72
79
  exportInterval: options.metricsExportInterval,
73
80
  shutdownTimeout: options.shutdownTimeout,
74
81
  temporality: options.metricsTemporality
@@ -77,6 +84,7 @@ export const layer = (options: {
77
84
  url: url("/v1/traces"),
78
85
  resource: options.resource,
79
86
  headers: options.headers,
87
+ compression: options.compression,
80
88
  exportInterval: options.tracerExportInterval,
81
89
  maxBatchSize: options.maxBatchSize,
82
90
  context: options.tracerContext,
@@ -138,6 +146,11 @@ export const layerJson: (options: {
138
146
  readonly attributes?: Record<string, unknown>
139
147
  } | undefined
140
148
  readonly headers?: Headers.Input | undefined
149
+ /**
150
+ * Request compression. Defaults to "none". Gzip uses
151
+ * HttpClientRequest's default minimum body size of 1KB.
152
+ */
153
+ readonly compression?: "gzip" | "none" | undefined
141
154
  readonly maxBatchSize?: number | undefined
142
155
  readonly tracerContext?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
143
156
  readonly loggerExportInterval?: Duration.Input | undefined
@@ -165,6 +178,11 @@ export const layerProtobuf: (options: {
165
178
  readonly attributes?: Record<string, unknown>
166
179
  } | undefined
167
180
  readonly headers?: Headers.Input | undefined
181
+ /**
182
+ * Request compression. Defaults to "none". Gzip uses
183
+ * HttpClientRequest's default minimum body size of 1KB.
184
+ */
185
+ readonly compression?: "gzip" | "none" | undefined
168
186
  readonly maxBatchSize?: number | undefined
169
187
  readonly tracerContext?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
170
188
  readonly loggerExportInterval?: Duration.Input | undefined
@@ -161,6 +161,12 @@ export const layerFlusher: Layer.Layer<Flusher> = Layer.sync(Flusher, () => {
161
161
  * after unhandled failures, and flushes during scope finalization up to
162
162
  * `shutdownTimeout`.
163
163
  *
164
+ * Optional gzip compression uses `HttpClientRequest.compressBody` before the
165
+ * retry loop, so retries reuse the compressed bytes. Bodies smaller than 1KB
166
+ * remain uncompressed. Compression requires runtime `CompressionStream` support;
167
+ * compression failures follow the same temporary-disable behavior as transport
168
+ * failures and do not fail flushing or shutdown.
169
+ *
164
170
  * @stability unstable
165
171
  * @category constructors
166
172
  * @since 4.0.0
@@ -169,6 +175,8 @@ export const make: (
169
175
  options: {
170
176
  readonly url: string
171
177
  readonly headers: Headers.Input | undefined
178
+ /** Gzip uses the default 1KB minimum body size; smaller batches remain uncompressed. */
179
+ readonly compression?: "gzip" | "none" | undefined
172
180
  readonly label: string
173
181
  readonly exportInterval: Duration.Input
174
182
  readonly maxBatchSize: number | "disabled"
@@ -218,9 +226,12 @@ export const make: (
218
226
  const items = buffer
219
227
  buffer = []
220
228
  const [body, onSuccess] = options.body(items)
221
- return client.execute(
222
- HttpClientRequest.setBody(request, body)
223
- ).pipe(
229
+ const batchRequest = HttpClientRequest.setBody(request, body)
230
+ // Compress before client.execute so transient retries reuse the encoded bytes.
231
+ const response = options.compression === "gzip"
232
+ ? HttpClientRequest.compressBody(batchRequest, "gzip").pipe(Effect.flatMap(client.execute))
233
+ : client.execute(batchRequest)
234
+ return response.pipe(
224
235
  Effect.andThen(onSuccess),
225
236
  Effect.asVoid,
226
237
  Effect.withTracerEnabled(false)