effect 4.0.6 → 4.1.0

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 (978) hide show
  1. package/README.md +1 -1
  2. package/dist/Arbitrary.d.ts +10 -2
  3. package/dist/Arbitrary.d.ts.map +1 -1
  4. package/dist/Arbitrary.js +2 -1
  5. package/dist/Arbitrary.js.map +1 -1
  6. package/dist/BigDecimal.d.ts.map +1 -1
  7. package/dist/BigDecimal.js +3 -0
  8. package/dist/BigDecimal.js.map +1 -1
  9. package/dist/Channel.d.ts +12 -6
  10. package/dist/Channel.d.ts.map +1 -1
  11. package/dist/Channel.js +30 -9
  12. package/dist/Channel.js.map +1 -1
  13. package/dist/Config.d.ts +7 -1
  14. package/dist/Config.d.ts.map +1 -1
  15. package/dist/Config.js.map +1 -1
  16. package/dist/Context.d.ts +18 -1
  17. package/dist/Context.d.ts.map +1 -1
  18. package/dist/Context.js +27 -8
  19. package/dist/Context.js.map +1 -1
  20. package/dist/Cron.d.ts +81 -11
  21. package/dist/Cron.d.ts.map +1 -1
  22. package/dist/Cron.js +101 -26
  23. package/dist/Cron.js.map +1 -1
  24. package/dist/Data.d.ts +54 -235
  25. package/dist/Data.d.ts.map +1 -1
  26. package/dist/Data.js.map +1 -1
  27. package/dist/Duration.d.ts +78 -0
  28. package/dist/Duration.d.ts.map +1 -1
  29. package/dist/Duration.js +123 -0
  30. package/dist/Duration.js.map +1 -1
  31. package/dist/Effect.d.ts +165 -8
  32. package/dist/Effect.d.ts.map +1 -1
  33. package/dist/Effect.js +12 -2
  34. package/dist/Effect.js.map +1 -1
  35. package/dist/Equal.d.ts.map +1 -1
  36. package/dist/Equal.js +8 -1
  37. package/dist/Equal.js.map +1 -1
  38. package/dist/Exit.d.ts +3 -3
  39. package/dist/Exit.d.ts.map +1 -1
  40. package/dist/Exit.js.map +1 -1
  41. package/dist/FileSystem.d.ts +59 -4
  42. package/dist/FileSystem.d.ts.map +1 -1
  43. package/dist/FileSystem.js +9 -2
  44. package/dist/FileSystem.js.map +1 -1
  45. package/dist/Formatter.d.ts.map +1 -1
  46. package/dist/Formatter.js +27 -13
  47. package/dist/Formatter.js.map +1 -1
  48. package/dist/Glob.d.ts +165 -0
  49. package/dist/Glob.d.ts.map +1 -0
  50. package/dist/Glob.js +127 -0
  51. package/dist/Glob.js.map +1 -0
  52. package/dist/Hash.d.ts.map +1 -1
  53. package/dist/Hash.js +4 -1
  54. package/dist/Hash.js.map +1 -1
  55. package/dist/JsonPatch.js +4 -0
  56. package/dist/JsonPatch.js.map +1 -1
  57. package/dist/JsonSchema.d.ts.map +1 -1
  58. package/dist/JsonSchema.js +13 -40
  59. package/dist/JsonSchema.js.map +1 -1
  60. package/dist/Match.d.ts +86 -0
  61. package/dist/Match.d.ts.map +1 -1
  62. package/dist/Match.js +72 -0
  63. package/dist/Match.js.map +1 -1
  64. package/dist/Metric.d.ts +3 -3
  65. package/dist/MutableList.d.ts +5 -2
  66. package/dist/MutableList.d.ts.map +1 -1
  67. package/dist/MutableList.js +7 -5
  68. package/dist/MutableList.js.map +1 -1
  69. package/dist/MutableRef.d.ts.map +1 -1
  70. package/dist/MutableRef.js +7 -0
  71. package/dist/MutableRef.js.map +1 -1
  72. package/dist/PartitionedSemaphore.d.ts +2 -1
  73. package/dist/PartitionedSemaphore.d.ts.map +1 -1
  74. package/dist/PartitionedSemaphore.js +22 -12
  75. package/dist/PartitionedSemaphore.js.map +1 -1
  76. package/dist/Pool.d.ts +51 -0
  77. package/dist/Pool.d.ts.map +1 -1
  78. package/dist/Pool.js +136 -36
  79. package/dist/Pool.js.map +1 -1
  80. package/dist/PubSub.d.ts.map +1 -1
  81. package/dist/PubSub.js +5 -6
  82. package/dist/PubSub.js.map +1 -1
  83. package/dist/Random.d.ts.map +1 -1
  84. package/dist/Random.js +3 -3
  85. package/dist/Random.js.map +1 -1
  86. package/dist/RcMap.d.ts.map +1 -1
  87. package/dist/RcMap.js +8 -2
  88. package/dist/RcMap.js.map +1 -1
  89. package/dist/Ref.d.ts.map +1 -1
  90. package/dist/Ref.js +8 -0
  91. package/dist/Ref.js.map +1 -1
  92. package/dist/Schedule.d.ts +18 -1
  93. package/dist/Schedule.d.ts.map +1 -1
  94. package/dist/Schedule.js +8 -3
  95. package/dist/Schedule.js.map +1 -1
  96. package/dist/Schema.d.ts +690 -49
  97. package/dist/Schema.d.ts.map +1 -1
  98. package/dist/Schema.js +687 -28
  99. package/dist/Schema.js.map +1 -1
  100. package/dist/SchemaAST.d.ts +4 -2
  101. package/dist/SchemaAST.d.ts.map +1 -1
  102. package/dist/SchemaAST.js +14 -17
  103. package/dist/SchemaAST.js.map +1 -1
  104. package/dist/SchemaGetter.d.ts +6 -1
  105. package/dist/SchemaGetter.d.ts.map +1 -1
  106. package/dist/SchemaGetter.js +22 -11
  107. package/dist/SchemaGetter.js.map +1 -1
  108. package/dist/SchemaParser.d.ts.map +1 -1
  109. package/dist/SchemaParser.js +48 -22
  110. package/dist/SchemaParser.js.map +1 -1
  111. package/dist/SchemaRepresentation.d.ts +33 -10
  112. package/dist/SchemaRepresentation.d.ts.map +1 -1
  113. package/dist/SchemaRepresentation.js +39 -13
  114. package/dist/SchemaRepresentation.js.map +1 -1
  115. package/dist/SchemaTransformation.d.ts +12 -4
  116. package/dist/SchemaTransformation.d.ts.map +1 -1
  117. package/dist/SchemaTransformation.js +15 -5
  118. package/dist/SchemaTransformation.js.map +1 -1
  119. package/dist/ScopedCache.d.ts.map +1 -1
  120. package/dist/ScopedCache.js +5 -7
  121. package/dist/ScopedCache.js.map +1 -1
  122. package/dist/Semaphore.d.ts +130 -0
  123. package/dist/Semaphore.d.ts.map +1 -1
  124. package/dist/Semaphore.js +94 -35
  125. package/dist/Semaphore.js.map +1 -1
  126. package/dist/Sink.d.ts.map +1 -1
  127. package/dist/Sink.js +7 -2
  128. package/dist/Sink.js.map +1 -1
  129. package/dist/Stream.d.ts +13 -4
  130. package/dist/Stream.d.ts.map +1 -1
  131. package/dist/Stream.js +31 -10
  132. package/dist/Stream.js.map +1 -1
  133. package/dist/String.d.ts +3 -0
  134. package/dist/String.d.ts.map +1 -1
  135. package/dist/String.js +3 -0
  136. package/dist/String.js.map +1 -1
  137. package/dist/Terminal.d.ts +18 -1
  138. package/dist/Terminal.d.ts.map +1 -1
  139. package/dist/Terminal.js +1 -1
  140. package/dist/Terminal.js.map +1 -1
  141. package/dist/Tracer.d.ts +11 -1
  142. package/dist/Tracer.d.ts.map +1 -1
  143. package/dist/Tracer.js +6 -1
  144. package/dist/Tracer.js.map +1 -1
  145. package/dist/ai/AiError.d.ts +43 -116
  146. package/dist/ai/AiError.d.ts.map +1 -1
  147. package/dist/ai/AiError.js +53 -72
  148. package/dist/ai/AiError.js.map +1 -1
  149. package/dist/ai/Decision.d.ts +196 -15
  150. package/dist/ai/Decision.d.ts.map +1 -1
  151. package/dist/ai/Decision.js +130 -0
  152. package/dist/ai/Decision.js.map +1 -1
  153. package/dist/ai/DecisionModel.d.ts +54 -13
  154. package/dist/ai/DecisionModel.d.ts.map +1 -1
  155. package/dist/ai/DecisionModel.js +123 -12
  156. package/dist/ai/DecisionModel.js.map +1 -1
  157. package/dist/ai/LanguageModel.d.ts.map +1 -1
  158. package/dist/ai/LanguageModel.js +9 -0
  159. package/dist/ai/LanguageModel.js.map +1 -1
  160. package/dist/ai/McpSchema.d.ts +110 -110
  161. package/dist/ai/McpSchema.d.ts.map +1 -1
  162. package/dist/ai/McpServer.d.ts +630 -29
  163. package/dist/ai/McpServer.d.ts.map +1 -1
  164. package/dist/ai/McpServer.js +583 -131
  165. package/dist/ai/McpServer.js.map +1 -1
  166. package/dist/ai/McpTest.d.ts +130 -0
  167. package/dist/ai/McpTest.d.ts.map +1 -0
  168. package/dist/ai/McpTest.js +237 -0
  169. package/dist/ai/McpTest.js.map +1 -0
  170. package/dist/ai/Prompt.d.ts +52 -0
  171. package/dist/ai/Prompt.d.ts.map +1 -1
  172. package/dist/ai/Prompt.js +38 -0
  173. package/dist/ai/Prompt.js.map +1 -1
  174. package/dist/ai/Response.d.ts +17 -28
  175. package/dist/ai/Response.d.ts.map +1 -1
  176. package/dist/ai/Response.js +5 -10
  177. package/dist/ai/Response.js.map +1 -1
  178. package/dist/ai/Tool.d.ts +43 -1
  179. package/dist/ai/Tool.d.ts.map +1 -1
  180. package/dist/ai/Tool.js +5 -2
  181. package/dist/ai/Tool.js.map +1 -1
  182. package/dist/ai/Toolkit.d.ts.map +1 -1
  183. package/dist/ai/Toolkit.js +8 -1
  184. package/dist/ai/Toolkit.js.map +1 -1
  185. package/dist/ai/index.d.ts +5 -0
  186. package/dist/ai/index.d.ts.map +1 -1
  187. package/dist/ai/index.js +5 -0
  188. package/dist/ai/index.js.map +1 -1
  189. package/dist/ai/internal/mcpCore.js +17 -12
  190. package/dist/ai/internal/mcpCore.js.map +1 -1
  191. package/dist/ai/internal/mcpProtocol/v2024_11_05.js +4 -5
  192. package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
  193. package/dist/ai/internal/mcpProtocol/v2025_03_26.js +4 -5
  194. package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
  195. package/dist/ai/internal/mcpProtocol/v2025_06_18.js +4 -5
  196. package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
  197. package/dist/ai/internal/mcpProtocol/v2025_11_25.js +4 -5
  198. package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
  199. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +18 -10
  200. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  201. package/dist/ai/internal/mcpProtocol.js +13 -0
  202. package/dist/ai/internal/mcpProtocol.js.map +1 -1
  203. package/dist/ai/internal/mcpRuntime.js +5 -3
  204. package/dist/ai/internal/mcpRuntime.js.map +1 -1
  205. package/dist/ai/internal/mcpStatefulRuntime.js +6 -1
  206. package/dist/ai/internal/mcpStatefulRuntime.js.map +1 -1
  207. package/dist/cli/Argument.d.ts +21 -0
  208. package/dist/cli/Argument.d.ts.map +1 -1
  209. package/dist/cli/Argument.js +7 -0
  210. package/dist/cli/Argument.js.map +1 -1
  211. package/dist/cli/CliError.d.ts +78 -0
  212. package/dist/cli/CliError.d.ts.map +1 -1
  213. package/dist/cli/CliError.js +65 -3
  214. package/dist/cli/CliError.js.map +1 -1
  215. package/dist/cli/CliOutput.d.ts +220 -7
  216. package/dist/cli/CliOutput.d.ts.map +1 -1
  217. package/dist/cli/CliOutput.js +156 -43
  218. package/dist/cli/CliOutput.js.map +1 -1
  219. package/dist/cli/Command.d.ts +47 -16
  220. package/dist/cli/Command.d.ts.map +1 -1
  221. package/dist/cli/Command.js +67 -29
  222. package/dist/cli/Command.js.map +1 -1
  223. package/dist/cli/Completions.d.ts +58 -0
  224. package/dist/cli/Completions.d.ts.map +1 -1
  225. package/dist/cli/Completions.js +49 -9
  226. package/dist/cli/Completions.js.map +1 -1
  227. package/dist/cli/Flag.d.ts +122 -0
  228. package/dist/cli/Flag.d.ts.map +1 -1
  229. package/dist/cli/Flag.js +42 -0
  230. package/dist/cli/Flag.js.map +1 -1
  231. package/dist/cli/GlobalFlag.d.ts.map +1 -1
  232. package/dist/cli/GlobalFlag.js +15 -7
  233. package/dist/cli/GlobalFlag.js.map +1 -1
  234. package/dist/cli/HelpDoc.d.ts +15 -1
  235. package/dist/cli/HelpDoc.d.ts.map +1 -1
  236. package/dist/cli/Param.d.ts +15 -0
  237. package/dist/cli/Param.d.ts.map +1 -1
  238. package/dist/cli/Param.js +23 -2
  239. package/dist/cli/Param.js.map +1 -1
  240. package/dist/cli/Primitive.d.ts +14 -0
  241. package/dist/cli/Primitive.d.ts.map +1 -1
  242. package/dist/cli/Primitive.js +15 -1
  243. package/dist/cli/Primitive.js.map +1 -1
  244. package/dist/cli/Prompt.d.ts +1 -0
  245. package/dist/cli/Prompt.d.ts.map +1 -1
  246. package/dist/cli/Prompt.js +1 -0
  247. package/dist/cli/Prompt.js.map +1 -1
  248. package/dist/cli/TerminalCapabilities.d.ts +3 -2
  249. package/dist/cli/TerminalCapabilities.d.ts.map +1 -1
  250. package/dist/cli/TerminalCapabilities.js +3 -1
  251. package/dist/cli/TerminalCapabilities.js.map +1 -1
  252. package/dist/cli/internal/completions/bash.js +73 -27
  253. package/dist/cli/internal/completions/bash.js.map +1 -1
  254. package/dist/cli/internal/help.d.ts.map +1 -1
  255. package/dist/cli/internal/help.js +17 -10
  256. package/dist/cli/internal/help.js.map +1 -1
  257. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  258. package/dist/cluster/ClusterWorkflowEngine.js +45 -10
  259. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  260. package/dist/cluster/MessageStorage.d.ts +23 -45
  261. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  262. package/dist/cluster/MessageStorage.js +6 -7
  263. package/dist/cluster/MessageStorage.js.map +1 -1
  264. package/dist/cluster/Sharding.d.ts.map +1 -1
  265. package/dist/cluster/Sharding.js +151 -49
  266. package/dist/cluster/Sharding.js.map +1 -1
  267. package/dist/cluster/ShardingConfig.d.ts +76 -0
  268. package/dist/cluster/ShardingConfig.d.ts.map +1 -1
  269. package/dist/cluster/ShardingConfig.js +4 -0
  270. package/dist/cluster/ShardingConfig.js.map +1 -1
  271. package/dist/cluster/SingleRunner.d.ts +3 -3
  272. package/dist/cluster/SingleRunner.d.ts.map +1 -1
  273. package/dist/cluster/SingleRunner.js +1 -1
  274. package/dist/cluster/Snowflake.d.ts.map +1 -1
  275. package/dist/cluster/Snowflake.js +3 -7
  276. package/dist/cluster/Snowflake.js.map +1 -1
  277. package/dist/cluster/SqlMessageStorage.d.ts +37 -6
  278. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  279. package/dist/cluster/SqlMessageStorage.js +34 -7
  280. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  281. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  282. package/dist/cluster/SqlRunnerStorage.js +109 -46
  283. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  284. package/dist/cluster/internal/entityManager.js +54 -20
  285. package/dist/cluster/internal/entityManager.js.map +1 -1
  286. package/dist/crypto/Aead.d.ts +322 -0
  287. package/dist/crypto/Aead.d.ts.map +1 -0
  288. package/dist/crypto/Aead.js +331 -0
  289. package/dist/crypto/Aead.js.map +1 -0
  290. package/dist/crypto/Cipher.d.ts +235 -0
  291. package/dist/crypto/Cipher.d.ts.map +1 -0
  292. package/dist/crypto/Cipher.js +233 -0
  293. package/dist/crypto/Cipher.js.map +1 -0
  294. package/dist/crypto/CryptoError.d.ts +212 -0
  295. package/dist/crypto/CryptoError.d.ts.map +1 -0
  296. package/dist/crypto/CryptoError.js +202 -0
  297. package/dist/crypto/CryptoError.js.map +1 -0
  298. package/dist/crypto/Digest.d.ts +143 -0
  299. package/dist/crypto/Digest.d.ts.map +1 -0
  300. package/dist/crypto/Digest.js +146 -0
  301. package/dist/crypto/Digest.js.map +1 -0
  302. package/dist/crypto/Kdf.d.ts +220 -0
  303. package/dist/crypto/Kdf.d.ts.map +1 -0
  304. package/dist/crypto/Kdf.js +212 -0
  305. package/dist/crypto/Kdf.js.map +1 -0
  306. package/dist/crypto/KeyAgreement.d.ts +323 -0
  307. package/dist/crypto/KeyAgreement.d.ts.map +1 -0
  308. package/dist/crypto/KeyAgreement.js +303 -0
  309. package/dist/crypto/KeyAgreement.js.map +1 -0
  310. package/dist/crypto/KeyFormat.d.ts +309 -0
  311. package/dist/crypto/KeyFormat.d.ts.map +1 -0
  312. package/dist/crypto/KeyFormat.js +556 -0
  313. package/dist/crypto/KeyFormat.js.map +1 -0
  314. package/dist/crypto/KeyWrap.d.ts +389 -0
  315. package/dist/crypto/KeyWrap.d.ts.map +1 -0
  316. package/dist/crypto/KeyWrap.js +381 -0
  317. package/dist/crypto/KeyWrap.js.map +1 -0
  318. package/dist/crypto/Mac.d.ts +277 -0
  319. package/dist/crypto/Mac.d.ts.map +1 -0
  320. package/dist/crypto/Mac.js +288 -0
  321. package/dist/crypto/Mac.js.map +1 -0
  322. package/dist/crypto/SecureRandom.d.ts +340 -0
  323. package/dist/crypto/SecureRandom.d.ts.map +1 -0
  324. package/dist/crypto/SecureRandom.js +257 -0
  325. package/dist/crypto/SecureRandom.js.map +1 -0
  326. package/dist/crypto/Signature.d.ts +448 -0
  327. package/dist/crypto/Signature.d.ts.map +1 -0
  328. package/dist/crypto/Signature.js +484 -0
  329. package/dist/crypto/Signature.js.map +1 -0
  330. package/dist/crypto/WebCrypto.d.ts +61 -0
  331. package/dist/crypto/WebCrypto.d.ts.map +1 -0
  332. package/dist/crypto/WebCrypto.js +63 -0
  333. package/dist/crypto/WebCrypto.js.map +1 -0
  334. package/dist/crypto/index.d.ts +65 -0
  335. package/dist/crypto/index.d.ts.map +1 -0
  336. package/dist/crypto/index.js +66 -0
  337. package/dist/crypto/index.js.map +1 -0
  338. package/dist/crypto/internal/asymmetric.d.ts +2 -0
  339. package/dist/crypto/internal/asymmetric.d.ts.map +1 -0
  340. package/dist/crypto/internal/asymmetric.js +210 -0
  341. package/dist/crypto/internal/asymmetric.js.map +1 -0
  342. package/dist/crypto/internal/chacha.d.ts +46 -0
  343. package/dist/crypto/internal/chacha.d.ts.map +1 -0
  344. package/dist/crypto/internal/chacha.js +885 -0
  345. package/dist/crypto/internal/chacha.js.map +1 -0
  346. package/dist/crypto/internal/der.d.ts +6 -0
  347. package/dist/crypto/internal/der.d.ts.map +1 -0
  348. package/dist/crypto/internal/der.js +86 -0
  349. package/dist/crypto/internal/der.js.map +1 -0
  350. package/dist/crypto/internal/digest.d.ts +2 -0
  351. package/dist/crypto/internal/digest.d.ts.map +1 -0
  352. package/dist/crypto/internal/digest.js +15 -0
  353. package/dist/crypto/internal/digest.js.map +1 -0
  354. package/dist/crypto/internal/key.d.ts +2 -0
  355. package/dist/crypto/internal/key.d.ts.map +1 -0
  356. package/dist/crypto/internal/key.js +170 -0
  357. package/dist/crypto/internal/key.js.map +1 -0
  358. package/dist/crypto/internal/md5.d.ts +39 -0
  359. package/dist/crypto/internal/md5.d.ts.map +1 -0
  360. package/dist/crypto/internal/md5.js +179 -0
  361. package/dist/crypto/internal/md5.js.map +1 -0
  362. package/dist/crypto/internal/webCrypto.d.ts +2 -0
  363. package/dist/crypto/internal/webCrypto.d.ts.map +1 -0
  364. package/dist/crypto/internal/webCrypto.js +111 -0
  365. package/dist/crypto/internal/webCrypto.js.map +1 -0
  366. package/dist/devtools/DevToolsClient.d.ts.map +1 -1
  367. package/dist/devtools/DevToolsClient.js +2 -0
  368. package/dist/devtools/DevToolsClient.js.map +1 -1
  369. package/dist/encoding/Base64.js +36 -0
  370. package/dist/encoding/Base64.js.map +1 -1
  371. package/dist/encoding/Base64Url.d.ts.map +1 -1
  372. package/dist/encoding/Base64Url.js +3 -2
  373. package/dist/encoding/Base64Url.js.map +1 -1
  374. package/dist/encoding/Csv.d.ts +608 -0
  375. package/dist/encoding/Csv.d.ts.map +1 -0
  376. package/dist/encoding/Csv.js +718 -0
  377. package/dist/encoding/Csv.js.map +1 -0
  378. package/dist/encoding/Hex.d.ts +3 -2
  379. package/dist/encoding/Hex.d.ts.map +1 -1
  380. package/dist/encoding/Hex.js +3 -2
  381. package/dist/encoding/Hex.js.map +1 -1
  382. package/dist/encoding/SchemaBinary.d.ts.map +1 -1
  383. package/dist/encoding/SchemaBinary.js +42 -4
  384. package/dist/encoding/SchemaBinary.js.map +1 -1
  385. package/dist/encoding/Sse.d.ts.map +1 -1
  386. package/dist/encoding/Sse.js +3 -3
  387. package/dist/encoding/Sse.js.map +1 -1
  388. package/dist/encoding/Toml.d.ts +17 -3
  389. package/dist/encoding/Toml.d.ts.map +1 -1
  390. package/dist/encoding/Toml.js +124 -31
  391. package/dist/encoding/Toml.js.map +1 -1
  392. package/dist/encoding/Yaml.d.ts +8 -1
  393. package/dist/encoding/Yaml.d.ts.map +1 -1
  394. package/dist/encoding/Yaml.js +54 -18
  395. package/dist/encoding/Yaml.js.map +1 -1
  396. package/dist/encoding/index.d.ts +5 -0
  397. package/dist/encoding/index.d.ts.map +1 -1
  398. package/dist/encoding/index.js +5 -0
  399. package/dist/encoding/index.js.map +1 -1
  400. package/dist/eventlog/EventJournal.d.ts.map +1 -1
  401. package/dist/eventlog/EventJournal.js +6 -3
  402. package/dist/eventlog/EventJournal.js.map +1 -1
  403. package/dist/eventlog/EventLog.d.ts +119 -4
  404. package/dist/eventlog/EventLog.d.ts.map +1 -1
  405. package/dist/eventlog/EventLog.js +200 -12
  406. package/dist/eventlog/EventLog.js.map +1 -1
  407. package/dist/eventlog/EventLogEncryption.d.ts +41 -4
  408. package/dist/eventlog/EventLogEncryption.d.ts.map +1 -1
  409. package/dist/eventlog/EventLogEncryption.js +83 -64
  410. package/dist/eventlog/EventLogEncryption.js.map +1 -1
  411. package/dist/eventlog/EventLogMessage.d.ts +279 -0
  412. package/dist/eventlog/EventLogMessage.d.ts.map +1 -1
  413. package/dist/eventlog/EventLogMessage.js +217 -1
  414. package/dist/eventlog/EventLogMessage.js.map +1 -1
  415. package/dist/eventlog/EventLogRemote.d.ts +200 -28
  416. package/dist/eventlog/EventLogRemote.d.ts.map +1 -1
  417. package/dist/eventlog/EventLogRemote.js +186 -16
  418. package/dist/eventlog/EventLogRemote.js.map +1 -1
  419. package/dist/eventlog/EventLogServer.d.ts +3 -0
  420. package/dist/eventlog/EventLogServer.d.ts.map +1 -1
  421. package/dist/eventlog/EventLogServer.js +18 -10
  422. package/dist/eventlog/EventLogServer.js.map +1 -1
  423. package/dist/eventlog/EventLogServerEncrypted.d.ts +326 -5
  424. package/dist/eventlog/EventLogServerEncrypted.d.ts.map +1 -1
  425. package/dist/eventlog/EventLogServerEncrypted.js +769 -14
  426. package/dist/eventlog/EventLogServerEncrypted.js.map +1 -1
  427. package/dist/eventlog/EventLogSessionAuth.d.ts +5 -3
  428. package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
  429. package/dist/eventlog/EventLogSessionAuth.js +41 -64
  430. package/dist/eventlog/EventLogSessionAuth.js.map +1 -1
  431. package/dist/eventlog/EventRow.d.ts +4 -0
  432. package/dist/eventlog/EventRow.d.ts.map +1 -1
  433. package/dist/eventlog/EventRow.js +12 -2
  434. package/dist/eventlog/EventRow.js.map +1 -1
  435. package/dist/eventlog/SqlEventJournal.d.ts.map +1 -1
  436. package/dist/eventlog/SqlEventJournal.js +47 -27
  437. package/dist/eventlog/SqlEventJournal.js.map +1 -1
  438. package/dist/eventlog/SqlEventLogServerEncrypted.d.ts +21 -5
  439. package/dist/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -1
  440. package/dist/eventlog/SqlEventLogServerEncrypted.js +1048 -113
  441. package/dist/eventlog/SqlEventLogServerEncrypted.js.map +1 -1
  442. package/dist/eventlog/SqlEventLogServerUnencrypted.js +2 -2
  443. package/dist/eventlog/SqlEventLogServerUnencrypted.js.map +1 -1
  444. package/dist/eventlog/internal/batches.d.ts +12 -0
  445. package/dist/eventlog/internal/batches.d.ts.map +1 -0
  446. package/dist/eventlog/internal/batches.js +26 -0
  447. package/dist/eventlog/internal/batches.js.map +1 -0
  448. package/dist/eventlog/internal/compaction.d.ts +38 -0
  449. package/dist/eventlog/internal/compaction.d.ts.map +1 -0
  450. package/dist/eventlog/internal/compaction.js +33 -0
  451. package/dist/eventlog/internal/compaction.js.map +1 -0
  452. package/dist/eventlog/internal/conflicts.d.ts +0 -1
  453. package/dist/eventlog/internal/conflicts.d.ts.map +1 -1
  454. package/dist/eventlog/internal/conflicts.js +0 -3
  455. package/dist/eventlog/internal/conflicts.js.map +1 -1
  456. package/dist/eventlog/internal/identityRootSecretDerivation.js +66 -67
  457. package/dist/eventlog/internal/identityRootSecretDerivation.js.map +1 -1
  458. package/dist/eventlog/internal/remoteCompaction.d.ts +31 -0
  459. package/dist/eventlog/internal/remoteCompaction.d.ts.map +1 -0
  460. package/dist/eventlog/internal/remoteCompaction.js +76 -0
  461. package/dist/eventlog/internal/remoteCompaction.js.map +1 -0
  462. package/dist/http/Cookies.d.ts +172 -0
  463. package/dist/http/Cookies.d.ts.map +1 -1
  464. package/dist/http/Cookies.js +153 -10
  465. package/dist/http/Cookies.js.map +1 -1
  466. package/dist/http/FetchHttpClient.d.ts +26 -2
  467. package/dist/http/FetchHttpClient.d.ts.map +1 -1
  468. package/dist/http/FetchHttpClient.js +78 -15
  469. package/dist/http/FetchHttpClient.js.map +1 -1
  470. package/dist/http/HttpClient.d.ts +502 -8
  471. package/dist/http/HttpClient.d.ts.map +1 -1
  472. package/dist/http/HttpClient.js +296 -12
  473. package/dist/http/HttpClient.js.map +1 -1
  474. package/dist/http/HttpClientError.d.ts +8 -1
  475. package/dist/http/HttpClientError.d.ts.map +1 -1
  476. package/dist/http/HttpClientError.js +37 -1
  477. package/dist/http/HttpClientError.js.map +1 -1
  478. package/dist/http/HttpClientRequest.d.ts +9 -5
  479. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  480. package/dist/http/HttpClientRequest.js +3 -4
  481. package/dist/http/HttpClientRequest.js.map +1 -1
  482. package/dist/http/HttpClientResponse.d.ts.map +1 -1
  483. package/dist/http/HttpClientResponse.js +3 -2
  484. package/dist/http/HttpClientResponse.js.map +1 -1
  485. package/dist/http/HttpMethod.d.ts +7 -1
  486. package/dist/http/HttpMethod.d.ts.map +1 -1
  487. package/dist/http/HttpMethod.js +3 -8
  488. package/dist/http/HttpMethod.js.map +1 -1
  489. package/dist/http/HttpMiddleware.d.ts +1 -1
  490. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  491. package/dist/http/HttpMiddleware.js +14 -16
  492. package/dist/http/HttpMiddleware.js.map +1 -1
  493. package/dist/http/HttpRouter.d.ts.map +1 -1
  494. package/dist/http/HttpRouter.js +2 -1
  495. package/dist/http/HttpRouter.js.map +1 -1
  496. package/dist/http/HttpServerError.js +2 -2
  497. package/dist/http/HttpServerError.js.map +1 -1
  498. package/dist/http/HttpServerRequest.d.ts +11 -3
  499. package/dist/http/HttpServerRequest.d.ts.map +1 -1
  500. package/dist/http/HttpServerRequest.js +77 -25
  501. package/dist/http/HttpServerRequest.js.map +1 -1
  502. package/dist/http/HttpServerResponse.d.ts.map +1 -1
  503. package/dist/http/HttpServerResponse.js +3 -2
  504. package/dist/http/HttpServerResponse.js.map +1 -1
  505. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  506. package/dist/http/HttpStaticServer.js +48 -21
  507. package/dist/http/HttpStaticServer.js.map +1 -1
  508. package/dist/http/HttpTraceContext.d.ts +2 -1
  509. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  510. package/dist/http/HttpTraceContext.js +7 -5
  511. package/dist/http/HttpTraceContext.js.map +1 -1
  512. package/dist/http/MultipartParser/internal/contentType.js +3 -3
  513. package/dist/http/ProblemDetails.d.ts +96 -0
  514. package/dist/http/ProblemDetails.d.ts.map +1 -0
  515. package/dist/http/ProblemDetails.js +98 -0
  516. package/dist/http/ProblemDetails.js.map +1 -0
  517. package/dist/http/UrlParams.d.ts +3 -1
  518. package/dist/http/UrlParams.d.ts.map +1 -1
  519. package/dist/http/UrlParams.js +4 -2
  520. package/dist/http/UrlParams.js.map +1 -1
  521. package/dist/http/index.d.ts +5 -0
  522. package/dist/http/index.d.ts.map +1 -1
  523. package/dist/http/index.js +5 -0
  524. package/dist/http/index.js.map +1 -1
  525. package/dist/http/internal/compression.js +4 -5
  526. package/dist/http/internal/compression.js.map +1 -1
  527. package/dist/http/internal/decompression.js +6 -2
  528. package/dist/http/internal/decompression.js.map +1 -1
  529. package/dist/http/internal/httpBody.js +8 -1
  530. package/dist/http/internal/httpBody.js.map +1 -1
  531. package/dist/http/internal/urlParams.js +33 -0
  532. package/dist/http/internal/urlParams.js.map +1 -1
  533. package/dist/http-api/HttpApi.d.ts +3 -6
  534. package/dist/http-api/HttpApi.d.ts.map +1 -1
  535. package/dist/http-api/HttpApi.js +3 -6
  536. package/dist/http-api/HttpApi.js.map +1 -1
  537. package/dist/http-api/HttpApiBuilder.d.ts.map +1 -1
  538. package/dist/http-api/HttpApiBuilder.js +43 -13
  539. package/dist/http-api/HttpApiBuilder.js.map +1 -1
  540. package/dist/http-api/HttpApiClient.d.ts +140 -7
  541. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  542. package/dist/http-api/HttpApiClient.js +193 -44
  543. package/dist/http-api/HttpApiClient.js.map +1 -1
  544. package/dist/http-api/HttpApiEndpoint.d.ts +24 -7
  545. package/dist/http-api/HttpApiEndpoint.d.ts.map +1 -1
  546. package/dist/http-api/HttpApiEndpoint.js +123 -32
  547. package/dist/http-api/HttpApiEndpoint.js.map +1 -1
  548. package/dist/http-api/HttpApiError.d.ts +185 -13
  549. package/dist/http-api/HttpApiError.d.ts.map +1 -1
  550. package/dist/http-api/HttpApiError.js +128 -0
  551. package/dist/http-api/HttpApiError.js.map +1 -1
  552. package/dist/http-api/HttpApiMiddleware.d.ts +64 -17
  553. package/dist/http-api/HttpApiMiddleware.d.ts.map +1 -1
  554. package/dist/http-api/HttpApiMiddleware.js +85 -1
  555. package/dist/http-api/HttpApiMiddleware.js.map +1 -1
  556. package/dist/http-api/HttpApiSchema.d.ts +142 -33
  557. package/dist/http-api/HttpApiSchema.d.ts.map +1 -1
  558. package/dist/http-api/HttpApiSchema.js +123 -59
  559. package/dist/http-api/HttpApiSchema.js.map +1 -1
  560. package/dist/http-api/HttpApiTest.d.ts.map +1 -1
  561. package/dist/http-api/HttpApiTest.js +3 -2
  562. package/dist/http-api/HttpApiTest.js.map +1 -1
  563. package/dist/http-api/OpenApi.d.ts.map +1 -1
  564. package/dist/http-api/OpenApi.js +25 -16
  565. package/dist/http-api/OpenApi.js.map +1 -1
  566. package/dist/http-api/internal/toCodecHttpProperties.d.ts +2 -0
  567. package/dist/http-api/internal/toCodecHttpProperties.d.ts.map +1 -0
  568. package/dist/http-api/internal/toCodecHttpProperties.js +38 -0
  569. package/dist/http-api/internal/toCodecHttpProperties.js.map +1 -0
  570. package/dist/index.d.ts +5 -5
  571. package/dist/index.d.ts.map +1 -1
  572. package/dist/index.js +5 -5
  573. package/dist/index.js.map +1 -1
  574. package/dist/internal/arbitrary/runner.d.ts.map +1 -1
  575. package/dist/internal/arbitrary/runner.js +120 -44
  576. package/dist/internal/arbitrary/runner.js.map +1 -1
  577. package/dist/internal/core.js +2 -0
  578. package/dist/internal/core.js.map +1 -1
  579. package/dist/internal/effect.js +46 -27
  580. package/dist/internal/effect.js.map +1 -1
  581. package/dist/internal/equal.js +14 -0
  582. package/dist/internal/equal.js.map +1 -1
  583. package/dist/internal/glob.d.ts +2 -0
  584. package/dist/internal/glob.d.ts.map +1 -0
  585. package/dist/internal/glob.js +630 -0
  586. package/dist/internal/glob.js.map +1 -0
  587. package/dist/internal/jsonSchema.d.ts +2 -0
  588. package/dist/internal/jsonSchema.d.ts.map +1 -0
  589. package/dist/internal/jsonSchema.js +28 -0
  590. package/dist/internal/jsonSchema.js.map +1 -0
  591. package/dist/internal/matcher.js +11 -1
  592. package/dist/internal/matcher.js.map +1 -1
  593. package/dist/internal/persistence.js +9 -0
  594. package/dist/internal/persistence.js.map +1 -1
  595. package/dist/internal/rcRef.js +4 -1
  596. package/dist/internal/rcRef.js.map +1 -1
  597. package/dist/internal/schema/compilerRegistry.js +4 -1
  598. package/dist/internal/schema/compilerRegistry.js.map +1 -1
  599. package/dist/internal/schema/content.d.ts +2 -0
  600. package/dist/internal/schema/content.d.ts.map +1 -0
  601. package/dist/internal/schema/content.js +13 -0
  602. package/dist/internal/schema/content.js.map +1 -0
  603. package/dist/internal/schema/fromJsonSchemaDocument.js +173 -185
  604. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  605. package/dist/internal/schema/fromRepresentation.js +2 -1
  606. package/dist/internal/schema/fromRepresentation.js.map +1 -1
  607. package/dist/internal/schema/standardSchema.js +8 -2
  608. package/dist/internal/schema/standardSchema.js.map +1 -1
  609. package/dist/internal/schema/toCodeDocument.js +9 -6
  610. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  611. package/dist/internal/schema/toCodec.js +21 -24
  612. package/dist/internal/schema/toCodec.js.map +1 -1
  613. package/dist/internal/schema/toJsonSchemaDocument.js +205 -140
  614. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  615. package/dist/internal/schema/toRepresentation.js +107 -129
  616. package/dist/internal/schema/toRepresentation.js.map +1 -1
  617. package/dist/internal/sql.d.ts +2 -0
  618. package/dist/internal/sql.d.ts.map +1 -0
  619. package/dist/internal/sql.js +13 -0
  620. package/dist/internal/sql.js.map +1 -0
  621. package/dist/internal/trie.js +42 -13
  622. package/dist/internal/trie.js.map +1 -1
  623. package/dist/internal/ulid.js +41 -0
  624. package/dist/internal/ulid.js.map +1 -1
  625. package/dist/internal/uuid.js +52 -8
  626. package/dist/internal/uuid.js.map +1 -1
  627. package/dist/internal/version.js +1 -1
  628. package/dist/observability/Otlp.d.ts +15 -4
  629. package/dist/observability/Otlp.d.ts.map +1 -1
  630. package/dist/observability/Otlp.js +10 -0
  631. package/dist/observability/Otlp.js.map +1 -1
  632. package/dist/observability/OtlpProxy.d.ts +154 -0
  633. package/dist/observability/OtlpProxy.d.ts.map +1 -0
  634. package/dist/observability/OtlpProxy.js +235 -0
  635. package/dist/observability/OtlpProxy.js.map +1 -0
  636. package/dist/observability/OtlpResource.d.ts +7 -0
  637. package/dist/observability/OtlpResource.d.ts.map +1 -1
  638. package/dist/observability/OtlpResource.js +11 -1
  639. package/dist/observability/OtlpResource.js.map +1 -1
  640. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  641. package/dist/observability/OtlpTracer.js +4 -0
  642. package/dist/observability/OtlpTracer.js.map +1 -1
  643. package/dist/observability/index.d.ts +5 -0
  644. package/dist/observability/index.d.ts.map +1 -1
  645. package/dist/observability/index.js +5 -0
  646. package/dist/observability/index.js.map +1 -1
  647. package/dist/persistence/CircuitBreaker.d.ts +897 -0
  648. package/dist/persistence/CircuitBreaker.d.ts.map +1 -0
  649. package/dist/persistence/CircuitBreaker.js +1165 -0
  650. package/dist/persistence/CircuitBreaker.js.map +1 -0
  651. package/dist/persistence/KeyValueStore.d.ts +47 -2
  652. package/dist/persistence/KeyValueStore.d.ts.map +1 -1
  653. package/dist/persistence/KeyValueStore.js +129 -18
  654. package/dist/persistence/KeyValueStore.js.map +1 -1
  655. package/dist/persistence/PersistedQueue.d.ts +9 -0
  656. package/dist/persistence/PersistedQueue.d.ts.map +1 -1
  657. package/dist/persistence/PersistedQueue.js +94 -52
  658. package/dist/persistence/PersistedQueue.js.map +1 -1
  659. package/dist/persistence/Persistence.d.ts +10 -4
  660. package/dist/persistence/Persistence.d.ts.map +1 -1
  661. package/dist/persistence/Persistence.js +18 -11
  662. package/dist/persistence/Persistence.js.map +1 -1
  663. package/dist/persistence/Redis.d.ts +78 -2
  664. package/dist/persistence/Redis.d.ts.map +1 -1
  665. package/dist/persistence/Redis.js +62 -7
  666. package/dist/persistence/Redis.js.map +1 -1
  667. package/dist/persistence/index.d.ts +5 -0
  668. package/dist/persistence/index.d.ts.map +1 -1
  669. package/dist/persistence/index.js +5 -0
  670. package/dist/persistence/index.js.map +1 -1
  671. package/dist/process/ChildProcess.d.ts +237 -71
  672. package/dist/process/ChildProcess.d.ts.map +1 -1
  673. package/dist/process/ChildProcess.js +56 -158
  674. package/dist/process/ChildProcess.js.map +1 -1
  675. package/dist/process/ChildProcessSpawner.d.ts +35 -0
  676. package/dist/process/ChildProcessSpawner.d.ts.map +1 -1
  677. package/dist/process/ChildProcessSpawner.js +5 -2
  678. package/dist/process/ChildProcessSpawner.js.map +1 -1
  679. package/dist/process/CommandLine.d.ts +294 -0
  680. package/dist/process/CommandLine.d.ts.map +1 -0
  681. package/dist/process/CommandLine.js +481 -0
  682. package/dist/process/CommandLine.js.map +1 -0
  683. package/dist/process/index.d.ts +5 -0
  684. package/dist/process/index.d.ts.map +1 -1
  685. package/dist/process/index.js +5 -0
  686. package/dist/process/index.js.map +1 -1
  687. package/dist/reactivity/AsyncResult.d.ts +1 -1
  688. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  689. package/dist/reactivity/Atom.d.ts +2 -1
  690. package/dist/reactivity/Atom.d.ts.map +1 -1
  691. package/dist/reactivity/Atom.js +3 -4
  692. package/dist/reactivity/Atom.js.map +1 -1
  693. package/dist/reactivity/AtomRegistry.js.map +1 -1
  694. package/dist/rpc/RpcClient.d.ts +16 -1
  695. package/dist/rpc/RpcClient.d.ts.map +1 -1
  696. package/dist/rpc/RpcClient.js +59 -24
  697. package/dist/rpc/RpcClient.js.map +1 -1
  698. package/dist/rpc/RpcMessage.d.ts +10 -1
  699. package/dist/rpc/RpcMessage.d.ts.map +1 -1
  700. package/dist/rpc/RpcMessage.js +1 -0
  701. package/dist/rpc/RpcMessage.js.map +1 -1
  702. package/dist/rpc/RpcSerialization.d.ts +14 -0
  703. package/dist/rpc/RpcSerialization.d.ts.map +1 -1
  704. package/dist/rpc/RpcSerialization.js +26 -8
  705. package/dist/rpc/RpcSerialization.js.map +1 -1
  706. package/dist/rpc/RpcServer.d.ts +31 -0
  707. package/dist/rpc/RpcServer.d.ts.map +1 -1
  708. package/dist/rpc/RpcServer.js +139 -56
  709. package/dist/rpc/RpcServer.js.map +1 -1
  710. package/dist/rpc/RpcTest.d.ts +1 -0
  711. package/dist/rpc/RpcTest.d.ts.map +1 -1
  712. package/dist/rpc/RpcTest.js +1 -0
  713. package/dist/rpc/RpcTest.js.map +1 -1
  714. package/dist/schema/Model.d.ts +14 -0
  715. package/dist/schema/Model.d.ts.map +1 -1
  716. package/dist/schema/Model.js +18 -3
  717. package/dist/schema/Model.js.map +1 -1
  718. package/dist/sql/Migrator.d.ts +2 -20
  719. package/dist/sql/Migrator.d.ts.map +1 -1
  720. package/dist/sql/Migrator.js +13 -55
  721. package/dist/sql/Migrator.js.map +1 -1
  722. package/dist/sql/MigratorFileSystem.d.ts +21 -0
  723. package/dist/sql/MigratorFileSystem.d.ts.map +1 -0
  724. package/dist/sql/MigratorFileSystem.js +57 -0
  725. package/dist/sql/MigratorFileSystem.js.map +1 -0
  726. package/dist/sql/SqlClient.d.ts +9 -4
  727. package/dist/sql/SqlClient.d.ts.map +1 -1
  728. package/dist/sql/SqlClient.js +13 -10
  729. package/dist/sql/SqlClient.js.map +1 -1
  730. package/dist/sql/SqlResolver.d.ts +4 -0
  731. package/dist/sql/SqlResolver.d.ts.map +1 -1
  732. package/dist/sql/SqlResolver.js +18 -4
  733. package/dist/sql/SqlResolver.js.map +1 -1
  734. package/dist/sql/Statement.d.ts +27 -6
  735. package/dist/sql/Statement.d.ts.map +1 -1
  736. package/dist/sql/Statement.js +21 -15
  737. package/dist/sql/Statement.js.map +1 -1
  738. package/dist/sql/index.js +1 -1
  739. package/dist/testing/index.d.ts +0 -5
  740. package/dist/testing/index.d.ts.map +1 -1
  741. package/dist/testing/index.js +0 -5
  742. package/dist/testing/index.js.map +1 -1
  743. package/dist/workflow/DurableDeferred.d.ts.map +1 -1
  744. package/dist/workflow/DurableDeferred.js +21 -2
  745. package/dist/workflow/DurableDeferred.js.map +1 -1
  746. package/dist/workflow/internal/crypto.js +9 -8
  747. package/dist/workflow/internal/crypto.js.map +1 -1
  748. package/package.json +3 -1
  749. package/src/Arbitrary.ts +10 -2
  750. package/src/BigDecimal.ts +3 -0
  751. package/src/Channel.ts +64 -29
  752. package/src/Config.ts +7 -1
  753. package/src/Context.ts +34 -10
  754. package/src/Cron.ts +178 -29
  755. package/src/Data.ts +60 -265
  756. package/src/Duration.ts +140 -0
  757. package/src/Effect.ts +915 -86
  758. package/src/Equal.ts +8 -1
  759. package/src/Exit.ts +3 -3
  760. package/src/FileSystem.ts +65 -4
  761. package/src/Formatter.ts +28 -13
  762. package/src/Glob.ts +205 -0
  763. package/src/Hash.ts +4 -1
  764. package/src/JsonPatch.ts +4 -0
  765. package/src/JsonSchema.ts +12 -40
  766. package/src/Match.ts +127 -0
  767. package/src/Metric.ts +3 -3
  768. package/src/MutableList.ts +7 -5
  769. package/src/MutableRef.ts +8 -1
  770. package/src/PartitionedSemaphore.ts +24 -13
  771. package/src/Pool.ts +182 -52
  772. package/src/PubSub.ts +5 -6
  773. package/src/Random.ts +3 -3
  774. package/src/RcMap.ts +5 -1
  775. package/src/Ref.ts +8 -0
  776. package/src/Schedule.ts +27 -4
  777. package/src/Schema.ts +1237 -87
  778. package/src/SchemaAST.ts +20 -20
  779. package/src/SchemaGetter.ts +27 -11
  780. package/src/SchemaParser.ts +51 -18
  781. package/src/SchemaRepresentation.ts +50 -34
  782. package/src/SchemaTransformation.ts +17 -5
  783. package/src/ScopedCache.ts +5 -9
  784. package/src/Semaphore.ts +208 -40
  785. package/src/Sink.ts +15 -2
  786. package/src/Stream.ts +48 -22
  787. package/src/String.ts +3 -0
  788. package/src/Terminal.ts +19 -1
  789. package/src/Tracer.ts +15 -1
  790. package/src/ai/AiError.ts +56 -80
  791. package/src/ai/Decision.ts +257 -6
  792. package/src/ai/DecisionModel.ts +171 -28
  793. package/src/ai/LanguageModel.ts +9 -0
  794. package/src/ai/McpServer.ts +1283 -299
  795. package/src/ai/McpTest.ts +380 -0
  796. package/src/ai/Prompt.ts +65 -0
  797. package/src/ai/Response.ts +36 -31
  798. package/src/ai/Tool.ts +65 -10
  799. package/src/ai/Toolkit.ts +4 -1
  800. package/src/ai/index.ts +6 -0
  801. package/src/ai/internal/mcpCore.ts +78 -44
  802. package/src/ai/internal/mcpProtocol/v2024_11_05.ts +4 -7
  803. package/src/ai/internal/mcpProtocol/v2025_03_26.ts +4 -7
  804. package/src/ai/internal/mcpProtocol/v2025_06_18.ts +4 -7
  805. package/src/ai/internal/mcpProtocol/v2025_11_25.ts +4 -7
  806. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +15 -10
  807. package/src/ai/internal/mcpProtocol.ts +29 -0
  808. package/src/ai/internal/mcpRuntime.ts +9 -5
  809. package/src/ai/internal/mcpStatefulRuntime.ts +14 -2
  810. package/src/cli/Argument.ts +21 -0
  811. package/src/cli/CliError.ts +90 -3
  812. package/src/cli/CliOutput.ts +300 -55
  813. package/src/cli/Command.ts +76 -41
  814. package/src/cli/Completions.ts +51 -0
  815. package/src/cli/Flag.ts +122 -0
  816. package/src/cli/GlobalFlag.ts +6 -7
  817. package/src/cli/HelpDoc.ts +18 -1
  818. package/src/cli/Param.ts +35 -6
  819. package/src/cli/Primitive.ts +16 -1
  820. package/src/cli/Prompt.ts +1 -0
  821. package/src/cli/TerminalCapabilities.ts +6 -3
  822. package/src/cli/internal/completions/bash.ts +79 -29
  823. package/src/cli/internal/help.ts +21 -9
  824. package/src/cluster/ClusterWorkflowEngine.ts +46 -16
  825. package/src/cluster/MessageStorage.ts +19 -14
  826. package/src/cluster/Sharding.ts +176 -47
  827. package/src/cluster/ShardingConfig.ts +43 -0
  828. package/src/cluster/SingleRunner.ts +3 -3
  829. package/src/cluster/Snowflake.ts +3 -7
  830. package/src/cluster/SqlMessageStorage.ts +49 -17
  831. package/src/cluster/SqlRunnerStorage.ts +143 -70
  832. package/src/cluster/internal/entityManager.ts +75 -22
  833. package/src/crypto/Aead.ts +517 -0
  834. package/src/crypto/Cipher.ts +326 -0
  835. package/src/crypto/CryptoError.ts +242 -0
  836. package/src/crypto/Digest.ts +167 -0
  837. package/src/crypto/Kdf.ts +315 -0
  838. package/src/crypto/KeyAgreement.ts +489 -0
  839. package/src/crypto/KeyFormat.ts +712 -0
  840. package/src/crypto/KeyWrap.ts +639 -0
  841. package/src/crypto/Mac.ts +386 -0
  842. package/src/crypto/SecureRandom.ts +459 -0
  843. package/src/crypto/Signature.ts +673 -0
  844. package/src/crypto/WebCrypto.ts +64 -0
  845. package/src/crypto/index.ts +78 -0
  846. package/src/crypto/internal/asymmetric.ts +329 -0
  847. package/src/crypto/internal/chacha.ts +1088 -0
  848. package/src/crypto/internal/der.ts +110 -0
  849. package/src/crypto/internal/digest.ts +16 -0
  850. package/src/crypto/internal/key.ts +235 -0
  851. package/src/crypto/internal/md5.ts +170 -0
  852. package/src/crypto/internal/webCrypto.ts +150 -0
  853. package/src/devtools/DevToolsClient.ts +2 -0
  854. package/src/encoding/Base64.ts +38 -0
  855. package/src/encoding/Base64Url.ts +5 -2
  856. package/src/encoding/Csv.ts +1298 -0
  857. package/src/encoding/Hex.ts +3 -2
  858. package/src/encoding/SchemaBinary.ts +40 -4
  859. package/src/encoding/Sse.ts +4 -3
  860. package/src/encoding/Toml.ts +117 -35
  861. package/src/encoding/Yaml.ts +61 -18
  862. package/src/encoding/index.ts +6 -0
  863. package/src/eventlog/EventJournal.ts +4 -3
  864. package/src/eventlog/EventLog.ts +340 -15
  865. package/src/eventlog/EventLogEncryption.ts +118 -110
  866. package/src/eventlog/EventLogMessage.ts +273 -1
  867. package/src/eventlog/EventLogRemote.ts +367 -23
  868. package/src/eventlog/EventLogServer.ts +23 -8
  869. package/src/eventlog/EventLogServerEncrypted.ts +1130 -67
  870. package/src/eventlog/EventLogSessionAuth.ts +56 -70
  871. package/src/eventlog/EventRow.ts +12 -3
  872. package/src/eventlog/SqlEventJournal.ts +68 -45
  873. package/src/eventlog/SqlEventLogServerEncrypted.ts +1244 -136
  874. package/src/eventlog/SqlEventLogServerUnencrypted.ts +2 -2
  875. package/src/eventlog/internal/batches.ts +30 -0
  876. package/src/eventlog/internal/compaction.ts +64 -0
  877. package/src/eventlog/internal/conflicts.ts +0 -4
  878. package/src/eventlog/internal/identityRootSecretDerivation.ts +93 -123
  879. package/src/eventlog/internal/remoteCompaction.ts +92 -0
  880. package/src/http/Cookies.ts +291 -12
  881. package/src/http/FetchHttpClient.ts +88 -16
  882. package/src/http/HttpClient.ts +636 -29
  883. package/src/http/HttpClientError.ts +35 -1
  884. package/src/http/HttpClientRequest.ts +21 -10
  885. package/src/http/HttpClientResponse.ts +3 -2
  886. package/src/http/HttpMethod.ts +10 -2
  887. package/src/http/HttpMiddleware.ts +15 -18
  888. package/src/http/HttpRouter.ts +2 -1
  889. package/src/http/HttpServerError.ts +2 -2
  890. package/src/http/HttpServerRequest.ts +101 -30
  891. package/src/http/HttpServerResponse.ts +3 -2
  892. package/src/http/HttpStaticServer.ts +51 -22
  893. package/src/http/HttpTraceContext.ts +7 -4
  894. package/src/http/MultipartParser/internal/contentType.ts +3 -3
  895. package/src/http/ProblemDetails.ts +149 -0
  896. package/src/http/UrlParams.ts +6 -1
  897. package/src/http/index.ts +6 -0
  898. package/src/http/internal/compression.ts +4 -5
  899. package/src/http/internal/decompression.ts +10 -4
  900. package/src/http/internal/httpBody.ts +10 -2
  901. package/src/http/internal/urlParams.ts +37 -0
  902. package/src/http-api/HttpApi.ts +3 -6
  903. package/src/http-api/HttpApiBuilder.ts +61 -19
  904. package/src/http-api/HttpApiClient.ts +284 -72
  905. package/src/http-api/HttpApiEndpoint.ts +135 -35
  906. package/src/http-api/HttpApiError.ts +250 -0
  907. package/src/http-api/HttpApiMiddleware.ts +95 -1
  908. package/src/http-api/HttpApiSchema.ts +236 -52
  909. package/src/http-api/HttpApiTest.ts +3 -2
  910. package/src/http-api/OpenApi.ts +20 -17
  911. package/src/http-api/internal/toCodecHttpProperties.ts +56 -0
  912. package/src/index.ts +6 -6
  913. package/src/internal/arbitrary/runner.ts +133 -36
  914. package/src/internal/core.ts +2 -0
  915. package/src/internal/effect.ts +58 -34
  916. package/src/internal/equal.ts +17 -0
  917. package/src/internal/glob.ts +736 -0
  918. package/src/internal/jsonSchema.ts +27 -0
  919. package/src/internal/matcher.ts +18 -1
  920. package/src/internal/persistence.ts +11 -0
  921. package/src/internal/rcRef.ts +4 -1
  922. package/src/internal/schema/compilerRegistry.ts +6 -1
  923. package/src/internal/schema/content.ts +18 -0
  924. package/src/internal/schema/fromJsonSchemaDocument.ts +228 -190
  925. package/src/internal/schema/fromRepresentation.ts +2 -1
  926. package/src/internal/schema/standardSchema.ts +2 -2
  927. package/src/internal/schema/toCodeDocument.ts +11 -7
  928. package/src/internal/schema/toCodec.ts +26 -22
  929. package/src/internal/schema/toJsonSchemaDocument.ts +189 -167
  930. package/src/internal/schema/toRepresentation.ts +122 -140
  931. package/src/internal/sql.ts +14 -0
  932. package/src/internal/trie.ts +43 -14
  933. package/src/internal/ulid.ts +44 -0
  934. package/src/internal/uuid.ts +57 -16
  935. package/src/internal/version.ts +1 -1
  936. package/src/observability/Otlp.ts +21 -4
  937. package/src/observability/OtlpProxy.ts +314 -0
  938. package/src/observability/OtlpResource.ts +9 -1
  939. package/src/observability/OtlpTracer.ts +4 -0
  940. package/src/observability/index.ts +6 -0
  941. package/src/persistence/CircuitBreaker.ts +1678 -0
  942. package/src/persistence/KeyValueStore.ts +219 -22
  943. package/src/persistence/PersistedQueue.ts +128 -59
  944. package/src/persistence/Persistence.ts +20 -13
  945. package/src/persistence/Redis.ts +129 -8
  946. package/src/persistence/index.ts +6 -0
  947. package/src/process/ChildProcess.ts +285 -264
  948. package/src/process/ChildProcessSpawner.ts +46 -2
  949. package/src/process/CommandLine.ts +630 -0
  950. package/src/process/index.ts +6 -0
  951. package/src/reactivity/AsyncResult.ts +1 -1
  952. package/src/reactivity/Atom.ts +5 -5
  953. package/src/reactivity/AtomRegistry.ts +1 -1
  954. package/src/rpc/RpcClient.ts +75 -36
  955. package/src/rpc/RpcMessage.ts +10 -1
  956. package/src/rpc/RpcSerialization.ts +35 -9
  957. package/src/rpc/RpcServer.ts +176 -63
  958. package/src/rpc/RpcTest.ts +3 -0
  959. package/src/schema/Model.ts +26 -3
  960. package/src/sql/Migrator.ts +15 -69
  961. package/src/sql/MigratorFileSystem.ts +71 -0
  962. package/src/sql/SqlClient.ts +13 -20
  963. package/src/sql/SqlResolver.ts +23 -9
  964. package/src/sql/Statement.ts +38 -9
  965. package/src/sql/index.ts +1 -1
  966. package/src/testing/index.ts +0 -6
  967. package/src/workflow/DurableDeferred.ts +23 -6
  968. package/src/workflow/internal/crypto.ts +15 -11
  969. package/dist/Crypto.d.ts +0 -244
  970. package/dist/Crypto.d.ts.map +0 -1
  971. package/dist/Crypto.js +0 -139
  972. package/dist/Crypto.js.map +0 -1
  973. package/dist/testing/TestCrypto.d.ts +0 -73
  974. package/dist/testing/TestCrypto.d.ts.map +0 -1
  975. package/dist/testing/TestCrypto.js +0 -87
  976. package/dist/testing/TestCrypto.js.map +0 -1
  977. package/src/Crypto.ts +0 -347
  978. package/src/testing/TestCrypto.ts +0 -94
