@effect-app/infra 4.0.0-beta.26 → 4.0.0-beta.260

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 (503) hide show
  1. package/CHANGELOG.md +1966 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +30 -11
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +35 -14
  6. package/dist/ClusterCosmos.d.ts +64 -0
  7. package/dist/ClusterCosmos.d.ts.map +1 -0
  8. package/dist/ClusterCosmos.js +487 -0
  9. package/dist/ClusterServiceBus.d.ts +67 -0
  10. package/dist/ClusterServiceBus.d.ts.map +1 -0
  11. package/dist/ClusterServiceBus.js +82 -0
  12. package/dist/ContextProvider.d.ts +34 -0
  13. package/dist/ContextProvider.d.ts.map +1 -0
  14. package/dist/ContextProvider.js +40 -0
  15. package/dist/Emailer/Sendgrid.d.ts +111 -147
  16. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  17. package/dist/Emailer/Sendgrid.js +24 -19
  18. package/dist/Emailer/fake.d.ts +2 -2
  19. package/dist/Emailer/fake.d.ts.map +1 -1
  20. package/dist/Emailer/fake.js +4 -4
  21. package/dist/MainFiberSet.d.ts +12 -9
  22. package/dist/MainFiberSet.d.ts.map +1 -1
  23. package/dist/MainFiberSet.js +10 -6
  24. package/dist/QueueMaker/SQLQueue.d.ts +8 -9
  25. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  26. package/dist/QueueMaker/SQLQueue.js +138 -120
  27. package/dist/QueueMaker/errors.d.ts +5 -3
  28. package/dist/QueueMaker/errors.d.ts.map +1 -1
  29. package/dist/QueueMaker/errors.js +4 -2
  30. package/dist/QueueMaker/memQueue.d.ts +10 -6
  31. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  32. package/dist/QueueMaker/memQueue.js +84 -68
  33. package/dist/QueueMaker/sbqueue.d.ts +9 -5
  34. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  35. package/dist/QueueMaker/sbqueue.js +60 -58
  36. package/dist/RequestFiberSet.d.ts +10 -7
  37. package/dist/RequestFiberSet.d.ts.map +1 -1
  38. package/dist/RequestFiberSet.js +13 -8
  39. package/dist/SQL/Model.d.ts +468 -0
  40. package/dist/SQL/Model.d.ts.map +1 -0
  41. package/dist/SQL/Model.js +469 -0
  42. package/dist/SQL.d.ts +2 -0
  43. package/dist/SQL.d.ts.map +1 -0
  44. package/dist/{adapters/SQL.js → SQL.js} +1 -1
  45. package/dist/ServiceBus.d.ts +61 -0
  46. package/dist/ServiceBus.d.ts.map +1 -0
  47. package/dist/ServiceBus.js +108 -0
  48. package/dist/Store/Cosmos/query.d.ts +15 -4
  49. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  50. package/dist/Store/Cosmos/query.js +179 -41
  51. package/dist/Store/Cosmos.d.ts +3 -3
  52. package/dist/Store/Cosmos.d.ts.map +1 -1
  53. package/dist/Store/Cosmos.js +344 -246
  54. package/dist/Store/Disk.d.ts +5 -5
  55. package/dist/Store/Disk.d.ts.map +1 -1
  56. package/dist/Store/Disk.js +78 -38
  57. package/dist/Store/Memory.d.ts +7 -10
  58. package/dist/Store/Memory.d.ts.map +1 -1
  59. package/dist/Store/Memory.js +326 -66
  60. package/dist/Store/SQL/Pg.d.ts +4 -0
  61. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  62. package/dist/Store/SQL/Pg.js +232 -0
  63. package/dist/Store/SQL/query.d.ts +49 -0
  64. package/dist/Store/SQL/query.d.ts.map +1 -0
  65. package/dist/Store/SQL/query.js +527 -0
  66. package/dist/Store/SQL.d.ts +21 -0
  67. package/dist/Store/SQL.d.ts.map +1 -0
  68. package/dist/Store/SQL.js +449 -0
  69. package/dist/Store/codeFilter.d.ts +5 -5
  70. package/dist/Store/codeFilter.d.ts.map +1 -1
  71. package/dist/Store/codeFilter.js +6 -3
  72. package/dist/Store/index.d.ts +7 -5
  73. package/dist/Store/index.d.ts.map +1 -1
  74. package/dist/Store/index.js +18 -5
  75. package/dist/Store/utils.d.ts +4 -3
  76. package/dist/Store/utils.d.ts.map +1 -1
  77. package/dist/Store/utils.js +5 -5
  78. package/dist/WorkflowEngineCosmos.d.ts +29 -0
  79. package/dist/WorkflowEngineCosmos.d.ts.map +1 -0
  80. package/dist/WorkflowEngineCosmos.js +521 -0
  81. package/dist/WorkflowEngineSqlite.d.ts +24 -0
  82. package/dist/WorkflowEngineSqlite.d.ts.map +1 -0
  83. package/dist/WorkflowEngineSqlite.js +550 -0
  84. package/dist/arbs.d.ts +2 -2
  85. package/dist/arbs.d.ts.map +1 -1
  86. package/dist/arbs.js +5 -3
  87. package/dist/codec.d.ts +5 -0
  88. package/dist/codec.d.ts.map +1 -0
  89. package/dist/codec.js +5 -0
  90. package/dist/cosmos-client.d.ts +16 -0
  91. package/dist/cosmos-client.d.ts.map +1 -0
  92. package/dist/cosmos-client.js +11 -0
  93. package/dist/errorReporter.d.ts +7 -5
  94. package/dist/errorReporter.d.ts.map +1 -1
  95. package/dist/errorReporter.js +23 -27
  96. package/dist/errors.d.ts +1 -1
  97. package/dist/fileUtil.d.ts +2 -2
  98. package/dist/fileUtil.d.ts.map +1 -1
  99. package/dist/fileUtil.js +2 -2
  100. package/dist/index.d.ts +3 -2
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +3 -2
  103. package/dist/internal/RequestContextMiddleware.d.ts +5 -0
  104. package/dist/internal/RequestContextMiddleware.d.ts.map +1 -0
  105. package/dist/internal/RequestContextMiddleware.js +45 -0
  106. package/dist/internal/auth.d.ts +53 -0
  107. package/dist/internal/auth.d.ts.map +1 -0
  108. package/dist/internal/auth.js +180 -0
  109. package/dist/internal/events.d.ts +11 -0
  110. package/dist/internal/events.d.ts.map +1 -0
  111. package/dist/internal/events.js +49 -0
  112. package/dist/internal/health.d.ts +3 -0
  113. package/dist/internal/health.d.ts.map +1 -0
  114. package/dist/internal/health.js +5 -0
  115. package/dist/layerUtils.d.ts +32 -0
  116. package/dist/layerUtils.d.ts.map +1 -0
  117. package/dist/layerUtils.js +17 -0
  118. package/dist/logger/jsonLogger.d.ts +2 -2
  119. package/dist/logger/jsonLogger.d.ts.map +1 -1
  120. package/dist/logger/jsonLogger.js +5 -3
  121. package/dist/logger/logFmtLogger.d.ts +2 -2
  122. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  123. package/dist/logger/logFmtLogger.js +3 -3
  124. package/dist/logger/shared.d.ts +3 -3
  125. package/dist/logger/shared.d.ts.map +1 -1
  126. package/dist/logger/shared.js +5 -5
  127. package/dist/logger.d.ts +1 -1
  128. package/dist/logger.d.ts.map +1 -1
  129. package/dist/memQueue.d.ts +15 -0
  130. package/dist/memQueue.d.ts.map +1 -0
  131. package/dist/memQueue.js +21 -0
  132. package/dist/middlewares.d.ts +10 -0
  133. package/dist/middlewares.d.ts.map +1 -0
  134. package/dist/{api/middlewares.js → middlewares.js} +1 -1
  135. package/dist/mongo-client.d.ts +11 -0
  136. package/dist/mongo-client.d.ts.map +1 -0
  137. package/dist/mongo-client.js +15 -0
  138. package/dist/otel.d.ts +75 -0
  139. package/dist/otel.d.ts.map +1 -0
  140. package/dist/otel.js +65 -0
  141. package/dist/rateLimit.d.ts +12 -4
  142. package/dist/rateLimit.d.ts.map +1 -1
  143. package/dist/rateLimit.js +7 -12
  144. package/dist/redis-client.d.ts +42 -0
  145. package/dist/redis-client.d.ts.map +1 -0
  146. package/dist/redis-client.js +98 -0
  147. package/dist/reportError.d.ts +4 -0
  148. package/dist/reportError.d.ts.map +1 -0
  149. package/dist/reportError.js +28 -0
  150. package/dist/routing/middleware/RouterMiddleware.d.ts +16 -0
  151. package/dist/routing/middleware/RouterMiddleware.d.ts.map +1 -0
  152. package/dist/{api/routing → routing}/middleware/RouterMiddleware.js +1 -1
  153. package/dist/routing/middleware/middleware.d.ts +48 -0
  154. package/dist/routing/middleware/middleware.d.ts.map +1 -0
  155. package/dist/routing/middleware/middleware.js +128 -0
  156. package/dist/routing/middleware.d.ts +3 -0
  157. package/dist/routing/middleware.d.ts.map +1 -0
  158. package/dist/{api/routing → routing}/middleware.js +1 -2
  159. package/dist/routing/schema/jwt.d.ts +4 -0
  160. package/dist/routing/schema/jwt.d.ts.map +1 -0
  161. package/dist/routing/schema/jwt.js +13 -0
  162. package/dist/routing/tsort.d.ts +8 -0
  163. package/dist/routing/tsort.d.ts.map +1 -0
  164. package/dist/routing/tsort.js +51 -0
  165. package/dist/routing/utils.d.ts +19 -0
  166. package/dist/routing/utils.d.ts.map +1 -0
  167. package/dist/routing/utils.js +45 -0
  168. package/dist/routing.d.ts +184 -0
  169. package/dist/routing.d.ts.map +1 -0
  170. package/dist/routing.js +236 -0
  171. package/dist/test.d.ts +3 -3
  172. package/dist/test.d.ts.map +1 -1
  173. package/dist/test.js +2 -2
  174. package/dist/util.d.ts +3 -0
  175. package/dist/util.d.ts.map +1 -0
  176. package/dist/util.js +14 -0
  177. package/dist/vitest.d.ts +1 -1
  178. package/docs/cluster-storage.md +26 -0
  179. package/docs/workflow-engine.md +262 -0
  180. package/examples/query.ts +47 -39
  181. package/package.json +31 -345
  182. package/run.sh +1 -0
  183. package/src/CUPS.ts +52 -13
  184. package/src/ClusterCosmos.ts +954 -0
  185. package/src/ClusterServiceBus.ts +242 -0
  186. package/src/{api/ContextProvider.ts → ContextProvider.ts} +19 -16
  187. package/src/Emailer/Sendgrid.ts +82 -59
  188. package/src/Emailer/fake.ts +3 -3
  189. package/src/MainFiberSet.ts +12 -10
  190. package/src/QueueMaker/SQLQueue.ts +153 -156
  191. package/src/QueueMaker/errors.ts +3 -1
  192. package/src/QueueMaker/memQueue.ts +113 -107
  193. package/src/QueueMaker/sbqueue.ts +78 -90
  194. package/src/RequestFiberSet.ts +13 -8
  195. package/src/{adapters/SQL → SQL}/Model.ts +42 -41
  196. package/src/ServiceBus.ts +219 -0
  197. package/src/Store/Cosmos/query.ts +216 -52
  198. package/src/Store/Cosmos.ts +493 -353
  199. package/src/Store/Disk.ts +109 -69
  200. package/src/Store/Memory.ts +365 -96
  201. package/src/Store/SQL/Pg.ts +363 -0
  202. package/src/Store/SQL/query.ts +603 -0
  203. package/src/Store/SQL.ts +735 -0
  204. package/src/Store/codeFilter.ts +8 -5
  205. package/src/Store/index.ts +21 -6
  206. package/src/Store/utils.ts +26 -24
  207. package/src/WorkflowEngineCosmos.ts +719 -0
  208. package/src/WorkflowEngineSqlite.ts +813 -0
  209. package/src/arbs.ts +5 -3
  210. package/src/{adapters/cosmos-client.ts → cosmos-client.ts} +5 -3
  211. package/src/errorReporter.ts +66 -76
  212. package/src/fileUtil.ts +1 -1
  213. package/src/index.ts +2 -1
  214. package/src/{api/internal → internal}/RequestContextMiddleware.ts +23 -6
  215. package/src/internal/auth.ts +272 -0
  216. package/src/{api/internal → internal}/events.ts +22 -13
  217. package/src/{api/layerUtils.ts → layerUtils.ts} +14 -10
  218. package/src/logger/jsonLogger.ts +4 -2
  219. package/src/logger/logFmtLogger.ts +2 -2
  220. package/src/logger/shared.ts +5 -4
  221. package/src/{adapters/memQueue.ts → memQueue.ts} +5 -4
  222. package/src/{adapters/mongo-client.ts → mongo-client.ts} +4 -2
  223. package/src/otel.ts +152 -0
  224. package/src/rateLimit.ts +34 -23
  225. package/src/{adapters/redis-client.ts → redis-client.ts} +7 -3
  226. package/src/{api/reportError.ts → reportError.ts} +3 -2
  227. package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +5 -4
  228. package/src/{api/routing → routing}/middleware/middleware.ts +62 -17
  229. package/src/routing/middleware.ts +4 -0
  230. package/src/{api/routing → routing}/schema/jwt.ts +2 -1
  231. package/src/{api/routing → routing}/utils.ts +2 -1
  232. package/src/routing.ts +768 -0
  233. package/src/test.ts +2 -2
  234. package/test/auth.test.ts +101 -0
  235. package/test/cluster-cosmos.test.ts +503 -0
  236. package/test/cluster-servicebus.test.ts +180 -0
  237. package/test/contextProvider.test.ts +15 -12
  238. package/test/controller.test.ts +28 -32
  239. package/test/cosmos-query.test.ts +159 -0
  240. package/test/dist/_check-agg-infer.test-d.d.ts +2 -0
  241. package/test/dist/_check-agg-infer.test-d.d.ts.map +1 -0
  242. package/test/dist/_check-agg-infer.test-d.js +19 -0
  243. package/test/dist/_check-proj-infer.test-d.d.ts +2 -0
  244. package/test/dist/_check-proj-infer.test-d.d.ts.map +1 -0
  245. package/test/dist/_check-proj-infer.test-d.js +16 -0
  246. package/test/dist/_check-tighten.test-d.d.ts +2 -0
  247. package/test/dist/_check-tighten.test-d.d.ts.map +1 -0
  248. package/test/dist/_check-tighten.test-d.js +21 -0
  249. package/test/dist/auth.test.d.ts.map +1 -0
  250. package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
  251. package/test/dist/cluster-servicebus.test.d.ts.map +1 -0
  252. package/test/dist/contextProvider.test.d.ts.map +1 -1
  253. package/test/dist/controller.test.d.ts.map +1 -1
  254. package/test/dist/cosmos-query.test.d.ts.map +1 -0
  255. package/test/dist/date-query.test.d.ts.map +1 -0
  256. package/test/dist/fixtures.d.ts +30 -12
  257. package/test/dist/fixtures.d.ts.map +1 -1
  258. package/test/dist/fixtures.js +17 -10
  259. package/test/dist/query.test.d.ts.map +1 -1
  260. package/test/dist/rawQuery.test.d.ts.map +1 -1
  261. package/test/dist/repository-ext.test.d.ts.map +1 -0
  262. package/test/dist/requires.test.d.ts.map +1 -1
  263. package/test/dist/router-generator.test.d.ts.map +1 -0
  264. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  265. package/test/dist/rpc-context-map-streaming.test.d.ts.map +1 -0
  266. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  267. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  268. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  269. package/test/dist/sql-store.test.d.ts.map +1 -0
  270. package/test/dist/workflow-engine-cosmos.test.d.ts.map +1 -0
  271. package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
  272. package/test/fixtures.ts +16 -9
  273. package/test/layerUtils.test.ts +2 -2
  274. package/test/query.test.ts +905 -40
  275. package/test/rawQuery.test.ts +340 -22
  276. package/test/repository-ext.test.ts +62 -0
  277. package/test/requires.test.ts +10 -5
  278. package/test/router-generator.test.ts +187 -0
  279. package/test/routing-interruptibility.test.ts +66 -0
  280. package/test/rpc-context-map-streaming.test.ts +262 -0
  281. package/test/rpc-e2e-invalidation.test.ts +256 -0
  282. package/test/rpc-multi-middleware.test.ts +85 -10
  283. package/test/rpc-stream-fullstack.test.ts +304 -0
  284. package/test/sql-store.test.ts +1711 -0
  285. package/test/validateSample.test.ts +26 -14
  286. package/test/workflow-engine-cosmos.test.ts +354 -0
  287. package/test/workflow-engine-sqlite.test.ts +299 -0
  288. package/tsconfig.examples.json +1 -1
  289. package/tsconfig.json +2 -1
  290. package/tsconfig.json.bak +2 -2
  291. package/tsconfig.src.json +35 -35
  292. package/tsconfig.test.json +2 -2
  293. package/dist/Emailer/service.d.ts +0 -55
  294. package/dist/Emailer/service.d.ts.map +0 -1
  295. package/dist/Emailer/service.js +0 -6
  296. package/dist/Emailer.d.ts +0 -2
  297. package/dist/Emailer.d.ts.map +0 -1
  298. package/dist/Emailer.js +0 -2
  299. package/dist/Model/Repository/ext.d.ts +0 -41
  300. package/dist/Model/Repository/ext.d.ts.map +0 -1
  301. package/dist/Model/Repository/ext.js +0 -65
  302. package/dist/Model/Repository/internal/internal.d.ts +0 -59
  303. package/dist/Model/Repository/internal/internal.d.ts.map +0 -1
  304. package/dist/Model/Repository/internal/internal.js +0 -316
  305. package/dist/Model/Repository/legacy.d.ts +0 -19
  306. package/dist/Model/Repository/legacy.d.ts.map +0 -1
  307. package/dist/Model/Repository/legacy.js +0 -2
  308. package/dist/Model/Repository/makeRepo.d.ts +0 -49
  309. package/dist/Model/Repository/makeRepo.d.ts.map +0 -1
  310. package/dist/Model/Repository/makeRepo.js +0 -24
  311. package/dist/Model/Repository/service.d.ts +0 -89
  312. package/dist/Model/Repository/service.d.ts.map +0 -1
  313. package/dist/Model/Repository/service.js +0 -2
  314. package/dist/Model/Repository/validation.d.ts +0 -42
  315. package/dist/Model/Repository/validation.d.ts.map +0 -1
  316. package/dist/Model/Repository/validation.js +0 -32
  317. package/dist/Model/Repository.d.ts +0 -6
  318. package/dist/Model/Repository.d.ts.map +0 -1
  319. package/dist/Model/Repository.js +0 -6
  320. package/dist/Model/dsl.d.ts +0 -32
  321. package/dist/Model/dsl.d.ts.map +0 -1
  322. package/dist/Model/dsl.js +0 -44
  323. package/dist/Model/filter/filterApi.d.ts +0 -30
  324. package/dist/Model/filter/filterApi.d.ts.map +0 -1
  325. package/dist/Model/filter/filterApi.js +0 -2
  326. package/dist/Model/filter/types/errors.d.ts +0 -29
  327. package/dist/Model/filter/types/errors.d.ts.map +0 -1
  328. package/dist/Model/filter/types/errors.js +0 -2
  329. package/dist/Model/filter/types/fields.d.ts +0 -15
  330. package/dist/Model/filter/types/fields.d.ts.map +0 -1
  331. package/dist/Model/filter/types/fields.js +0 -2
  332. package/dist/Model/filter/types/path/common.d.ts +0 -316
  333. package/dist/Model/filter/types/path/common.d.ts.map +0 -1
  334. package/dist/Model/filter/types/path/common.js +0 -2
  335. package/dist/Model/filter/types/path/eager.d.ts +0 -95
  336. package/dist/Model/filter/types/path/eager.d.ts.map +0 -1
  337. package/dist/Model/filter/types/path/eager.js +0 -31
  338. package/dist/Model/filter/types/path/index.d.ts +0 -4
  339. package/dist/Model/filter/types/path/index.d.ts.map +0 -1
  340. package/dist/Model/filter/types/path/index.js +0 -3
  341. package/dist/Model/filter/types/utils.d.ts +0 -79
  342. package/dist/Model/filter/types/utils.d.ts.map +0 -1
  343. package/dist/Model/filter/types/utils.js +0 -2
  344. package/dist/Model/filter/types/validator.d.ts +0 -30
  345. package/dist/Model/filter/types/validator.d.ts.map +0 -1
  346. package/dist/Model/filter/types/validator.js +0 -2
  347. package/dist/Model/filter/types.d.ts +0 -5
  348. package/dist/Model/filter/types.d.ts.map +0 -1
  349. package/dist/Model/filter/types.js +0 -7
  350. package/dist/Model/query/dsl.d.ts +0 -248
  351. package/dist/Model/query/dsl.d.ts.map +0 -1
  352. package/dist/Model/query/dsl.js +0 -104
  353. package/dist/Model/query/new-kid-interpreter.d.ts +0 -28
  354. package/dist/Model/query/new-kid-interpreter.d.ts.map +0 -1
  355. package/dist/Model/query/new-kid-interpreter.js +0 -165
  356. package/dist/Model/query.d.ts +0 -15
  357. package/dist/Model/query.d.ts.map +0 -1
  358. package/dist/Model/query.js +0 -3
  359. package/dist/Model.d.ts +0 -4
  360. package/dist/Model.d.ts.map +0 -1
  361. package/dist/Model.js +0 -4
  362. package/dist/Operations.d.ts +0 -55
  363. package/dist/Operations.d.ts.map +0 -1
  364. package/dist/Operations.js +0 -102
  365. package/dist/OperationsRepo.d.ts +0 -41
  366. package/dist/OperationsRepo.d.ts.map +0 -1
  367. package/dist/OperationsRepo.js +0 -14
  368. package/dist/QueueMaker/service.d.ts +0 -11
  369. package/dist/QueueMaker/service.d.ts.map +0 -1
  370. package/dist/QueueMaker/service.js +0 -4
  371. package/dist/RequestContext.d.ts +0 -63
  372. package/dist/RequestContext.d.ts.map +0 -1
  373. package/dist/RequestContext.js +0 -49
  374. package/dist/Store/ContextMapContainer.d.ts +0 -14
  375. package/dist/Store/ContextMapContainer.d.ts.map +0 -1
  376. package/dist/Store/ContextMapContainer.js +0 -16
  377. package/dist/Store/service.d.ts +0 -108
  378. package/dist/Store/service.d.ts.map +0 -1
  379. package/dist/Store/service.js +0 -71
  380. package/dist/Store.d.ts +0 -2
  381. package/dist/Store.d.ts.map +0 -1
  382. package/dist/Store.js +0 -2
  383. package/dist/adapters/SQL/Model.d.ts +0 -479
  384. package/dist/adapters/SQL/Model.d.ts.map +0 -1
  385. package/dist/adapters/SQL/Model.js +0 -478
  386. package/dist/adapters/SQL.d.ts +0 -2
  387. package/dist/adapters/SQL.d.ts.map +0 -1
  388. package/dist/adapters/ServiceBus.d.ts +0 -58
  389. package/dist/adapters/ServiceBus.d.ts.map +0 -1
  390. package/dist/adapters/ServiceBus.js +0 -99
  391. package/dist/adapters/cosmos-client.d.ts +0 -14
  392. package/dist/adapters/cosmos-client.d.ts.map +0 -1
  393. package/dist/adapters/cosmos-client.js +0 -9
  394. package/dist/adapters/index.d.ts +0 -2
  395. package/dist/adapters/index.d.ts.map +0 -1
  396. package/dist/adapters/index.js +0 -2
  397. package/dist/adapters/logger.d.ts +0 -9
  398. package/dist/adapters/logger.d.ts.map +0 -1
  399. package/dist/adapters/logger.js +0 -3
  400. package/dist/adapters/memQueue.d.ts +0 -13
  401. package/dist/adapters/memQueue.d.ts.map +0 -1
  402. package/dist/adapters/memQueue.js +0 -20
  403. package/dist/adapters/mongo-client.d.ts +0 -10
  404. package/dist/adapters/mongo-client.d.ts.map +0 -1
  405. package/dist/adapters/mongo-client.js +0 -13
  406. package/dist/adapters/redis-client.d.ts +0 -39
  407. package/dist/adapters/redis-client.d.ts.map +0 -1
  408. package/dist/adapters/redis-client.js +0 -94
  409. package/dist/api/ContextProvider.d.ts +0 -31
  410. package/dist/api/ContextProvider.d.ts.map +0 -1
  411. package/dist/api/ContextProvider.js +0 -38
  412. package/dist/api/codec.d.ts +0 -5
  413. package/dist/api/codec.d.ts.map +0 -1
  414. package/dist/api/codec.js +0 -5
  415. package/dist/api/internal/RequestContextMiddleware.d.ts +0 -5
  416. package/dist/api/internal/RequestContextMiddleware.d.ts.map +0 -1
  417. package/dist/api/internal/RequestContextMiddleware.js +0 -35
  418. package/dist/api/internal/auth.d.ts +0 -15
  419. package/dist/api/internal/auth.d.ts.map +0 -1
  420. package/dist/api/internal/auth.js +0 -47
  421. package/dist/api/internal/events.d.ts +0 -9
  422. package/dist/api/internal/events.d.ts.map +0 -1
  423. package/dist/api/internal/events.js +0 -42
  424. package/dist/api/internal/health.d.ts +0 -3
  425. package/dist/api/internal/health.d.ts.map +0 -1
  426. package/dist/api/internal/health.js +0 -5
  427. package/dist/api/layerUtils.d.ts +0 -24
  428. package/dist/api/layerUtils.d.ts.map +0 -1
  429. package/dist/api/layerUtils.js +0 -16
  430. package/dist/api/middlewares.d.ts +0 -10
  431. package/dist/api/middlewares.d.ts.map +0 -1
  432. package/dist/api/reportError.d.ts +0 -4
  433. package/dist/api/reportError.d.ts.map +0 -1
  434. package/dist/api/reportError.js +0 -27
  435. package/dist/api/routing/middleware/RouterMiddleware.d.ts +0 -15
  436. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +0 -1
  437. package/dist/api/routing/middleware/middleware.d.ts +0 -9
  438. package/dist/api/routing/middleware/middleware.d.ts.map +0 -1
  439. package/dist/api/routing/middleware/middleware.js +0 -92
  440. package/dist/api/routing/middleware.d.ts +0 -4
  441. package/dist/api/routing/middleware.d.ts.map +0 -1
  442. package/dist/api/routing/schema/jwt.d.ts +0 -4
  443. package/dist/api/routing/schema/jwt.d.ts.map +0 -1
  444. package/dist/api/routing/schema/jwt.js +0 -12
  445. package/dist/api/routing/tsort.d.ts +0 -8
  446. package/dist/api/routing/tsort.d.ts.map +0 -1
  447. package/dist/api/routing/tsort.js +0 -51
  448. package/dist/api/routing/utils.d.ts +0 -19
  449. package/dist/api/routing/utils.d.ts.map +0 -1
  450. package/dist/api/routing/utils.js +0 -44
  451. package/dist/api/routing.d.ts +0 -138
  452. package/dist/api/routing.d.ts.map +0 -1
  453. package/dist/api/routing.js +0 -166
  454. package/dist/api/setupRequest.d.ts +0 -12
  455. package/dist/api/setupRequest.d.ts.map +0 -1
  456. package/dist/api/setupRequest.js +0 -44
  457. package/dist/api/util.d.ts +0 -3
  458. package/dist/api/util.d.ts.map +0 -1
  459. package/dist/api/util.js +0 -14
  460. package/eslint.config.mjs +0 -24
  461. package/src/Emailer/service.ts +0 -52
  462. package/src/Emailer.ts +0 -1
  463. package/src/Model/Repository/ext.ts +0 -283
  464. package/src/Model/Repository/internal/internal.ts +0 -577
  465. package/src/Model/Repository/legacy.ts +0 -27
  466. package/src/Model/Repository/makeRepo.ts +0 -139
  467. package/src/Model/Repository/service.ts +0 -627
  468. package/src/Model/Repository/validation.ts +0 -31
  469. package/src/Model/Repository.ts +0 -5
  470. package/src/Model/dsl.ts +0 -128
  471. package/src/Model/filter/filterApi.ts +0 -60
  472. package/src/Model/filter/types/errors.ts +0 -47
  473. package/src/Model/filter/types/fields.ts +0 -50
  474. package/src/Model/filter/types/path/common.ts +0 -404
  475. package/src/Model/filter/types/path/eager.ts +0 -298
  476. package/src/Model/filter/types/path/index.ts +0 -4
  477. package/src/Model/filter/types/utils.ts +0 -128
  478. package/src/Model/filter/types/validator.ts +0 -46
  479. package/src/Model/filter/types.ts +0 -6
  480. package/src/Model/query/dsl.ts +0 -2110
  481. package/src/Model/query/new-kid-interpreter.ts +0 -210
  482. package/src/Model/query.ts +0 -13
  483. package/src/Model.ts +0 -3
  484. package/src/Operations.ts +0 -235
  485. package/src/OperationsRepo.ts +0 -16
  486. package/src/QueueMaker/service.ts +0 -17
  487. package/src/RequestContext.ts +0 -63
  488. package/src/Store/ContextMapContainer.ts +0 -20
  489. package/src/Store/service.ts +0 -184
  490. package/src/Store.ts +0 -1
  491. package/src/adapters/ServiceBus.ts +0 -209
  492. package/src/adapters/index.ts +0 -0
  493. package/src/adapters/logger.ts +0 -3
  494. package/src/api/internal/auth.ts +0 -68
  495. package/src/api/routing/middleware.ts +0 -6
  496. package/src/api/routing.ts +0 -598
  497. package/src/api/setupRequest.ts +0 -84
  498. /package/src/{adapters/SQL.ts → SQL.ts} +0 -0
  499. /package/src/{api/codec.ts → codec.ts} +0 -0
  500. /package/src/{api/internal → internal}/health.ts +0 -0
  501. /package/src/{api/middlewares.ts → middlewares.ts} +0 -0
  502. /package/src/{api/routing → routing}/tsort.ts +0 -0
  503. /package/src/{api/util.ts → util.ts} +0 -0
