@effect-app/infra 4.0.0-beta.99 → 4.0.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 (486) hide show
  1. package/CHANGELOG.md +2884 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +28 -9
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +35 -14
  6. package/dist/ClusterCosmos.d.ts +67 -0
  7. package/dist/ClusterCosmos.d.ts.map +1 -0
  8. package/dist/ClusterCosmos.js +588 -0
  9. package/dist/ClusterServiceBus.d.ts +67 -0
  10. package/dist/ClusterServiceBus.d.ts.map +1 -0
  11. package/dist/ClusterServiceBus.js +84 -0
  12. package/dist/{api/ContextProvider.d.ts → ContextProvider.d.ts} +7 -4
  13. package/dist/ContextProvider.d.ts.map +1 -0
  14. package/dist/ContextProvider.js +40 -0
  15. package/dist/Emailer/Sendgrid.d.ts +110 -146
  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 +1 -1
  19. package/dist/Emailer/fake.d.ts.map +1 -1
  20. package/dist/Emailer/fake.js +4 -4
  21. package/dist/MainFiberSet.d.ts +11 -7
  22. package/dist/MainFiberSet.d.ts.map +1 -1
  23. package/dist/MainFiberSet.js +22 -6
  24. package/dist/QueueMaker/errors.d.ts +3 -1
  25. package/dist/QueueMaker/errors.d.ts.map +1 -1
  26. package/dist/QueueMaker/errors.js +4 -2
  27. package/dist/{adapters/SQL → SQL}/Model.d.ts +34 -45
  28. package/dist/SQL/Model.d.ts.map +1 -0
  29. package/dist/SQL/Model.js +469 -0
  30. package/dist/SQL.d.ts.map +1 -0
  31. package/dist/{adapters/SQL.js → SQL.js} +1 -1
  32. package/dist/Store/Cosmos/query.d.ts +16 -4
  33. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  34. package/dist/Store/Cosmos/query.js +238 -42
  35. package/dist/Store/Cosmos.d.ts +2 -2
  36. package/dist/Store/Cosmos.d.ts.map +1 -1
  37. package/dist/Store/Cosmos.js +394 -319
  38. package/dist/Store/Disk.d.ts +4 -4
  39. package/dist/Store/Disk.d.ts.map +1 -1
  40. package/dist/Store/Disk.js +91 -39
  41. package/dist/Store/Memory.d.ts +7 -10
  42. package/dist/Store/Memory.d.ts.map +1 -1
  43. package/dist/Store/Memory.js +348 -78
  44. package/dist/Store/SQL/Pg.d.ts +2 -2
  45. package/dist/Store/SQL/Pg.d.ts.map +1 -1
  46. package/dist/Store/SQL/Pg.js +229 -154
  47. package/dist/Store/SQL/query.d.ts +19 -3
  48. package/dist/Store/SQL/query.d.ts.map +1 -1
  49. package/dist/Store/SQL/query.js +306 -12
  50. package/dist/Store/SQL.d.ts +22 -5
  51. package/dist/Store/SQL.d.ts.map +1 -1
  52. package/dist/Store/SQL.js +369 -98
  53. package/dist/Store/codeFilter.d.ts +4 -4
  54. package/dist/Store/codeFilter.d.ts.map +1 -1
  55. package/dist/Store/codeFilter.js +71 -22
  56. package/dist/Store/index.d.ts +6 -4
  57. package/dist/Store/index.d.ts.map +1 -1
  58. package/dist/Store/index.js +9 -6
  59. package/dist/Store/jsonDocument.d.ts +67 -0
  60. package/dist/Store/jsonDocument.d.ts.map +1 -0
  61. package/dist/Store/jsonDocument.js +107 -0
  62. package/dist/Store/utils.d.ts +21 -2
  63. package/dist/Store/utils.d.ts.map +1 -1
  64. package/dist/Store/utils.js +253 -10
  65. package/dist/WorkflowEngineCosmos.d.ts +30 -0
  66. package/dist/WorkflowEngineCosmos.d.ts.map +1 -0
  67. package/dist/WorkflowEngineCosmos.js +542 -0
  68. package/dist/WorkflowEngineSqlite.d.ts +24 -0
  69. package/dist/WorkflowEngineSqlite.d.ts.map +1 -0
  70. package/dist/WorkflowEngineSqlite.js +550 -0
  71. package/dist/arbs.d.ts +6 -3
  72. package/dist/arbs.d.ts.map +1 -1
  73. package/dist/arbs.js +20 -4
  74. package/dist/codec.d.ts.map +1 -0
  75. package/dist/codec.js +5 -0
  76. package/dist/cosmos-client.d.ts +40 -0
  77. package/dist/cosmos-client.d.ts.map +1 -0
  78. package/dist/cosmos-client.js +41 -0
  79. package/dist/errorReporter.d.ts +6 -4
  80. package/dist/errorReporter.d.ts.map +1 -1
  81. package/dist/errorReporter.js +23 -27
  82. package/dist/fileUtil.d.ts +1 -1
  83. package/dist/fileUtil.d.ts.map +1 -1
  84. package/dist/fileUtil.js +2 -2
  85. package/dist/index.d.ts +2 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +3 -2
  88. package/dist/internal/RequestContextMiddleware.d.ts +5 -0
  89. package/dist/internal/RequestContextMiddleware.d.ts.map +1 -0
  90. package/dist/internal/RequestContextMiddleware.js +45 -0
  91. package/dist/internal/auth.d.ts +53 -0
  92. package/dist/internal/auth.d.ts.map +1 -0
  93. package/dist/internal/auth.js +180 -0
  94. package/dist/internal/events.d.ts +11 -0
  95. package/dist/internal/events.d.ts.map +1 -0
  96. package/dist/internal/events.js +49 -0
  97. package/dist/{api/internal → internal}/health.d.ts +1 -1
  98. package/dist/internal/health.d.ts.map +1 -0
  99. package/dist/internal/health.js +5 -0
  100. package/dist/{api/layerUtils.d.ts → layerUtils.d.ts} +10 -2
  101. package/dist/layerUtils.d.ts.map +1 -0
  102. package/dist/layerUtils.js +17 -0
  103. package/dist/logger/jsonLogger.d.ts +1 -1
  104. package/dist/logger/jsonLogger.d.ts.map +1 -1
  105. package/dist/logger/jsonLogger.js +5 -3
  106. package/dist/logger/logFmtLogger.d.ts +1 -1
  107. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  108. package/dist/logger/logFmtLogger.js +3 -3
  109. package/dist/logger/shared.d.ts +2 -2
  110. package/dist/logger/shared.d.ts.map +1 -1
  111. package/dist/logger/shared.js +6 -6
  112. package/dist/logger.d.ts.map +1 -1
  113. package/dist/middlewares.d.ts.map +1 -0
  114. package/dist/{api/middlewares.js → middlewares.js} +1 -1
  115. package/dist/{adapters/mongo-client.d.ts → mongo-client.d.ts} +2 -1
  116. package/dist/mongo-client.d.ts.map +1 -0
  117. package/dist/mongo-client.js +15 -0
  118. package/dist/otel.d.ts +75 -0
  119. package/dist/otel.d.ts.map +1 -0
  120. package/dist/otel.js +84 -0
  121. package/dist/rateLimit.d.ts +11 -3
  122. package/dist/rateLimit.d.ts.map +1 -1
  123. package/dist/rateLimit.js +7 -12
  124. package/dist/{adapters/redis-client.d.ts → redis-client.d.ts} +4 -1
  125. package/dist/redis-client.d.ts.map +1 -0
  126. package/dist/redis-client.js +98 -0
  127. package/dist/{api/reportError.d.ts → reportError.d.ts} +1 -1
  128. package/dist/reportError.d.ts.map +1 -0
  129. package/dist/reportError.js +28 -0
  130. package/dist/{api/routing → routing}/middleware/RouterMiddleware.d.ts +2 -1
  131. package/dist/routing/middleware/RouterMiddleware.d.ts.map +1 -0
  132. package/dist/{api/routing → routing}/middleware/RouterMiddleware.js +1 -1
  133. package/dist/{api/routing → routing}/middleware/middleware.d.ts +11 -6
  134. package/dist/routing/middleware/middleware.d.ts.map +1 -0
  135. package/dist/routing/middleware/middleware.js +152 -0
  136. package/dist/{api/routing → routing}/middleware.d.ts +0 -1
  137. package/dist/routing/middleware.d.ts.map +1 -0
  138. package/dist/{api/routing → routing}/middleware.js +1 -2
  139. package/dist/routing/schema/jwt.d.ts.map +1 -0
  140. package/dist/routing/schema/jwt.js +12 -0
  141. package/dist/routing/tsort.d.ts.map +1 -0
  142. package/dist/routing/tsort.js +51 -0
  143. package/dist/{api/routing → routing}/utils.d.ts +3 -3
  144. package/dist/routing/utils.d.ts.map +1 -0
  145. package/dist/routing/utils.js +45 -0
  146. package/dist/routing.d.ts +182 -0
  147. package/dist/routing.d.ts.map +1 -0
  148. package/dist/routing.js +256 -0
  149. package/dist/test.d.ts +2 -2
  150. package/dist/test.d.ts.map +1 -1
  151. package/dist/test.js +5 -5
  152. package/dist/util.d.ts.map +1 -0
  153. package/dist/util.js +14 -0
  154. package/docs/cluster-storage.md +26 -0
  155. package/docs/workflow-engine.md +262 -0
  156. package/examples/query.ts +23 -19
  157. package/package.json +36 -364
  158. package/src/CUPS.ts +53 -14
  159. package/src/ClusterCosmos.ts +1170 -0
  160. package/src/ClusterServiceBus.ts +244 -0
  161. package/src/{api/ContextProvider.ts → ContextProvider.ts} +10 -7
  162. package/src/Emailer/Sendgrid.ts +83 -60
  163. package/src/Emailer/fake.ts +4 -4
  164. package/src/MainFiberSet.ts +31 -12
  165. package/src/QueueMaker/errors.ts +3 -1
  166. package/src/{adapters/SQL → SQL}/Model.ts +48 -47
  167. package/src/SQL.ts +1 -0
  168. package/src/Store/Cosmos/query.ts +275 -55
  169. package/src/Store/Cosmos.ts +597 -511
  170. package/src/Store/Disk.ts +145 -71
  171. package/src/Store/Memory.ts +420 -115
  172. package/src/Store/SQL/Pg.ts +352 -268
  173. package/src/Store/SQL/query.ts +366 -20
  174. package/src/Store/SQL.ts +742 -293
  175. package/src/Store/codeFilter.ts +78 -28
  176. package/src/Store/index.ts +18 -13
  177. package/src/Store/jsonDocument.ts +143 -0
  178. package/src/Store/utils.ts +307 -30
  179. package/src/WorkflowEngineCosmos.ts +749 -0
  180. package/src/WorkflowEngineSqlite.ts +813 -0
  181. package/src/arbs.ts +25 -5
  182. package/src/cosmos-client.ts +72 -0
  183. package/src/errorReporter.ts +68 -78
  184. package/src/fileUtil.ts +1 -1
  185. package/src/index.ts +2 -1
  186. package/src/{api/internal → internal}/RequestContextMiddleware.ts +23 -6
  187. package/src/internal/auth.ts +272 -0
  188. package/src/{api/internal → internal}/events.ts +18 -9
  189. package/src/{api/layerUtils.ts → layerUtils.ts} +7 -3
  190. package/src/logger/jsonLogger.ts +5 -3
  191. package/src/logger/logFmtLogger.ts +3 -3
  192. package/src/logger/shared.ts +6 -5
  193. package/src/middlewares.ts +10 -0
  194. package/src/{adapters/mongo-client.ts → mongo-client.ts} +3 -1
  195. package/src/otel.ts +177 -0
  196. package/src/rateLimit.ts +35 -24
  197. package/src/{adapters/redis-client.ts → redis-client.ts} +6 -2
  198. package/src/{api/reportError.ts → reportError.ts} +3 -2
  199. package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +2 -1
  200. package/src/{api/routing → routing}/middleware/middleware.ts +75 -50
  201. package/src/routing/middleware.ts +4 -0
  202. package/src/{api/routing → routing}/schema/jwt.ts +2 -2
  203. package/src/{api/routing → routing}/utils.ts +2 -1
  204. package/src/routing.ts +823 -0
  205. package/src/test.ts +6 -6
  206. package/src/vitest.ts +1 -1
  207. package/test/arbs-magnitude.test.ts +33 -0
  208. package/test/arbs.test.ts +41 -0
  209. package/test/auth.test.ts +101 -0
  210. package/test/cluster-cosmos.test.ts +634 -0
  211. package/test/cluster-servicebus.test.ts +180 -0
  212. package/test/cluster-sqlite.test.ts +207 -0
  213. package/test/contextProvider.test.ts +11 -8
  214. package/test/controller.test.ts +23 -28
  215. package/test/cosmos-container-throughput.test.ts +80 -0
  216. package/test/cosmos-query.test.ts +368 -0
  217. package/test/dist/arbs-magnitude.test.d.ts.map +1 -0
  218. package/test/dist/arbs.test.d.ts.map +1 -0
  219. package/test/dist/auth.test.d.ts.map +1 -0
  220. package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
  221. package/test/dist/cluster-servicebus.test.d.ts.map +1 -0
  222. package/test/dist/cluster-sqlite.test.d.ts.map +1 -0
  223. package/test/dist/contextProvider.test.d.ts.map +1 -1
  224. package/test/dist/controller.test.d.ts.map +1 -1
  225. package/test/dist/cosmos-container-throughput.test.d.ts.map +1 -0
  226. package/test/dist/cosmos-query.test.d.ts.map +1 -0
  227. package/test/dist/fixtures.d.ts +17 -9
  228. package/test/dist/fixtures.d.ts.map +1 -1
  229. package/test/dist/fixtures.js +8 -3
  230. package/test/dist/json-lower.test.d.ts.map +1 -0
  231. package/test/dist/query.test.d.ts.map +1 -1
  232. package/test/dist/rawQuery.test.d.ts.map +1 -1
  233. package/test/dist/repository-ext.test.d.ts.map +1 -0
  234. package/test/dist/repository-legacy-document.test.d.ts.map +1 -0
  235. package/test/dist/requires.test.d.ts.map +1 -1
  236. package/test/dist/router-generator.test.d.ts.map +1 -0
  237. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  238. package/test/dist/rpc-context-map-streaming.test.d.ts.map +1 -0
  239. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  240. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  241. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  242. package/test/dist/workflow-engine-cosmos.test.d.ts.map +1 -0
  243. package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
  244. package/test/fixtures.ts +7 -2
  245. package/test/json-lower.test.ts +129 -0
  246. package/test/layerUtils.test.ts +2 -2
  247. package/test/query.test.ts +1232 -42
  248. package/test/rawQuery.test.ts +432 -7
  249. package/test/repository-ext.test.ts +456 -0
  250. package/test/repository-legacy-document.test.ts +394 -0
  251. package/test/requires.test.ts +7 -3
  252. package/test/router-generator.test.ts +187 -0
  253. package/test/routing-interruptibility.test.ts +66 -0
  254. package/test/rpc-context-map-streaming.test.ts +265 -0
  255. package/test/rpc-e2e-invalidation.test.ts +421 -0
  256. package/test/rpc-multi-middleware.test.ts +17 -11
  257. package/test/rpc-stream-fullstack.test.ts +307 -0
  258. package/test/sql-store.test.ts +1202 -47
  259. package/test/validateSample.test.ts +70 -42
  260. package/test/workflow-engine-cosmos.test.ts +345 -0
  261. package/test/workflow-engine-sqlite.test.ts +297 -0
  262. package/tsconfig.examples.json +1 -1
  263. package/tsconfig.json +9 -1
  264. package/tsconfig.src.json +35 -35
  265. package/tsconfig.test.json +2 -2
  266. package/dist/Emailer/service.d.ts +0 -55
  267. package/dist/Emailer/service.d.ts.map +0 -1
  268. package/dist/Emailer/service.js +0 -6
  269. package/dist/Emailer.d.ts +0 -2
  270. package/dist/Emailer.d.ts.map +0 -1
  271. package/dist/Emailer.js +0 -2
  272. package/dist/Model/Repository/ext.d.ts +0 -41
  273. package/dist/Model/Repository/ext.d.ts.map +0 -1
  274. package/dist/Model/Repository/ext.js +0 -65
  275. package/dist/Model/Repository/internal/internal.d.ts +0 -59
  276. package/dist/Model/Repository/internal/internal.d.ts.map +0 -1
  277. package/dist/Model/Repository/internal/internal.js +0 -320
  278. package/dist/Model/Repository/legacy.d.ts +0 -19
  279. package/dist/Model/Repository/legacy.d.ts.map +0 -1
  280. package/dist/Model/Repository/legacy.js +0 -2
  281. package/dist/Model/Repository/makeRepo.d.ts +0 -49
  282. package/dist/Model/Repository/makeRepo.d.ts.map +0 -1
  283. package/dist/Model/Repository/makeRepo.js +0 -24
  284. package/dist/Model/Repository/service.d.ts +0 -89
  285. package/dist/Model/Repository/service.d.ts.map +0 -1
  286. package/dist/Model/Repository/service.js +0 -2
  287. package/dist/Model/Repository/validation.d.ts +0 -43
  288. package/dist/Model/Repository/validation.d.ts.map +0 -1
  289. package/dist/Model/Repository/validation.js +0 -32
  290. package/dist/Model/Repository.d.ts +0 -6
  291. package/dist/Model/Repository.d.ts.map +0 -1
  292. package/dist/Model/Repository.js +0 -6
  293. package/dist/Model/dsl.d.ts +0 -32
  294. package/dist/Model/dsl.d.ts.map +0 -1
  295. package/dist/Model/dsl.js +0 -44
  296. package/dist/Model/filter/filterApi.d.ts +0 -30
  297. package/dist/Model/filter/filterApi.d.ts.map +0 -1
  298. package/dist/Model/filter/filterApi.js +0 -2
  299. package/dist/Model/filter/types/errors.d.ts +0 -29
  300. package/dist/Model/filter/types/errors.d.ts.map +0 -1
  301. package/dist/Model/filter/types/errors.js +0 -2
  302. package/dist/Model/filter/types/fields.d.ts +0 -15
  303. package/dist/Model/filter/types/fields.d.ts.map +0 -1
  304. package/dist/Model/filter/types/fields.js +0 -2
  305. package/dist/Model/filter/types/path/common.d.ts +0 -316
  306. package/dist/Model/filter/types/path/common.d.ts.map +0 -1
  307. package/dist/Model/filter/types/path/common.js +0 -2
  308. package/dist/Model/filter/types/path/eager.d.ts +0 -95
  309. package/dist/Model/filter/types/path/eager.d.ts.map +0 -1
  310. package/dist/Model/filter/types/path/eager.js +0 -31
  311. package/dist/Model/filter/types/path/index.d.ts +0 -4
  312. package/dist/Model/filter/types/path/index.d.ts.map +0 -1
  313. package/dist/Model/filter/types/path/index.js +0 -3
  314. package/dist/Model/filter/types/utils.d.ts +0 -79
  315. package/dist/Model/filter/types/utils.d.ts.map +0 -1
  316. package/dist/Model/filter/types/utils.js +0 -2
  317. package/dist/Model/filter/types/validator.d.ts +0 -30
  318. package/dist/Model/filter/types/validator.d.ts.map +0 -1
  319. package/dist/Model/filter/types/validator.js +0 -2
  320. package/dist/Model/filter/types.d.ts +0 -5
  321. package/dist/Model/filter/types.d.ts.map +0 -1
  322. package/dist/Model/filter/types.js +0 -7
  323. package/dist/Model/query/dsl.d.ts +0 -248
  324. package/dist/Model/query/dsl.d.ts.map +0 -1
  325. package/dist/Model/query/dsl.js +0 -104
  326. package/dist/Model/query/new-kid-interpreter.d.ts +0 -28
  327. package/dist/Model/query/new-kid-interpreter.d.ts.map +0 -1
  328. package/dist/Model/query/new-kid-interpreter.js +0 -165
  329. package/dist/Model/query.d.ts +0 -15
  330. package/dist/Model/query.d.ts.map +0 -1
  331. package/dist/Model/query.js +0 -3
  332. package/dist/Model.d.ts +0 -4
  333. package/dist/Model.d.ts.map +0 -1
  334. package/dist/Model.js +0 -4
  335. package/dist/Operations.d.ts +0 -55
  336. package/dist/Operations.d.ts.map +0 -1
  337. package/dist/Operations.js +0 -102
  338. package/dist/OperationsRepo.d.ts +0 -41
  339. package/dist/OperationsRepo.d.ts.map +0 -1
  340. package/dist/OperationsRepo.js +0 -14
  341. package/dist/QueueMaker/SQLQueue.d.ts +0 -14
  342. package/dist/QueueMaker/SQLQueue.d.ts.map +0 -1
  343. package/dist/QueueMaker/SQLQueue.js +0 -132
  344. package/dist/QueueMaker/memQueue.d.ts +0 -11
  345. package/dist/QueueMaker/memQueue.d.ts.map +0 -1
  346. package/dist/QueueMaker/memQueue.js +0 -76
  347. package/dist/QueueMaker/sbqueue.d.ts +0 -12
  348. package/dist/QueueMaker/sbqueue.d.ts.map +0 -1
  349. package/dist/QueueMaker/sbqueue.js +0 -91
  350. package/dist/QueueMaker/service.d.ts +0 -11
  351. package/dist/QueueMaker/service.d.ts.map +0 -1
  352. package/dist/QueueMaker/service.js +0 -4
  353. package/dist/RequestContext.d.ts +0 -68
  354. package/dist/RequestContext.d.ts.map +0 -1
  355. package/dist/RequestContext.js +0 -49
  356. package/dist/RequestFiberSet.d.ts +0 -36
  357. package/dist/RequestFiberSet.d.ts.map +0 -1
  358. package/dist/RequestFiberSet.js +0 -78
  359. package/dist/Store/ContextMapContainer.d.ts +0 -25
  360. package/dist/Store/ContextMapContainer.d.ts.map +0 -1
  361. package/dist/Store/ContextMapContainer.js +0 -77
  362. package/dist/Store/service.d.ts +0 -111
  363. package/dist/Store/service.d.ts.map +0 -1
  364. package/dist/Store/service.js +0 -79
  365. package/dist/Store.d.ts +0 -2
  366. package/dist/Store.d.ts.map +0 -1
  367. package/dist/Store.js +0 -2
  368. package/dist/adapters/SQL/Model.d.ts.map +0 -1
  369. package/dist/adapters/SQL/Model.js +0 -478
  370. package/dist/adapters/SQL.d.ts.map +0 -1
  371. package/dist/adapters/ServiceBus.d.ts +0 -58
  372. package/dist/adapters/ServiceBus.d.ts.map +0 -1
  373. package/dist/adapters/ServiceBus.js +0 -99
  374. package/dist/adapters/cosmos-client.d.ts +0 -14
  375. package/dist/adapters/cosmos-client.d.ts.map +0 -1
  376. package/dist/adapters/cosmos-client.js +0 -9
  377. package/dist/adapters/index.d.ts +0 -2
  378. package/dist/adapters/index.d.ts.map +0 -1
  379. package/dist/adapters/index.js +0 -2
  380. package/dist/adapters/logger.d.ts +0 -9
  381. package/dist/adapters/logger.d.ts.map +0 -1
  382. package/dist/adapters/logger.js +0 -3
  383. package/dist/adapters/memQueue.d.ts +0 -13
  384. package/dist/adapters/memQueue.d.ts.map +0 -1
  385. package/dist/adapters/memQueue.js +0 -20
  386. package/dist/adapters/mongo-client.d.ts.map +0 -1
  387. package/dist/adapters/mongo-client.js +0 -13
  388. package/dist/adapters/redis-client.d.ts.map +0 -1
  389. package/dist/adapters/redis-client.js +0 -94
  390. package/dist/api/ContextProvider.d.ts.map +0 -1
  391. package/dist/api/ContextProvider.js +0 -38
  392. package/dist/api/codec.d.ts.map +0 -1
  393. package/dist/api/codec.js +0 -5
  394. package/dist/api/internal/RequestContextMiddleware.d.ts +0 -5
  395. package/dist/api/internal/RequestContextMiddleware.d.ts.map +0 -1
  396. package/dist/api/internal/RequestContextMiddleware.js +0 -35
  397. package/dist/api/internal/auth.d.ts +0 -15
  398. package/dist/api/internal/auth.d.ts.map +0 -1
  399. package/dist/api/internal/auth.js +0 -47
  400. package/dist/api/internal/events.d.ts +0 -9
  401. package/dist/api/internal/events.d.ts.map +0 -1
  402. package/dist/api/internal/events.js +0 -42
  403. package/dist/api/internal/health.d.ts.map +0 -1
  404. package/dist/api/internal/health.js +0 -5
  405. package/dist/api/layerUtils.d.ts.map +0 -1
  406. package/dist/api/layerUtils.js +0 -16
  407. package/dist/api/middlewares.d.ts.map +0 -1
  408. package/dist/api/reportError.d.ts.map +0 -1
  409. package/dist/api/reportError.js +0 -27
  410. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +0 -1
  411. package/dist/api/routing/middleware/middleware.d.ts.map +0 -1
  412. package/dist/api/routing/middleware/middleware.js +0 -130
  413. package/dist/api/routing/middleware.d.ts.map +0 -1
  414. package/dist/api/routing/schema/jwt.d.ts.map +0 -1
  415. package/dist/api/routing/schema/jwt.js +0 -12
  416. package/dist/api/routing/tsort.d.ts.map +0 -1
  417. package/dist/api/routing/tsort.js +0 -51
  418. package/dist/api/routing/utils.d.ts.map +0 -1
  419. package/dist/api/routing/utils.js +0 -44
  420. package/dist/api/routing.d.ts +0 -134
  421. package/dist/api/routing.d.ts.map +0 -1
  422. package/dist/api/routing.js +0 -167
  423. package/dist/api/setupRequest.d.ts +0 -15
  424. package/dist/api/setupRequest.d.ts.map +0 -1
  425. package/dist/api/setupRequest.js +0 -49
  426. package/dist/api/util.d.ts.map +0 -1
  427. package/dist/api/util.js +0 -14
  428. package/eslint.config.mjs +0 -24
  429. package/src/Emailer/service.ts +0 -52
  430. package/src/Emailer.ts +0 -1
  431. package/src/Model/Repository/ext.ts +0 -283
  432. package/src/Model/Repository/internal/internal.ts +0 -580
  433. package/src/Model/Repository/legacy.ts +0 -27
  434. package/src/Model/Repository/makeRepo.ts +0 -139
  435. package/src/Model/Repository/service.ts +0 -627
  436. package/src/Model/Repository/validation.ts +0 -31
  437. package/src/Model/Repository.ts +0 -5
  438. package/src/Model/dsl.ts +0 -128
  439. package/src/Model/filter/filterApi.ts +0 -60
  440. package/src/Model/filter/types/errors.ts +0 -47
  441. package/src/Model/filter/types/fields.ts +0 -50
  442. package/src/Model/filter/types/path/common.ts +0 -404
  443. package/src/Model/filter/types/path/eager.ts +0 -298
  444. package/src/Model/filter/types/path/index.ts +0 -4
  445. package/src/Model/filter/types/utils.ts +0 -128
  446. package/src/Model/filter/types/validator.ts +0 -46
  447. package/src/Model/filter/types.ts +0 -6
  448. package/src/Model/query/dsl.ts +0 -2110
  449. package/src/Model/query/new-kid-interpreter.ts +0 -210
  450. package/src/Model/query.ts +0 -13
  451. package/src/Model.ts +0 -3
  452. package/src/Operations.ts +0 -235
  453. package/src/OperationsRepo.ts +0 -16
  454. package/src/QueueMaker/SQLQueue.ts +0 -190
  455. package/src/QueueMaker/memQueue.ts +0 -130
  456. package/src/QueueMaker/sbqueue.ts +0 -137
  457. package/src/QueueMaker/service.ts +0 -17
  458. package/src/RequestContext.ts +0 -63
  459. package/src/RequestFiberSet.ts +0 -105
  460. package/src/Store/ContextMapContainer.ts +0 -116
  461. package/src/Store/index.test.ts.bak +0 -112
  462. package/src/Store/service.ts +0 -193
  463. package/src/Store.ts +0 -1
  464. package/src/adapters/SQL.ts +0 -1
  465. package/src/adapters/ServiceBus.ts +0 -209
  466. package/src/adapters/cosmos-client.ts +0 -16
  467. package/src/adapters/index.ts +0 -0
  468. package/src/adapters/logger.ts +0 -3
  469. package/src/adapters/memQueue.ts +0 -21
  470. package/src/api/internal/auth.ts +0 -68
  471. package/src/api/middlewares.ts +0 -10
  472. package/src/api/routing/middleware.ts +0 -6
  473. package/src/api/routing.ts +0 -598
  474. package/src/api/setupRequest.ts +0 -104
  475. package/test/dist/date-query.test.d.ts.map +0 -1
  476. package/tsconfig.json.bak +0 -12
  477. /package/dist/{adapters/SQL.d.ts → SQL.d.ts} +0 -0
  478. /package/dist/{api/codec.d.ts → codec.d.ts} +0 -0
  479. /package/dist/{api/middlewares.d.ts → middlewares.d.ts} +0 -0
  480. /package/dist/{api/routing → routing}/schema/jwt.d.ts +0 -0
  481. /package/dist/{api/routing → routing}/tsort.d.ts +0 -0
  482. /package/dist/{api/util.d.ts → util.d.ts} +0 -0
  483. /package/src/{api/codec.ts → codec.ts} +0 -0
  484. /package/src/{api/internal → internal}/health.ts +0 -0
  485. /package/src/{api/routing → routing}/tsort.ts +0 -0
  486. /package/src/{api/util.ts → util.ts} +0 -0
