effect 4.0.6 → 4.0.7

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 (480) hide show
  1. package/dist/Arbitrary.d.ts +10 -2
  2. package/dist/Arbitrary.d.ts.map +1 -1
  3. package/dist/Arbitrary.js +2 -1
  4. package/dist/Arbitrary.js.map +1 -1
  5. package/dist/Channel.d.ts.map +1 -1
  6. package/dist/Channel.js +21 -6
  7. package/dist/Channel.js.map +1 -1
  8. package/dist/Data.d.ts +54 -235
  9. package/dist/Data.d.ts.map +1 -1
  10. package/dist/Data.js.map +1 -1
  11. package/dist/Effect.d.ts +1 -1
  12. package/dist/Effect.js +1 -1
  13. package/dist/Equal.d.ts.map +1 -1
  14. package/dist/Equal.js +8 -1
  15. package/dist/Equal.js.map +1 -1
  16. package/dist/Exit.d.ts +3 -3
  17. package/dist/Exit.d.ts.map +1 -1
  18. package/dist/Exit.js.map +1 -1
  19. package/dist/Formatter.d.ts.map +1 -1
  20. package/dist/Formatter.js +27 -13
  21. package/dist/Formatter.js.map +1 -1
  22. package/dist/Hash.d.ts.map +1 -1
  23. package/dist/Hash.js +4 -1
  24. package/dist/Hash.js.map +1 -1
  25. package/dist/JsonPatch.js +4 -0
  26. package/dist/JsonPatch.js.map +1 -1
  27. package/dist/JsonSchema.d.ts.map +1 -1
  28. package/dist/JsonSchema.js +13 -40
  29. package/dist/JsonSchema.js.map +1 -1
  30. package/dist/Metric.d.ts +3 -3
  31. package/dist/MutableList.d.ts +5 -2
  32. package/dist/MutableList.d.ts.map +1 -1
  33. package/dist/MutableList.js +7 -5
  34. package/dist/MutableList.js.map +1 -1
  35. package/dist/MutableRef.d.ts.map +1 -1
  36. package/dist/MutableRef.js +7 -0
  37. package/dist/MutableRef.js.map +1 -1
  38. package/dist/PartitionedSemaphore.d.ts +2 -1
  39. package/dist/PartitionedSemaphore.d.ts.map +1 -1
  40. package/dist/PartitionedSemaphore.js +22 -12
  41. package/dist/PartitionedSemaphore.js.map +1 -1
  42. package/dist/Pool.d.ts.map +1 -1
  43. package/dist/Pool.js +77 -27
  44. package/dist/Pool.js.map +1 -1
  45. package/dist/PubSub.d.ts.map +1 -1
  46. package/dist/PubSub.js +5 -6
  47. package/dist/PubSub.js.map +1 -1
  48. package/dist/Ref.d.ts.map +1 -1
  49. package/dist/Ref.js +8 -0
  50. package/dist/Ref.js.map +1 -1
  51. package/dist/Schedule.js +1 -1
  52. package/dist/Schedule.js.map +1 -1
  53. package/dist/Schema.d.ts +24 -8
  54. package/dist/Schema.d.ts.map +1 -1
  55. package/dist/Schema.js +37 -28
  56. package/dist/Schema.js.map +1 -1
  57. package/dist/SchemaAST.d.ts +4 -2
  58. package/dist/SchemaAST.d.ts.map +1 -1
  59. package/dist/SchemaAST.js +14 -17
  60. package/dist/SchemaAST.js.map +1 -1
  61. package/dist/SchemaParser.d.ts.map +1 -1
  62. package/dist/SchemaParser.js +48 -22
  63. package/dist/SchemaParser.js.map +1 -1
  64. package/dist/SchemaRepresentation.d.ts +19 -10
  65. package/dist/SchemaRepresentation.d.ts.map +1 -1
  66. package/dist/SchemaRepresentation.js +23 -13
  67. package/dist/SchemaRepresentation.js.map +1 -1
  68. package/dist/SchemaTransformation.d.ts +4 -1
  69. package/dist/SchemaTransformation.d.ts.map +1 -1
  70. package/dist/SchemaTransformation.js +9 -2
  71. package/dist/SchemaTransformation.js.map +1 -1
  72. package/dist/Semaphore.d.ts.map +1 -1
  73. package/dist/Semaphore.js +54 -35
  74. package/dist/Semaphore.js.map +1 -1
  75. package/dist/Sink.d.ts.map +1 -1
  76. package/dist/Sink.js +7 -2
  77. package/dist/Sink.js.map +1 -1
  78. package/dist/Stream.d.ts.map +1 -1
  79. package/dist/Stream.js +22 -10
  80. package/dist/Stream.js.map +1 -1
  81. package/dist/String.d.ts +3 -0
  82. package/dist/String.d.ts.map +1 -1
  83. package/dist/String.js +3 -0
  84. package/dist/String.js.map +1 -1
  85. package/dist/Tracer.d.ts +11 -1
  86. package/dist/Tracer.d.ts.map +1 -1
  87. package/dist/Tracer.js +6 -1
  88. package/dist/Tracer.js.map +1 -1
  89. package/dist/ai/LanguageModel.d.ts.map +1 -1
  90. package/dist/ai/LanguageModel.js +9 -0
  91. package/dist/ai/LanguageModel.js.map +1 -1
  92. package/dist/ai/McpSchema.d.ts +110 -110
  93. package/dist/ai/McpSchema.d.ts.map +1 -1
  94. package/dist/ai/McpServer.d.ts.map +1 -1
  95. package/dist/ai/McpServer.js +21 -15
  96. package/dist/ai/McpServer.js.map +1 -1
  97. package/dist/ai/Prompt.d.ts +52 -0
  98. package/dist/ai/Prompt.d.ts.map +1 -1
  99. package/dist/ai/Prompt.js +38 -0
  100. package/dist/ai/Prompt.js.map +1 -1
  101. package/dist/ai/Response.d.ts +16 -3
  102. package/dist/ai/Response.d.ts.map +1 -1
  103. package/dist/ai/Response.js +4 -0
  104. package/dist/ai/Response.js.map +1 -1
  105. package/dist/ai/Tool.d.ts +24 -0
  106. package/dist/ai/Tool.d.ts.map +1 -1
  107. package/dist/ai/Tool.js +5 -2
  108. package/dist/ai/Tool.js.map +1 -1
  109. package/dist/ai/Toolkit.d.ts.map +1 -1
  110. package/dist/ai/Toolkit.js +8 -1
  111. package/dist/ai/Toolkit.js.map +1 -1
  112. package/dist/cli/Argument.d.ts +15 -0
  113. package/dist/cli/Argument.d.ts.map +1 -1
  114. package/dist/cli/Argument.js +5 -0
  115. package/dist/cli/Argument.js.map +1 -1
  116. package/dist/cli/CliError.d.ts +78 -0
  117. package/dist/cli/CliError.d.ts.map +1 -1
  118. package/dist/cli/CliError.js +65 -3
  119. package/dist/cli/CliError.js.map +1 -1
  120. package/dist/cli/CliOutput.d.ts +155 -6
  121. package/dist/cli/CliOutput.d.ts.map +1 -1
  122. package/dist/cli/CliOutput.js +62 -6
  123. package/dist/cli/CliOutput.js.map +1 -1
  124. package/dist/cli/Command.d.ts +24 -16
  125. package/dist/cli/Command.d.ts.map +1 -1
  126. package/dist/cli/Command.js +59 -29
  127. package/dist/cli/Command.js.map +1 -1
  128. package/dist/cli/Completions.d.ts +58 -0
  129. package/dist/cli/Completions.d.ts.map +1 -1
  130. package/dist/cli/Completions.js +49 -9
  131. package/dist/cli/Completions.js.map +1 -1
  132. package/dist/cli/Flag.d.ts +15 -0
  133. package/dist/cli/Flag.d.ts.map +1 -1
  134. package/dist/cli/Flag.js +5 -0
  135. package/dist/cli/Flag.js.map +1 -1
  136. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  137. package/dist/cli/GlobalFlag.js +15 -7
  138. package/dist/cli/GlobalFlag.js.map +1 -1
  139. package/dist/cli/Param.d.ts +9 -0
  140. package/dist/cli/Param.d.ts.map +1 -1
  141. package/dist/cli/Param.js +21 -2
  142. package/dist/cli/Param.js.map +1 -1
  143. package/dist/cli/TerminalCapabilities.d.ts +3 -2
  144. package/dist/cli/TerminalCapabilities.d.ts.map +1 -1
  145. package/dist/cli/TerminalCapabilities.js +3 -1
  146. package/dist/cli/TerminalCapabilities.js.map +1 -1
  147. package/dist/cli/internal/completions/bash.js +73 -27
  148. package/dist/cli/internal/completions/bash.js.map +1 -1
  149. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  150. package/dist/cluster/ClusterWorkflowEngine.js +45 -10
  151. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  152. package/dist/cluster/MessageStorage.d.ts +23 -45
  153. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  154. package/dist/cluster/MessageStorage.js +6 -7
  155. package/dist/cluster/MessageStorage.js.map +1 -1
  156. package/dist/cluster/Sharding.d.ts.map +1 -1
  157. package/dist/cluster/Sharding.js +61 -21
  158. package/dist/cluster/Sharding.js.map +1 -1
  159. package/dist/cluster/ShardingConfig.d.ts +10 -0
  160. package/dist/cluster/ShardingConfig.d.ts.map +1 -1
  161. package/dist/cluster/ShardingConfig.js.map +1 -1
  162. package/dist/cluster/Snowflake.d.ts.map +1 -1
  163. package/dist/cluster/Snowflake.js +3 -7
  164. package/dist/cluster/Snowflake.js.map +1 -1
  165. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  166. package/dist/cluster/SqlRunnerStorage.js +87 -39
  167. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  168. package/dist/cluster/internal/entityManager.js +54 -20
  169. package/dist/cluster/internal/entityManager.js.map +1 -1
  170. package/dist/devtools/DevToolsClient.d.ts.map +1 -1
  171. package/dist/devtools/DevToolsClient.js +2 -0
  172. package/dist/devtools/DevToolsClient.js.map +1 -1
  173. package/dist/encoding/Base64.js +36 -0
  174. package/dist/encoding/Base64.js.map +1 -1
  175. package/dist/encoding/Sse.d.ts.map +1 -1
  176. package/dist/encoding/Sse.js +3 -3
  177. package/dist/encoding/Sse.js.map +1 -1
  178. package/dist/encoding/Toml.d.ts.map +1 -1
  179. package/dist/encoding/Toml.js +21 -16
  180. package/dist/encoding/Toml.js.map +1 -1
  181. package/dist/eventlog/SqlEventJournal.d.ts.map +1 -1
  182. package/dist/eventlog/SqlEventJournal.js +47 -27
  183. package/dist/eventlog/SqlEventJournal.js.map +1 -1
  184. package/dist/eventlog/internal/conflicts.d.ts +0 -1
  185. package/dist/eventlog/internal/conflicts.d.ts.map +1 -1
  186. package/dist/eventlog/internal/conflicts.js +0 -3
  187. package/dist/eventlog/internal/conflicts.js.map +1 -1
  188. package/dist/http/Cookies.d.ts.map +1 -1
  189. package/dist/http/Cookies.js +10 -9
  190. package/dist/http/Cookies.js.map +1 -1
  191. package/dist/http/HttpClient.d.ts +423 -0
  192. package/dist/http/HttpClient.d.ts.map +1 -1
  193. package/dist/http/HttpClient.js +231 -7
  194. package/dist/http/HttpClient.js.map +1 -1
  195. package/dist/http/HttpClientError.d.ts +8 -1
  196. package/dist/http/HttpClientError.d.ts.map +1 -1
  197. package/dist/http/HttpClientError.js +37 -1
  198. package/dist/http/HttpClientError.js.map +1 -1
  199. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  200. package/dist/http/HttpClientRequest.js +1 -1
  201. package/dist/http/HttpClientRequest.js.map +1 -1
  202. package/dist/http/HttpMiddleware.d.ts +1 -1
  203. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  204. package/dist/http/HttpMiddleware.js +14 -16
  205. package/dist/http/HttpMiddleware.js.map +1 -1
  206. package/dist/http/HttpRouter.d.ts.map +1 -1
  207. package/dist/http/HttpRouter.js +2 -1
  208. package/dist/http/HttpRouter.js.map +1 -1
  209. package/dist/http/HttpServerError.js +2 -2
  210. package/dist/http/HttpServerError.js.map +1 -1
  211. package/dist/http/HttpServerRequest.d.ts +6 -1
  212. package/dist/http/HttpServerRequest.d.ts.map +1 -1
  213. package/dist/http/HttpServerRequest.js +73 -10
  214. package/dist/http/HttpServerRequest.js.map +1 -1
  215. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  216. package/dist/http/HttpStaticServer.js +48 -21
  217. package/dist/http/HttpStaticServer.js.map +1 -1
  218. package/dist/http/MultipartParser/internal/contentType.js +3 -3
  219. package/dist/http/UrlParams.d.ts +3 -1
  220. package/dist/http/UrlParams.d.ts.map +1 -1
  221. package/dist/http/UrlParams.js +4 -2
  222. package/dist/http/UrlParams.js.map +1 -1
  223. package/dist/http/internal/compression.js +4 -5
  224. package/dist/http/internal/compression.js.map +1 -1
  225. package/dist/http/internal/decompression.js +3 -0
  226. package/dist/http/internal/decompression.js.map +1 -1
  227. package/dist/http/internal/httpBody.js +8 -1
  228. package/dist/http/internal/httpBody.js.map +1 -1
  229. package/dist/http/internal/urlParams.js +25 -0
  230. package/dist/http/internal/urlParams.js.map +1 -1
  231. package/dist/http-api/HttpApi.d.ts +3 -6
  232. package/dist/http-api/HttpApi.d.ts.map +1 -1
  233. package/dist/http-api/HttpApi.js +3 -6
  234. package/dist/http-api/HttpApi.js.map +1 -1
  235. package/dist/http-api/HttpApiBuilder.d.ts.map +1 -1
  236. package/dist/http-api/HttpApiBuilder.js +27 -11
  237. package/dist/http-api/HttpApiBuilder.js.map +1 -1
  238. package/dist/http-api/HttpApiClient.d.ts +77 -7
  239. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  240. package/dist/http-api/HttpApiClient.js +142 -44
  241. package/dist/http-api/HttpApiClient.js.map +1 -1
  242. package/dist/http-api/HttpApiEndpoint.d.ts +24 -7
  243. package/dist/http-api/HttpApiEndpoint.d.ts.map +1 -1
  244. package/dist/http-api/HttpApiEndpoint.js +109 -27
  245. package/dist/http-api/HttpApiEndpoint.js.map +1 -1
  246. package/dist/http-api/HttpApiSchema.d.ts +76 -29
  247. package/dist/http-api/HttpApiSchema.d.ts.map +1 -1
  248. package/dist/http-api/HttpApiSchema.js +70 -59
  249. package/dist/http-api/HttpApiSchema.js.map +1 -1
  250. package/dist/http-api/HttpApiTest.d.ts.map +1 -1
  251. package/dist/http-api/HttpApiTest.js +3 -2
  252. package/dist/http-api/HttpApiTest.js.map +1 -1
  253. package/dist/http-api/OpenApi.d.ts.map +1 -1
  254. package/dist/http-api/OpenApi.js +25 -16
  255. package/dist/http-api/OpenApi.js.map +1 -1
  256. package/dist/http-api/internal/toCodecHttpProperties.d.ts +2 -0
  257. package/dist/http-api/internal/toCodecHttpProperties.d.ts.map +1 -0
  258. package/dist/http-api/internal/toCodecHttpProperties.js +38 -0
  259. package/dist/http-api/internal/toCodecHttpProperties.js.map +1 -0
  260. package/dist/internal/arbitrary/runner.d.ts.map +1 -1
  261. package/dist/internal/arbitrary/runner.js +120 -44
  262. package/dist/internal/arbitrary/runner.js.map +1 -1
  263. package/dist/internal/core.js +2 -0
  264. package/dist/internal/core.js.map +1 -1
  265. package/dist/internal/effect.js +21 -13
  266. package/dist/internal/effect.js.map +1 -1
  267. package/dist/internal/equal.js +14 -0
  268. package/dist/internal/equal.js.map +1 -1
  269. package/dist/internal/jsonSchema.d.ts +2 -0
  270. package/dist/internal/jsonSchema.d.ts.map +1 -0
  271. package/dist/internal/jsonSchema.js +28 -0
  272. package/dist/internal/jsonSchema.js.map +1 -0
  273. package/dist/internal/matcher.js +1 -1
  274. package/dist/internal/matcher.js.map +1 -1
  275. package/dist/internal/schema/compilerRegistry.js +4 -1
  276. package/dist/internal/schema/compilerRegistry.js.map +1 -1
  277. package/dist/internal/schema/content.d.ts +2 -0
  278. package/dist/internal/schema/content.d.ts.map +1 -0
  279. package/dist/internal/schema/content.js +13 -0
  280. package/dist/internal/schema/content.js.map +1 -0
  281. package/dist/internal/schema/fromJsonSchemaDocument.js +173 -185
  282. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  283. package/dist/internal/schema/fromRepresentation.js +2 -1
  284. package/dist/internal/schema/fromRepresentation.js.map +1 -1
  285. package/dist/internal/schema/standardSchema.js +8 -2
  286. package/dist/internal/schema/standardSchema.js.map +1 -1
  287. package/dist/internal/schema/toCodeDocument.js +3 -3
  288. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  289. package/dist/internal/schema/toCodec.js +21 -24
  290. package/dist/internal/schema/toCodec.js.map +1 -1
  291. package/dist/internal/schema/toJsonSchemaDocument.js +205 -140
  292. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  293. package/dist/internal/schema/toRepresentation.js +107 -129
  294. package/dist/internal/schema/toRepresentation.js.map +1 -1
  295. package/dist/internal/sql.d.ts +2 -0
  296. package/dist/internal/sql.d.ts.map +1 -0
  297. package/dist/internal/sql.js +13 -0
  298. package/dist/internal/sql.js.map +1 -0
  299. package/dist/internal/version.js +1 -1
  300. package/dist/observability/OtlpProxy.d.ts +154 -0
  301. package/dist/observability/OtlpProxy.d.ts.map +1 -0
  302. package/dist/observability/OtlpProxy.js +235 -0
  303. package/dist/observability/OtlpProxy.js.map +1 -0
  304. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  305. package/dist/observability/OtlpTracer.js +4 -0
  306. package/dist/observability/OtlpTracer.js.map +1 -1
  307. package/dist/observability/index.d.ts +5 -0
  308. package/dist/observability/index.d.ts.map +1 -1
  309. package/dist/observability/index.js +5 -0
  310. package/dist/observability/index.js.map +1 -1
  311. package/dist/persistence/CircuitBreaker.d.ts +897 -0
  312. package/dist/persistence/CircuitBreaker.d.ts.map +1 -0
  313. package/dist/persistence/CircuitBreaker.js +1165 -0
  314. package/dist/persistence/CircuitBreaker.js.map +1 -0
  315. package/dist/persistence/Redis.d.ts +78 -2
  316. package/dist/persistence/Redis.d.ts.map +1 -1
  317. package/dist/persistence/Redis.js +62 -7
  318. package/dist/persistence/Redis.js.map +1 -1
  319. package/dist/persistence/index.d.ts +5 -0
  320. package/dist/persistence/index.d.ts.map +1 -1
  321. package/dist/persistence/index.js +5 -0
  322. package/dist/persistence/index.js.map +1 -1
  323. package/dist/process/ChildProcess.d.ts +30 -4
  324. package/dist/process/ChildProcess.d.ts.map +1 -1
  325. package/dist/process/ChildProcess.js.map +1 -1
  326. package/dist/process/ChildProcessSpawner.d.ts +30 -0
  327. package/dist/process/ChildProcessSpawner.d.ts.map +1 -1
  328. package/dist/process/ChildProcessSpawner.js +5 -2
  329. package/dist/process/ChildProcessSpawner.js.map +1 -1
  330. package/dist/reactivity/AsyncResult.d.ts +1 -1
  331. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  332. package/dist/rpc/RpcClient.d.ts.map +1 -1
  333. package/dist/rpc/RpcClient.js +9 -8
  334. package/dist/rpc/RpcClient.js.map +1 -1
  335. package/dist/rpc/RpcSerialization.d.ts +14 -0
  336. package/dist/rpc/RpcSerialization.d.ts.map +1 -1
  337. package/dist/rpc/RpcSerialization.js +20 -8
  338. package/dist/rpc/RpcSerialization.js.map +1 -1
  339. package/dist/rpc/RpcServer.d.ts.map +1 -1
  340. package/dist/rpc/RpcServer.js +11 -1
  341. package/dist/rpc/RpcServer.js.map +1 -1
  342. package/dist/rpc/RpcTest.d.ts +1 -0
  343. package/dist/rpc/RpcTest.d.ts.map +1 -1
  344. package/dist/rpc/RpcTest.js +1 -0
  345. package/dist/rpc/RpcTest.js.map +1 -1
  346. package/dist/sql/Migrator.d.ts +2 -1
  347. package/dist/sql/Migrator.d.ts.map +1 -1
  348. package/dist/sql/Migrator.js +11 -8
  349. package/dist/sql/Migrator.js.map +1 -1
  350. package/dist/sql/SqlClient.d.ts +9 -4
  351. package/dist/sql/SqlClient.d.ts.map +1 -1
  352. package/dist/sql/SqlClient.js +8 -1
  353. package/dist/sql/SqlClient.js.map +1 -1
  354. package/dist/sql/SqlResolver.d.ts +4 -0
  355. package/dist/sql/SqlResolver.d.ts.map +1 -1
  356. package/dist/sql/SqlResolver.js +18 -4
  357. package/dist/sql/SqlResolver.js.map +1 -1
  358. package/dist/sql/Statement.d.ts +7 -0
  359. package/dist/sql/Statement.d.ts.map +1 -1
  360. package/dist/sql/Statement.js +21 -2
  361. package/dist/sql/Statement.js.map +1 -1
  362. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  363. package/dist/workflow/DurableDeferred.js +21 -2
  364. package/dist/workflow/DurableDeferred.js.map +1 -1
  365. package/package.json +1 -1
  366. package/src/Arbitrary.ts +10 -2
  367. package/src/Channel.ts +47 -18
  368. package/src/Data.ts +60 -265
  369. package/src/Effect.ts +1 -1
  370. package/src/Equal.ts +8 -1
  371. package/src/Exit.ts +3 -3
  372. package/src/Formatter.ts +28 -13
  373. package/src/Hash.ts +4 -1
  374. package/src/JsonPatch.ts +4 -0
  375. package/src/JsonSchema.ts +12 -40
  376. package/src/Metric.ts +3 -3
  377. package/src/MutableList.ts +7 -5
  378. package/src/MutableRef.ts +8 -1
  379. package/src/PartitionedSemaphore.ts +24 -13
  380. package/src/Pool.ts +109 -37
  381. package/src/PubSub.ts +5 -6
  382. package/src/Ref.ts +8 -0
  383. package/src/Schedule.ts +1 -1
  384. package/src/Schema.ts +48 -25
  385. package/src/SchemaAST.ts +20 -20
  386. package/src/SchemaParser.ts +51 -18
  387. package/src/SchemaRepresentation.ts +31 -34
  388. package/src/SchemaTransformation.ts +9 -2
  389. package/src/Semaphore.ts +67 -40
  390. package/src/Sink.ts +15 -2
  391. package/src/Stream.ts +33 -16
  392. package/src/String.ts +3 -0
  393. package/src/Tracer.ts +15 -1
  394. package/src/ai/LanguageModel.ts +9 -0
  395. package/src/ai/McpServer.ts +14 -13
  396. package/src/ai/Prompt.ts +65 -0
  397. package/src/ai/Response.ts +34 -9
  398. package/src/ai/Tool.ts +38 -2
  399. package/src/ai/Toolkit.ts +4 -1
  400. package/src/cli/Argument.ts +15 -0
  401. package/src/cli/CliError.ts +90 -3
  402. package/src/cli/CliOutput.ts +183 -6
  403. package/src/cli/Command.ts +53 -41
  404. package/src/cli/Completions.ts +51 -0
  405. package/src/cli/Flag.ts +15 -0
  406. package/src/cli/GlobalFlag.ts +6 -7
  407. package/src/cli/Param.ts +29 -6
  408. package/src/cli/TerminalCapabilities.ts +6 -3
  409. package/src/cli/internal/completions/bash.ts +79 -29
  410. package/src/cluster/ClusterWorkflowEngine.ts +46 -16
  411. package/src/cluster/MessageStorage.ts +19 -14
  412. package/src/cluster/Sharding.ts +83 -21
  413. package/src/cluster/ShardingConfig.ts +5 -0
  414. package/src/cluster/Snowflake.ts +3 -7
  415. package/src/cluster/SqlRunnerStorage.ts +110 -47
  416. package/src/cluster/internal/entityManager.ts +75 -22
  417. package/src/devtools/DevToolsClient.ts +2 -0
  418. package/src/encoding/Base64.ts +38 -0
  419. package/src/encoding/Sse.ts +4 -3
  420. package/src/encoding/Toml.ts +33 -16
  421. package/src/eventlog/SqlEventJournal.ts +68 -45
  422. package/src/eventlog/internal/conflicts.ts +0 -4
  423. package/src/http/Cookies.ts +10 -11
  424. package/src/http/HttpClient.ts +547 -7
  425. package/src/http/HttpClientError.ts +35 -1
  426. package/src/http/HttpClientRequest.ts +9 -1
  427. package/src/http/HttpMiddleware.ts +15 -18
  428. package/src/http/HttpRouter.ts +2 -1
  429. package/src/http/HttpServerError.ts +2 -2
  430. package/src/http/HttpServerRequest.ts +102 -16
  431. package/src/http/HttpStaticServer.ts +51 -22
  432. package/src/http/MultipartParser/internal/contentType.ts +3 -3
  433. package/src/http/UrlParams.ts +6 -1
  434. package/src/http/internal/compression.ts +4 -5
  435. package/src/http/internal/decompression.ts +6 -0
  436. package/src/http/internal/httpBody.ts +10 -2
  437. package/src/http/internal/urlParams.ts +29 -0
  438. package/src/http-api/HttpApi.ts +3 -6
  439. package/src/http-api/HttpApiBuilder.ts +45 -15
  440. package/src/http-api/HttpApiClient.ts +218 -72
  441. package/src/http-api/HttpApiEndpoint.ts +121 -30
  442. package/src/http-api/HttpApiSchema.ts +141 -48
  443. package/src/http-api/HttpApiTest.ts +3 -2
  444. package/src/http-api/OpenApi.ts +20 -17
  445. package/src/http-api/internal/toCodecHttpProperties.ts +56 -0
  446. package/src/internal/arbitrary/runner.ts +133 -36
  447. package/src/internal/core.ts +2 -0
  448. package/src/internal/effect.ts +24 -16
  449. package/src/internal/equal.ts +17 -0
  450. package/src/internal/jsonSchema.ts +27 -0
  451. package/src/internal/matcher.ts +1 -1
  452. package/src/internal/schema/compilerRegistry.ts +6 -1
  453. package/src/internal/schema/content.ts +18 -0
  454. package/src/internal/schema/fromJsonSchemaDocument.ts +228 -190
  455. package/src/internal/schema/fromRepresentation.ts +2 -1
  456. package/src/internal/schema/standardSchema.ts +2 -2
  457. package/src/internal/schema/toCodeDocument.ts +4 -4
  458. package/src/internal/schema/toCodec.ts +26 -22
  459. package/src/internal/schema/toJsonSchemaDocument.ts +189 -167
  460. package/src/internal/schema/toRepresentation.ts +122 -140
  461. package/src/internal/sql.ts +14 -0
  462. package/src/internal/version.ts +1 -1
  463. package/src/observability/OtlpProxy.ts +314 -0
  464. package/src/observability/OtlpTracer.ts +4 -0
  465. package/src/observability/index.ts +6 -0
  466. package/src/persistence/CircuitBreaker.ts +1678 -0
  467. package/src/persistence/Redis.ts +129 -8
  468. package/src/persistence/index.ts +6 -0
  469. package/src/process/ChildProcess.ts +30 -4
  470. package/src/process/ChildProcessSpawner.ts +41 -2
  471. package/src/reactivity/AsyncResult.ts +1 -1
  472. package/src/rpc/RpcClient.ts +13 -10
  473. package/src/rpc/RpcSerialization.ts +32 -9
  474. package/src/rpc/RpcServer.ts +16 -1
  475. package/src/rpc/RpcTest.ts +3 -0
  476. package/src/sql/Migrator.ts +12 -7
  477. package/src/sql/SqlClient.ts +11 -4
  478. package/src/sql/SqlResolver.ts +23 -9
  479. package/src/sql/Statement.ts +30 -2
  480. package/src/workflow/DurableDeferred.ts +23 -6