@@ -0,0 +1,813 @@
1
+ /**
2
+ * SQLite backed {@link WorkflowEngine} implementation.
3
+ *
4
+ * Persists workflow state across four tables:
5
+ * - `workflow_exec` — one row per execution; tracks status, lease, etag
6
+ * - `workflow_activity` — recorded activity results keyed by (exec, name, attempt)
7
+ * - `workflow_deferred` — durable deferred completions keyed by (exec, name)
8
+ * - `workflow_clock` — scheduled clocks with `fire_at`
9
+ *
10
+ * Atomicity: multi-statement operations are wrapped in `sql.withTransaction`
11
+ * (BEGIN/COMMIT) so concurrent writers do not observe partial state.
12
+ *
13
+ * Optimistic concurrency:
14
+ * - exec state transitions use `UPDATE ... WHERE etag = ? RETURNING etag`;
15
+ * a zero-row result is an `OptimisticConcurrencyException`.
16
+ * - activity / deferred / clock inserts use `INSERT ... ON CONFLICT DO
17
+ * NOTHING RETURNING ...` for first-writer-wins semantics across drivers.
18
+ *
19
+ * Durability — everything that crosses the storage boundary is round-tripped
20
+ * through schema codecs (`S.fromJsonString(S.toCodecJson(...))`), exactly like
21
+ * the cluster engine:
22
+ *
23
+ * - The workflow payload and the top-level `Workflow.Result` are encoded with
24
+ * the workflow's own `payloadSchema` / `successSchema` / `errorSchema`, so
25
+ * typed values (dates, branded ids, schema classes) survive a restart.
26
+ * - Activity results flow through the engine already encoded, so they are
27
+ * persisted with an opaque `Workflow.Result({ success: AnyOrVoid, error:
28
+ * AnyOrVoid })` codec — same trick the cluster `ActivityRpc` uses.
29
+ * - Durable-deferred exits use an opaque `Exit` codec.
30
+ *
31
+ * Crash recovery: each driver holds a time-bound lease on the exec row,
32
+ * renewed by a heartbeat fiber. A scope-bound recovery poller re-drives any
33
+ * exec whose lease has lapsed. A clock poller fires due clocks even when
34
+ * the in-process timer is missing (e.g. after a restart).
35
+ */
36
+ import * as Effect from "effect-app/Effect"
37
+ import * as Layer from "effect-app/Layer"
38
+ import * as Option from "effect-app/Option"
39
+ import * as S from "effect-app/Schema"
40
+ import * as Duration from "effect/Duration"
41
+ import * as Exit from "effect/Exit"
42
+ import * as Fiber from "effect/Fiber"
43
+ import * as FiberMap from "effect/FiberMap"
44
+ import * as Schedule from "effect/Schedule"
45
+ import type * as Scope from "effect/Scope"
46
+ import { SqlClient } from "effect/unstable/sql"
47
+ import * as Workflow from "effect/unstable/workflow/Workflow"
48
+ import { type Encoded, makeUnsafe, WorkflowEngine, WorkflowInstance } from "effect/unstable/workflow/WorkflowEngine"
49
+ import { randomUUID } from "node:crypto"
50
+ import { OptimisticConcurrencyException } from "./errors.js"
51
+ import { annotateDb } from "./otel.js"
52
+
53
+ export interface WorkflowEngineSqliteConfig {
54
+ /** Optional prefix for table names (e.g. `tenant_`). */
55
+ readonly prefix?: string
56
+ /** Lease duration before a claim is considered stale. Default 30s. */
57
+ readonly leaseTtl?: Duration.Duration
58
+ /** Renewal cadence — should be < leaseTtl. Default 10s. */
59
+ readonly heartbeatInterval?: Duration.Duration
60
+ /** Cadence for scanning stale leases. Default 15s. Set to `Duration.zero` to disable. */
61
+ readonly recoveryInterval?: Duration.Duration
62
+ /** Cadence for scanning due clocks. Default 5s. Set to `Duration.zero` to disable. */
63
+ readonly clockPollInterval?: Duration.Duration
64
+ /** Stable worker identity; defaults to a random UUID per process. */
65
+ readonly workerId?: string
66
+ }
67
+
68
+ type ExecStatus = "running" | "complete" | "interrupted"
69
+
70
+ interface ExecRow {
71
+ readonly execution_id: string
72
+ readonly workflow_name: string
73
+ /** Schema-encoded (JSON string) workflow payload. */
74
+ readonly payload: string
75
+ readonly parent: string | null
76
+ readonly status: ExecStatus
77
+ readonly suspended: number
78
+ readonly interrupted: number
79
+ /** Schema-encoded (JSON string) top-level `Workflow.Result`, set on completion. */
80
+ readonly completed_result: string | null
81
+ readonly worker: string | null
82
+ readonly lease_expires_at: number | null
83
+ readonly etag: string
84
+ }
85
+
86
+ interface ExecState {
87
+ readonly executionId: string
88
+ readonly workflowName: string
89
+ readonly payload: string
90
+ readonly parent: string | undefined
91
+ readonly status: ExecStatus
92
+ readonly suspended: boolean
93
+ readonly interrupted: boolean
94
+ readonly completedResult: string | undefined
95
+ readonly worker: string | undefined
96
+ readonly leaseExpiresAt: number | undefined
97
+ readonly etag: string
98
+ }
99
+
100
+ const parseExec = (row: ExecRow): ExecState => ({
101
+ executionId: row.execution_id,
102
+ workflowName: row.workflow_name,
103
+ payload: row.payload,
104
+ parent: row.parent ?? undefined,
105
+ status: row.status,
106
+ suspended: row.suspended !== 0,
107
+ interrupted: row.interrupted !== 0,
108
+ completedResult: row.completed_result ?? undefined,
109
+ worker: row.worker ?? undefined,
110
+ leaseExpiresAt: row.lease_expires_at ?? undefined,
111
+ etag: row.etag
112
+ })
113
+
114
+ // --- Storage codecs ---------------------------------------------------------
115
+ // Values flowing through the engine's activity / deferred boundary are already
116
+ // schema-encoded, so the structure is round-tripped while the payload stays
117
+ // opaque (mirrors the cluster engine's `AnyOrVoid` usage).
118
+ const AnyOrVoid = S.Union([S.Any, S.Void])
119
+ const ActivityResultCodec = S.fromJsonString(S.toCodecJson(Workflow.Result({ success: AnyOrVoid, error: AnyOrVoid })))
120
+ const DeferredExitCodec = S.fromJsonString(S.toCodecJson(S.Exit(AnyOrVoid, AnyOrVoid, S.Defect)))
121
+
122
+ const encodeActivityResult = (r: Workflow.Result<unknown, unknown>) =>
123
+ Effect.orDie(S.encodeEffect(ActivityResultCodec)(r))
124
+ const decodeActivityResult = (s: string) => Effect.orDie(S.decodeEffect(ActivityResultCodec)(s))
125
+ const encodeDeferredExit = (e: Exit.Exit<unknown, unknown>) => Effect.orDie(S.encodeEffect(DeferredExitCodec)(e))
126
+ const decodeDeferredExit = (s: string) => Effect.orDie(S.decodeEffect(DeferredExitCodec)(s))
127
+
128
+ const makeSqliteWorkflowEngine = Effect.fnUntraced(function*(cfg: WorkflowEngineSqliteConfig) {
129
+ const sql = yield* SqlClient.SqlClient
130
+ const scope = yield* Effect.scope
131
+ const prefix = cfg.prefix ?? ""
132
+ const execTable = `${prefix}workflow_exec`
133
+ const activityTable = `${prefix}workflow_activity`
134
+ const deferredTable = `${prefix}workflow_deferred`
135
+ const clockTable = `${prefix}workflow_clock`
136
+
137
+ const workerId = cfg.workerId ?? randomUUID()
138
+ const leaseTtl = cfg.leaseTtl ?? Duration.seconds(30)
139
+ const heartbeatInterval = cfg.heartbeatInterval ?? Duration.seconds(10)
140
+ const recoveryInterval = cfg.recoveryInterval ?? Duration.seconds(15)
141
+ const clockPollInterval = cfg.clockPollInterval ?? Duration.seconds(5)
142
+
143
+ const annotate = (operation: string, executionId?: string) =>
144
+ annotateDb({
145
+ operation,
146
+ system: "sqlite",
147
+ collection: execTable,
148
+ entity: "workflow",
149
+ extra: executionId !== undefined ? { "app.entity.id": executionId } : undefined
150
+ })
151
+
152
+ const exec = (query: string, params: ReadonlyArray<unknown> = []) =>
153
+ sql.unsafe(query, params as Array<any>).pipe(Effect.orDie)
154
+
155
+ // --- Schema -----------------------------------------------------------
156
+
157
+ yield* exec(
158
+ `CREATE TABLE IF NOT EXISTS "${execTable}" (
159
+ execution_id TEXT PRIMARY KEY,
160
+ workflow_name TEXT NOT NULL,
161
+ payload TEXT NOT NULL,
162
+ parent TEXT,
163
+ status TEXT NOT NULL,
164
+ suspended INTEGER NOT NULL DEFAULT 0,
165
+ interrupted INTEGER NOT NULL DEFAULT 0,
166
+ completed_result TEXT,
167
+ worker TEXT,
168
+ lease_expires_at INTEGER,
169
+ etag TEXT NOT NULL
170
+ )`
171
+ )
172
+ yield* exec(
173
+ `CREATE INDEX IF NOT EXISTS "${execTable}_recovery" ON "${execTable}" (status, lease_expires_at)`
174
+ )
175
+ yield* exec(
176
+ `CREATE TABLE IF NOT EXISTS "${activityTable}" (
177
+ execution_id TEXT NOT NULL,
178
+ name TEXT NOT NULL,
179
+ attempt INTEGER NOT NULL,
180
+ result TEXT NOT NULL,
181
+ PRIMARY KEY (execution_id, name, attempt)
182
+ )`
183
+ )
184
+ yield* exec(
185
+ `CREATE TABLE IF NOT EXISTS "${deferredTable}" (
186
+ execution_id TEXT NOT NULL,
187
+ name TEXT NOT NULL,
188
+ exit TEXT NOT NULL,
189
+ PRIMARY KEY (execution_id, name)
190
+ )`
191
+ )
192
+ yield* exec(
193
+ `CREATE TABLE IF NOT EXISTS "${clockTable}" (
194
+ execution_id TEXT NOT NULL,
195
+ name TEXT NOT NULL,
196
+ workflow_name TEXT NOT NULL,
197
+ deferred_name TEXT NOT NULL,
198
+ fire_at INTEGER NOT NULL,
199
+ PRIMARY KEY (execution_id, name)
200
+ )`
201
+ )
202
+ yield* exec(
203
+ `CREATE INDEX IF NOT EXISTS "${clockTable}_due" ON "${clockTable}" (fire_at)`
204
+ )
205
+
206
+ // --- In-process bookkeeping -------------------------------------------
207
+
208
+ type Registered = {
209
+ readonly workflow: Workflow.Any
210
+ readonly execute: (
211
+ payload: object,
212
+ executionId: string
213
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
214
+ readonly scope: Scope.Scope
215
+ }
216
+ const workflows = new Map<string, Registered>()
217
+
218
+ type LocalExec = {
219
+ instance: WorkflowInstance["Service"]
220
+ fiber: Fiber.Fiber<Workflow.Result<unknown, unknown>> | undefined
221
+ parent: string | undefined
222
+ }
223
+ const locals = new Map<string, LocalExec>()
224
+ const clocks = yield* FiberMap.make<string>()
225
+
226
+ // Per-workflow codecs for the typed payload + top-level result. Cached by
227
+ // workflow name; derived from the workflow's own schemas so typed values
228
+ // (dates, branded ids, schema classes) survive the storage round-trip.
229
+ const makePayloadCodec = (workflow: Workflow.Any) => S.fromJsonString(S.toCodecJson(workflow.payloadSchema))
230
+ const payloadCodecCache = new Map<string, ReturnType<typeof makePayloadCodec>>()
231
+ const payloadCodecFor = (workflow: Workflow.Any) => {
232
+ let c = payloadCodecCache.get(workflow.name)
233
+ if (!c) {
234
+ c = makePayloadCodec(workflow)
235
+ payloadCodecCache.set(workflow.name, c)
236
+ }
237
+ return c
238
+ }
239
+
240
+ const makeResultCodec = (workflow: Workflow.Any) =>
241
+ S.fromJsonString(S.toCodecJson(Workflow.Result({ success: workflow.successSchema, error: workflow.errorSchema })))
242
+ const resultCodecCache = new Map<string, ReturnType<typeof makeResultCodec>>()
243
+ const resultCodecFor = (workflow: Workflow.Any) => {
244
+ let c = resultCodecCache.get(workflow.name)
245
+ if (!c) {
246
+ c = makeResultCodec(workflow)
247
+ resultCodecCache.set(workflow.name, c)
248
+ }
249
+ return c
250
+ }
251
+
252
+ const encodePayload = (workflow: Workflow.Any, payload: object) =>
253
+ Effect.orDie(S.encodeEffect(payloadCodecFor(workflow))(payload)) as Effect.Effect<string>
254
+ const decodePayload = (workflow: Workflow.Any, s: string) =>
255
+ Effect.orDie(S.decodeEffect(payloadCodecFor(workflow))(s)) as Effect.Effect<object>
256
+ const encodeResult = (workflow: Workflow.Any, r: Workflow.Result<unknown, unknown>) =>
257
+ Effect.orDie(S.encodeEffect(resultCodecFor(workflow))(r)) as Effect.Effect<string>
258
+ const decodeResult = (workflow: Workflow.Any, s: string) =>
259
+ Effect.orDie(S.decodeEffect(resultCodecFor(workflow))(s)) as Effect.Effect<Workflow.Result<unknown, unknown>>
260
+
261
+ // --- Core SQL operations ----------------------------------------------
262
+
263
+ const readExec = (executionId: string): Effect.Effect<Option.Option<ExecState>> =>
264
+ exec(
265
+ `SELECT * FROM "${execTable}" WHERE execution_id = ?`,
266
+ [executionId]
267
+ )
268
+ .pipe(
269
+ Effect.map((rows) => {
270
+ const r = (rows as ReadonlyArray<ExecRow>)[0]
271
+ return r ? Option.some(parseExec(r)) : Option.none<ExecState>()
272
+ }),
273
+ annotate("readExec", executionId)
274
+ )
275
+
276
+ /**
277
+ * OCC-guarded write. Generates a fresh etag on success; returns
278
+ * `OptimisticConcurrencyException` when no row matches the prior etag.
279
+ */
280
+ const replaceExec = (
281
+ state: ExecState,
282
+ next: Partial<Omit<ExecState, "executionId" | "etag" | "workflowName" | "payload" | "parent">>
283
+ ) =>
284
+ Effect
285
+ .gen(function*() {
286
+ const newEtag = randomUUID()
287
+ const merged = { ...state, ...next, etag: newEtag }
288
+ const rows = yield* exec(
289
+ `UPDATE "${execTable}"
290
+ SET status = ?,
291
+ suspended = ?,
292
+ interrupted = ?,
293
+ completed_result = ?,
294
+ worker = ?,
295
+ lease_expires_at = ?,
296
+ etag = ?
297
+ WHERE execution_id = ? AND etag = ?
298
+ RETURNING etag`,
299
+ [
300
+ merged.status,
301
+ merged.suspended ? 1 : 0,
302
+ merged.interrupted ? 1 : 0,
303
+ merged.completedResult ?? null,
304
+ merged.worker ?? null,
305
+ merged.leaseExpiresAt ?? null,
306
+ newEtag,
307
+ state.executionId,
308
+ state.etag
309
+ ]
310
+ )
311
+ if ((rows as ReadonlyArray<unknown>).length === 0) {
312
+ return yield* new OptimisticConcurrencyException({
313
+ type: "workflow.exec",
314
+ id: state.executionId,
315
+ code: 412
316
+ })
317
+ }
318
+ return merged
319
+ })
320
+ .pipe(annotate("replaceExec", state.executionId))
321
+
322
+ const createExec = (initial: ExecState): Effect.Effect<boolean> =>
323
+ exec(
324
+ `INSERT INTO "${execTable}"
325
+ (execution_id, workflow_name, payload, parent, status, suspended, interrupted, etag)
326
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
327
+ ON CONFLICT DO NOTHING
328
+ RETURNING execution_id`,
329
+ [
330
+ initial.executionId,
331
+ initial.workflowName,
332
+ initial.payload,
333
+ initial.parent ?? null,
334
+ initial.status,
335
+ initial.suspended ? 1 : 0,
336
+ initial.interrupted ? 1 : 0,
337
+ initial.etag
338
+ ]
339
+ )
340
+ .pipe(
341
+ Effect.map((rows) => (rows as ReadonlyArray<unknown>).length > 0),
342
+ annotate("createExec", initial.executionId)
343
+ )
344
+
345
+ // First-writer-wins persistence of an activity result; returns true if this
346
+ // call won, false if another writer beat us to the (exec, name, attempt) row.
347
+ const createActivity = (
348
+ executionId: string,
349
+ name: string,
350
+ attempt: number,
351
+ encoded: string
352
+ ): Effect.Effect<boolean> =>
353
+ exec(
354
+ `INSERT INTO "${activityTable}" (execution_id, name, attempt, result)
355
+ VALUES (?, ?, ?, ?)
356
+ ON CONFLICT DO NOTHING
357
+ RETURNING execution_id`,
358
+ [executionId, name, attempt, encoded]
359
+ )
360
+ .pipe(Effect.map((rows) => (rows as ReadonlyArray<unknown>).length > 0))
361
+
362
+ // Overwrites a previously persisted *suspended* activity result so the next
363
+ // attempt can record its real outcome.
364
+ const upsertActivity = (
365
+ executionId: string,
366
+ name: string,
367
+ attempt: number,
368
+ encoded: string
369
+ ): Effect.Effect<void> =>
370
+ exec(
371
+ `INSERT INTO "${activityTable}" (execution_id, name, attempt, result)
372
+ VALUES (?, ?, ?, ?)
373
+ ON CONFLICT(execution_id, name, attempt) DO UPDATE SET result = excluded.result`,
374
+ [executionId, name, attempt, encoded]
375
+ )
376
+ .pipe(Effect.asVoid)
377
+
378
+ const readActivity = (
379
+ executionId: string,
380
+ name: string,
381
+ attempt: number
382
+ ): Effect.Effect<Option.Option<string>> =>
383
+ exec(
384
+ `SELECT result FROM "${activityTable}" WHERE execution_id = ? AND name = ? AND attempt = ?`,
385
+ [executionId, name, attempt]
386
+ )
387
+ .pipe(
388
+ Effect.map((rows) => {
389
+ const r = (rows as ReadonlyArray<{ result: string }>)[0]
390
+ return r ? Option.some(r.result) : Option.none<string>()
391
+ })
392
+ )
393
+
394
+ const createDeferred = (
395
+ executionId: string,
396
+ name: string,
397
+ encoded: string
398
+ ): Effect.Effect<boolean> =>
399
+ exec(
400
+ `INSERT INTO "${deferredTable}" (execution_id, name, exit)
401
+ VALUES (?, ?, ?)
402
+ ON CONFLICT DO NOTHING
403
+ RETURNING execution_id`,
404
+ [executionId, name, encoded]
405
+ )
406
+ .pipe(Effect.map((rows) => (rows as ReadonlyArray<unknown>).length > 0))
407
+
408
+ const readDeferred = (
409
+ executionId: string,
410
+ name: string
411
+ ): Effect.Effect<Option.Option<string>> =>
412
+ exec(
413
+ `SELECT exit FROM "${deferredTable}" WHERE execution_id = ? AND name = ?`,
414
+ [executionId, name]
415
+ )
416
+ .pipe(
417
+ Effect.map((rows) => {
418
+ const r = (rows as ReadonlyArray<{ exit: string }>)[0]
419
+ return r ? Option.some(r.exit) : Option.none<string>()
420
+ })
421
+ )
422
+
423
+ const insertClock = (
424
+ executionId: string,
425
+ name: string,
426
+ workflowName: string,
427
+ deferredName: string,
428
+ fireAt: number
429
+ ): Effect.Effect<boolean> =>
430
+ exec(
431
+ `INSERT INTO "${clockTable}" (execution_id, name, workflow_name, deferred_name, fire_at)
432
+ VALUES (?, ?, ?, ?, ?)
433
+ ON CONFLICT DO NOTHING
434
+ RETURNING execution_id`,
435
+ [executionId, name, workflowName, deferredName, fireAt]
436
+ )
437
+ .pipe(Effect.map((rows) => (rows as ReadonlyArray<unknown>).length > 0))
438
+
439
+ const deleteClock = (executionId: string, name: string) =>
440
+ exec(
441
+ `DELETE FROM "${clockTable}" WHERE execution_id = ? AND name = ?`,
442
+ [executionId, name]
443
+ )
444
+
445
+ // --- Workflow result helpers ------------------------------------------
446
+
447
+ const completeResult = (
448
+ workflow: Workflow.Any,
449
+ state: ExecState
450
+ ): Effect.Effect<Option.Option<Workflow.Result<unknown, unknown>>> =>
451
+ state.status === "complete" && state.completedResult
452
+ ? Effect.map(decodeResult(workflow, state.completedResult), Option.some)
453
+ : Effect.succeedNone
454
+
455
+ // --- Lease / claim ----------------------------------------------------
456
+
457
+ const leaseActive = (state: ExecState, now: number): boolean =>
458
+ state.worker !== undefined
459
+ && state.worker !== workerId
460
+ && state.leaseExpiresAt !== undefined
461
+ && state.leaseExpiresAt > now
462
+
463
+ const tryClaim = (state: ExecState): Effect.Effect<Option.Option<ExecState>> =>
464
+ Effect.gen(function*() {
465
+ const now = Date.now()
466
+ if (leaseActive(state, now)) return Option.none<ExecState>()
467
+ return yield* replaceExec(state, {
468
+ worker: workerId,
469
+ leaseExpiresAt: now + Duration.toMillis(leaseTtl)
470
+ })
471
+ .pipe(
472
+ Effect.map(Option.some),
473
+ Effect.catchTag("OptimisticConcurrencyException", () => Effect.succeed(Option.none<ExecState>()))
474
+ )
475
+ })
476
+
477
+ const heartbeat = (executionId: string): Effect.Effect<void> =>
478
+ Effect.gen(function*() {
479
+ while (true) {
480
+ yield* Effect.sleep(heartbeatInterval)
481
+ const local = locals.get(executionId)
482
+ const polled = local?.fiber?.pollUnsafe()
483
+ if (!local?.fiber || polled) return
484
+ const cur = yield* readExec(executionId).pipe(
485
+ Effect.catchCause(() => Effect.succeed(Option.none<ExecState>()))
486
+ )
487
+ if (Option.isNone(cur)) continue
488
+ const state = cur.value
489
+ if (state.status === "complete" || state.worker !== workerId) return
490
+ yield* replaceExec(state, {
491
+ leaseExpiresAt: Date.now() + Duration.toMillis(leaseTtl)
492
+ })
493
+ .pipe(
494
+ Effect.catchTag("OptimisticConcurrencyException", () => Effect.void),
495
+ Effect.catchCause(() => Effect.void)
496
+ )
497
+ }
498
+ })
499
+
500
+ // --- Drive logic ------------------------------------------------------
501
+
502
+ const drive = (
503
+ executionId: string,
504
+ payload: object,
505
+ parent: string | undefined,
506
+ entry: Registered
507
+ ): Effect.Effect<void> =>
508
+ Effect.gen(function*() {
509
+ let local = locals.get(executionId)
510
+ if (local?.fiber) {
511
+ const polled = local.fiber.pollUnsafe()
512
+ const stillRunning = !polled
513
+ const completedNotResume = polled && polled._tag === "Success" && polled.value._tag === "Complete"
514
+ if (stillRunning || completedNotResume) return
515
+ }
516
+
517
+ const stateOpt = yield* readExec(executionId)
518
+ if (Option.isNone(stateOpt) || stateOpt.value.status === "complete") return
519
+
520
+ const claimed = yield* tryClaim(stateOpt.value)
521
+ const state = Option.isSome(claimed) ? claimed.value : stateOpt.value
522
+
523
+ const instance = WorkflowInstance.initial(entry.workflow, executionId)
524
+ instance.interrupted = state.interrupted
525
+ if (!local) {
526
+ local = { instance, fiber: undefined, parent }
527
+ locals.set(executionId, local)
528
+ } else {
529
+ local.instance = instance
530
+ }
531
+
532
+ const onComplete = Effect.fnUntraced(function*(result: Workflow.Result<unknown, unknown>) {
533
+ const current = yield* readExec(executionId)
534
+ if (Option.isNone(current) || current.value.status === "complete") return
535
+ const isComplete = result._tag === "Complete"
536
+ const completedResult = isComplete ? yield* encodeResult(entry.workflow, result) : undefined
537
+ yield* replaceExec(current.value, {
538
+ status: isComplete ? "complete" : current.value.status,
539
+ suspended: result._tag === "Suspended",
540
+ interrupted: instance.interrupted,
541
+ completedResult,
542
+ worker: isComplete ? undefined : current.value.worker,
543
+ leaseExpiresAt: isComplete ? undefined : current.value.leaseExpiresAt
544
+ })
545
+ .pipe(Effect.catchTag("OptimisticConcurrencyException", () => Effect.void))
546
+ if (parent && isComplete) {
547
+ yield* Effect.forkIn(driveById(parent), scope)
548
+ }
549
+ })
550
+
551
+ local.fiber = yield* entry.execute(payload, executionId).pipe(
552
+ Effect.onExit(() => {
553
+ if (!instance.interrupted) return Effect.void
554
+ instance.suspended = false
555
+ return Effect.withFiber((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
556
+ }),
557
+ Workflow.intoResult,
558
+ Effect.provideService(WorkflowInstance, instance),
559
+ Effect.provideService(WorkflowEngine, engine),
560
+ Effect.tap(onComplete),
561
+ Effect.forkIn(entry.scope)
562
+ )
563
+
564
+ if (Option.isSome(claimed)) {
565
+ yield* Effect.forkIn(heartbeat(executionId), scope)
566
+ }
567
+ })
568
+
569
+ const driveById = (executionId: string): Effect.Effect<void> =>
570
+ Effect.gen(function*() {
571
+ const stateOpt = yield* readExec(executionId)
572
+ if (Option.isNone(stateOpt)) return
573
+ const state = stateOpt.value
574
+ const entry = workflows.get(state.workflowName)
575
+ if (!entry) return
576
+ const payload = yield* decodePayload(entry.workflow, state.payload)
577
+ yield* drive(executionId, payload, state.parent, entry)
578
+ })
579
+
580
+ // --- Clock firing -----------------------------------------------------
581
+
582
+ const fireClock = (
583
+ executionId: string,
584
+ name: string,
585
+ deferredName: string
586
+ ): Effect.Effect<void> =>
587
+ Effect
588
+ .gen(function*() {
589
+ const encoded = yield* encodeDeferredExit(Exit.void)
590
+ const inserted = yield* sql
591
+ .withTransaction(Effect.gen(function*() {
592
+ const got = yield* createDeferred(executionId, deferredName, encoded)
593
+ yield* deleteClock(executionId, name)
594
+ return got
595
+ }))
596
+ .pipe(Effect.orDie)
597
+ if (inserted) yield* driveById(executionId)
598
+ })
599
+ .pipe(annotate("clockFire", executionId))
600
+
601
+ // --- Encoded engine ---------------------------------------------------
602
+
603
+ const encoded: Encoded = {
604
+ register: Effect.fnUntraced(function*(workflow, execute) {
605
+ workflows.set(workflow.name, {
606
+ workflow,
607
+ execute,
608
+ scope: yield* Effect.scope
609
+ })
610
+ }),
611
+ execute: Effect.fnUntraced(function*(workflow, options) {
612
+ const entry = workflows.get(workflow.name)
613
+ if (!entry) {
614
+ return yield* Effect.orDie(Effect.fail(`Workflow ${workflow.name} is not registered`))
615
+ }
616
+ const initial: ExecState = {
617
+ executionId: options.executionId,
618
+ workflowName: workflow.name,
619
+ payload: yield* encodePayload(workflow, options.payload),
620
+ parent: options.parent?.executionId,
621
+ status: "running",
622
+ suspended: false,
623
+ interrupted: false,
624
+ completedResult: undefined,
625
+ worker: undefined,
626
+ leaseExpiresAt: undefined,
627
+ etag: randomUUID()
628
+ }
629
+ yield* createExec(initial)
630
+ yield* drive(options.executionId, options.payload, options.parent?.executionId, entry)
631
+ if (options.discard) return undefined as any
632
+ const local = locals.get(options.executionId)
633
+ if (local?.fiber) {
634
+ return (yield* Fiber.join(local.fiber)) as any
635
+ }
636
+ // Foreign-driver fallback: poll the persisted result until completion.
637
+ while (true) {
638
+ const cur = yield* readExec(options.executionId)
639
+ if (Option.isSome(cur)) {
640
+ const r = yield* completeResult(workflow, cur.value)
641
+ if (Option.isSome(r)) return r.value as any
642
+ }
643
+ yield* Effect.sleep(Duration.millis(500))
644
+ }
645
+ }),
646
+ poll: (workflow, executionId) =>
647
+ Effect.gen(function*() {
648
+ const local = locals.get(executionId)
649
+ if (local?.fiber) {
650
+ const exitVal = local.fiber.pollUnsafe()
651
+ if (!exitVal) return Option.none<Workflow.Result<unknown, unknown>>()
652
+ if (exitVal._tag !== "Success") return yield* Effect.die(exitVal.cause)
653
+ return Option.some(exitVal.value)
654
+ }
655
+ const state = yield* readExec(executionId)
656
+ if (Option.isNone(state)) return Option.none<Workflow.Result<unknown, unknown>>()
657
+ return yield* completeResult(workflow, state.value)
658
+ }),
659
+ interrupt: Effect.fnUntraced(function*(_workflow, executionId) {
660
+ const local = locals.get(executionId)
661
+ if (local) local.instance.interrupted = true
662
+ const current = yield* readExec(executionId)
663
+ if (Option.isNone(current) || current.value.status === "complete") return
664
+ yield* replaceExec(current.value, { interrupted: true }).pipe(
665
+ Effect.catchTag("OptimisticConcurrencyException", () => Effect.void)
666
+ )
667
+ yield* driveById(executionId)
668
+ }),
669
+ interruptUnsafe: Effect.fnUntraced(function*(_workflow, executionId) {
670
+ const local = locals.get(executionId)
671
+ if (local) local.instance.interrupted = true
672
+ const current = yield* readExec(executionId)
673
+ if (Option.isSome(current) && current.value.status !== "complete") {
674
+ yield* replaceExec(current.value, { interrupted: true }).pipe(
675
+ Effect.catchTag("OptimisticConcurrencyException", () => Effect.void)
676
+ )
677
+ }
678
+ if (local?.fiber) yield* Fiber.interrupt(local.fiber)
679
+ }),
680
+ resume: (_workflow, executionId) => driveById(executionId),
681
+ activityExecute: Effect.fnUntraced(function*(activity, attempt) {
682
+ const instance = yield* WorkflowInstance
683
+ const existing = yield* readActivity(instance.executionId, activity.name, attempt)
684
+ if (Option.isSome(existing)) {
685
+ const prev = yield* decodeActivityResult(existing.value)
686
+ // A completed activity is replayed from its persisted result; a
687
+ // suspended one must re-run (it parked on a clock/deferred).
688
+ if (prev._tag === "Complete") return prev
689
+ }
690
+
691
+ const activityInstance = WorkflowInstance.initial(instance.workflow, instance.executionId)
692
+ activityInstance.interrupted = instance.interrupted
693
+
694
+ const result = yield* activity.executeEncoded.pipe(
695
+ Workflow.intoResult,
696
+ Effect.provideService(WorkflowInstance, activityInstance)
697
+ )
698
+ const encodedResult = yield* encodeActivityResult(result)
699
+
700
+ if (Option.isSome(existing)) {
701
+ // Overwrite the previously persisted *suspended* result.
702
+ yield* upsertActivity(instance.executionId, activity.name, attempt, encodedResult)
703
+ return result
704
+ }
705
+ // First-writer-wins: if persistence loses the race, use the persisted result.
706
+ const persisted = yield* createActivity(instance.executionId, activity.name, attempt, encodedResult)
707
+ if (persisted) return result
708
+ const winner = yield* readActivity(instance.executionId, activity.name, attempt)
709
+ if (Option.isSome(winner)) {
710
+ const w = yield* decodeActivityResult(winner.value)
711
+ if (w._tag === "Complete") return w
712
+ }
713
+ return result
714
+ }),
715
+ deferredResult: Effect.fnUntraced(function*(deferred) {
716
+ const instance = yield* WorkflowInstance
717
+ const got = yield* readDeferred(instance.executionId, deferred.name)
718
+ if (Option.isNone(got)) return Option.none<Exit.Exit<unknown, unknown>>()
719
+ return Option.some(yield* decodeDeferredExit(got.value))
720
+ }),
721
+ deferredDone: Effect.fnUntraced(function*(options) {
722
+ const encoded = yield* encodeDeferredExit(options.exit)
723
+ const inserted = yield* createDeferred(options.executionId, options.deferredName, encoded)
724
+ if (!inserted) return
725
+ yield* driveById(options.executionId)
726
+ }),
727
+ scheduleClock: (workflow, options) => {
728
+ const fireAt = Date.now() + Duration.toMillis(options.clock.duration)
729
+ return Effect.gen(function*() {
730
+ yield* insertClock(
731
+ options.executionId,
732
+ options.clock.name,
733
+ workflow.name,
734
+ options.clock.deferred.name,
735
+ fireAt
736
+ )
737
+ yield* fireClock(options.executionId, options.clock.name, options.clock.deferred.name).pipe(
738
+ Effect.delay(options.clock.duration),
739
+ FiberMap.run(clocks, `${options.executionId}/${options.clock.name}`, { onlyIfMissing: true }),
740
+ Effect.asVoid
741
+ )
742
+ })
743
+ }
744
+ }
745
+
746
+ const engine = makeUnsafe(encoded)
747
+
748
+ // --- Recovery poller --------------------------------------------------
749
+
750
+ if (Duration.toMillis(recoveryInterval) > 0) {
751
+ const recoverStep = Effect
752
+ .gen(function*() {
753
+ const rows = yield* exec(
754
+ `SELECT execution_id, workflow_name FROM "${execTable}"
755
+ WHERE status = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
756
+ LIMIT 100`,
757
+ [Date.now()]
758
+ )
759
+ for (const row of rows as ReadonlyArray<{ execution_id: string; workflow_name: string }>) {
760
+ if (!workflows.has(row.workflow_name)) continue
761
+ const local = locals.get(row.execution_id)
762
+ if (local?.fiber && !local.fiber.pollUnsafe()) continue
763
+ yield* Effect.forkIn(driveById(row.execution_id), scope)
764
+ }
765
+ })
766
+ .pipe(annotate("recoveryScan"), Effect.catchCause(() => Effect.void))
767
+
768
+ yield* recoverStep.pipe(
769
+ Effect.repeat(Schedule.spaced(recoveryInterval)),
770
+ Effect.forkIn(scope)
771
+ )
772
+ }
773
+
774
+ // --- Clock poller -----------------------------------------------------
775
+
776
+ if (Duration.toMillis(clockPollInterval) > 0) {
777
+ const clockStep = Effect
778
+ .gen(function*() {
779
+ const rows = yield* exec(
780
+ `SELECT execution_id, name, deferred_name FROM "${clockTable}"
781
+ WHERE fire_at <= ?
782
+ LIMIT 100`,
783
+ [Date.now()]
784
+ )
785
+ for (
786
+ const row of rows as ReadonlyArray<{
787
+ execution_id: string
788
+ name: string
789
+ deferred_name: string
790
+ }>
791
+ ) {
792
+ yield* Effect.forkIn(fireClock(row.execution_id, row.name, row.deferred_name), scope)
793
+ }
794
+ })
795
+ .pipe(annotate("clockScan"), Effect.catchCause(() => Effect.void))
796
+
797
+ yield* clockStep.pipe(
798
+ Effect.repeat(Schedule.spaced(clockPollInterval)),
799
+ Effect.forkIn(scope)
800
+ )
801
+ }
802
+
803
+ return engine
804
+ })
805
+
806
+ /**
807
+ * SQLite backed `WorkflowEngine` layer. Requires an ambient `SqlClient`
808
+ * (`@effect/sql-sqlite-node` or a compatible client).
809
+ */
810
+ export const layerSqlite = (
811
+ cfg: WorkflowEngineSqliteConfig = {}
812
+ ): Layer.Layer<WorkflowEngine, never, SqlClient.SqlClient> =>
813
+ Layer.effect(WorkflowEngine)(makeSqliteWorkflowEngine(cfg))