@@ -5,29 +5,45 @@
5
5
  * `EventLogServerEncrypted` when entries should be stored without exposing
6
6
  * plaintext event data to the database. It persists the server remote id,
7
7
  * session authentication bindings, and encrypted batch tables, assigns stable
8
- * sequence numbers, and streams changes. Clients remain responsible for
9
- * encrypting writes and decrypting reads.
8
+ * sequence numbers, streams changes, and stores encrypted compaction snapshots.
9
+ * Clients remain responsible for encrypting writes and decrypting reads.
10
10
  *
11
11
  * @stability unstable
12
12
  * @since 4.0.0
13
13
  */
14
14
  import * as Arr from "../Array.ts"
15
15
  import type { NonEmptyArray } from "../Array.ts"
16
+ import * as Cause from "../Cause.ts"
17
+ import * as Clock from "../Clock.ts"
18
+ import * as Digest from "../crypto/Digest.ts"
19
+ import * as SecureRandom from "../crypto/SecureRandom.ts"
20
+ import * as Duration from "../Duration.ts"
16
21
  import * as Effect from "../Effect.ts"
17
22
  import * as Layer from "../Layer.ts"
23
+ import * as Option from "../Option.ts"
18
24
  import * as PubSub from "../PubSub.ts"
19
25
  import * as RcMap from "../RcMap.ts"