@@ -0,0 +1,1165 @@
1
+ /**
2
+ * Keyed circuit breakers for calls to unreliable dependencies.
3
+ *
4
+ * A `CircuitBreaker` holds one circuit per key, so one policy can guard many
5
+ * hosts or tenants. State lives in a `CircuitBreakerStore`: `layerStoreMemory`
6
+ * keeps it in the process, `layerStoreRedis` shares it between processes.
7
+ *
8
+ * **Details**
9
+ *
10
+ * - Closed: calls run, and the trip policy decides when the circuit opens.
11
+ * - Open: calls fail with `CircuitOpen` until the open time ends.
12
+ * - HalfOpen: the next call is the probe, and other calls fail with
13
+ * `CircuitHalfOpen` while it runs. A successful probe closes the circuit and
14
+ * a failed one reopens it. An interrupted or `Ignored` probe frees the slot.
15
+ * - The backoff count survives a close. It resets once the circuit has stayed
16
+ * closed as long as it was last open.
17
+ *
18
+ * The breaker never waits. Retry rejections outside it, as in "Retrying
19
+ * rejections" below. Put `Effect.retry` and `Effect.timeout` inside it, so it
20
+ * sees one outcome per call. With `ExecutionPlan`, put a breaker inside each
21
+ * step under its own key.
22
+ *
23
+ * The breaker emits no metrics or spans. Observe it through `state(key)`,
24
+ * `transitions` and the typed error.
25
+ *
26
+ * **Gotchas**
27
+ *
28
+ * - Use one policy per key. Breakers that share a key and a store share one
29
+ * circuit, and nothing checks that their policies match. Separate them with
30
+ * distinct keys or a Redis `prefix`.
31
+ * - Calls admitted before a trip still run to completion, but their outcomes
32
+ * change nothing.
33
+ * - "Consecutive" counts in order of completion, not of start.
34
+ * - `state(key)` reads the shared store. `transitions` only shows what this
35
+ * breaker instance did.
36
+ * - Store errors the breaker swallows (a fail-open admit, a failed record, a
37
+ * failed Redis lease renewal) are reported with their cause at
38
+ * `References.UnhandledLogLevel`. Set it to `undefined` to silence them.
39
+ * - Cap an exponential `openDuration`, with `Schedule.upTo` or with
40
+ * `Schedule.min` and a `Schedule.spaced`, or it keeps growing for a
41
+ * dependency that flaps.
42
+ *
43
+ * **Example** (Rejecting calls once a circuit opens)
44
+ *
45
+ * ```ts import.meta.vitest
46
+ * import { Data, Effect } from "effect"
47
+ * import { CircuitBreaker } from "effect/persistence"
48
+ *
49
+ * class PaymentsError extends Data.TaggedError("PaymentsError") {}
50
+ *
51
+ * const program = Effect.gen(function*() {
52
+ * const breaker = yield* CircuitBreaker.make({
53
+ * tripPolicy: CircuitBreaker.consecutive(2),
54
+ * openDuration: "30 seconds"
55
+ * })
56
+ * const charge = Effect.fail(new PaymentsError()).pipe(
57
+ * breaker.execute({ key: "payments" })
58
+ * )
59
+ *
60
+ * // Two failures in a row open the circuit.
61
+ * yield* Effect.flip(charge)
62
+ * yield* Effect.flip(charge)
63
+ *
64
+ * // The third call is rejected without running.
65
+ * return yield* charge.pipe(
66
+ * Effect.catchReason("CircuitBreakerError", "CircuitOpen", (reason) => Effect.succeed(`${reason.key} is open`))
67
+ * )
68
+ * }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
69
+ *
70
+ * await Effect.runPromise(program) // => "payments is open"
71
+ * ```
72
+ *
73
+ * **Example** (Retrying rejections)
74
+ *
75
+ * For `CircuitOpen`, wait at least the rejection's `retryAfter`. For
76
+ * `CircuitHalfOpen`, the jittered backoff keeps waiting callers from retrying
77
+ * at the same moment. Store errors and the call's own errors are not retried.
78
+ *
79
+ * ```ts import.meta.vitest
80
+ * import { Data, Duration, Effect, Ref, Schedule } from "effect"
81
+ * import { CircuitBreaker } from "effect/persistence"
82
+ *
83
+ * const isRejection = (error: unknown): error is CircuitBreaker.CircuitBreakerError =>
84
+ * error instanceof CircuitBreaker.CircuitBreakerError && error.reason._tag !== "CircuitBreakerStoreError"
85
+ *
86
+ * const retryRejections = Schedule.exponential("20 millis").pipe(
87
+ * Schedule.jittered,
88
+ * Schedule.modifyDelay(({ duration, input }) =>
89
+ * Effect.succeed(
90
+ * isRejection(input) && input.reason._tag === "CircuitOpen"
91
+ * ? Duration.max(duration, input.reason.retryAfter)
92
+ * : duration
93
+ * )
94
+ * )
95
+ * )
96
+ *
97
+ * class QuoteError extends Data.TaggedError("QuoteError") {}
98
+ *
99
+ * const program = Effect.gen(function*() {
100
+ * const breaker = yield* CircuitBreaker.make({
101
+ * tripPolicy: CircuitBreaker.consecutive(1),
102
+ * openDuration: "50 millis"
103
+ * })
104
+ * const healthy = yield* Ref.make(false)
105
+ * const quote = Effect.flatMap(Ref.get(healthy), (ok) => ok ? Effect.succeed(42) : Effect.fail(new QuoteError()))
106
+ *
107
+ * // One failure opens the circuit, then the dependency recovers.
108
+ * yield* Effect.flip(quote.pipe(breaker.execute({ key: "quotes" })))
109
+ * yield* Ref.set(healthy, true)
110
+ *
111
+ * // Rejected while open, then retried once the circuit lets a probe through.
112
+ * return yield* quote.pipe(
113
+ * breaker.execute({ key: "quotes" }),
114
+ * Effect.retry({ schedule: retryRejections, while: isRejection, times: 5 })
115
+ * )
116
+ * }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
117
+ *
118
+ * await Effect.runPromise(program) // => 42
119
+ * ```
120
+ *
121
+ * @stability experimental
122
+ * @since 4.0.6
123
+ */
124
+ import * as Cause from "../Cause.js";
125
+ import * as Clock from "../Clock.js";
126
+ import * as Config from "../Config.js";
127
+ import * as Context from "../Context.js";
128
+ import * as Duration from "../Duration.js";
129
+ import * as Effect from "../Effect.js";
130
+ import * as Exit from "../Exit.js";
131
+ import { dual } from "../Function.js";
132
+ import { findRecoverableError } from "../internal/effect.js";
133
+ import * as Layer from "../Layer.js";
134
+ import * as PubSub from "../PubSub.js";
135
+ import { UnhandledLogLevel } from "../References.js";
136
+ import * as Schedule from "../Schedule.js";
137
+ import * as Schema from "../Schema.js";
138
+ import * as Scope from "../Scope.js";
139
+ import * as Stream from "../Stream.js";
140
+ import * as Redis from "./Redis.js";
141
+ /**
142
+ * Runtime type identifier for `CircuitBreaker` values.
143
+ *
144
+ * @stability experimental
145
+ * @category type IDs
146
+ * @since 4.0.6
147
+ */
148
+ export const TypeId = "~effect/persistence/CircuitBreaker";
149
+ /**
150
+ * Service tag for the default `CircuitBreaker`.
151
+ *
152
+ * **When to use**
153
+ *
154
+ * Use when one breaker policy serves the whole context. For several policies,
155
+ * use a `LayerMap` keyed by policy, with every entry sharing one store, or
156
+ * define your own tags.
157
+ *
158
+ * **Example** (Several breaker policies with LayerMap)
159
+ *
160
+ * ```ts import.meta.vitest
161
+ * import { Effect, LayerMap, Schedule } from "effect"
162
+ * import { CircuitBreaker } from "effect/persistence"
163
+ *
164
+ * class Breakers extends LayerMap.Service<Breakers>()("app/Breakers", {
165
+ * lookup: (dependency: "payments" | "search") =>
166
+ * dependency === "payments"
167
+ * ? CircuitBreaker.layer({ tripPolicy: CircuitBreaker.consecutive(5), openDuration: "30 seconds" })
168
+ * : CircuitBreaker.layer({
169
+ * tripPolicy: CircuitBreaker.rate({ threshold: 0.5, size: 20 }),
170
+ * openDuration: Schedule.exponential("1 second")
171
+ * }),
172
+ * // One store shared by every entry.
173
+ * dependencies: [CircuitBreaker.layerStoreMemory()],
174
+ * idleTimeToLive: "10 minutes"
175
+ * }) {}
176
+ *
177
+ * const search = Effect.gen(function*() {
178
+ * const breaker = yield* CircuitBreaker.CircuitBreaker
179
+ * return yield* Effect.succeed(["result"]).pipe(breaker.execute({ key: "search:eu" }))
180
+ * }).pipe(Effect.provide(Breakers.get("search")))
181
+ *
182
+ * await Effect.runPromise(search.pipe(Effect.provide(Breakers.layer))) // => ["result"]
183
+ * ```
184
+ *
185
+ * @stability experimental
186
+ * @category services
187
+ * @since 4.0.6
188
+ */
189
+ export const CircuitBreaker = /*#__PURE__*/Context.Service(TypeId);
190
+ /**
191
+ * Creates a `CircuitBreaker` from the current `CircuitBreakerStore`.
192
+ *
193
+ * **Details**
194
+ *
195
+ * - `tripPolicy` decides when a Closed circuit opens.
196
+ * - `openDuration` is a duration or a `Schedule`. The n-th consecutive opening
197
+ * lasts the schedule's n-th delay, and the last delay repeats once the
198
+ * schedule ends. Elapsed-time combinators such as `upTo` and `during`
199
+ * measure the time spent open, not wall time. Schedules that read the
200
+ * current date, such as `cron`, are not supported.
201
+ * - `maxOpenDuration` (default one hour) caps a `Retry-After` hint. It never
202
+ * caps `openDuration`.
203
+ * - `onStoreError` decides what a call does when the store cannot admit it:
204
+ * `"allow"` (the default) runs it unrecorded, `"fail"` fails it with
205
+ * `CircuitBreakerStoreError`.
206
+ *
207
+ * The backoff count resets once the circuit has stayed closed as long as its
208
+ * last opening, `Retry-After` hint included.
209
+ *
210
+ * **Example** (Capping backoff growth by time spent open)
211
+ *
212
+ * ```ts import.meta.vitest
213
+ * import { Effect, Schedule } from "effect"
214
+ * import { CircuitBreaker } from "effect/persistence"
215
+ *
216
+ * // Openings last 1, 2, 4, 8, 16 and 32 seconds. By then 63 seconds have been
217
+ * // spent open, past the one-minute cap, so later openings stay at 32 seconds.
218
+ * const program = Effect.gen(function*() {
219
+ * const breaker = yield* CircuitBreaker.make({
220
+ * tripPolicy: CircuitBreaker.consecutive(5),
221
+ * openDuration: Schedule.exponential("1 second").pipe(
222
+ * Schedule.upTo({ duration: "1 minute" })
223
+ * )
224
+ * })
225
+ * return yield* Effect.succeed("ok").pipe(breaker.execute({ key: "payments" }))
226
+ * }).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
227
+ *
228
+ * await Effect.runPromise(program) // => "ok"
229
+ * ```
230
+ *
231
+ * @stability experimental
232
+ * @category constructors
233
+ * @since 4.0.6
234
+ */
235
+ export const make = /*#__PURE__*/Effect.fnUntraced(function* (options) {
236
+ const store = yield* CircuitBreakerStore;
237
+ const scope = yield* Effect.scope;
238
+ const pubsub = yield* PubSub.sliding(128);
239
+ yield* Scope.addFinalizer(scope, PubSub.end(pubsub, Exit.void));
240
+ const maxOpenDuration = Duration.fromInputUnsafe(options.maxOpenDuration ?? "1 hour");
241
+ const onStoreError = options.onStoreError ?? "allow";
242
+ const openDurationFor = makeOpenDuration(options.openDuration);
243
+ const publish = transition => transition === undefined ? Effect.void : PubSub.publish(pubsub, [transition]);
244
+ // Only an outcome that can open the circuit needs the schedule replayed.
245
+ // An Ignored outcome never opens it, and neither does a Success unless it
246
+ // can trip a rate policy: a probe's Success closes the circuit. Only a
247
+ // Failure carries a hint.
248
+ const openFor = (outcome, admission) => outcome._tag === "Ignored" || outcome._tag === "Success" && (admission.probe || options.tripPolicy._tag === "Consecutive") ? Effect.succeed(Duration.zero) : Effect.map(openDurationFor(admission.consecutiveOpenings + 1), delay => outcome._tag === "Failure" && outcome.retryAfter !== undefined ? Duration.max(delay, Duration.min(outcome.retryAfter, maxOpenDuration)) : delay);
249
+ const execute = (self, {
250
+ classify,
251
+ key
252
+ }) => Effect.uninterruptibleMask(restore => Effect.scopedWith(Effect.fnUntraced(function* (admitScope) {
253
+ const admitted = yield* Effect.exit(Scope.provide(store.admit(key), admitScope));
254
+ if (Exit.isFailure(admitted)) {
255
+ const error = Cause.findErrorOption(admitted.cause);
256
+ if (onStoreError === "fail" || error._tag === "None" || error.value.reason._tag !== "CircuitBreakerStoreError") {
257
+ return yield* Effect.failCause(admitted.cause);
258
+ }
259
+ yield* reportUnhandled("CircuitBreaker store unavailable, running the call unrecorded", admitted.cause);
260
+ return yield* restore(self);
261
+ }
262
+ const admission = admitted.value;
263
+ yield* publish(admission.transition);
264
+ const exit = yield* Effect.exit(restore(self));
265
+ const classified = yield* classifyExit(exit, classify, restore);
266
+ const outcome = Exit.isSuccess(classified) ? classified.value : ignored;
267
+ yield* store.record({
268
+ key,
269
+ generation: admission.generation,
270
+ outcome: outcome._tag,
271
+ tripPolicy: options.tripPolicy,
272
+ openFor: yield* openFor(outcome, admission)
273
+ }).pipe(Effect.flatMap(publish), Effect.catchCauseFilter(findRecoverableError, (_, cause) => reportUnhandled("CircuitBreaker failed to record an outcome", cause)));
274
+ if (Exit.isSuccess(classified)) return yield* exit;
275
+ return yield* Effect.failCause(Exit.isFailure(exit) ? Cause.combine(exit.cause, classified.cause) : classified.cause);
276
+ })));
277
+ return CircuitBreaker.of({
278
+ [TypeId]: TypeId,
279
+ execute: dual(2, execute),
280
+ state: store.state,
281
+ transitions: Stream.fromPubSubTake(pubsub)
282
+ });
283
+ });
284
+ /**
285
+ * Provides `CircuitBreaker` using the current `CircuitBreakerStore`.
286
+ *
287
+ * **Details**
288
+ *
289
+ * Takes the same options as `make`.
290
+ *
291
+ * @stability experimental
292
+ * @category layers
293
+ * @since 4.0.6
294
+ */
295
+ export const layer = options => Layer.effect(CircuitBreaker, make(options));
296
+ // Reports an error that has no caller to receive it at `UnhandledLogLevel`, as
297
+ // `Pool` and `ScopedCache` do. An undefined level silences it. Callers recover
298
+ // with `findRecoverableError`, as `Effect.catch` does, so a cause that holds a
299
+ // defect still propagates.
300
+ const reportUnhandled = (message, cause) => Effect.withFiber(fiber => {
301
+ const level = fiber.getRef(UnhandledLogLevel);
302
+ return level === undefined ? Effect.void : Effect.logWithLevel(level)(message, cause);
303
+ });
304
+ const classifyExit = (exit, classify, restore) => {
305
+ if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)) {
306
+ return Effect.succeed(Exit.succeed(ignored));
307
+ }
308
+ if (classify === undefined) {
309
+ return Effect.succeed(Exit.succeed(Exit.isSuccess(exit) ? success : failure()));
310
+ }
311
+ return Effect.exit(restore(Effect.suspend(() => {
312
+ const outcome = classify(exit);
313
+ return Effect.isEffect(outcome) ? outcome : Effect.succeed(outcome);
314
+ })));
315
+ };
316
+ const makeOpenDuration = openDuration => {
317
+ if (!Schedule.isSchedule(openDuration)) {
318
+ const duration = Effect.succeed(Duration.fromInputUnsafe(openDuration));
319
+ return () => duration;
320
+ }
321
+ // Replays the schedule against a simulated clock, so `upTo` and `during`
322
+ // measure the time spent open rather than wall time.
323
+ return consecutiveOpenings => Effect.gen(function* () {
324
+ const step = yield* Schedule.toStep(openDuration);
325
+ let now = yield* Clock.currentTimeMillis;
326
+ let delay = Duration.zero;
327
+ for (let attempt = 1; attempt <= consecutiveOpenings; attempt++) {
328
+ const result = yield* Effect.exit(step(now, attempt));
329
+ if (Exit.isFailure(result)) break;
330
+ delay = result.value[1];
331
+ now += Duration.toMillis(delay);
332
+ }
333
+ return delay;
334
+ });
335
+ };
336
+ /**
337
+ * Creates a trip policy that opens after `failures` consecutive failures.
338
+ *
339
+ * **Gotchas**
340
+ *
341
+ * Throws a `RangeError` when `failures` is not an integer of at least 1.
342
+ *
343
+ * @stability experimental
344
+ * @category constructors
345
+ * @since 4.0.6
346
+ */
347
+ export const consecutive = failures => {
348
+ if (!Number.isInteger(failures) || failures < 1) {
349
+ throw new RangeError(`CircuitBreaker.consecutive: failures must be an integer >= 1, got ${failures}`);
350
+ }
351
+ return {
352
+ _tag: "Consecutive",
353
+ failures
354
+ };
355
+ };
356
+ /**
357
+ * Creates a trip policy that opens when the failure rate over the last
358
+ * recorded calls reaches a threshold.
359
+ *
360
+ * **Details**
361
+ *
362
+ * `threshold` is a fraction in `[0, 1]` and defaults to `0.5`. `size` is the
363
+ * number of recorded calls kept and defaults to `100`. `minimumCalls`
364
+ * defaults to `size`. A ring with no failures never opens the circuit.
365
+ *
366
+ * **Gotchas**
367
+ *
368
+ * Throws a `RangeError` when `threshold` is outside `[0, 1]`, when `size` is
369
+ * not an integer of at least 1, or when `minimumCalls` is not an integer
370
+ * between 1 and `size`.
371
+ *
372
+ * @stability experimental
373
+ * @category constructors
374
+ * @since 4.0.6
375
+ */
376
+ export const rate = options => {
377
+ const threshold = options.threshold ?? 0.5;
378
+ const size = options.size ?? 100;
379
+ const minimumCalls = options.minimumCalls ?? size;
380
+ if (!(threshold >= 0 && threshold <= 1)) {
381
+ throw new RangeError(`CircuitBreaker.rate: threshold must be in [0, 1], got ${threshold}`);
382
+ }
383
+ if (!Number.isInteger(size) || size < 1) {
384
+ throw new RangeError(`CircuitBreaker.rate: size must be an integer >= 1, got ${size}`);
385
+ }
386
+ if (!Number.isInteger(minimumCalls) || minimumCalls < 1 || minimumCalls > size) {
387
+ throw new RangeError(`CircuitBreaker.rate: minimumCalls must be an integer in [1, size], got ${minimumCalls}`);
388
+ }
389
+ return {
390
+ _tag: "Rate",
391
+ threshold,
392
+ minimumCalls,
393
+ size
394
+ };
395
+ };
396
+ /**
397
+ * Outcome that records a success and resets a consecutive failure streak.
398
+ *
399
+ * @stability experimental
400
+ * @category constructors
401
+ * @since 4.0.6
402
+ */
403
+ export const success = {
404
+ _tag: "Success"
405
+ };
406
+ /**
407
+ * Outcome that records nothing.
408
+ *
409
+ * @stability experimental
410
+ * @category constructors
411
+ * @since 4.0.6
412
+ */
413
+ export const ignored = {
414
+ _tag: "Ignored"
415
+ };
416
+ /**
417
+ * Creates an outcome that records a failure.
418
+ *
419
+ * **Details**
420
+ *
421
+ * `retryAfter` is a hint, such as an HTTP `Retry-After` header. It is used
422
+ * only when this failure opens the circuit, can only lengthen the opening,
423
+ * and is capped at `maxOpenDuration`.
424
+ *
425
+ * @stability experimental
426
+ * @category constructors
427
+ * @since 4.0.6
428
+ */
429
+ export const failure = options => ({
430
+ _tag: "Failure",
431
+ retryAfter: options?.retryAfter === undefined ? undefined : Duration.fromInputUnsafe(options.retryAfter)
432
+ });
433
+ /**
434
+ * Error reason for a call rejected by an Open circuit.
435
+ *
436
+ * @stability experimental
437
+ * @category errors
438
+ * @since 4.0.6
439
+ */
440
+ export class CircuitOpen extends /*#__PURE__*/Schema.Error("effect/persistence/CircuitBreaker/CircuitOpen")({
441
+ _tag: /*#__PURE__*/Schema.tag("CircuitOpen"),
442
+ key: Schema.String,
443
+ retryAfter: Schema.DurationFromMillis
444
+ }) {
445
+ /**
446
+ * Public message used when an Open circuit rejects a call.
447
+ *
448
+ * @stability experimental
449
+ * @since 4.0.6
450
+ */
451
+ get message() {
452
+ return `Circuit open`;
453
+ }
454
+ }
455
+ /**
456
+ * Error reason for a call rejected while a HalfOpen circuit's probe runs.
457
+ *
458
+ * @stability experimental
459
+ * @category errors
460
+ * @since 4.0.6
461
+ */
462
+ export class CircuitHalfOpen extends /*#__PURE__*/Schema.Error("effect/persistence/CircuitBreaker/CircuitHalfOpen")({
463
+ _tag: /*#__PURE__*/Schema.tag("CircuitHalfOpen"),
464
+ key: Schema.String
465
+ }) {
466
+ /**
467
+ * Public message used when a HalfOpen circuit rejects a call.
468
+ *
469
+ * @stability experimental
470
+ * @since 4.0.6
471
+ */
472
+ get message() {
473
+ return `Circuit half-open`;
474
+ }
475
+ }
476
+ /**
477
+ * Error reason for failures in the backing `CircuitBreakerStore`.
478
+ *
479
+ * @stability experimental
480
+ * @category errors
481
+ * @since 4.0.6
482
+ */
483
+ export class CircuitBreakerStoreError extends /*#__PURE__*/Schema.Error("effect/persistence/CircuitBreaker/CircuitBreakerStoreError")({
484
+ _tag: /*#__PURE__*/Schema.tag("CircuitBreakerStoreError"),
485
+ message: Schema.String,
486
+ cause: /*#__PURE__*/Schema.optional(/*#__PURE__*/Schema.Defect())
487
+ }) {}
488
+ /**
489
+ * Schema for all reasons that can be carried by `CircuitBreakerError`.
490
+ *
491
+ * @stability experimental
492
+ * @category errors
493
+ * @since 4.0.6
494
+ */
495
+ export const CircuitBreakerErrorReason = /*#__PURE__*/Schema.Union([CircuitOpen, CircuitHalfOpen, CircuitBreakerStoreError]);
496
+ /**
497
+ * Runtime type identifier for `CircuitBreakerError`.
498
+ *
499
+ * @stability experimental
500
+ * @category type IDs
501
+ * @since 4.0.6
502
+ */
503
+ export const ErrorTypeId = "~effect/persistence/CircuitBreaker/CircuitBreakerError";
504
+ /**
505
+ * Error raised by circuit breaker operations, wrapping a concrete failure
506
+ * `reason`.
507
+ *
508
+ * @stability experimental
509
+ * @category errors
510
+ * @since 4.0.6
511
+ */
512
+ export class CircuitBreakerError extends /*#__PURE__*/Schema.Error(ErrorTypeId)({
513
+ _tag: /*#__PURE__*/Schema.tag("CircuitBreakerError"),
514
+ reason: CircuitBreakerErrorReason
515
+ }) {
516
+ // @effect-diagnostics-next-line overriddenSchemaConstructor:off
517
+ constructor(props) {
518
+ if ("cause" in props.reason) {
519
+ super({
520
+ ...props,
521
+ cause: props.reason.cause
522
+ });
523
+ } else {
524
+ super(props);
525
+ }
526
+ }
527
+ /**
528
+ * Marks this value as a circuit breaker error for runtime guards.
529
+ *
530
+ * @stability experimental
531
+ * @since 4.0.6
532
+ */
533
+ [ErrorTypeId] = ErrorTypeId;
534
+ get message() {
535
+ return this.reason.message;
536
+ }
537
+ }
538
+ /**
539
+ * Store that holds circuit state for a `CircuitBreaker`.
540
+ *
541
+ * **Details**
542
+ *
543
+ * - `admit` returns an `Admission`, or fails with `CircuitOpen` or
544
+ * `CircuitHalfOpen`. Its scope stays open while the call runs.
545
+ * - `record` applies an outcome atomically and opens the circuit for
546
+ * `openFor` if it trips. An outcome stamped with an older generation
547
+ * changes nothing.
548
+ * - `state` reads a key without changing it.
549
+ *
550
+ * Each operation reads the current time inside the store. A close keeps
551
+ * `consecutiveOpenings` and sets a reset deadline of the close time plus the
552
+ * last `openFor`. The first `admit` at or after the deadline resets the count
553
+ * to 0. `record` never reads the deadline, so a call admitted before a reset
554
+ * still opens with the `openFor` it computed.
555
+ *
556
+ * @stability experimental
557
+ * @category services
558
+ * @since 4.0.6
559
+ */
560
+ export class CircuitBreakerStore extends /*#__PURE__*/Context.Service()("effect/persistence/CircuitBreaker/CircuitBreakerStore") {}
561
+ /**
562
+ * Creates an in-memory `CircuitBreakerStore` layer.
563
+ *
564
+ * **Details**
565
+ *
566
+ * State is local to the layer and uses `Clock`. `idleTimeToLive` (default
567
+ * one hour) drops a key once it has been idle that long. An Open key is kept
568
+ * that long past its open time, and a key whose probe is running is never
569
+ * dropped. A dropped key starts again as Closed with a backoff count of 0.
570
+ *
571
+ * @stability experimental
572
+ * @category layers
573
+ * @since 4.0.6
574
+ */
575
+ export const layerStoreMemory = options => Layer.sync(CircuitBreakerStore, () => {
576
+ const idleTimeToLive = Duration.toMillis(Duration.fromInputUnsafe(options?.idleTimeToLive ?? "1 hour"));
577
+ const entries = new Map();
578
+ let nextGeneration = 0;
579
+ let lastSweep = 0;
580
+ const isExpired = (entry, now) => {
581
+ switch (entry.state) {
582
+ case "Closed":
583
+ return now - entry.lastActivity >= idleTimeToLive;
584
+ case "Open":
585
+ return now - Math.max(entry.lastActivity, entry.openUntil) >= idleTimeToLive;
586
+ case "HalfOpen":
587
+ return !entry.probing && now - entry.lastActivity >= idleTimeToLive;
588
+ }
589
+ };
590
+ const sweep = now => {
591
+ if (now - lastSweep < idleTimeToLive) return;
592
+ lastSweep = now;
593
+ for (const [key, entry] of entries) {
594
+ if (isExpired(entry, now)) entries.delete(key);
595
+ }
596
+ };
597
+ const lookup = (key, now) => {
598
+ const entry = entries.get(key);
599
+ return entry !== undefined && !isExpired(entry, now) ? entry : undefined;
600
+ };
601
+ const resetCounters = entry => {
602
+ entry.streak = 0;
603
+ entry.ring = [];
604
+ entry.ringFailures = 0;
605
+ };
606
+ const open = (key, entry, from, openFor, now) => {
607
+ entry.state = "Open";
608
+ entry.generation = nextGeneration++;
609
+ entry.lastOpenFor = Duration.toMillis(openFor);
610
+ entry.openUntil = now + entry.lastOpenFor;
611
+ entry.consecutiveOpenings++;
612
+ entry.probing = false;
613
+ resetCounters(entry);
614
+ return {
615
+ key,
616
+ from,
617
+ to: "Open",
618
+ retryAfter: openFor,
619
+ consecutiveOpenings: entry.consecutiveOpenings
620
+ };
621
+ };
622
+ const trips = (entry, policy) => {
623
+ if (policy._tag === "Consecutive") return entry.streak >= policy.failures;
624
+ return entry.ring.length >= policy.minimumCalls && entry.ringFailures > 0 && entry.ringFailures / entry.ring.length >= policy.threshold;
625
+ };
626
+ return CircuitBreakerStore.of({
627
+ admit: key => Effect.flatMap(Clock.currentTimeMillis, now => {
628
+ sweep(now);
629
+ let entry = lookup(key, now);
630
+ if (entry === undefined) {
631
+ entry = {
632
+ state: "Closed",
633
+ generation: nextGeneration++,
634
+ lastActivity: now,
635
+ streak: 0,
636
+ ring: [],
637
+ ringFailures: 0,
638
+ openUntil: 0,
639
+ consecutiveOpenings: 0,
640
+ lastOpenFor: 0,
641
+ resetAt: 0,
642
+ probing: false
643
+ };
644
+ entries.set(key, entry);
645
+ }
646
+ let transition;
647
+ switch (entry.state) {
648
+ case "Closed":
649
+ {
650
+ // A call admitted before this reset can still trip the circuit.
651
+ // It opens with the openFor it computed from its older count,
652
+ // and the stored count restarts at 1.
653
+ if (entry.consecutiveOpenings > 0 && now >= entry.resetAt) {
654
+ entry.consecutiveOpenings = 0;
655
+ }
656
+ break;
657
+ }
658
+ case "Open":
659
+ {
660
+ if (now < entry.openUntil) {
661
+ return Effect.fail(new CircuitBreakerError({
662
+ reason: new CircuitOpen({
663
+ key,
664
+ retryAfter: Duration.millis(entry.openUntil - now)
665
+ })
666
+ }));
667
+ }
668
+ entry.state = "HalfOpen";
669
+ transition = {
670
+ key,
671
+ from: "Open",
672
+ to: "HalfOpen"
673
+ };
674
+ break;
675
+ }
676
+ case "HalfOpen":
677
+ {
678
+ if (entry.probing) {
679
+ return Effect.fail(new CircuitBreakerError({
680
+ reason: new CircuitHalfOpen({
681
+ key
682
+ })
683
+ }));
684
+ }
685
+ break;
686
+ }
687
+ }
688
+ entry.lastActivity = now;
689
+ if (entry.state === "HalfOpen") {
690
+ entry.probing = true;
691
+ entry.generation = nextGeneration++;
692
+ }
693
+ return Effect.succeed({
694
+ generation: entry.generation,
695
+ probe: entry.state === "HalfOpen",
696
+ consecutiveOpenings: entry.consecutiveOpenings,
697
+ transition
698
+ });
699
+ }),
700
+ record: ({
701
+ generation,
702
+ key,
703
+ openFor,
704
+ outcome,
705
+ tripPolicy
706
+ }) => Effect.map(Clock.currentTimeMillis, now => {
707
+ const entry = lookup(key, now);
708
+ if (entry === undefined || entry.generation !== generation) return undefined;
709
+ entry.lastActivity = now;
710
+ if (entry.state === "HalfOpen") {
711
+ switch (outcome) {
712
+ case "Ignored":
713
+ {
714
+ entry.probing = false;
715
+ return undefined;
716
+ }
717
+ case "Failure":
718
+ return open(key, entry, "HalfOpen", openFor, now);
719
+ case "Success":
720
+ {
721
+ entry.state = "Closed";
722
+ entry.generation = nextGeneration++;
723
+ entry.resetAt = now + entry.lastOpenFor;
724
+ entry.probing = false;
725
+ resetCounters(entry);
726
+ return {
727
+ key,
728
+ from: "HalfOpen",
729
+ to: "Closed"
730
+ };
731
+ }
732
+ }
733
+ }
734
+ if (entry.state !== "Closed" || outcome === "Ignored") return undefined;
735
+ const failed = outcome === "Failure";
736
+ entry.streak = failed ? entry.streak + 1 : 0;
737
+ if (tripPolicy._tag === "Rate") {
738
+ entry.ring.push(failed);
739
+ if (failed) entry.ringFailures++;
740
+ while (entry.ring.length > tripPolicy.size) {
741
+ if (entry.ring.shift()) entry.ringFailures--;
742
+ }
743
+ }
744
+ return trips(entry, tripPolicy) ? open(key, entry, "Closed", openFor, now) : undefined;
745
+ }),
746
+ state: key => Effect.map(Clock.currentTimeMillis, now => {
747
+ const entry = lookup(key, now);
748
+ if (entry === undefined) return {
749
+ _tag: "Closed",
750
+ key
751
+ };
752
+ switch (entry.state) {
753
+ case "Closed":
754
+ return {
755
+ _tag: "Closed",
756
+ key
757
+ };
758
+ case "Open":
759
+ return {
760
+ _tag: "Open",
761
+ key,
762
+ retryAfter: Duration.millis(Math.max(0, entry.openUntil - now)),
763
+ consecutiveOpenings: entry.consecutiveOpenings
764
+ };
765
+ case "HalfOpen":
766
+ return {
767
+ _tag: "HalfOpen",
768
+ key,
769
+ probing: entry.probing,
770
+ consecutiveOpenings: entry.consecutiveOpenings
771
+ };
772
+ }
773
+ })
774
+ });
775
+ });
776
+ /**
777
+ * Creates a Redis-backed `CircuitBreakerStore` layer.
778
+ *
779
+ * **Details**
780
+ *
781
+ * - Each circuit is one hash at `${prefix}${key}`, and `prefix` defaults to
782
+ * `"circuitbreaker:"`. Times come from Redis `TIME`, so client clock skew
783
+ * never matters. Requires Redis 5 or later.
784
+ * - The probe holds a lease of `probeLease` (default 10 seconds), renewed
785
+ * while it runs. If the lease lapses, another caller takes over and the old
786
+ * probe's outcome is discarded.
787
+ * - `idleTimeToLive` (default one hour) expires idle keys. An Open key, or one
788
+ * whose probe holds the lease, is kept that long past its open time or
789
+ * lease, so expiry never cuts a probe short or resets the backoff early. An
790
+ * expired key starts again as Closed with a backoff count of 0.
791
+ *
792
+ * **Gotchas**
793
+ *
794
+ * - If renewals cannot reach Redis, the lease lapses and a second probe can
795
+ * start.
796
+ * - Durations above 2^50 milliseconds cannot be stored. An operation that
797
+ * needs one fails with `CircuitBreakerStoreError`.
798
+ *
799
+ * @stability experimental
800
+ * @category layers
801
+ * @since 4.0.6
802
+ */
803
+ export const layerStoreRedis = options => Layer.effect(CircuitBreakerStore, makeStoreRedis(options));
804
+ /**
805
+ * Creates a Redis-backed `CircuitBreakerStore` layer from wrapped
806
+ * configuration options.
807
+ *
808
+ * **Details**
809
+ *
810
+ * Takes the same options as `layerStoreRedis`.
811
+ *
812
+ * @stability experimental
813
+ * @category layers
814
+ * @since 4.0.6
815
+ */
816
+ export const layerStoreRedisConfig = options => Layer.effect(CircuitBreakerStore, Effect.flatMap(Config.unwrap(options), makeStoreRedis));
817
+ const makeStoreRedis = /*#__PURE__*/Effect.fnUntraced(function* (options) {
818
+ const redis = yield* Redis.Redis;
819
+ const prefix = options?.prefix ?? "circuitbreaker:";
820
+ const leaseMillis = toRedisMillis(options?.probeLease ?? "10 seconds");
821
+ const idleMillis = toRedisMillis(options?.idleTimeToLive ?? "1 hour");
822
+ const renewInterval = Duration.millis(leaseMillis / 3);
823
+ const admitRedis = redis.eval(admitScript);
824
+ const recordRedis = redis.eval(recordScript);
825
+ const stateRedis = redis.eval(stateScript);
826
+ const renewRedis = redis.eval(renewScript);
827
+ const storeError = message => error => new CircuitBreakerError({
828
+ reason: new CircuitBreakerStoreError({
829
+ message,
830
+ cause: error.cause
831
+ })
832
+ });
833
+ // Stops once the lease is gone, because a renewal can never win it back.
834
+ const renew = Effect.fnUntraced(function* (redisKey, generation) {
835
+ while (true) {
836
+ yield* Effect.sleep(renewInterval);
837
+ const renewed = yield* renewRedis(redisKey, generation, leaseMillis, idleMillis).pipe(Effect.catchCauseFilter(findRecoverableError, (_, cause) => Effect.as(reportUnhandled("CircuitBreaker failed to renew a probe lease, retrying", cause), 1)));
838
+ if (renewed !== 1) return;
839
+ }
840
+ });
841
+ return CircuitBreakerStore.of({
842
+ admit: Effect.fnUntraced(function* (key) {
843
+ const redisKey = `${prefix}${key}`;
844
+ const result = yield* admitRedis(redisKey, leaseMillis, idleMillis).pipe(Effect.mapError(storeError("Failed to admit a call through the circuit")));
845
+ switch (result[0]) {
846
+ case "open":
847
+ return yield* new CircuitBreakerError({
848
+ reason: new CircuitOpen({
849
+ key,
850
+ retryAfter: Duration.millis(Math.max(0, Number(result[1])))
851
+ })
852
+ });
853
+ case "halfopen":
854
+ return yield* new CircuitBreakerError({
855
+ reason: new CircuitHalfOpen({
856
+ key
857
+ })
858
+ });
859
+ }
860
+ const [, generation, probe, consecutiveOpenings, moved] = result;
861
+ if (probe === 1) {
862
+ yield* renew(redisKey, generation).pipe(Effect.interruptible, Effect.forkScoped);
863
+ }
864
+ return {
865
+ generation: Number(generation),
866
+ probe: probe === 1,
867
+ consecutiveOpenings: Number(consecutiveOpenings),
868
+ transition: moved === 1 ? {
869
+ key,
870
+ from: "Open",
871
+ to: "HalfOpen"
872
+ } : undefined
873
+ };
874
+ }),
875
+ record: ({
876
+ generation,
877
+ key,
878
+ openFor,
879
+ outcome,
880
+ tripPolicy
881
+ }) => recordRedis(`${prefix}${key}`, String(generation), outcome, tripPolicy, Math.max(0, Math.ceil(Duration.toMillis(openFor))), idleMillis).pipe(Effect.mapError(storeError("Failed to record an outcome")), Effect.map(([moved, consecutiveOpenings]) => {
882
+ switch (moved) {
883
+ case "closed-open":
884
+ case "halfopen-open":
885
+ return {
886
+ key,
887
+ from: moved === "closed-open" ? "Closed" : "HalfOpen",
888
+ to: "Open",
889
+ retryAfter: openFor,
890
+ consecutiveOpenings: Number(consecutiveOpenings)
891
+ };
892
+ case "halfopen-closed":
893
+ return {
894
+ key,
895
+ from: "HalfOpen",
896
+ to: "Closed"
897
+ };
898
+ default:
899
+ return undefined;
900
+ }
901
+ })),
902
+ state: key => stateRedis(`${prefix}${key}`).pipe(Effect.mapError(storeError("Failed to read the circuit state")), Effect.map(([state, remaining, consecutiveOpenings, probing]) => {
903
+ switch (state) {
904
+ case "Open":
905
+ return {
906
+ _tag: "Open",
907
+ key,
908
+ retryAfter: Duration.millis(Math.max(0, Number(remaining))),
909
+ consecutiveOpenings: Number(consecutiveOpenings)
910
+ };
911
+ case "HalfOpen":
912
+ return {
913
+ _tag: "HalfOpen",
914
+ key,
915
+ probing: probing === 1,
916
+ consecutiveOpenings: Number(consecutiveOpenings)
917
+ };
918
+ default:
919
+ return {
920
+ _tag: "Closed",
921
+ key
922
+ };
923
+ }
924
+ }))
925
+ });
926
+ });
927
+ const toRedisMillis = input => Math.max(1, Math.ceil(Duration.toMillis(Duration.fromInputUnsafe(input))));
928
+ // Shared Lua prelude.
929
+ //
930
+ // Numeric arguments are capped at 2^50 (about 35,000 years in milliseconds),
931
+ // so every TTL and timestamp derived from them stays an exact integer. Redis
932
+ // formats a Lua number above 1e17 with an exponent, which PEXPIRE rejects
933
+ // after earlier writes have already happened. `int` formats every number
934
+ // written to Redis without one.
935
+ //
936
+ // Generations are the larger of the previous generation plus one and the
937
+ // current Redis time in microseconds, so a key that expired and was recreated
938
+ // never reissues a generation an old permit still holds. They are kept as
939
+ // integer strings so they never pass through a float format.
940
+ const luaPrelude = `
941
+ local maxValue = 1125899906842624
942
+ local function bounded(value, min)
943
+ local n = tonumber(value)
944
+ if n == nil or n ~= n or n < min or n > maxValue then return nil end
945
+ return n
946
+ end
947
+ local function positive(value) return bounded(value, 1) end
948
+ local function nonNegative(value) return bounded(value, 0) end
949
+ local function int(n) return string.format("%.0f", n) end
950
+ local time = redis.call("TIME")
951
+ local nowMicros = tonumber(time[1]) * 1000000 + tonumber(time[2])
952
+ local now = math.floor(nowMicros / 1000)
953
+ local function nextGeneration(current)
954
+ return int(math.max((tonumber(current) or 0) + 1, nowMicros))
955
+ end
956
+ `;
957
+ // Returns { "ok", generation, probe, consecutiveOpenings, movedToHalfOpen },
958
+ // { "open", remainingMillis } or { "halfopen" }.
959
+ const admitScript = /*#__PURE__*/Redis.script((key, leaseMillis, idleMillis) => [key, leaseMillis, idleMillis], {
960
+ numberOfKeys: 1,
961
+ lua: `${luaPrelude}
962
+ local key = KEYS[1]
963
+ local lease = positive(ARGV[1])
964
+ local idle = positive(ARGV[2])
965
+ if not lease or not idle then
966
+ return redis.error_reply("CircuitBreaker admit: invalid arguments")
967
+ end
968
+
969
+ local fields = redis.call("HMGET", key, "state", "generation", "openUntil", "openings", "leaseUntil", "resetAt")
970
+ local state = fields[1]
971
+ local generation = fields[2]
972
+ local openings = tonumber(fields[4]) or 0
973
+
974
+ if not state then
975
+ generation = nextGeneration(0)
976
+ redis.call("HSET", key, "state", "Closed", "generation", generation)
977
+ redis.call("PEXPIRE", key, int(idle))
978
+ return { "ok", generation, 0, 0, 0 }
979
+ end
980
+
981
+ if state == "Closed" then
982
+ -- A call admitted before this reset can still trip the circuit. It opens
983
+ -- with the openFor it computed from its older count, and the stored count
984
+ -- restarts at 1.
985
+ if openings > 0 and now >= (tonumber(fields[6]) or 0) then
986
+ openings = 0
987
+ redis.call("HSET", key, "openings", 0)
988
+ end
989
+ redis.call("PEXPIRE", key, int(idle))
990
+ return { "ok", generation, 0, openings, 0 }
991
+ end
992
+
993
+ local moved = 0
994
+ if state == "Open" then
995
+ local openUntil = tonumber(fields[3]) or 0
996
+ if now < openUntil then
997
+ return { "open", openUntil - now }
998
+ end
999
+ moved = 1
1000
+ elseif (tonumber(fields[5]) or 0) > now then
1001
+ return { "halfopen" }
1002
+ end
1003
+
1004
+ -- Every probe admission, including a takeover of a lapsed lease, starts a
1005
+ -- new generation.
1006
+ generation = nextGeneration(generation)
1007
+ redis.call("HSET", key, "state", "HalfOpen", "generation", generation, "leaseUntil", int(now + lease))
1008
+ redis.call("PEXPIRE", key, int(lease + idle))
1009
+ return { "ok", generation, 1, openings, moved }
1010
+ `
1011
+ }).withReturnType();
1012
+ // Returns { transition, consecutiveOpenings }, where transition is "none",
1013
+ // "closed-open", "halfopen-open" or "halfopen-closed".
1014
+ //
1015
+ // ARGV holds the generation, the outcome, the policy tag, the policy's
1016
+ // failures (Consecutive) or threshold (Rate), minimumCalls and size (0 under
1017
+ // Consecutive), then the open time and idle time in milliseconds.
1018
+ const recordScript = /*#__PURE__*/Redis.script((key, generation, outcome, tripPolicy, openForMillis, idleMillis) => [key, generation, outcome, tripPolicy._tag, ...(tripPolicy._tag === "Consecutive" ? [tripPolicy.failures, 0, 0] : [tripPolicy.threshold, tripPolicy.minimumCalls, tripPolicy.size]), openForMillis, idleMillis], {
1019
+ numberOfKeys: 1,
1020
+ lua: `${luaPrelude}
1021
+ local key = KEYS[1]
1022
+ local expected = ARGV[1]
1023
+ local outcome = ARGV[2]
1024
+ local policy = ARGV[3]
1025
+ local openFor = nonNegative(ARGV[7])
1026
+ local idle = positive(ARGV[8])
1027
+ local valid = openFor ~= nil and idle ~= nil and
1028
+ (outcome == "Success" or outcome == "Failure" or outcome == "Ignored")
1029
+ local failuresNeeded, threshold, minimumCalls, size
1030
+ if policy == "Consecutive" then
1031
+ failuresNeeded = positive(ARGV[4])
1032
+ valid = valid and failuresNeeded ~= nil
1033
+ elseif policy == "Rate" then
1034
+ threshold = nonNegative(ARGV[4])
1035
+ minimumCalls = positive(ARGV[5])
1036
+ size = positive(ARGV[6])
1037
+ valid = valid and threshold ~= nil and threshold <= 1 and minimumCalls ~= nil and size ~= nil and
1038
+ minimumCalls <= size
1039
+ else
1040
+ valid = false
1041
+ end
1042
+ if not valid then
1043
+ return redis.error_reply("CircuitBreaker record: invalid arguments")
1044
+ end
1045
+
1046
+ local fields = redis.call("HMGET", key, "state", "generation", "streak", "ring", "failures", "openings", "lastOpenFor")
1047
+ local state = fields[1]
1048
+ if not state or fields[2] ~= expected then
1049
+ return { "none", 0 }
1050
+ end
1051
+ local openings = tonumber(fields[6]) or 0
1052
+
1053
+ local function open()
1054
+ openings = openings + 1
1055
+ redis.call(
1056
+ "HSET", key,
1057
+ "state", "Open",
1058
+ "generation", nextGeneration(fields[2]),
1059
+ "openUntil", int(now + openFor),
1060
+ "openings", openings,
1061
+ "lastOpenFor", int(openFor),
1062
+ "streak", 0,
1063
+ "ring", "",
1064
+ "failures", 0,
1065
+ "leaseUntil", 0
1066
+ )
1067
+ redis.call("PEXPIRE", key, int(openFor + idle))
1068
+ end
1069
+
1070
+ if state == "HalfOpen" then
1071
+ if outcome == "Ignored" then
1072
+ -- A new generation fences a renewal already on its way, so the freed slot
1073
+ -- stays free.
1074
+ redis.call("HSET", key, "generation", nextGeneration(fields[2]), "leaseUntil", 0)
1075
+ redis.call("PEXPIRE", key, int(idle))
1076
+ return { "none", openings }
1077
+ end
1078
+ if outcome == "Failure" then
1079
+ open()
1080
+ return { "halfopen-open", openings }
1081
+ end
1082
+ redis.call(
1083
+ "HSET", key,
1084
+ "state", "Closed",
1085
+ "generation", nextGeneration(fields[2]),
1086
+ "resetAt", int(now + (tonumber(fields[7]) or 0)),
1087
+ "streak", 0,
1088
+ "ring", "",
1089
+ "failures", 0,
1090
+ "leaseUntil", 0
1091
+ )
1092
+ redis.call("PEXPIRE", key, int(idle))
1093
+ return { "halfopen-closed", openings }
1094
+ end
1095
+
1096
+ if state ~= "Closed" then
1097
+ return { "none", openings }
1098
+ end
1099
+ if outcome == "Ignored" then
1100
+ redis.call("PEXPIRE", key, int(idle))
1101
+ return { "none", openings }
1102
+ end
1103
+
1104
+ local failed = outcome == "Failure"
1105
+ local streak = failed and (tonumber(fields[3]) or 0) + 1 or 0
1106
+ local trips
1107
+ local ring = fields[4] or ""
1108
+ local failures = tonumber(fields[5]) or 0
1109
+ if policy == "Consecutive" then
1110
+ trips = streak >= failuresNeeded
1111
+ else
1112
+ ring = ring .. (failed and "1" or "0")
1113
+ if failed then failures = failures + 1 end
1114
+ if #ring > size then
1115
+ local _, dropped = string.gsub(string.sub(ring, 1, #ring - size), "1", "")
1116
+ failures = failures - dropped
1117
+ ring = string.sub(ring, #ring - size + 1)
1118
+ end
1119
+ trips = #ring >= minimumCalls and failures > 0 and failures / #ring >= threshold
1120
+ end
1121
+
1122
+ if trips then
1123
+ open()
1124
+ return { "closed-open", openings }
1125
+ end
1126
+ redis.call("HSET", key, "streak", streak, "ring", ring, "failures", failures)
1127
+ redis.call("PEXPIRE", key, int(idle))
1128
+ return { "none", openings }
1129
+ `
1130
+ }).withReturnType();
1131
+ // Read-only. Returns { state, remainingOpenMillis, consecutiveOpenings, probing }.
1132
+ const stateScript = /*#__PURE__*/Redis.script(key => [key], {
1133
+ numberOfKeys: 1,
1134
+ lua: `${luaPrelude}
1135
+ local fields = redis.call("HMGET", KEYS[1], "state", "openUntil", "openings", "leaseUntil")
1136
+ local state = fields[1]
1137
+ if not state then
1138
+ return { "Closed", 0, 0, 0 }
1139
+ end
1140
+ local remaining = math.max(0, (tonumber(fields[2]) or 0) - now)
1141
+ local probing = (tonumber(fields[4]) or 0) > now and 1 or 0
1142
+ return { state, remaining, tonumber(fields[3]) or 0, probing }
1143
+ `
1144
+ }).withReturnType();
1145
+ // Extends the probe lease only while the generation still holds it. Returns 1
1146
+ // when renewed, 0 when the lease is gone.
1147
+ const renewScript = /*#__PURE__*/Redis.script((key, generation, leaseMillis, idleMillis) => [key, generation, leaseMillis, idleMillis], {
1148
+ numberOfKeys: 1,
1149
+ lua: `${luaPrelude}
1150
+ local key = KEYS[1]
1151
+ local lease = positive(ARGV[2])
1152
+ local idle = positive(ARGV[3])
1153
+ if not lease or not idle then
1154
+ return redis.error_reply("CircuitBreaker renew: invalid arguments")
1155
+ end
1156
+ local fields = redis.call("HMGET", key, "state", "generation")
1157
+ if fields[1] ~= "HalfOpen" or fields[2] ~= ARGV[1] then
1158
+ return 0
1159
+ end
1160
+ redis.call("HSET", key, "leaseUntil", int(now + lease))
1161
+ redis.call("PEXPIRE", key, int(lease + idle))
1162
+ return 1
1163
+ `
1164
+ }).withReturnType();
1165
+ //# sourceMappingURL=CircuitBreaker.js.map