@@ -0,0 +1,265 @@
1
+ /**
2
+ * E2E test for commit bb3f51d03 — `fix(infra): bind ContextMap to request scope
3
+ * for SSE streams`.
4
+ *
5
+ * Background
6
+ * ----------
7
+ * The RpcServer returns `HttpServerResponse.stream(...)` for streaming RPC
8
+ * resources. The body of that response keeps producing chunks AFTER the
9
+ * outer Effect that built the response has returned. `RequestContextMiddleware`
10
+ * provisions `ContextMapContainer` for the request. The acquireRelease
11
+ * inside `ContextMapContainer.layer` calls `clear()` on finalize, wiping
12
+ * the etag map and the per-request resolver/store cache.
13
+ *
14
+ * If that layer is built against a sub-scope of the outer Effect (the
15
+ * pre-fix behaviour: `Effect.provide(layer)`), the finalizer fires as soon
16
+ * as the middleware Effect returns the HttpServerResponse — i.e. between
17
+ * "handler done" and "first chunk written" — wiping ContextMap state that
18
+ * later chunks still need. In production this surfaces as spurious
19
+ * OptimisticConcurrencyException on writes that follow a streaming read.
20
+ *
21
+ * The fix binds the layer to the ambient request scope via
22
+ * `provideOnRequestScope`, so `clear()` only runs once the response body
23
+ * has fully drained.
24
+ *
25
+ * Reproduction strategy
26
+ * ---------------------
27
+ * - Mirror the production wiring: apply `RequestContextMiddleware` to the
28
+ * RPC router (see `boilerplate/api/src/router.ts`).
29
+ * - The stream handler sets an etag on the ContextMap BEFORE returning the
30
+ * Stream value.
31
+ * - The Stream emits three values 100ms apart; each emission reads back the
32
+ * etag via `getContextMap` and yields 1 if the value is still present,
33
+ * 0 otherwise.
34
+ * - Expectation: [1, 1, 1]. If the layer's `clear()` runs mid-stream the
35
+ * later chunks observe an empty map and the assertion fails (typically
36
+ * with [1, 0, 0] or [0, 0, 0]).
37
+ */
38
+ import { NodeHttpServer } from "@effect/platform-node"
39
+ import { expect, it } from "@effect/vitest"
40
+ import { ApiClientFactory, makeRpcClient } from "effect-app/client"
41
+ import { HttpRouter, HttpServer } from "effect-app/http"
42
+ import { DefaultGenericMiddlewares } from "effect-app/middleware"
43
+ import { MiddlewareMaker } from "effect-app/rpc"
44
+ import * as S from "effect-app/Schema"
45
+ import { getContextMap } from "effect-app/Store"
46
+ import * as Effect from "effect/Effect"
47
+ import { FetchHttpClient } from "effect/http"
48
+ import * as Layer from "effect/Layer"
49
+ import * as NetAddress from "effect/net/NetAddress"
50
+ import * as Option from "effect/Option"
51
+ import { RpcSerialization } from "effect/rpc"
52
+ import * as Stream from "effect/Stream"
53
+ import { createServer } from "http"
54
+ import { RequestContextMiddleware } from "../src/internal/RequestContextMiddleware.js"
55
+ import { makeRouter } from "../src/routing.js"
56
+ import { DefaultGenericMiddlewaresLive } from "../src/routing/middleware.js"
57
+ import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, SomeElseMiddleware, SomeElseMiddlewareLive, SomeService, Test, TestLive } from "./fixtures.js"
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Middleware — mirrors AppMiddleware shape used by the other rpc e2e tests.
61
+ // ---------------------------------------------------------------------------
62
+
63
+ class AppMiddleware extends MiddlewareMaker
64
+ .Tag<AppMiddleware>()("AppMiddleware", RequestContextMap)
65
+ .middleware(RequireRoles, Test)
66
+ .middleware(AllowAnonymous)
67
+ .middleware(SomeElseMiddleware)
68
+ .middleware(...DefaultGenericMiddlewares)
69
+ {
70
+ static Default = this.layer.pipe(
71
+ Layer.provide(
72
+ [
73
+ RequireRolesLive.pipe(Layer.provide(SomeService.Default)),
74
+ AllowAnonymousLive,
75
+ TestLive,
76
+ SomeElseMiddlewareLive,
77
+ DefaultGenericMiddlewaresLive
78
+ ] as const
79
+ )
80
+ )
81
+ }
82
+
83
+ const { Router, matchAll } = makeRouter(AppMiddleware.Default)
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Resource — single streaming command that exercises ContextMap mid-stream.
87
+ // ---------------------------------------------------------------------------
88
+
89
+ const { TaggedRequestFor } = makeRpcClient(AppMiddleware)
90
+ const Req = TaggedRequestFor("CtxMap")
91
+
92
+ class StreamEtag extends Req.Command<StreamEtag>()("StreamEtag", {}, {
93
+ stream: true,
94
+ allowAnonymous: true,
95
+ success: S.Number
96
+ }) {}
97
+
98
+ // Per-request isolation probes: each handler writes a caller-supplied value to
99
+ // the SHARED key, then emits 3 chunks each re-reading the SHARED key. If two
100
+ // concurrent requests share a ContextMap, the second writer overwrites the
101
+ // first and the first request observes the wrong value mid-stream.
102
+ class StreamWithEtag extends Req.Command<StreamWithEtag>()("StreamWithEtag", {
103
+ value: S.String
104
+ }, {
105
+ stream: true,
106
+ allowAnonymous: true,
107
+ success: S.String
108
+ }) {}
109
+
110
+ class ReadEtagOnce extends Req.Query<ReadEtagOnce>()("ReadEtagOnce", {}, {
111
+ allowAnonymous: true,
112
+ success: S.String
113
+ }) {}
114
+
115
+ const Rsc = { StreamEtag, StreamWithEtag, ReadEtagOnce }
116
+
117
+ // Distinct constants so an assertion failure points squarely at "the etag
118
+ // the handler wrote was no longer there when later chunks ran".
119
+ const ETAG_ID = "ctxmap-test-id"
120
+ const ETAG_VALUE = "v1"
121
+ const SHARED_KEY = "ctxmap-shared-key"
122
+ const MISSING = "<missing>"
123
+
124
+ const router = Router(Rsc)({
125
+ *effect(match) {
126
+ return match({
127
+ StreamEtag: () =>
128
+ Effect
129
+ .gen(function*() {
130
+ // 1) Acquire the request-scoped ContextMap. Fails (dies) if the
131
+ // container is still the default "root" — which would mean
132
+ // RequestContextMiddleware did not run for this request.
133
+ const ctxMap = yield* getContextMap.pipe(Effect.orDie)
134
+ // 2) Seed an etag BEFORE handing back the Stream. This write is
135
+ // what the per-chunk readers below verify.
136
+ ctxMap.set(ETAG_ID, ETAG_VALUE)
137
+ // 3) Emit three values 100ms apart so chunks are produced AFTER
138
+ // the outer Effect that built the response has returned. Each
139
+ // emission re-reads the etag from the request-scoped ContextMap.
140
+ return Stream.fromIterable([0, 1, 2]).pipe(
141
+ Stream.mapEffect(() =>
142
+ Effect.sleep("100 millis").pipe(
143
+ Effect.flatMap(() => getContextMap.pipe(Effect.orDie)),
144
+ Effect.map((m) => m.get(ETAG_ID) === ETAG_VALUE ? 1 : 0)
145
+ )
146
+ )
147
+ )
148
+ })
149
+ .pipe(Stream.unwrap),
150
+ StreamWithEtag: ({ value }: { readonly value: string }) =>
151
+ Effect
152
+ .gen(function*() {
153
+ const ctxMap = yield* getContextMap.pipe(Effect.orDie)
154
+ ctxMap.set(SHARED_KEY, value)
155
+ return Stream.fromIterable([0, 1, 2]).pipe(
156
+ Stream.mapEffect(() =>
157
+ Effect.sleep("100 millis").pipe(
158
+ Effect.flatMap(() => getContextMap.pipe(Effect.orDie)),
159
+ Effect.map((m) => m.get(SHARED_KEY) ?? MISSING)
160
+ )
161
+ )
162
+ )
163
+ })
164
+ .pipe(Stream.unwrap),
165
+ ReadEtagOnce: () => getContextMap.pipe(Effect.orDie, Effect.map((m) => m.get(SHARED_KEY) ?? MISSING))
166
+ })
167
+ }
168
+ })
169
+
170
+ const RpcRouterLayer = matchAll({ router })
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // HTTP wiring — fresh server on a loopback port per `it.live`. The critical
174
+ // difference vs. rpc-stream-fullstack: we apply `RequestContextMiddleware`
175
+ // here, exactly as the production boilerplate does, so the fix code path
176
+ // is what runs.
177
+ // ---------------------------------------------------------------------------
178
+
179
+ const NodeServerLayer = NodeHttpServer.layer(() => createServer(), { port: 0 })
180
+
181
+ const RequestContextMiddlewareLayer = HttpRouter.middleware(RequestContextMiddleware()).layer
182
+
183
+ const ServerLayer = HttpRouter
184
+ .serve(
185
+ RpcRouterLayer.pipe(Layer.provide(RequestContextMiddlewareLayer))
186
+ )
187
+ .pipe(
188
+ Layer.provide(NodeServerLayer),
189
+ Layer.provide(RpcSerialization.layerNdjson)
190
+ )
191
+
192
+ const ClientLayer = Layer
193
+ .unwrap(
194
+ Effect.gen(function*() {
195
+ const server = yield* HttpServer.HttpServer
196
+ const addr = server.address
197
+ if (NetAddress.isUnixPathAddress(addr)) {
198
+ return yield* Effect.die(new Error("expected inet address"))
199
+ }
200
+ const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address)
201
+ const url = `http://${host}:${addr.port}`
202
+ return ApiClientFactory
203
+ .layer({ url, headers: Option.none() })
204
+ .pipe(Layer.provide(FetchHttpClient.layer))
205
+ })
206
+ )
207
+ .pipe(Layer.provide(NodeServerLayer))
208
+
209
+ const TestLayer = Layer.mergeAll(ServerLayer, ClientLayer)
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Test
213
+ // ---------------------------------------------------------------------------
214
+
215
+ it.live(
216
+ "ContextMap survives mid-stream: etag set in handler is readable by every chunk",
217
+ Effect.fnUntraced(function*() {
218
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(Rsc)
219
+ const values = yield* Stream.runCollect(client.StreamEtag.handler())
220
+ // All three chunks emit 1 → the etag was still readable when each chunk
221
+ // executed. If the layer-bound ContextMap's `clear()` finalizer fired
222
+ // mid-stream (the pre-fix behaviour), later chunks would emit 0.
223
+ expect(values).toStrictEqual([1, 1, 1])
224
+ }, Effect.provide(TestLayer)),
225
+ { timeout: 10_000 }
226
+ )
227
+
228
+ it.live(
229
+ "succeeding requests get a fresh ContextMap: request N+1 cannot see request N's writes",
230
+ Effect.fnUntraced(function*() {
231
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(Rsc)
232
+ // 1st request writes SHARED_KEY = "first" and drains its stream so its
233
+ // request scope (and ContextMap) is closed before request 2 starts.
234
+ const first = yield* Stream.runCollect(client.StreamWithEtag.handler({ value: "first" }))
235
+ expect(first).toStrictEqual(["first", "first", "first"])
236
+ // 2nd request must NOT observe the previous request's value at any point.
237
+ const peek = yield* client.ReadEtagOnce.handler()
238
+ expect(peek).toBe(MISSING)
239
+ // 3rd request writes a different value and drains; must not be polluted by request 1.
240
+ const third = yield* Stream.runCollect(client.StreamWithEtag.handler({ value: "third" }))
241
+ expect(third).toStrictEqual(["third", "third", "third"])
242
+ }, Effect.provide(TestLayer)),
243
+ { timeout: 10_000 }
244
+ )
245
+
246
+ it.live(
247
+ "overlapping requests get isolated ContextMaps: concurrent streams see only their own writes",
248
+ Effect.fnUntraced(function*() {
249
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(Rsc)
250
+ // Two streams in flight at the same time, each writing the SAME key with a
251
+ // different value. With per-request maps each stream reads back only its
252
+ // own value across all chunks. With a shared map the later writer's value
253
+ // would leak into the earlier stream's later chunks.
254
+ const [a, b] = yield* Effect.all(
255
+ [
256
+ Stream.runCollect(client.StreamWithEtag.handler({ value: "alpha" })),
257
+ Stream.runCollect(client.StreamWithEtag.handler({ value: "beta" }))
258
+ ],
259
+ { concurrency: "unbounded" }
260
+ )
261
+ expect(a).toStrictEqual(["alpha", "alpha", "alpha"])
262
+ expect(b).toStrictEqual(["beta", "beta", "beta"])
263
+ }, Effect.provide(TestLayer)),
264
+ { timeout: 10_000 }
265
+ )
@@ -0,0 +1,421 @@
1
+ /**
2
+ * E2E tests for the invalidation key flow exercised end-to-end via the
3
+ * production wrap/unwrap path:
4
+ *
5
+ * server: routing.ts wraps command success with `CommandResponseWithMetaData`
6
+ * and handler-thrown failure with `CommandFailureWithMetaData`;
7
+ * routing wraps stream values into `{_tag:"value"|"metadata"|"done"}`
8
+ * chunks. `InvalidationSet.use(...)` inside a handler accumulates keys.
9
+ * client: apiClientFactory unwraps both envelopes and forwards keys to
10
+ * `InvalidationKeysFromServer`.
11
+ *
12
+ * Transport is real HTTP (NodeHttpServer on a loopback port) so the wire
13
+ * encoding is exercised too.
14
+ */
15
+ import { NodeHttpServer } from "@effect/platform-node"
16
+ import { expect, it } from "@effect/vitest"
17
+ import { ApiClientFactory, DataDependencies, InvalidationKeysFromServer, InvalidStateError, makeInvalidationKeysService, makeRpcClient, OptimisticConcurrencyException } from "effect-app/client"
18
+ import * as Context from "effect-app/Context"
19
+ import { HttpRouter, HttpServer } from "effect-app/http"
20
+ import { DefaultGenericMiddlewares } from "effect-app/middleware"
21
+ import { makeRepo, RepositoryRegistryLive } from "effect-app/Model"
22
+ import { Invalidation, MiddlewareMaker } from "effect-app/rpc"
23
+ import * as S from "effect-app/Schema"
24
+ import { TaggedErrorClass } from "effect-app/Schema"
25
+ import * as Cause from "effect/Cause"
26
+ import * as Effect from "effect/Effect"
27
+ import * as Exit from "effect/Exit"
28
+ import { FetchHttpClient } from "effect/http"
29
+ import * as Layer from "effect/Layer"
30
+ import * as NetAddress from "effect/net/NetAddress"
31
+ import * as Option from "effect/Option"
32
+ import * as Ref from "effect/Ref"
33
+ import { RpcSerialization } from "effect/rpc"
34
+ import * as Stream from "effect/Stream"
35
+ import { createServer } from "http"
36
+ import { RequestContextMiddleware } from "../src/internal/RequestContextMiddleware.js"
37
+ import { makeRouter } from "../src/routing.js"
38
+ import { DefaultGenericMiddlewaresLive } from "../src/routing/middleware.js"
39
+ import { MemoryStoreLive } from "../src/Store/Memory.js"
40
+ import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, SomeElseMiddleware, SomeElseMiddlewareLive, SomeService, Test, TestLive } from "./fixtures.js"
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Middleware (mirrors AppMiddleware shape — same composite as other e2e tests).
44
+ // ---------------------------------------------------------------------------
45
+
46
+ class AppMiddleware extends MiddlewareMaker
47
+ .Tag<AppMiddleware>()("AppMiddleware", RequestContextMap)
48
+ .middleware(RequireRoles, Test)
49
+ .middleware(AllowAnonymous)
50
+ .middleware(SomeElseMiddleware)
51
+ .middleware(...DefaultGenericMiddlewares)
52
+ {
53
+ static Default = this.layer.pipe(
54
+ Layer.provide(
55
+ [
56
+ RequireRolesLive.pipe(Layer.provide(SomeService.Default)),
57
+ AllowAnonymousLive,
58
+ TestLive,
59
+ SomeElseMiddlewareLive,
60
+ DefaultGenericMiddlewaresLive
61
+ ] as const
62
+ )
63
+ )
64
+ }
65
+
66
+ const { Router, matchAll } = makeRouter(AppMiddleware.Default)
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Resources
70
+ // ---------------------------------------------------------------------------
71
+
72
+ const DynamicKey: Invalidation.InvalidationKey = ["dynamic", "key"]
73
+ const ExtraKey: Invalidation.InvalidationKey = ["extra", "key"]
74
+ const StreamKey: Invalidation.InvalidationKey = ["stream", "key"]
75
+
76
+ const { TaggedRequestFor } = makeRpcClient(AppMiddleware)
77
+ const Req = TaggedRequestFor("Inv")
78
+
79
+ class CmdBoom extends TaggedErrorClass<CmdBoom>()("CmdBoom", { reason: S.String }) {}
80
+
81
+ class DoNothing extends Req.Command<DoNothing>()("DoNothing", {}, {
82
+ allowAnonymous: true,
83
+ success: S.Void
84
+ }) {}
85
+
86
+ class DoWithDynamicKey extends Req.Command<DoWithDynamicKey>()("DoWithDynamicKey", {}, {
87
+ allowAnonymous: true,
88
+ success: S.String
89
+ }) {}
90
+
91
+ class DoWithBothKeys extends Req.Command<DoWithBothKeys>()("DoWithBothKeys", {}, {
92
+ allowAnonymous: true,
93
+ success: S.Number
94
+ }) {}
95
+
96
+ class DoAndFail extends Req.Command<DoAndFail>()("DoAndFail", {}, {
97
+ allowAnonymous: true,
98
+ success: S.Void,
99
+ error: CmdBoom
100
+ }) {}
101
+
102
+ class StreamWithKey extends Req.Command<StreamWithKey>()("StreamWithKey", {}, {
103
+ stream: true,
104
+ allowAnonymous: true,
105
+ success: S.Number
106
+ }) {}
107
+
108
+ class StreamWithRepoWrite extends Req.Command<StreamWithRepoWrite>()("StreamWithRepoWrite", {}, {
109
+ stream: true,
110
+ allowAnonymous: true,
111
+ success: S.Number
112
+ }) {}
113
+
114
+ class RepoItem extends S.Class<RepoItem>("RepoItem")({
115
+ id: S.String,
116
+ label: S.String
117
+ }) {}
118
+
119
+ class GetRepoCount extends Req.Query<GetRepoCount>()("GetRepoCount", {}, {
120
+ allowAnonymous: true,
121
+ success: S.Number
122
+ }) {}
123
+
124
+ class SaveRepoItem extends Req.Command<SaveRepoItem>()("SaveRepoItem", {
125
+ id: S.String,
126
+ label: S.String
127
+ }, {
128
+ allowAnonymous: true,
129
+ error: S.Union([InvalidStateError, OptimisticConcurrencyException]),
130
+ success: S.Void
131
+ }) {}
132
+
133
+ class RepoItems extends Context.Service<RepoItems>()("RepoItems", {
134
+ make: makeRepo("RepoItem", RepoItem, {})
135
+ }) {
136
+ static Default = Layer.effect(this, this.make).pipe(
137
+ Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
138
+ )
139
+ }
140
+
141
+ // A second, unrelated repo — its writes must NOT invalidate a query that only read `RepoItem`.
142
+ class OtherItem extends S.Class<OtherItem>("OtherItem")({
143
+ id: S.String,
144
+ label: S.String
145
+ }) {}
146
+
147
+ class SaveOtherItem extends Req.Command<SaveOtherItem>()("SaveOtherItem", {
148
+ id: S.String,
149
+ label: S.String
150
+ }, {
151
+ allowAnonymous: true,
152
+ success: S.Void
153
+ }) {}
154
+
155
+ class OtherItems extends Context.Service<OtherItems>()("OtherItems", {
156
+ make: makeRepo("OtherItem", OtherItem, {})
157
+ }) {
158
+ static Default = Layer.effect(this, this.make).pipe(
159
+ Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
160
+ )
161
+ }
162
+
163
+ const InvRsc = {
164
+ DoNothing,
165
+ DoWithDynamicKey,
166
+ DoWithBothKeys,
167
+ DoAndFail,
168
+ StreamWithKey,
169
+ StreamWithRepoWrite,
170
+ GetRepoCount,
171
+ SaveRepoItem,
172
+ SaveOtherItem
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Controllers / router
177
+ // ---------------------------------------------------------------------------
178
+
179
+ const router = Router(InvRsc)({
180
+ dependencies: [RepoItems.Default, OtherItems.Default],
181
+ *effect(match) {
182
+ const repo = yield* RepoItems
183
+ const otherRepo = yield* OtherItems
184
+ return match({
185
+ DoNothing: () => Effect.void,
186
+ DoWithDynamicKey: Effect.fnUntraced(function*() {
187
+ yield* Invalidation.InvalidationSet.use((_) => _.add(DynamicKey))
188
+ return "done"
189
+ }),
190
+ DoWithBothKeys: Effect.fnUntraced(function*() {
191
+ yield* Invalidation.InvalidationSet.use((_) => _.add(DynamicKey))
192
+ yield* Invalidation.InvalidationSet.use((_) => _.add(ExtraKey))
193
+ return 99
194
+ }),
195
+ DoAndFail: Effect.fnUntraced(function*() {
196
+ yield* Invalidation.InvalidationSet.use((_) => _.add(DynamicKey))
197
+ return yield* Effect.fail(new CmdBoom({ reason: "intentional failure" }))
198
+ }),
199
+ StreamWithKey: () =>
200
+ Stream.fromIterable([1, 2, 3]).pipe(
201
+ Stream.tap(() => Invalidation.InvalidationSet.use((_) => _.add(StreamKey)))
202
+ ),
203
+ StreamWithRepoWrite: () =>
204
+ Stream.fromIterable([1, 2, 3]).pipe(
205
+ Stream.tap((n) => repo.save(new RepoItem({ id: String(n), label: "x" })).pipe(Effect.orDie))
206
+ ),
207
+ GetRepoCount: () => repo.all.pipe(Effect.map((_) => _.length), Effect.orDie),
208
+ SaveRepoItem: ({ id, label }) => repo.save(new RepoItem({ id, label })).pipe(Effect.orDie),
209
+ SaveOtherItem: ({ id, label }) => otherRepo.save(new OtherItem({ id, label })).pipe(Effect.orDie)
210
+ })
211
+ }
212
+ })
213
+
214
+ const RpcRouterLayer = matchAll({ router })
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // HTTP wiring — fresh server on loopback per `it.live`.
218
+ // ---------------------------------------------------------------------------
219
+
220
+ const NodeServerLayer = NodeHttpServer.layer(() => createServer(), { port: 0 })
221
+
222
+ const ServerLayer = HttpRouter
223
+ .serve(RpcRouterLayer.pipe(Layer.provide(HttpRouter.middleware(RequestContextMiddleware()).layer)))
224
+ .pipe(
225
+ Layer.provide(NodeServerLayer),
226
+ Layer.provide(RpcSerialization.layerNdjson)
227
+ )
228
+
229
+ const ClientLayer = Layer
230
+ .unwrap(
231
+ Effect.gen(function*() {
232
+ const server = yield* HttpServer.HttpServer
233
+ const addr = server.address
234
+ if (NetAddress.isUnixPathAddress(addr)) {
235
+ return yield* Effect.die(new Error("expected inet address"))
236
+ }
237
+ const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address)
238
+ const url = `http://${host}:${addr.port}`
239
+ return ApiClientFactory
240
+ .layer({ url, headers: Option.none() })
241
+ .pipe(Layer.provide(FetchHttpClient.layer))
242
+ })
243
+ )
244
+ .pipe(Layer.provide(NodeServerLayer))
245
+
246
+ const TestLayer = Layer.mergeAll(ServerLayer, ClientLayer)
247
+
248
+ // Helper: provide a fresh `InvalidationKeysFromServer` and capture forwarded keys.
249
+ const withCapture = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
250
+ Effect.gen(function*() {
251
+ const ref = yield* Ref.make<ReadonlyArray<Invalidation.InvalidationKey>>([])
252
+ const svc = makeInvalidationKeysService(ref)
253
+ const result = yield* eff.pipe(Effect.provideService(InvalidationKeysFromServer, svc), Effect.exit)
254
+ return { result, keys: yield* Ref.get(ref) }
255
+ })
256
+
257
+ const withDependencyCapture = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
258
+ Effect.gen(function*() {
259
+ const readsRef = yield* Ref.make(DataDependencies.empty())
260
+ const writesRef = yield* Ref.make(DataDependencies.empty())
261
+ const svc = DataDependencies.makeDataDependencyRecorder(readsRef, writesRef)
262
+ const result = yield* eff.pipe(Effect.provideService(DataDependencies.DataDependencyRecorder, svc), Effect.exit)
263
+ return { result, dependencies: yield* svc.get }
264
+ })
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Tests
268
+ // ---------------------------------------------------------------------------
269
+
270
+ it.live(
271
+ "command with no invalidation keys: caller sees raw payload, no keys forwarded",
272
+ Effect.fnUntraced(function*() {
273
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
274
+ const { result, keys } = yield* withCapture(client.DoNothing.handler())
275
+ expect(Exit.isSuccess(result)).toBe(true)
276
+ expect(keys).toStrictEqual([])
277
+ }, Effect.provide(TestLayer)),
278
+ { timeout: 10_000 }
279
+ )
280
+
281
+ it.live(
282
+ "command with dynamic InvalidationSet.use: payload + key forwarded",
283
+ Effect.fnUntraced(function*() {
284
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
285
+ const { result, keys } = yield* withCapture(client.DoWithDynamicKey.handler())
286
+ expect(Exit.isSuccess(result) && result.value).toBe("done")
287
+ expect(keys).toStrictEqual([DynamicKey])
288
+ }, Effect.provide(TestLayer)),
289
+ { timeout: 10_000 }
290
+ )
291
+
292
+ it.live(
293
+ "command accumulating multiple dynamic keys: all keys forwarded in order",
294
+ Effect.fnUntraced(function*() {
295
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
296
+ const { result, keys } = yield* withCapture(client.DoWithBothKeys.handler())
297
+ expect(Exit.isSuccess(result) && result.value).toBe(99)
298
+ expect(keys).toStrictEqual([DynamicKey, ExtraKey])
299
+ }, Effect.provide(TestLayer)),
300
+ { timeout: 10_000 }
301
+ )
302
+
303
+ it.live(
304
+ "per-request isolation: each command call starts with a fresh InvalidationSet",
305
+ Effect.fnUntraced(function*() {
306
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
307
+ const r1 = yield* withCapture(client.DoWithDynamicKey.handler())
308
+ const r2 = yield* withCapture(client.DoWithDynamicKey.handler())
309
+ // Each call must have exactly one key — no accumulation across calls
310
+ expect(r1.keys).toStrictEqual([DynamicKey])
311
+ expect(r2.keys).toStrictEqual([DynamicKey])
312
+ }, Effect.provide(TestLayer)),
313
+ { timeout: 10_000 }
314
+ )
315
+
316
+ it.live(
317
+ "command failure (V2): keys accumulated before fail still reach the client; original error re-thrown",
318
+ Effect.fnUntraced(function*() {
319
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
320
+ const { result, keys } = yield* withCapture(client.DoAndFail.handler())
321
+ expect(Exit.isFailure(result)).toBe(true)
322
+ if (Exit.isFailure(result)) {
323
+ const error = Cause.findErrorOption(result.cause)
324
+ expect(Option.isSome(error)).toBe(true)
325
+ if (Option.isSome(error)) {
326
+ expect(error.value._tag).toBe("CmdBoom")
327
+ expect(error.value.reason).toBe("intentional failure")
328
+ }
329
+ }
330
+ expect(keys).toStrictEqual([DynamicKey])
331
+ }, Effect.provide(TestLayer)),
332
+ { timeout: 10_000 }
333
+ )
334
+
335
+ it.live(
336
+ "stream: per-chunk metadata accumulates distinct keys",
337
+ Effect.fnUntraced(function*() {
338
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
339
+ const ref = yield* Ref.make<ReadonlyArray<Invalidation.InvalidationKey>>([])
340
+ const svc = makeInvalidationKeysService(ref)
341
+ const values = yield* Stream.runCollect(client.StreamWithKey.handler()).pipe(
342
+ Effect.provideService(InvalidationKeysFromServer, svc)
343
+ )
344
+ const keys = yield* Ref.get(ref)
345
+ expect(values).toStrictEqual([1, 2, 3])
346
+ expect(keys).toStrictEqual([StreamKey])
347
+ }, Effect.provide(TestLayer)),
348
+ { timeout: 10_000 }
349
+ )
350
+
351
+ it.live(
352
+ "stream: per-chunk repo writes are drained and forwarded to the client recorder",
353
+ Effect.fnUntraced(function*() {
354
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
355
+ const readsRef = yield* Ref.make(DataDependencies.empty())
356
+ const writesRef = yield* Ref.make(DataDependencies.empty())
357
+ const svc = DataDependencies.makeDataDependencyRecorder(readsRef, writesRef)
358
+ const values = yield* Stream.runCollect(client.StreamWithRepoWrite.handler()).pipe(
359
+ Effect.provideService(DataDependencies.DataDependencyRecorder, svc)
360
+ )
361
+ const writes = yield* Ref.get(writesRef)
362
+ expect(values).toStrictEqual([1, 2, 3])
363
+ // Each emitted value writes to RepoItem; routing drains the writes per chunk and the client
364
+ // accumulates their entity ids into one dependency.
365
+ expect(writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem", ["1", "2", "3"])]))
366
+ }, Effect.provide(TestLayer)),
367
+ { timeout: 10_000 }
368
+ )
369
+
370
+ it.live(
371
+ "repository dependencies flow through query and command metadata",
372
+ Effect.fnUntraced(function*() {
373
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
374
+
375
+ const query = yield* withDependencyCapture(client.GetRepoCount.handler())
376
+ expect(Exit.isSuccess(query.result) && query.result.value).toBe(0)
377
+ expect(query.dependencies.reads).toStrictEqual(new Set([DataDependencies.repo("RepoItem")]))
378
+ expect(query.dependencies.writes).toStrictEqual(DataDependencies.empty())
379
+
380
+ const command = yield* withDependencyCapture(client.SaveRepoItem.handler({ id: "1", label: "one" }))
381
+ expect(Exit.isSuccess(command.result)).toBe(true)
382
+ expect(command.dependencies.reads).toStrictEqual(DataDependencies.empty())
383
+ expect(command.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("RepoItem", ["1"])]))
384
+ }, Effect.provide(TestLayer)),
385
+ { timeout: 10_000 }
386
+ )
387
+
388
+ it.live(
389
+ "query invalidation: a command's writes invalidate exactly the queries whose reads intersect",
390
+ Effect.fnUntraced(function*() {
391
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(InvRsc)
392
+
393
+ // Client-side query registry: queryKey -> read dependencies forwarded by the server. Mirrors
394
+ // @effect-app/vue's `dependencyMetadata`; the derivation predicate (`intersects`) is the exact
395
+ // one the vue mutate engine uses to pick invalidation targets from a command's writes.
396
+ const queryReads = new Map<string, DataDependencies.DataDependencies>()
397
+ const invalidatedBy = (writes: DataDependencies.DataDependencies) =>
398
+ [...queryReads]
399
+ .filter(([, reads]) => DataDependencies.intersects(reads, writes))
400
+ .map(([key]) => key)
401
+
402
+ // Run the query through the real client; register its forwarded reads under its key.
403
+ const query = yield* withDependencyCapture(client.GetRepoCount.handler())
404
+ expect(Exit.isSuccess(query.result)).toBe(true)
405
+ queryReads.set("GetRepoCount", query.dependencies.reads)
406
+ // Before any command, nothing is invalidated.
407
+ expect(invalidatedBy(DataDependencies.empty())).toStrictEqual([])
408
+
409
+ // A command writing the SAME repo the query read => the query is selected for invalidation.
410
+ const save = yield* withDependencyCapture(client.SaveRepoItem.handler({ id: "1", label: "one" }))
411
+ expect(Exit.isSuccess(save.result)).toBe(true)
412
+ expect(invalidatedBy(save.dependencies.writes)).toStrictEqual(["GetRepoCount"])
413
+
414
+ // A command writing an UNRELATED repo => the query is NOT invalidated (negative control).
415
+ const saveOther = yield* withDependencyCapture(client.SaveOtherItem.handler({ id: "2", label: "two" }))
416
+ expect(Exit.isSuccess(saveOther.result)).toBe(true)
417
+ expect(saveOther.dependencies.writes).toStrictEqual(new Set([DataDependencies.repo("OtherItem", ["2"])]))
418
+ expect(invalidatedBy(saveOther.dependencies.writes)).toStrictEqual([])
419
+ }, Effect.provide(TestLayer)),
420
+ { timeout: 10_000 }
421
+ )