26
+ import * as Schedule from "../Schedule.ts"
20
27
  import * as Schema from "../Schema.ts"
21
28
  import type * as Scope from "../Scope.ts"
22
29
  import * as Semaphore from "../Semaphore.ts"
23
30
  import * as SqlClient from "../sql/SqlClient.ts"
24
- import type * as SqlError from "../sql/SqlError.ts"
31
+ import * as SqlError from "../sql/SqlError.ts"
25
32
  import type * as Statement from "../sql/Statement.ts"
26
33
  import * as Stream from "../Stream.ts"
27
34
  import { type EntryId, makeRemoteIdUnsafe, type RemoteId } from "./EventJournal.ts"
28
- import * as EventLogEncryption from "./EventLogEncryption.ts"
29
- import type { WriteAck } from "./EventLogMessage.ts"
35
+ import type * as EventLogEncryption from "./EventLogEncryption.ts"
36
+ import type { StoreId, WriteAck } from "./EventLogMessage.ts"
30
37
  import * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts"
38
+ import {
39
+ hourMillis,
40
+ lagHour,
41
+ logCommit,
42
+ logNoOp,
43
+ logRollback,
44
+ maxSegments,
45
+ positionsPerSegment
46
+ } from "./internal/compaction.ts"
31
47
 
32
48
  /**
33
49
  * Creates encrypted event-log server `Storage` backed by SQL.
@@ -38,6 +54,11 @@ import * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts"
38
54
  * batches in dialect-specific tables, creating per-identity/store batch and entry
39
55
  * id tables as needed.
40
56
  *
57
+ * The storage always provides `compaction`. Its tables are created on startup
58
+ * and when a store is first opened. A sweep in the surrounding scope prunes
59
+ * superseded generations every `sweepInterval`. Startup dies on invalid
60
+ * compaction options (see `EventLogServerEncrypted.makeCompactionConfig`).
61
+ *
41
62
  * **Gotchas**
42
63
  *
43
64
  * Storage does not retry sequence conflicts between competing instances.
@@ -46,6 +67,14 @@ import * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts"
46
67
  * are local to each storage instance; clients on other instances must reconnect
47
68
  * to read those writes.
48
69
  *
70
+ * Inside a caller's transaction, `write` and `compaction.rollback` publish to
71
+ * live readers after the outermost commit, and not at all if it rolls back.
72
+ * Concurrent caller transactions may reach live readers out of sequence order.
73
+ * `changes` always reads in committed order.
74
+ *
75
+ * Instances sharing a database should share compaction options. The instance
76
+ * that writes a generation or stage fixes its prune time or expiry.
77
+ *
49
78
  * @stability unstable
50
79
  * @category constructors
51
80
  * @since 4.0.0
@@ -53,14 +82,20 @@ import * as EventLogServerEncrypted from "./EventLogServerEncrypted.ts"
53
82
  export const makeStorage = (options?: {
54
83
  readonly entryTablePrefix?: string
55
84
  readonly remoteIdTable?: string
85
+ readonly compaction?: EventLogServerEncrypted.CompactionOptions | undefined
56
86
  }): Effect.Effect<
57
87
  EventLogServerEncrypted.Storage["Service"],
58
88
  SqlError.SqlError,
59
- SqlClient.SqlClient | EventLogEncryption.EventLogEncryption | Scope.Scope
89
+ SqlClient.SqlClient | Digest.Digest | Scope.Scope
60
90
  > =>
61
91
  Effect.gen(function*() {
62
- const encryptions = yield* EventLogEncryption.EventLogEncryption
92
+ const digest = yield* Digest.Digest
63
93
  const sql = (yield* SqlClient.SqlClient).withoutTransforms()
94
+ const config = yield* EventLogServerEncrypted.makeCompactionConfig(options?.compaction)
95
+ const graceMillis = Duration.toMillis(config.grace)
96
+ const lagMillis = Duration.toMillis(config.lag)
97
+ const stageTtlMillis = Duration.toMillis(config.stageTtl)
98
+ const sweepIntervalMillis = Duration.toMillis(config.sweepInterval)
64
99
 
65
100
  const tablePrefix = options?.entryTablePrefix ?? "effect_events"
66
101
  const remoteIdTable = options?.remoteIdTable ?? "effect_remote_id"
@@ -121,6 +156,120 @@ export const makeStorage = (options?: {
121
156
  )`
122
157
  })
123
158
 
159
+ const types = sql.onDialectOrElse({
160
+ pg: () => ({ id: "TEXT", int: "INTEGER", bigint: "BIGINT", bytes: "BYTEA", entryId: "BYTEA", iv: "BYTEA" }),
161
+ mysql: () => ({
162
+ id: "VARCHAR(36)",
163
+ int: "INT",
164
+ bigint: "BIGINT",
165
+ bytes: "LONGBLOB",
166
+ entryId: "BINARY(16)",
167
+ iv: "BINARY(12)"
168
+ }),
169
+ mssql: () => ({
170
+ id: "NVARCHAR(36)",
171
+ int: "INT",
172
+ bigint: "BIGINT",
173
+ bytes: "VARBINARY(MAX)",
174
+ entryId: "VARBINARY(16)",
175
+ iv: "VARBINARY(12)"
176
+ }),
177
+ orElse: () => ({ id: "TEXT", int: "INTEGER", bigint: "INTEGER", bytes: "BLOB", entryId: "BLOB", iv: "BLOB" })
178
+ })
179
+ // Two instances can open the same table at once, and PostgreSQL and SQL
180
+ // Server can fail the loser of a concurrent create, so creation retries.
181
+ const createTable = (name: string, columns: string, indexes: ReadonlyArray<string> = []) =>
182
+ sql.onDialectOrElse({
183
+ mysql: () => {
184
+ const definitions = [columns, ...indexes.map((index) => `INDEX (${index})`)].join(", ")
185
+ return Effect.asVoid(sql`CREATE TABLE IF NOT EXISTS ${sql(name)} (${sql.literal(definitions)})`)
186
+ },
187
+ mssql: () =>
188
+ sql`IF OBJECT_ID(${name}, N'U') IS NULL CREATE TABLE ${sql(name)} (${sql.literal(columns)})`.pipe(
189
+ Effect.andThen(Effect.forEach(indexes, (index, i) => {
190
+ const indexName = `${name}_i${i}`
191
+ return sql`
192
+ IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${indexName} AND object_id = OBJECT_ID(${name}))
193
+ CREATE INDEX ${sql(indexName)} ON ${sql(name)} (${sql.literal(index)})`
194
+ }, { discard: true }))
195
+ ),
196
+ orElse: () =>
197
+ sql`CREATE TABLE IF NOT EXISTS ${sql(name)} (${sql.literal(columns)})`.pipe(
198
+ Effect.andThen(Effect.forEach(
199
+ indexes,
200
+ (index, i) =>
201
+ sql`CREATE INDEX IF NOT EXISTS ${sql(`${name}_i${i}`)} ON ${sql(name)} (${sql.literal(index)})`,
202
+ { discard: true }
203
+ ))
204
+ )
205
+ }).pipe(Effect.retry({ times: 3 }))
206
+
207
+ const storesTable = `${tablePrefix}_compaction_stores`
208
+ const bucketsTable = `${tablePrefix}_compaction_buckets`
209
+ const activationsTable = `${tablePrefix}_compaction_activations`
210
+ const stagesTable = `${tablePrefix}_compaction_stages`
211
+ const storesSql = sql(storesTable)
212
+ const bucketsSql = sql(bucketsTable)
213
+ const activationsSql = sql(activationsTable)
214
+ const stagesSql = sql(stagesTable)
215
+
216
+ // One row per store, keyed by the scope hash its tables are named after.
217
+ // "Nothing" is 0 for the base, `pruned_through` and the lag head.
218
+ yield* createTable(
219
+ storesTable,
220
+ `scope ${types.id} PRIMARY KEY,
221
+ next_sequence ${types.int} NOT NULL,
222
+ active_activation ${types.id} NOT NULL,
223
+ active_batch_set ${types.id} NULL,
224
+ base_sequence ${types.int} NOT NULL,
225
+ active_kept_count ${types.int} NOT NULL,
226
+ no_op_at ${types.int} NULL,
227
+ no_op_count ${types.int} NOT NULL,
228
+ open_stage ${types.id} NULL,
229
+ next_sweep_at ${types.bigint} NULL,
230
+ sweep_lease_until ${types.bigint} NULL,
231
+ sweep_lease_owner ${types.id} NULL,
232
+ pruned_through ${types.int} NOT NULL`,
233
+ ["next_sweep_at"]
234
+ )
235
+ // The highest last sequence stored in each storage-time hour.
236
+ yield* createTable(
237
+ bucketsTable,
238
+ `scope ${types.id} NOT NULL,
239
+ bucket_hour ${types.bigint} NOT NULL,
240
+ last_sequence ${types.int} NOT NULL,
241
+ PRIMARY KEY (scope, bucket_hour)`
242
+ )
243
+ // A `batch_set` of null means the originals only. `prunable_at` is set when
244
+ // the activation is superseded or withdrawn.
245
+ yield* createTable(
246
+ activationsTable,
247
+ `id ${types.id} PRIMARY KEY,
248
+ scope ${types.id} NOT NULL,
249
+ ordinal ${types.int} NOT NULL,
250
+ batch_set ${types.id} NULL,
251
+ base_sequence ${types.int} NOT NULL,
252
+ kept_count ${types.int} NULL,
253
+ previous ${types.id} NULL,
254
+ superseded_at ${types.bigint} NULL,
255
+ prunable_at ${types.bigint} NULL,
256
+ prune_started_at ${types.bigint} NULL`,
257
+ ["scope, ordinal"]
258
+ )
259
+ // A stage's pending batch set is its own id. A committed stage's row is
260
+ // deleted at commit. Other stages go from `open` to `closed`, and the
261
+ // sweep deletes what they staged, then the row.
262
+ yield* createTable(
263
+ stagesTable,
264
+ `id ${types.id} PRIMARY KEY,
265
+ scope ${types.id} NOT NULL,
266
+ state ${types.id} NOT NULL,
267
+ pinned ${types.id} NOT NULL,
268
+ through_sequence ${types.int} NOT NULL,
269
+ expires_at ${types.bigint} NOT NULL`,
270
+ ["scope, state", "state, expires_at"]
271
+ )
272
+
124
273
  const remoteId = yield* sql<{ remote_id: Uint8Array }>`SELECT remote_id FROM ${remoteIdTableSql}`.pipe(
125
274
  Effect.flatMap((results) => {
126
275
  if (results.length > 0) {
@@ -134,120 +283,348 @@ export const makeStorage = (options?: {
134
283
  })
135
284
  )
136
285
 
137
- const resources = yield* RcMap.make({
138
- lookup: Effect.fnUntraced(function*(scopeKey: string) {
139
- const scopeHash = (yield* encryptions.sha256String(new TextEncoder().encode(scopeKey))).slice(0, 16)
140
- const table = `${tablePrefix}_${scopeHash}`
141
- const tableSql = sql(table)
142
- const idsTableSql = sql(`${table}_ids`)
143
- const indexSql = sql(`${table}_last_sequence`)
144
-
145
- yield* sql.onDialectOrElse({
146
- pg: () =>
147
- sql`
148
- CREATE TABLE IF NOT EXISTS ${tableSql} (
149
- sequence INTEGER PRIMARY KEY,
150
- last_sequence INTEGER NOT NULL,
151
- iv BYTEA NOT NULL,
152
- entry_ids BYTEA NOT NULL,
153
- encrypted_batch BYTEA NOT NULL
154
- )`.pipe(
155
- Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`),
156
- Effect.andThen(sql`
157
- CREATE TABLE IF NOT EXISTS ${idsTableSql} (
158
- entry_id BYTEA PRIMARY KEY,
159
- sequence INTEGER NOT NULL
160
- )`)
161
- ),
162
- mysql: () =>
163
- sql`
164
- CREATE TABLE IF NOT EXISTS ${tableSql} (
165
- sequence INT PRIMARY KEY,
166
- last_sequence INT NOT NULL,
167
- iv BINARY(12) NOT NULL,
168
- entry_ids LONGBLOB NOT NULL,
169
- encrypted_batch LONGBLOB NOT NULL,
170
- INDEX (last_sequence)
171
- )`.pipe(
172
- Effect.andThen(sql`
173
- CREATE TABLE IF NOT EXISTS ${idsTableSql} (
174
- entry_id BINARY(16) PRIMARY KEY,
175
- sequence INT NOT NULL
176
- )`)
177
- ),
178
- mssql: () =>
179
- sql`
180
- IF OBJECT_ID(${table}, N'U') IS NULL
181
- CREATE TABLE ${tableSql} (
182
- sequence INT PRIMARY KEY,
183
- last_sequence INT NOT NULL,
184
- iv VARBINARY(12) NOT NULL,
185
- entry_ids VARBINARY(MAX) NOT NULL,
186
- encrypted_batch VARBINARY(MAX) NOT NULL,
187
- INDEX ${indexSql} NONCLUSTERED (last_sequence)
188
- )`.pipe(
189
- Effect.andThen(sql`
190
- IF OBJECT_ID(${`${table}_ids`}, N'U') IS NULL
191
- CREATE TABLE ${idsTableSql} (
192
- entry_id VARBINARY(16) PRIMARY KEY,
193
- sequence INT NOT NULL
194
- )`)
195
- ),
196
- orElse: () =>
197
- sql`
198
- CREATE TABLE IF NOT EXISTS ${tableSql} (
199
- sequence INTEGER PRIMARY KEY,
200
- last_sequence INTEGER NOT NULL,
201
- iv BLOB NOT NULL,
202
- entry_ids BLOB NOT NULL,
203
- encrypted_batch BLOB NOT NULL
204
- )`.pipe(
205
- Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`),
206
- Effect.andThen(sql`
207
- CREATE TABLE IF NOT EXISTS ${idsTableSql} (
208
- entry_id BLOB PRIMARY KEY,
209
- sequence INTEGER NOT NULL
210
- )`)
211
- )
212
- })
286
+ const scopeTables = (scope: string) => {
287
+ const table = `${tablePrefix}_${scope}`
288
+ return {
289
+ tableSql: sql(table),
290
+ idsTableSql: sql(`${table}_ids`),
291
+ snapshotTableSql: sql(`${table}_snapshot`)
292
+ }
293
+ }
294
+ type ScopeTables = ReturnType<typeof scopeTables>
213
295
 
214
- const pubsub = yield* Effect.acquireRelease(
215
- PubSub.unbounded<EventLogEncryption.EncryptedRemoteBatch>(),
216
- PubSub.shutdown
217
- )
218
- // Serializes writes within this storage instance.
219
- const semaphore = yield* Semaphore.make(1)
220
- return { pubsub, semaphore, tableSql, idsTableSql } as const
221
- }, withTracerDisabled),
222
- idleTimeToLive: "5 minutes"
296
+ const ensureTables = Effect.fnUntraced(function*(scope: string, tables: ScopeTables) {
297
+ const table = `${tablePrefix}_${scope}`
298
+ const { idsTableSql, tableSql } = tables
299
+ const indexSql = sql(`${table}_last_sequence`)
300
+
301
+ yield* sql.onDialectOrElse({
302
+ pg: () =>
303
+ sql`
304
+ CREATE TABLE IF NOT EXISTS ${tableSql} (
305
+ sequence INTEGER PRIMARY KEY,
306
+ last_sequence INTEGER NOT NULL,
307
+ iv BYTEA NOT NULL,
308
+ entry_ids BYTEA NOT NULL,
309
+ encrypted_batch BYTEA NOT NULL
310
+ )`.pipe(
311
+ Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`),
312
+ Effect.andThen(sql`
313
+ CREATE TABLE IF NOT EXISTS ${idsTableSql} (
314
+ entry_id BYTEA PRIMARY KEY,
315
+ sequence INTEGER NOT NULL
316
+ )`)
317
+ ),
318
+ mysql: () =>
319
+ sql`
320
+ CREATE TABLE IF NOT EXISTS ${tableSql} (
321
+ sequence INT PRIMARY KEY,
322
+ last_sequence INT NOT NULL,
323
+ iv BINARY(12) NOT NULL,
324
+ entry_ids LONGBLOB NOT NULL,
325
+ encrypted_batch LONGBLOB NOT NULL,
326
+ INDEX (last_sequence)
327
+ )`.pipe(
328
+ Effect.andThen(sql`
329
+ CREATE TABLE IF NOT EXISTS ${idsTableSql} (
330
+ entry_id BINARY(16) PRIMARY KEY,
331
+ sequence INT NOT NULL
332
+ )`)
333
+ ),
334
+ mssql: () =>
335
+ sql`
336
+ IF OBJECT_ID(${table}, N'U') IS NULL
337
+ CREATE TABLE ${tableSql} (
338
+ sequence INT PRIMARY KEY,
339
+ last_sequence INT NOT NULL,
340
+ iv VARBINARY(12) NOT NULL,
341
+ entry_ids VARBINARY(MAX) NOT NULL,
342
+ encrypted_batch VARBINARY(MAX) NOT NULL,
343
+ INDEX ${indexSql} NONCLUSTERED (last_sequence)
344
+ )`.pipe(
345
+ Effect.andThen(sql`
346
+ IF OBJECT_ID(${`${table}_ids`}, N'U') IS NULL
347
+ CREATE TABLE ${idsTableSql} (
348
+ entry_id VARBINARY(16) PRIMARY KEY,
349
+ sequence INT NOT NULL
350
+ )`)
351
+ ),
352
+ orElse: () =>
353
+ sql`
354
+ CREATE TABLE IF NOT EXISTS ${tableSql} (
355
+ sequence INTEGER PRIMARY KEY,
356
+ last_sequence INTEGER NOT NULL,
357
+ iv BLOB NOT NULL,
358
+ entry_ids BLOB NOT NULL,
359
+ encrypted_batch BLOB NOT NULL
360
+ )`.pipe(
361
+ Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`),
362
+ Effect.andThen(sql`
363
+ CREATE TABLE IF NOT EXISTS ${idsTableSql} (
364
+ entry_id BLOB PRIMARY KEY,
365
+ sequence INTEGER NOT NULL
366
+ )`)
367
+ )
368
+ }).pipe(Effect.retry({ times: 3 }))
369
+ // Staged and committed snapshot batches. `sequence` and
370
+ // `last_sequence` stay null until commit.
371
+ yield* createTable(
372
+ `${table}_snapshot`,
373
+ `batch_set ${types.id} NOT NULL,
374
+ batch_position ${types.bigint} NOT NULL,
375
+ sequence ${types.int} NULL,
376
+ last_sequence ${types.int} NULL,
377
+ entry_count ${types.int} NOT NULL,
378
+ iv ${types.iv} NOT NULL,
379
+ entry_ids ${types.bytes} NOT NULL,
380
+ encrypted_batch ${types.bytes} NOT NULL,
381
+ PRIMARY KEY (batch_set, batch_position)`,
382
+ ["batch_set, last_sequence"]
383
+ )
384
+ yield* ensureStore(scope, tables)
223
385
  })
224
386
 
225
- // Returns how many of the ids were not stored yet. Stored ids keep their
226
- // first sequence.
227
- const insertNewIds = (idsTableSql: Statement.Identifier, rows: ReadonlyArray<IdRow>) =>
387
+ // Stores whose tables and row this instance has ensured, so a store
388
+ // reopened after idling skips the DDL.
389
+ const ensured = new Set<string>()
390
+ const lookup = Effect.fnUntraced(function*(scopeKey: string) {
391
+ const scope = (yield* digest.digestHex(Digest.sha256, new TextEncoder().encode(scopeKey))).slice(0, 16)
392
+ const tables = scopeTables(scope)
393
+ if (!ensured.has(scope)) {
394
+ yield* ensureTables(scope, tables)
395
+ ensured.add(scope)
396
+ }
397
+
398
+ const pubsub = yield* Effect.acquireRelease(
399
+ PubSub.unbounded<EventLogEncryption.EncryptedRemoteBatch>(),
400
+ PubSub.shutdown
401
+ )
402
+ const semaphore = yield* Semaphore.make(1)
403
+ return { pubsub, semaphore, scope, ...tables } as const
404
+ }, withTracerDisabled)
405
+ type StoreResources = Effect.Success<ReturnType<typeof lookup>>
406
+ const resources = yield* RcMap.make({ lookup, idleTimeToLive: "5 minutes" })
407
+
408
+ // Statements that return rows the caller decodes.
409
+ const query = (statement: Statement.Statement<unknown>) =>
410
+ statement as unknown as Effect.Effect<ReadonlyArray<Record<string, unknown>>, SqlError.SqlError>
411
+
412
+ // Inserts the rows whose `keys` are not stored yet, leaving stored rows as
413
+ // they are, and returns how many were inserted. Every row has the same
414
+ // columns, and SQL Server's MERGE needs them unique by key.
415
+ const insertNew = (
416
+ tableSql: Statement.Identifier,
417
+ rows: Arr.NonEmptyReadonlyArray<Record<string, unknown>>,
418
+ keys: ReadonlyArray<string>
419
+ ) =>
228
420
  sql.onDialectOrElse({
229
421
  mysql: () =>
230
- sql`INSERT IGNORE INTO ${idsTableSql} ${sql.insert(rows)}`.raw.pipe(
422
+ sql`INSERT IGNORE INTO ${tableSql} ${sql.insert(rows)}`.raw.pipe(
231
423
  Effect.map((result) => (result as { affectedRows: number }).affectedRows)
232
424
  ),
425
+ mssql: () => {
426
+ const columns = Object.keys(rows[0])
427
+ const values = sql.join(", ", false)(
428
+ rows.map((row) => sql`(${sql.join(", ", false)(columns.map((column) => sql`${row[column]}`))})`)
429
+ )
430
+ return sql`
431
+ MERGE ${tableSql} WITH (HOLDLOCK) AS target
432
+ USING (VALUES ${values}) AS source (${sql.literal(columns.join(", "))})
433
+ ON ${sql.literal(keys.map((key) => `target.${key} = source.${key}`).join(" AND "))}
434
+ WHEN NOT MATCHED THEN
435
+ INSERT (${sql.literal(columns.join(", "))})
436
+ VALUES (${sql.literal(columns.map((column) => `source.${column}`).join(", "))})
437
+ OUTPUT inserted.${sql.literal(keys[0])};
438
+ `.pipe(Effect.map((inserted) => inserted.length))
439
+ },
440
+ orElse: () =>
441
+ sql`INSERT INTO ${tableSql} ${sql.insert(rows)} ON CONFLICT DO NOTHING RETURNING ${sql(keys[0])}`.pipe(
442
+ Effect.map((inserted) => inserted.length)
443
+ )
444
+ })
445
+
446
+ const upsertBucket = (scope: string, hour: number, lastSequence: number) =>
447
+ sql.onDialectOrElse({
448
+ pg: () =>
449
+ sql`
450
+ INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
451
+ ON CONFLICT (scope, bucket_hour)
452
+ DO UPDATE SET last_sequence = GREATEST(${bucketsSql}.last_sequence, EXCLUDED.last_sequence)`,
453
+ mysql: () =>
454
+ sql`
455
+ INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
456
+ ON DUPLICATE KEY UPDATE last_sequence = GREATEST(last_sequence, VALUES(last_sequence))`,
233
457
  mssql: () =>
234
458
  sql`
235
- MERGE ${idsTableSql} WITH (HOLDLOCK) AS target
236
- USING (VALUES ${
237
- sql.join(",", false)(rows.map((row) => sql`(${row.entry_id}, ${row.sequence})`))
238
- }) AS source (entry_id, sequence)
239
- ON target.entry_id = source.entry_id
459
+ MERGE ${bucketsSql} WITH (HOLDLOCK) AS target
460
+ USING (VALUES (${scope}, ${hour}, ${lastSequence})) AS source (scope, bucket_hour, last_sequence)
461
+ ON target.scope = source.scope AND target.bucket_hour = source.bucket_hour
462
+ WHEN MATCHED AND source.last_sequence > target.last_sequence THEN
463
+ UPDATE SET last_sequence = source.last_sequence
240
464
  WHEN NOT MATCHED THEN
241
- INSERT (entry_id, sequence) VALUES (source.entry_id, source.sequence)
242
- OUTPUT inserted.entry_id;
243
- `.pipe(Effect.map((inserted) => inserted.length)),
465
+ INSERT (scope, bucket_hour, last_sequence)
466
+ VALUES (source.scope, source.bucket_hour, source.last_sequence);`,
244
467
  orElse: () =>
245
468
  sql`
246
- INSERT INTO ${idsTableSql} ${sql.insert(rows)}
247
- ON CONFLICT DO NOTHING RETURNING entry_id
248
- `.pipe(Effect.map((inserted) => inserted.length))
469
+ INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
470
+ ON CONFLICT (scope, bucket_hour)
471
+ DO UPDATE SET last_sequence = MAX(${bucketsSql}.last_sequence, excluded.last_sequence)`
249
472
  })
250
473
 
474
+ const maxLastSequence = (tableSql: Statement.Identifier) =>
475
+ Effect.map(
476
+ query(sql`SELECT MAX(last_sequence) AS last_sequence FROM ${tableSql}`),
477
+ ([row]) => Number(row?.last_sequence ?? 0)
478
+ )
479
+
480
+ // Creates the store row on first open. The first instance to insert it
481
+ // also records the originals activation and seeds a bucket at the current
482
+ // last sequence, so data written before the upgrade becomes compactable.
483
+ const ensureStore = Effect.fnUntraced(function*(scope: string, tables: ScopeTables) {
484
+ const existing = yield* query(sql`SELECT scope FROM ${storesSql} WHERE scope = ${scope}`)
485
+ if (existing.length > 0) return
486
+ const now = yield* Clock.currentTimeMillis
487
+ yield* sql.withTransaction(Effect.gen(function*() {
488
+ const lastSequence = yield* maxLastSequence(tables.tableSql)
489
+ const originals = SecureRandom.uuidV4Unsafe()
490
+ const inserted = yield* insertNew(storesSql, [{
491
+ scope,
492
+ next_sequence: lastSequence + 1,
493
+ active_activation: originals,
494
+ active_batch_set: null,
495
+ base_sequence: 0,
496
+ active_kept_count: 0,
497
+ no_op_at: null,
498
+ no_op_count: 0,
499
+ open_stage: null,
500
+ next_sweep_at: null,
501
+ sweep_lease_until: null,
502
+ sweep_lease_owner: null,
503
+ pruned_through: 0
504
+ }], ["scope"])
505
+ if (inserted === 0) return
506
+ yield* sql`INSERT INTO ${activationsSql} ${
507
+ sql.insert({
508
+ id: originals,
509
+ scope,
510
+ ordinal: 0,
511
+ batch_set: null,
512
+ base_sequence: 0,
513
+ kept_count: null,
514
+ previous: null,
515
+ superseded_at: null,
516
+ prunable_at: null,
517
+ prune_started_at: null
518
+ })
519
+ }`
520
+ if (lastSequence > 0) yield* upsertBucket(scope, Math.floor(now / hourMillis), lastSequence)
521
+ }))
522
+ })
523
+
524
+ const storeColumns = sql.literal(
525
+ "next_sequence, active_activation, active_batch_set, base_sequence, active_kept_count, no_op_at, " +
526
+ "no_op_count, open_stage, next_sweep_at, sweep_lease_until, sweep_lease_owner, pruned_through"
527
+ )
528
+ const selectStore = (scope: string) =>
529
+ Effect.map(
530
+ query(sql`SELECT ${storeColumns} FROM ${storesSql} WHERE scope = ${scope}`),
531
+ ([row]) => row === undefined ? undefined : toStoreRow(row)
532
+ )
533
+ // Row locks held for the rest of the transaction. SQLite transactions take
534
+ // the database write lock instead.
535
+ const lockHint = sql.onDialectOrElse({
536
+ mssql: () => sql.literal("WITH (UPDLOCK, ROWLOCK, HOLDLOCK)"),
537
+ orElse: () => sql.literal("")
538
+ })
539
+ const forUpdate = sql.onDialectOrElse({
540
+ pg: () => sql.literal("FOR UPDATE"),
541
+ mysql: () => sql.literal("FOR UPDATE"),
542
+ orElse: () => sql.literal("")
543
+ })
544
+ const noClause = sql.literal("")
545
+ // Locks the store row for the rest of the transaction. Lock order is store
546
+ // row, then stage row, then snapshot rows.
547
+ const lockStore = (scope: string) =>
548
+ query(sql`SELECT ${storeColumns} FROM ${storesSql} ${lockHint} WHERE scope = ${scope} ${forUpdate}`).pipe(
549
+ Effect.flatMap(([row]) =>
550
+ row === undefined
551
+ ? Effect.die(new Cause.NoSuchElementError(`No compaction store row for scope ${scope}`))
552
+ : Effect.succeed(toStoreRow(row))
553
+ )
554
+ )
555
+
556
+ const stageColumns = sql.literal("id, scope, state, pinned, through_sequence, expires_at")
557
+ const selectStage = (scope: string, stageId: string, lock: boolean) =>
558
+ query(sql`
559
+ SELECT ${stageColumns} FROM ${stagesSql} ${lock ? lockHint : noClause}
560
+ WHERE id = ${stageId} ${lock ? forUpdate : noClause}`).pipe(
561
+ Effect.map(([row]) => {
562
+ if (row === undefined) return undefined
563
+ const stage = toStageRow(row)
564
+ return stage.scope === scope ? stage : undefined
565
+ })
566
+ )
567
+
568
+ const selectActivation = (id: string) =>
569
+ Effect.map(
570
+ query(sql`SELECT ${activationColumns} FROM ${activationsSql} WHERE id = ${id}`),
571
+ ([row]) => row === undefined ? undefined : toActivationRow(row)
572
+ )
573
+ const selectActivations = (scope: string) =>
574
+ Effect.map(
575
+ query(sql`SELECT ${activationColumns} FROM ${activationsSql} WHERE scope = ${scope} ORDER BY ordinal`),
576
+ (rows) => rows.map(toActivationRow)
577
+ )
578
+ const activationColumns = sql.literal(
579
+ "id, ordinal, batch_set, base_sequence, kept_count, previous, superseded_at, prunable_at, prune_started_at"
580
+ )
581
+
582
+ const releaseClaim = (scope: string, stageId: string) =>
583
+ sql`UPDATE ${storesSql} SET open_stage = NULL WHERE scope = ${scope} AND open_stage = ${stageId}`
584
+ // Closes an open stage, leaving what it staged to the sweep, and releases
585
+ // the store's busy claim.
586
+ const closeStage = (scope: string, stageId: string) =>
587
+ sql`UPDATE ${stagesSql} SET state = ${"closed"} WHERE id = ${stageId} AND state = ${"open"}`.pipe(
588
+ Effect.andThen(releaseClaim(scope, stageId))
589
+ )
590
+
591
+ const scheduleSweep = (scope: string, at: number) =>
592
+ sql`
593
+ UPDATE ${storesSql}
594
+ SET next_sweep_at = CASE WHEN next_sweep_at IS NULL OR next_sweep_at > ${at} THEN ${at} ELSE next_sweep_at END
595
+ WHERE scope = ${scope}`
596
+
597
+ // The clauses that cap a statement at `rows` rows, for `SELECT ${top} ... ${limit}`.
598
+ const rowCap = (rows: number) => ({
599
+ top: sql.onDialectOrElse({ mssql: () => sql.literal(`TOP (${rows})`), orElse: () => noClause }),
600
+ limit: sql.onDialectOrElse({ mssql: () => noClause, orElse: () => sql.literal(`LIMIT ${rows}`) })
601
+ })
602
+ const { limit, top } = rowCap(pageSize)
603
+ const { limit: chunkLimit, top: chunkTop } = rowCap(pruneChunkRows)
604
+ // Streams rows in key order, one page per statement.
605
+ const paged = <A>(
606
+ first: number,
607
+ page: (after: number) => Effect.Effect<ReadonlyArray<A>, SqlError.SqlError>,
608
+ key: (row: A) => number
609
+ ) =>
610
+ Stream.paginate(first, (after) =>
611
+ Effect.map(page(after), (rows) =>
612
+ [
613
+ rows,
614
+ rows.length < pageSize ? Option.none<number>() : Option.some(key(rows[rows.length - 1]))
615
+ ] as const))
616
+
617
+ const setRows = (snapshotTableSql: Statement.Identifier, batchSet: string) =>
618
+ paged(
619
+ -1,
620
+ (after) =>
621
+ query(sql`
622
+ SELECT ${top} batch_position, sequence, entry_ids FROM ${snapshotTableSql}
623
+ WHERE batch_set = ${batchSet} AND batch_position > ${after}
624
+ ORDER BY batch_position ${limit}`),
625
+ (row) => Number(row.batch_position)
626
+ )
627
+
251
628
  const getSessionAuthBinding = (publicKey: string) =>
252
629
  sql`
253
630
  SELECT public_key, signing_public_key
@@ -262,6 +639,644 @@ export const makeStorage = (options?: {
262
639
  Effect.orDie
263
640
  )
264
641
 
642
+ // Runs `f` with the store's resources and the time, read before any
643
+ // transaction starts. SQL failures are defects.
644
+ const withStore = <A, E>(
645
+ publicKey: string,
646
+ storeId: StoreId,
647
+ f: (store: StoreResources, now: number) => Effect.Effect<A, E | SqlError.SqlError>
648
+ ): Effect.Effect<A, Exclude<E, SqlError.SqlError>> =>
649
+ Effect.zip(RcMap.get(resources, makeEncryptedScopeKey(publicKey, storeId)), Clock.currentTimeMillis).pipe(
650
+ Effect.flatMap(([store, now]) => f(store, now)),
651
+ dieOnSqlError,
652
+ Effect.scoped,
653
+ withTracerDisabled
654
+ )
655
+
656
+ const notFound = (stageId: string) => Effect.fail(new EventLogServerEncrypted.CompactionNotFound({ stageId }))
657
+
658
+ // An open stage whose pin isn't being pruned yet, for Input and Fetch.
659
+ const findInputStage = Effect.fnUntraced(function*(scope: string, stageId: string, now: number) {
660
+ const stage = yield* selectStage(scope, stageId, false)
661
+ if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) return undefined
662
+ const pinned = yield* selectActivation(stage.pinned)
663
+ if (pinned === undefined || pinned.pruneStartedAt !== undefined) return undefined
664
+ return { stage, pinned }
665
+ })
666
+ // Fails a read that may have been truncated by a prune of its pin.
667
+ const recheckPin = (stageId: string, pinned: string) =>
668
+ Effect.flatMap(
669
+ selectActivation(pinned),
670
+ (activation) =>
671
+ activation === undefined || activation.pruneStartedAt !== undefined ? notFound(stageId) : Effect.void
672
+ )
673
+
674
+ const status = Effect.fnUntraced(function*({ scope, tableSql }: StoreResources, now: number) {
675
+ const store = (yield* selectStore(scope))!
676
+ const lastSequence = yield* maxLastSequence(tableSql)
677
+ const [lag] = yield* query(sql`
678
+ SELECT MAX(last_sequence) AS last_sequence FROM ${bucketsSql}
679
+ WHERE scope = ${scope} AND bucket_hour <= ${lagHour(now, lagMillis)}`)
680
+ return {
681
+ head: Math.max(store.nextSequence - 1, lastSequence),
682
+ base: store.base,
683
+ activeKeptCount: store.activeKeptCount,
684
+ lagHead: Number(lag?.last_sequence ?? 0),
685
+ noOp: store.noOpAt === undefined ? undefined : { at: store.noOpAt, count: store.noOpCount }
686
+ }
687
+ })
688
+
689
+ const begin = Effect.fnUntraced(function*({ scope }: StoreResources, now: number, through: number) {
690
+ const store = yield* lockStore(scope)
691
+ if (store.openStage !== undefined) {
692
+ const open = yield* selectStage(scope, store.openStage, true)
693
+ if (open !== undefined && open.state === "open") {
694
+ if (open.expiresAt > now) {
695
+ return { _tag: "Busy", expiresAt: open.expiresAt } as const
696
+ }
697
+ yield* closeStage(scope, open.id)
698
+ }
699
+ }
700
+ const stageId = SecureRandom.uuidV4Unsafe()
701
+ yield* sql`INSERT INTO ${stagesSql} ${
702
+ sql.insert({
703
+ id: stageId,
704
+ scope,
705
+ state: "open",
706
+ pinned: store.activeActivation,
707
+ through_sequence: through,
708
+ expires_at: now + stageTtlMillis
709
+ })
710
+ }`
711
+ yield* sql`UPDATE ${storesSql} SET open_stage = ${stageId} WHERE scope = ${scope}`
712
+ return { _tag: "Started", stageId } as const
713
+ }, sql.withTransaction)
714
+
715
+ const inputHeaders = Effect.fnUntraced(function*(
716
+ { scope, snapshotTableSql, tableSql }: StoreResources,
717
+ now: number,
718
+ stageId: string
719
+ ) {
720
+ const found = yield* findInputStage(scope, stageId, now)
721
+ if (found === undefined) {
722
+ return yield* notFound(stageId)
723
+ }
724
+ const { pinned, stage } = found
725
+ // Membership comes from the batch rows, never from the id table.
726
+ const snapshot = pinned.batchSet === undefined ? Stream.empty : setRows(snapshotTableSql, pinned.batchSet)
727
+ const tail = paged(pinned.base, (after) =>
728
+ query(sql`
729
+ SELECT ${top} sequence, entry_ids FROM ${tableSql}
730
+ WHERE sequence > ${after} AND last_sequence <= ${stage.through}
731
+ ORDER BY sequence ${limit}`), (row) => Number(row.sequence))
732
+ return snapshot.pipe(
733
+ Stream.concat(tail),
734
+ Stream.map((row): EventLogServerEncrypted.CompactionBatchHeader => ({
735
+ sequence: Number(row.sequence),
736
+ entryIds: toEntryIds(row.entry_ids as Uint8Array)
737
+ })),
738
+ Stream.orDie,
739
+ Stream.concat(Stream.fromEffectDrain(dieOnSqlError(recheckPin(stageId, pinned.id))))
740
+ )
741
+ })
742
+
743
+ const fetch = Effect.fnUntraced(function*(
744
+ { scope, snapshotTableSql, tableSql }: StoreResources,
745
+ now: number,
746
+ stageId: string,
747
+ sequences: ReadonlyArray<number>
748
+ ) {
749
+ const found = yield* findInputStage(scope, stageId, now)
750
+ if (found === undefined) {
751
+ return yield* notFound(stageId)
752
+ }
753
+ const { pinned, stage } = found
754
+ const bySequence = new Map<number, EventLogEncryption.EncryptedRemoteBatch>()
755
+ if (pinned.batchSet !== undefined) {
756
+ for (const chunk of Arr.chunksOf(sequences.filter((sequence) => sequence <= pinned.base), 200)) {
757
+ const rows = yield* sql`
758
+ SELECT sequence, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
759
+ WHERE batch_set = ${pinned.batchSet} AND ${sql.in("sequence", chunk)}
760
+ `.pipe(Effect.flatMap(decodeBatches), Effect.orDie)
761
+ for (const batch of rows) bySequence.set(batch.sequence, batch)
762
+ }
763
+ }
764
+ const originals = sequences.filter((sequence) => sequence > pinned.base && sequence <= stage.through)
765
+ for (const chunk of Arr.chunksOf(originals, 200)) {
766
+ const rows = yield* sql`
767
+ SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
768
+ WHERE ${sql.in("sequence", chunk)}
769
+ `.pipe(Effect.flatMap(decodeBatches), Effect.orDie)
770
+ for (const batch of rows) bySequence.set(batch.sequence, batch)
771
+ }
772
+ yield* recheckPin(stageId, pinned.id)
773
+ return Stream.fromArray(sequences.flatMap((sequence) => {
774
+ const batch = bySequence.get(sequence)
775
+ return batch === undefined ? [] : [batch]
776
+ }))
777
+ })
778
+
779
+ const stagePart = (
780
+ store: StoreResources,
781
+ now: number,
782
+ stageId: string,
783
+ segment: number,
784
+ batches: Arr.NonEmptyReadonlyArray<EventLogEncryption.EncryptedBatch>
785
+ ): Effect.Effect<
786
+ "Stored" | "AlreadyStored",
787
+ EventLogServerEncrypted.CompactionInvalidRequest | EventLogServerEncrypted.CompactionNotFound | SqlError.SqlError
788
+ > => {
789
+ const invalid = !Number.isSafeInteger(segment) || segment < 0 || segment >= maxSegments
790
+ ? "Invalid segment index"
791
+ : batches.length > positionsPerSegment
792
+ ? "Too many batches in a segment"
793
+ : undefined
794
+ // A Part that fails validation closes the stage.
795
+ if (invalid !== undefined) {
796
+ return Effect.andThen(
797
+ abort(store, stageId),
798
+ Effect.fail(new EventLogServerEncrypted.CompactionInvalidRequest({ stageId, message: invalid }))
799
+ )
800
+ }
801
+ const { scope, snapshotTableSql } = store
802
+ return sql.withTransaction(Effect.gen(function*() {
803
+ yield* lockStore(scope)
804
+ const stage = yield* selectStage(scope, stageId, true)
805
+ if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
806
+ return yield* notFound(stageId)
807
+ }
808
+ // The snapshot table's key makes a retried segment insert nothing.
809
+ // `sequence` and `last_sequence` stay null until commit.
810
+ const rows = batches.map((batch, i) => ({
811
+ batch_set: stageId,
812
+ batch_position: segment * positionsPerSegment + i,
813
+ entry_count: batch.entryIds.length,
814
+ iv: batch.iv,
815
+ entry_ids: concatEntryIds(batch.entryIds),
816
+ encrypted_batch: batch.encryptedBatch
817
+ }))
818
+ let inserted = 0
819
+ for (const chunk of Arr.chunksOf(rows, 200)) {
820
+ inserted += yield* insertNew(snapshotTableSql, chunk, ["batch_set", "batch_position"])
821
+ }
822
+ if (inserted === 0) return "AlreadyStored" as const
823
+ yield* sql`UPDATE ${stagesSql} SET expires_at = ${now + stageTtlMillis} WHERE id = ${stageId}`
824
+ return "Stored" as const
825
+ }))
826
+ }
827
+
828
+ const stagedHeaders = Effect.fnUntraced(function*(
829
+ { scope, snapshotTableSql }: StoreResources,
830
+ now: number,
831
+ stageId: string
832
+ ) {
833
+ const stage = yield* selectStage(scope, stageId, false)
834
+ if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
835
+ return yield* notFound(stageId)
836
+ }
837
+ return setRows(snapshotTableSql, stageId).pipe(
838
+ Stream.map((row) => ({
839
+ position: Number(row.batch_position),
840
+ entryIds: toEntryIds(row.entry_ids as Uint8Array)
841
+ })),
842
+ Stream.orDie
843
+ )
844
+ })
845
+
846
+ const commit = Effect.fnUntraced(function*(
847
+ { scope, snapshotTableSql }: StoreResources,
848
+ now: number,
849
+ stageId: string,
850
+ virtualSequences: ReadonlyArray<number>
851
+ ) {
852
+ const store = yield* lockStore(scope)
853
+ const stage = yield* selectStage(scope, stageId, true)
854
+ if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
855
+ return yield* notFound(stageId)
856
+ }
857
+ const staged = yield* query(sql`
858
+ SELECT batch_position, entry_count FROM ${snapshotTableSql}
859
+ WHERE batch_set = ${stageId} ORDER BY batch_position`)
860
+ let keptCount = 0
861
+ for (const row of staged) keptCount += Number(row.entry_count)
862
+ const audit = { through: stage.through, keptCount }
863
+ if (stage.pinned !== store.activeActivation) {
864
+ yield* closeStage(scope, stageId)
865
+ return { result: "Superseded", activationId: undefined, ...audit } as const
866
+ }
867
+ if (staged.length !== virtualSequences.length) {
868
+ return yield* Effect.die(new Cause.IllegalArgumentError("Expected one virtual sequence per staged batch"))
869
+ }
870
+ for (let i = 0; i < staged.length; i++) {
871
+ const sequence = virtualSequences[i]
872
+ const lastSequence = sequence + Number(staged[i].entry_count) - 1
873
+ yield* sql`
874
+ UPDATE ${snapshotTableSql} SET sequence = ${sequence}, last_sequence = ${lastSequence}
875
+ WHERE batch_set = ${stageId} AND batch_position = ${Number(staged[i].batch_position)}`
876
+ }
877
+ const activationId = SecureRandom.uuidV4Unsafe()
878
+ yield* insertActivation(scope, {
879
+ id: activationId,
880
+ batch_set: stageId,
881
+ base_sequence: stage.through,
882
+ kept_count: keptCount,
883
+ previous: store.activeActivation
884
+ })
885
+ yield* supersede(store.activeActivation, now)
886
+ yield* sql`
887
+ UPDATE ${storesSql}
888
+ SET active_activation = ${activationId}, active_batch_set = ${stageId},
889
+ base_sequence = ${stage.through}, active_kept_count = ${keptCount},
890
+ no_op_at = NULL, no_op_count = 0
891
+ WHERE scope = ${scope}`
892
+ yield* scheduleSweep(scope, now + graceMillis)
893
+ // The staged rows are now the active batch set.
894
+ yield* sql`DELETE FROM ${stagesSql} WHERE id = ${stageId}`
895
+ yield* releaseClaim(scope, stageId)
896
+ return { result: "Committed", activationId, ...audit } as const
897
+ }, sql.withTransaction)
898
+
899
+ const recordNoOp = Effect.fnUntraced(function*({ scope }: StoreResources, now: number, stageId: string) {
900
+ const store = yield* lockStore(scope)
901
+ const stage = yield* selectStage(scope, stageId, true)
902
+ if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
903
+ return yield* notFound(stageId)
904
+ }
905
+ // A rollback since begin cleared the no-op state, so a stale run leaves it.
906
+ if (stage.pinned === store.activeActivation) {
907
+ yield* sql`
908
+ UPDATE ${storesSql} SET no_op_at = ${stage.through}, no_op_count = ${store.noOpCount + 1}
909
+ WHERE scope = ${scope}`
910
+ }
911
+ yield* closeStage(scope, stageId)
912
+ return stage.through
913
+ }, sql.withTransaction)
914
+
915
+ const rollbackTransaction = Effect.fnUntraced(function*(
916
+ { pubsub, scope, snapshotTableSql, tableSql }: StoreResources,
917
+ now: number,
918
+ expectedActivationId: string
919
+ ) {
920
+ const refuse = (reason: EventLogServerEncrypted.CompactionRollbackError["reason"]) =>
921
+ Effect.fail(new EventLogServerEncrypted.CompactionRollbackError({ reason }))
922
+ const store = yield* lockStore(scope)
923
+ if (store.activeActivation !== expectedActivationId) return yield* refuse("NotActive")
924
+ const withdrawn = (yield* selectActivation(store.activeActivation))!
925
+ const target = withdrawn.previous === undefined ? undefined : yield* selectActivation(withdrawn.previous)
926
+ if (target === undefined) return yield* refuse("NoPrevious")
927
+ if (target.pruneStartedAt !== undefined) return yield* refuse("PruneStarted")
928
+ const activationId = SecureRandom.uuidV4Unsafe()
929
+ yield* insertActivation(scope, {
930
+ id: activationId,
931
+ batch_set: target.batchSet ?? null,
932
+ base_sequence: target.base,
933
+ kept_count: target.keptCount ?? null,
934
+ previous: target.previous ?? null
935
+ })
936
+ yield* supersede(withdrawn.id, now)
937
+ yield* sql`
938
+ UPDATE ${storesSql}
939
+ SET active_activation = ${activationId}, active_batch_set = ${target.batchSet ?? null},
940
+ base_sequence = ${target.base}, active_kept_count = ${target.keptCount ?? 0},
941
+ no_op_at = NULL, no_op_count = 0
942
+ WHERE scope = ${scope}`
943
+ yield* scheduleSweep(scope, now + graceMillis)
944
+
945
+ // Re-appends the input of the stage that built the withdrawn generation,
946
+ // the target's batch set and then the originals between the two bases,
947
+ // at sequences taken as `write` takes them.
948
+ let nextSequence = Math.max(store.nextSequence, (yield* maxLastSequence(tableSql)) + 1)
949
+ const reappended: Array<EventLogEncryption.EncryptedRemoteBatch> = []
950
+ const reappend = (rows: ReadonlyArray<Record<string, unknown>>) => {
951
+ const inserted: Array<BatchRow> = []
952
+ for (const row of rows) {
953
+ const entry_ids = row.entry_ids as Uint8Array
954
+ const batch: EventLogEncryption.EncryptedRemoteBatch = {
955
+ sequence: nextSequence,
956
+ entryIds: toEntryIds(entry_ids),
957
+ iv: row.iv as Uint8Array<ArrayBuffer>,
958
+ encryptedBatch: row.encrypted_batch as Uint8Array<ArrayBuffer>
959
+ }
960
+ nextSequence += batch.entryIds.length
961
+ reappended.push(batch)
962
+ inserted.push({
963
+ sequence: batch.sequence,
964
+ last_sequence: nextSequence - 1,
965
+ iv: batch.iv,
966
+ entry_ids,
967
+ encrypted_batch: batch.encryptedBatch
968
+ })
969
+ }
970
+ return Effect.forEach(
971
+ Arr.chunksOf(inserted, 200),
972
+ (chunk) => sql`INSERT INTO ${tableSql} ${sql.insert(chunk)}`,
973
+ { discard: true }
974
+ )
975
+ }
976
+ if (target.batchSet !== undefined) {
977
+ yield* Stream.runForEachArray(
978
+ paged(
979
+ -1,
980
+ (after) =>
981
+ query(sql`
982
+ SELECT ${top} batch_position, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
983
+ WHERE batch_set = ${target.batchSet} AND batch_position > ${after}
984
+ ORDER BY batch_position ${limit}`),
985
+ (row) => Number(row.batch_position)
986
+ ),
987
+ reappend
988
+ )
989
+ }
990
+ // The copies land above `withdrawn.base`, so the pages never reach them.
991
+ yield* Stream.runForEachArray(
992
+ paged(
993
+ target.base,
994
+ (after) =>
995
+ query(sql`
996
+ SELECT ${top} sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
997
+ WHERE sequence > ${after} AND last_sequence <= ${withdrawn.base}
998
+ ORDER BY sequence ${limit}`),
999
+ (row) => Number(row.sequence)
1000
+ ),
1001
+ reappend
1002
+ )
1003
+ if (reappended.length > 0) {
1004
+ yield* sql`UPDATE ${storesSql} SET next_sequence = ${nextSequence} WHERE scope = ${scope}`
1005
+ yield* upsertBucket(scope, Math.floor(now / hourMillis), nextSequence - 1)
1006
+ yield* sql.afterCommit(Effect.asVoid(PubSub.publishAll(pubsub, reappended)))
1007
+ }
1008
+ return { activationId, target, withdrawn }
1009
+ }, sql.withTransaction)
1010
+
1011
+ // The re-appended batches are published after the outermost commit. When
1012
+ // the storage owns the transaction, that happens before the semaphore is
1013
+ // released, so live readers get them in sequence order.
1014
+ const rollback = (store: StoreResources, now: number, expectedActivationId: string) =>
1015
+ store.semaphore.withPermits(1)(rollbackTransaction(store, now, expectedActivationId))
1016
+
1017
+ const history = Effect.fnUntraced(function*({ scope }: StoreResources) {
1018
+ const store = (yield* selectStore(scope))!
1019
+ const activations = yield* selectActivations(scope)
1020
+ return activations.map((activation): EventLogServerEncrypted.CompactionActivation => {
1021
+ const active = activation.id === store.activeActivation
1022
+ return {
1023
+ activationId: activation.id,
1024
+ base: activation.base,
1025
+ keptCount: activation.keptCount,
1026
+ active,
1027
+ supersededAt: active ? undefined : activation.supersededAt,
1028
+ restorable: activation.pruneStartedAt === undefined
1029
+ }
1030
+ })
1031
+ })
1032
+
1033
+ const abort = Effect.fnUntraced(function*({ scope }: StoreResources, stageId: string) {
1034
+ yield* lockStore(scope)
1035
+ const stage = yield* selectStage(scope, stageId, true)
1036
+ if (stage?.state === "open") yield* closeStage(scope, stageId)
1037
+ }, sql.withTransaction)
1038
+
1039
+ const compaction: EventLogServerEncrypted.StorageCompaction = {
1040
+ config,
1041
+ status: (publicKey, storeId) => withStore(publicKey, storeId, status),
1042
+ begin: (publicKey, storeId, { through }) =>
1043
+ withStore(publicKey, storeId, (store, now) => begin(store, now, through)),
1044
+ inputHeaders: (publicKey, storeId, stageId) =>
1045
+ Stream.unwrap(withStore(publicKey, storeId, (store, now) => inputHeaders(store, now, stageId))),
1046
+ fetch: (publicKey, storeId, stageId, sequences) =>
1047
+ Stream.unwrap(withStore(publicKey, storeId, (store, now) => fetch(store, now, stageId, sequences))),
1048
+ stagePart: (publicKey, storeId, stageId, segment, batches) =>
1049
+ withStore(publicKey, storeId, (store, now) => stagePart(store, now, stageId, segment, batches)),
1050
+ stagedHeaders: (publicKey, storeId, stageId) =>
1051
+ Stream.unwrap(withStore(publicKey, storeId, (store, now) => stagedHeaders(store, now, stageId))),
1052
+ commit: (publicKey, storeId, stageId, virtualSequences) =>
1053
+ withStore(publicKey, storeId, (store, now) => commit(store, now, stageId, virtualSequences)).pipe(
1054
+ Effect.tap((outcome) => logCommit({ publicKey, storeId, ...outcome })),
1055
+ Effect.map((outcome) => outcome.result)
1056
+ ),
1057
+ recordNoOp: (publicKey, storeId, stageId) =>
1058
+ withStore(publicKey, storeId, (store, now) => recordNoOp(store, now, stageId)).pipe(
1059
+ Effect.flatMap((through) => logNoOp({ publicKey, storeId, stageId, through }))
1060
+ ),
1061
+ abort: (publicKey, storeId, stageId) => withStore(publicKey, storeId, (store) => abort(store, stageId)),
1062
+ rollback: (publicKey, storeId, expectedActivationId) =>
1063
+ withStore(publicKey, storeId, (store, now) => rollback(store, now, expectedActivationId)).pipe(
1064
+ Effect.tap(({ activationId, target, withdrawn }) =>
1065
+ logRollback({ publicKey, storeId, activation: { ...target, id: activationId }, withdrawn })
1066
+ ),
1067
+ Effect.map((outcome) => outcome.activationId)
1068
+ ),
1069
+ history: (publicKey, storeId) => withStore(publicKey, storeId, (store) => history(store))
1070
+ }
1071
+
1072
+ const insertActivation = Effect.fnUntraced(function*(scope: string, activation: {
1073
+ readonly id: string
1074
+ readonly batch_set: string | null
1075
+ readonly base_sequence: number
1076
+ readonly kept_count: number | null
1077
+ readonly previous: string | null
1078
+ }) {
1079
+ const [last] = yield* query(sql`SELECT MAX(ordinal) AS ordinal FROM ${activationsSql} WHERE scope = ${scope}`)
1080
+ yield* sql`INSERT INTO ${activationsSql} ${
1081
+ sql.insert({
1082
+ ...activation,
1083
+ scope,
1084
+ ordinal: Number(last?.ordinal ?? 0) + 1,
1085
+ superseded_at: null,
1086
+ prunable_at: null,
1087
+ prune_started_at: null
1088
+ })
1089
+ }`
1090
+ })
1091
+ // Supersedes or withdraws an activation, prunable `grace` later.
1092
+ const supersede = (activationId: string, now: number) =>
1093
+ sql`
1094
+ UPDATE ${activationsSql} SET superseded_at = ${now}, prunable_at = ${now + graceMillis}
1095
+ WHERE id = ${activationId}`
1096
+
1097
+ // Deletes up to `pruneChunkRows` rows matching `where` in key order and
1098
+ // returns whether it found any. No new rows match `where`, so a later call
1099
+ // resumes an interrupted one.
1100
+ const deleteChunk = Effect.fnUntraced(function*(
1101
+ tableSql: Statement.Identifier,
1102
+ key: string,
1103
+ where: Statement.Fragment
1104
+ ) {
1105
+ const keys = yield* query(sql`
1106
+ SELECT ${chunkTop} ${sql(key)} AS chunk_key FROM ${tableSql}
1107
+ WHERE ${where} ORDER BY ${sql(key)} ${chunkLimit}`)
1108
+ if (keys.length === 0) return false
1109
+ yield* sql`DELETE FROM ${tableSql} WHERE ${where} AND ${sql(key)} <= ${keys[keys.length - 1].chunk_key}`
1110
+ return true
1111
+ })
1112
+
1113
+ // Runs one chunk of a prune under the store row lock, if this sweeper
1114
+ // still holds the lease and the active activation hasn't changed.
1115
+ const pruneChunk = <A>(
1116
+ scope: string,
1117
+ now: number,
1118
+ lease: { readonly owner: string; readonly activeActivation: string },
1119
+ f: Effect.Effect<A, SqlError.SqlError>
1120
+ ) =>
1121
+ sql.withTransaction(Effect.gen(function*() {
1122
+ const store = yield* lockStore(scope)
1123
+ if (store.sweepLeaseOwner !== lease.owner || store.activeActivation !== lease.activeActivation) {
1124
+ return Option.none<A>()
1125
+ }
1126
+ const value = yield* f
1127
+ yield* sql`UPDATE ${storesSql} SET sweep_lease_until = ${now + sweepIntervalMillis} WHERE scope = ${scope}`
1128
+ return Option.some(value)
1129
+ }))
1130
+
1131
+ // Prunes what no activation still needs: the batch sets of generations
1132
+ // past their grace, and the originals at or below the lowest base still
1133
+ // kept. Deletes are idempotent, so a later sweep finishes one that died.
1134
+ const pruneStore = Effect.fnUntraced(function*(scope: string, now: number) {
1135
+ const { snapshotTableSql, tableSql } = scopeTables(scope)
1136
+ const owner = SecureRandom.uuidV4Unsafe()
1137
+ // Claim the store and mark what this prune removes before deleting anything.
1138
+ const plan = yield* sql.withTransaction(Effect.gen(function*() {
1139
+ const store = yield* lockStore(scope)
1140
+ if (store.nextSweepAt === undefined || store.nextSweepAt > now) return undefined
1141
+ if (store.sweepLeaseUntil !== undefined && store.sweepLeaseUntil > now) return undefined
1142
+ const activations = yield* selectActivations(scope)
1143
+ const isKept = (activation: ActivationRow) =>
1144
+ activation.id === store.activeActivation ||
1145
+ (activation.pruneStartedAt === undefined && (activation.prunableAt ?? Infinity) > now)
1146
+ const kept = activations.filter(isKept)
1147
+ const pruned = activations.filter((activation) => !isKept(activation))
1148
+ const starting = pruned.filter((activation) => activation.pruneStartedAt === undefined)
1149
+ for (const chunk of Arr.chunksOf(starting.map((activation) => activation.id), 200)) {
1150
+ yield* sql`UPDATE ${activationsSql} SET prune_started_at = ${now} WHERE ${sql.in("id", chunk)}`
1151
+ }
1152
+ const keptSets = new Set(kept.map((activation) => activation.batchSet))
1153
+ yield* sql`
1154
+ UPDATE ${storesSql} SET sweep_lease_until = ${now + sweepIntervalMillis}, sweep_lease_owner = ${owner}
1155
+ WHERE scope = ${scope}`
1156
+ return {
1157
+ lease: { owner, activeActivation: store.activeActivation },
1158
+ sets: Array.from(
1159
+ new Set(pruned.flatMap((activation) =>
1160
+ activation.batchSet === undefined || keptSets.has(activation.batchSet) ? [] : [activation.batchSet]
1161
+ ))
1162
+ ),
1163
+ keptBase: kept.reduce((base, activation) =>
1164
+ Math.min(base, activation.base), Infinity),
1165
+ prunedThrough: store.prunedThrough
1166
+ }
1167
+ }))
1168
+ if (plan === undefined) return
1169
+ for (const set of plan.sets) {
1170
+ while (true) {
1171
+ const deleted = yield* pruneChunk(
1172
+ scope,
1173
+ now,
1174
+ plan.lease,
1175
+ deleteChunk(snapshotTableSql, "batch_position", sql`batch_set = ${set}`)
1176
+ )
1177
+ if (Option.isNone(deleted)) return
1178
+ if (!deleted.value) break
1179
+ }
1180
+ }
1181
+ for (let start = plan.prunedThrough; start < plan.keptBase; start += pruneChunkSequences) {
1182
+ const end = Math.min(start + pruneChunkSequences, plan.keptBase)
1183
+ const held = yield* pruneChunk(
1184
+ scope,
1185
+ now,
1186
+ plan.lease,
1187
+ sql`DELETE FROM ${tableSql} WHERE last_sequence <= ${end}`.pipe(
1188
+ Effect.andThen(sql`
1189
+ UPDATE ${storesSql}
1190
+ SET pruned_through = CASE WHEN pruned_through < ${end} THEN ${end} ELSE pruned_through END
1191
+ WHERE scope = ${scope}`)
1192
+ )
1193
+ )
1194
+ if (Option.isNone(held)) return
1195
+ }
1196
+ yield* sql.withTransaction(Effect.gen(function*() {
1197
+ const store = yield* lockStore(scope)
1198
+ if (store.sweepLeaseOwner !== owner) return
1199
+ const [next] = yield* query(sql`
1200
+ SELECT MIN(prunable_at) AS next_sweep_at FROM ${activationsSql}
1201
+ WHERE scope = ${scope} AND id <> ${store.activeActivation} AND prune_started_at IS NULL`)
1202
+ yield* sql`
1203
+ UPDATE ${storesSql}
1204
+ SET next_sweep_at = ${next?.next_sweep_at ?? null}, sweep_lease_until = NULL, sweep_lease_owner = NULL
1205
+ WHERE scope = ${scope}`
1206
+ }))
1207
+ })
1208
+
1209
+ // Deletes a closed stage's rows matching `where`, one chunk per
1210
+ // transaction, each under the store row lock to keep the lock order.
1211
+ const deleteStaged = Effect.fnUntraced(function*(
1212
+ scope: string,
1213
+ tableSql: Statement.Identifier,
1214
+ key: string,
1215
+ where: Statement.Fragment
1216
+ ) {
1217
+ while (yield* sql.withTransaction(Effect.andThen(lockStore(scope), deleteChunk(tableSql, key, where)))) {
1218
+ // Each chunk commits on its own.
1219
+ }
1220
+ })
1221
+
1222
+ // Closes stages past their claim, then deletes closed stages. A closed
1223
+ // stage's staged rows are read by nothing, so they go first in short
1224
+ // chunks, and the stage row last.
1225
+ const sweepStages = Effect.fnUntraced(function*(now: number) {
1226
+ const expired = yield* query(sql`
1227
+ SELECT id, scope FROM ${stagesSql} WHERE state = ${"open"} AND expires_at <= ${now}`)
1228
+ for (const row of expired) {
1229
+ const scope = String(row.scope)
1230
+ const stageId = String(row.id)
1231
+ yield* sql.withTransaction(Effect.gen(function*() {
1232
+ yield* lockStore(scope)
1233
+ const stage = yield* selectStage(scope, stageId, true)
1234
+ if (stage?.state === "open" && stage.expiresAt <= now) {
1235
+ yield* closeStage(scope, stageId)
1236
+ }
1237
+ }))
1238
+ }
1239
+ const closed = yield* query(sql`SELECT id, scope FROM ${stagesSql} WHERE state = ${"closed"}`)
1240
+ for (const row of closed) {
1241
+ const scope = String(row.scope)
1242
+ const stageId = String(row.id)
1243
+ yield* deleteStaged(scope, scopeTables(scope).snapshotTableSql, "batch_position", sql`batch_set = ${stageId}`)
1244
+ yield* sql.withTransaction(
1245
+ Effect.andThen(lockStore(scope), sql`DELETE FROM ${stagesSql} WHERE id = ${stageId}`)
1246
+ )
1247
+ }
1248
+ })
1249
+
1250
+ // Only the newest bucket past the lag matters for the lag head.
1251
+ const sweepBuckets = Effect.fnUntraced(function*(now: number) {
1252
+ const rows = yield* query(sql`
1253
+ SELECT scope, MAX(bucket_hour) AS newest FROM ${bucketsSql}
1254
+ WHERE bucket_hour <= ${lagHour(now, lagMillis)}
1255
+ GROUP BY scope HAVING COUNT(*) > 1`)
1256
+ for (const row of rows) {
1257
+ yield* sql`DELETE FROM ${bucketsSql} WHERE scope = ${String(row.scope)} AND bucket_hour < ${Number(row.newest)}`
1258
+ }
1259
+ })
1260
+
1261
+ const logSweepFailure = (cause: Cause.Cause<unknown>) =>
1262
+ Effect.logWarning("Compaction sweep failed", cause).pipe(
1263
+ Effect.annotateLogs({ service: "SqlEventLogServerEncrypted" })
1264
+ )
1265
+ const sweep = Effect.gen(function*() {
1266
+ const now = yield* Clock.currentTimeMillis
1267
+ yield* Effect.catchCause(sweepStages(now), logSweepFailure)
1268
+ const due = yield* query(sql`SELECT scope FROM ${storesSql} WHERE next_sweep_at <= ${now}`)
1269
+ for (const row of due) {
1270
+ yield* Effect.catchCause(pruneStore(String(row.scope), now), logSweepFailure)
1271
+ }
1272
+ yield* Effect.catchCause(sweepBuckets(now), logSweepFailure)
1273
+ }).pipe(Effect.catchCause(logSweepFailure), withTracerDisabled)
1274
+ // A fixed schedule starts an overdue sweep straight away, so slow queries
1275
+ // don't push sweeps back.
1276
+ yield* Effect.forkScoped(
1277
+ Effect.delay(Effect.repeat(sweep, Schedule.fixed(config.sweepInterval)), config.sweepInterval)
1278
+ )
1279
+
265
1280
  return EventLogServerEncrypted.Storage.of({
266
1281
  getId: Effect.succeed(remoteId),
267
1282
  // Auto-increment sequences start at 1.
@@ -287,15 +1302,17 @@ export const makeStorage = (options?: {
287
1302
  function*(publicKey, storeId, batches) {
288
1303
  if (batches.length === 0) return []
289
1304
  const scopeKey = makeEncryptedScopeKey(publicKey, storeId)
290
- const { idsTableSql, pubsub, semaphore, tableSql } = yield* RcMap.get(resources, scopeKey)
1305
+ const { idsTableSql, pubsub, scope, semaphore, tableSql } = yield* RcMap.get(resources, scopeKey)
1306
+ const now = yield* Clock.currentTimeMillis
291
1307
 
292
1308
  const writeBatches = Effect.gen(function*() {
293
1309
  const stored: Array<EventLogEncryption.EncryptedRemoteBatch> = []
1310
+ // The store row lock makes sequence order commit order. The row's
1311
+ // `next_sequence` survives pruning, and `MAX(last_sequence)` covers
1312
+ // instances that predate the row.
1313
+ const store = yield* lockStore(scope)
294
1314
  // Read before inserting ids, which record the sequence they are stored at.
295
- const [last] = yield* sql<{ last_sequence: number | string | null }>`
296
- SELECT MAX(last_sequence) AS last_sequence FROM ${tableSql}
297
- `
298
- let nextSequence = Number(last?.last_sequence ?? 0) + 1
1315
+ let nextSequence = Math.max(store.nextSequence, (yield* maxLastSequence(tableSql)) + 1)
299
1316
  for (const batch of batches) {
300
1317
  // Inserting the ids decides whether the batch is new. SQL Server's
301
1318
  // MERGE also requires unique source ids.
@@ -307,7 +1324,8 @@ export const makeStorage = (options?: {
307
1324
  }
308
1325
  let inserted = 0
309
1326
  for (const chunk of Arr.chunksOf(ids.values(), 200)) {
310
- inserted += yield* insertNewIds(idsTableSql, chunk)
1327
+ // Stored ids keep their first sequence.
1328
+ inserted += yield* insertNew(idsTableSql, chunk, ["entry_id"])
311
1329
  }
312
1330
  if (inserted === 0) continue
313
1331
  const row: BatchRow = {
@@ -321,14 +1339,16 @@ export const makeStorage = (options?: {
321
1339
  stored.push({ sequence: nextSequence, ...batch })
322
1340
  nextSequence += batch.entryIds.length
323
1341
  }
1342
+ if (stored.length > 0) {
1343
+ yield* sql`UPDATE ${storesSql} SET next_sequence = ${nextSequence} WHERE scope = ${scope}`
1344
+ yield* upsertBucket(scope, Math.floor(now / hourMillis), nextSequence - 1)
1345
+ // Published after the outermost commit, as `rollback` publishes.
1346
+ yield* sql.afterCommit(Effect.asVoid(PubSub.publishAll(pubsub, stored)))
1347
+ }
324
1348
  return stored
325
1349
  })
326
1350
 
327
- return yield* semaphore.withPermits(1)(Effect.gen(function*() {
328
- const stored = yield* sql.withTransaction(writeBatches)
329
- yield* PubSub.publishAll(pubsub, stored)
330
- return stored
331
- }))
1351
+ return yield* semaphore.withPermits(1)(sql.withTransaction(writeBatches))
332
1352
  },
333
1353
  Effect.orDie,
334
1354
  Effect.scoped,
@@ -350,11 +1370,34 @@ export const makeStorage = (options?: {
350
1370
  Effect.scoped,
351
1371
  withTracerDisabled
352
1372
  ),
1373
+ // Reads the store row, the active set and the tail as separate
1374
+ // statements. A transaction would take the SQLite write lock on every
1375
+ // stream open, and set rows never change before they are pruned.
353
1376
  changes: Effect.fnUntraced(
354
1377
  function*(publicKey, storeId, startSequence) {
355
1378
  const scopeKey = makeEncryptedScopeKey(publicKey, storeId)
356
- const { pubsub, tableSql } = yield* RcMap.get(resources, scopeKey)
1379
+ const { pubsub, scope, snapshotTableSql, tableSql } = yield* RcMap.get(resources, scopeKey)
357
1380
  const subscription = yield* PubSub.subscribe(pubsub)
1381
+ const live = Stream.fromSubscription(subscription)
1382
+ const store = (yield* selectStore(scope))!
1383
+ // Below the base an active snapshot replaces the originals.
1384
+ if (store.activeBatchSet !== undefined && startSequence <= store.base) {
1385
+ const set = yield* sql`
1386
+ SELECT sequence, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
1387
+ WHERE batch_set = ${store.activeBatchSet} AND last_sequence >= ${startSequence}
1388
+ ORDER BY batch_position ASC
1389
+ `.pipe(Effect.flatMap(decodeBatches))
1390
+ // The final batch ends at the base, so only an empty set reads nothing.
1391
+ if (set.length === 0) {
1392
+ return Stream.die(new Error(`The active compaction snapshot of store ${storeId} has no batches`))
1393
+ }
1394
+ const tail = yield* sql`
1395
+ SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
1396
+ WHERE sequence > ${store.base}
1397
+ ORDER BY sequence ASC
1398
+ `.pipe(Effect.flatMap(decodeBatches))
1399
+ return Stream.fromArray([...set, ...tail]).pipe(Stream.concat(live))
1400
+ }
358
1401
  const initial = yield* sql`
359
1402
  SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
360
1403
  WHERE last_sequence >= ${startSequence}
@@ -362,15 +1405,76 @@ export const makeStorage = (options?: {
362
1405
  `.pipe(
363
1406
  Effect.flatMap(decodeBatches)
364
1407
  )
365
- return Stream.fromArray(initial).pipe(Stream.concat(Stream.fromSubscription(subscription)))
1408
+ return Stream.fromArray(initial).pipe(Stream.concat(live))
366
1409
  },
367
1410
  Effect.orDie,
368
1411
  withTracerDisabled,
369
1412
  Stream.unwrap
370
- )
1413
+ ),
1414
+ compaction
371
1415
  })
372
1416
  }).pipe(withTracerDisabled)
373
1417
 
1418
+ // Rows per statement when streaming compaction input or staged batches.
1419
+ const pageSize = 500
1420
+ // Sequences of originals deleted per prune chunk.
1421
+ const pruneChunkSequences = 10_000
1422
+ // Snapshot or staged rows deleted per prune chunk.
1423
+ const pruneChunkRows = 200
1424
+
1425
+ type StageState = "open" | "closed"
1426
+
1427
+ // SQL failures are defects for the compaction primitives.
1428
+ const dieOnSqlError = <A, E, R>(
1429
+ effect: Effect.Effect<A, E | SqlError.SqlError, R>
1430
+ ): Effect.Effect<A, Exclude<E, SqlError.SqlError>, R> =>
1431
+ Effect.catchIf(
1432
+ effect,
1433
+ (error): error is SqlError.SqlError => SqlError.isSqlError(error),
1434
+ (error) => Effect.die(error)
1435
+ ) as Effect.Effect<A, Exclude<E, SqlError.SqlError>, R>
1436
+
1437
+ const optionalNumber = (value: unknown) => value === null || value === undefined ? undefined : Number(value)
1438
+ const optionalString = (value: unknown) => value === null || value === undefined ? undefined : String(value)
1439
+
1440
+ const toStoreRow = (row: Record<string, unknown>) => ({
1441
+ nextSequence: Number(row.next_sequence),
1442
+ activeActivation: String(row.active_activation),
1443
+ activeBatchSet: optionalString(row.active_batch_set),
1444
+ base: Number(row.base_sequence),
1445
+ activeKeptCount: Number(row.active_kept_count),
1446
+ noOpAt: optionalNumber(row.no_op_at),
1447
+ noOpCount: Number(row.no_op_count),
1448
+ openStage: optionalString(row.open_stage),
1449
+ nextSweepAt: optionalNumber(row.next_sweep_at),
1450
+ sweepLeaseUntil: optionalNumber(row.sweep_lease_until),
1451
+ sweepLeaseOwner: optionalString(row.sweep_lease_owner),
1452
+ prunedThrough: Number(row.pruned_through)
1453
+ })
1454
+
1455
+ const toStageRow = (row: Record<string, unknown>) => ({
1456
+ id: String(row.id),
1457
+ scope: String(row.scope),
1458
+ state: String(row.state) as StageState,
1459
+ pinned: String(row.pinned),
1460
+ through: Number(row.through_sequence),
1461
+ expiresAt: Number(row.expires_at)
1462
+ })
1463
+
1464
+ type ActivationRow = ReturnType<typeof toActivationRow>
1465
+
1466
+ const toActivationRow = (row: Record<string, unknown>) => ({
1467
+ id: String(row.id),
1468
+ ordinal: Number(row.ordinal),
1469
+ batchSet: optionalString(row.batch_set),
1470
+ base: Number(row.base_sequence),
1471
+ keptCount: optionalNumber(row.kept_count),
1472
+ previous: optionalString(row.previous),
1473
+ supersededAt: optionalNumber(row.superseded_at),
1474
+ prunableAt: optionalNumber(row.prunable_at),
1475
+ pruneStartedAt: optionalNumber(row.prune_started_at)
1476
+ })
1477
+
374
1478
  type IdRow = {
375
1479
  readonly entry_id: Uint8Array
376
1480
  readonly sequence: number
@@ -395,6 +1499,14 @@ const concatEntryIds = (entryIds: ReadonlyArray<Uint8Array>): Uint8Array => {
395
1499
  return data
396
1500
  }
397
1501
 
1502
+ const toEntryIds = (data: Uint8Array): NonEmptyArray<EntryId> => {
1503
+ const entryIds: Array<EntryId> = []
1504
+ for (let offset = 0; offset < data.byteLength; offset += 16) {
1505
+ entryIds.push(data.subarray(offset, offset + 16) as EntryId)
1506
+ }
1507
+ return entryIds as NonEmptyArray<EntryId>
1508
+ }
1509
+
398
1510
  const EncryptedRemoteBatchSql = Schema.Struct({
399
1511
  sequence: Schema.Natural,
400
1512
  iv: Schema.Uint8Array,
@@ -414,18 +1526,12 @@ type SessionAuthBindingSql = Schema.Schema.Type<typeof SessionAuthBindingSql>
414
1526
  const decodeBatchRows = Schema.decodeUnknownEffect(Schema.Array(EncryptedRemoteBatchSql))
415
1527
  const decodeSessionAuthBindingRows = Schema.decodeUnknownEffect(Schema.Array(SessionAuthBindingSql))
416
1528
 
417
- const toEncryptedRemoteBatch = (row: EncryptedRemoteBatchSql): EventLogEncryption.EncryptedRemoteBatch => {
418
- const entryIds: Array<EntryId> = []
419
- for (let offset = 0; offset < row.entry_ids.byteLength; offset += 16) {
420
- entryIds.push(row.entry_ids.subarray(offset, offset + 16) as EntryId)
421
- }
422
- return {
423
- sequence: row.sequence,
424
- entryIds: entryIds as NonEmptyArray<EntryId>,
425
- iv: row.iv as Uint8Array<ArrayBuffer>,
426
- encryptedBatch: row.encrypted_batch as Uint8Array<ArrayBuffer>
427
- }
428
- }
1529
+ const toEncryptedRemoteBatch = (row: EncryptedRemoteBatchSql): EventLogEncryption.EncryptedRemoteBatch => ({
1530
+ sequence: row.sequence,
1531
+ entryIds: toEntryIds(row.entry_ids),
1532
+ iv: row.iv as Uint8Array<ArrayBuffer>,
1533
+ encryptedBatch: row.encrypted_batch as Uint8Array<ArrayBuffer>
1534
+ })
429
1535
 
430
1536
  const decodeBatches = (
431
1537
  rows: unknown
@@ -446,15 +1552,16 @@ const decodeSessionAuthBindings = (
446
1552
  export const layerStorage = (options?: {
447
1553
  readonly entryTablePrefix?: string
448
1554
  readonly remoteIdTable?: string
1555
+ readonly compaction?: EventLogServerEncrypted.CompactionOptions | undefined
449
1556
  }): Layer.Layer<
450
1557
  EventLogServerEncrypted.Storage,
451
1558
  SqlError.SqlError,
452
- SqlClient.SqlClient | EventLogEncryption.EventLogEncryption
1559
+ SqlClient.SqlClient | Digest.Digest
453
1560
  > => Layer.effect(EventLogServerEncrypted.Storage)(makeStorage(options))
454
1561
 
455
1562
  /**
456
1563
  * Provides SQL-backed encrypted server `Storage` and supplies the default Web
457
- * Crypto `EventLogEncryption` layer.
1564
+ * Crypto `Digest` layer.
458
1565
  *
459
1566
  * @stability unstable
460
1567
  * @category layers
@@ -463,9 +1570,10 @@ export const layerStorage = (options?: {
463
1570
  export const layerStorageSubtle = (options?: {
464
1571
  readonly entryTablePrefix?: string
465
1572
  readonly remoteIdTable?: string
1573
+ readonly compaction?: EventLogServerEncrypted.CompactionOptions | undefined
466
1574
  }): Layer.Layer<EventLogServerEncrypted.Storage, SqlError.SqlError, SqlClient.SqlClient> =>
467
1575
  layerStorage(options).pipe(
468
- Layer.provide(EventLogEncryption.layerSubtle)
1576
+ Layer.provide(Digest.layerWebCrypto)
469
1577
  )
470
1578
 
471
1579
  const makeEncryptedScopeKey = (