@effect-app/infra 4.0.0-beta.33 → 4.0.0-beta.330

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 (490) hide show
  1. package/CHANGELOG.md +2555 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +29 -10
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +36 -15
  6. package/dist/ClusterCosmos.d.ts +64 -0
  7. package/dist/ClusterCosmos.d.ts.map +1 -0
  8. package/dist/ClusterCosmos.js +576 -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/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 +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 +12 -8
  22. package/dist/MainFiberSet.d.ts.map +1 -1
  23. package/dist/MainFiberSet.js +23 -7
  24. package/dist/QueueMaker/errors.d.ts +4 -2
  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 +31 -42
  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 +395 -256
  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 +93 -42
  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 +349 -79
  44. package/dist/Store/SQL/Pg.d.ts +4 -0
  45. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  46. package/dist/Store/SQL/Pg.js +261 -0
  47. package/dist/Store/SQL/query.d.ts +53 -0
  48. package/dist/Store/SQL/query.d.ts.map +1 -0
  49. package/dist/Store/SQL/query.js +656 -0
  50. package/dist/Store/SQL.d.ts +28 -0
  51. package/dist/Store/SQL.d.ts.map +1 -0
  52. package/dist/Store/SQL.js +483 -0
  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 +18 -5
  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 +24 -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/internal/health.d.ts.map +1 -0
  98. package/dist/internal/health.js +5 -0
  99. package/dist/{api/layerUtils.d.ts → layerUtils.d.ts} +14 -6
  100. package/dist/layerUtils.d.ts.map +1 -0
  101. package/dist/layerUtils.js +17 -0
  102. package/dist/logger/jsonLogger.d.ts +1 -1
  103. package/dist/logger/jsonLogger.d.ts.map +1 -1
  104. package/dist/logger/jsonLogger.js +5 -3
  105. package/dist/logger/logFmtLogger.d.ts +1 -1
  106. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  107. package/dist/logger/logFmtLogger.js +3 -3
  108. package/dist/logger/shared.d.ts +2 -2
  109. package/dist/logger/shared.d.ts.map +1 -1
  110. package/dist/logger/shared.js +6 -6
  111. package/dist/logger.d.ts +4 -4
  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} +3 -2
  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} +5 -2
  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 +4 -3
  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/routing/middleware/middleware.d.ts +48 -0
  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 +25 -21
  157. package/package.json +38 -352
  158. package/src/CUPS.ts +54 -15
  159. package/src/ClusterCosmos.ts +1150 -0
  160. package/src/ClusterServiceBus.ts +244 -0
  161. package/src/{api/ContextProvider.ts → ContextProvider.ts} +20 -17
  162. package/src/Emailer/Sendgrid.ts +83 -60
  163. package/src/Emailer/fake.ts +4 -4
  164. package/src/MainFiberSet.ts +32 -13
  165. package/src/QueueMaker/errors.ts +3 -1
  166. package/src/{adapters/SQL → SQL}/Model.ts +42 -41
  167. package/src/SQL.ts +1 -0
  168. package/src/Store/Cosmos/query.ts +275 -55
  169. package/src/Store/Cosmos.ts +547 -366
  170. package/src/Store/Disk.ts +146 -73
  171. package/src/Store/Memory.ts +420 -115
  172. package/src/Store/SQL/Pg.ts +412 -0
  173. package/src/Store/SQL/query.ts +748 -0
  174. package/src/Store/SQL.ts +806 -0
  175. package/src/Store/codeFilter.ts +78 -28
  176. package/src/Store/index.ts +25 -10
  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 +56 -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 +22 -13
  189. package/src/{api/layerUtils.ts → layerUtils.ts} +14 -10
  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} +4 -2
  195. package/src/otel.ts +177 -0
  196. package/src/rateLimit.ts +35 -24
  197. package/src/{adapters/redis-client.ts → redis-client.ts} +7 -3
  198. package/src/{api/reportError.ts → reportError.ts} +3 -2
  199. package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +5 -4
  200. package/src/routing/middleware/middleware.ts +196 -0
  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 +612 -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 +15 -12
  214. package/test/controller.test.ts +27 -31
  215. package/test/cosmos-container-throughput.test.ts +61 -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 +29 -11
  228. package/test/dist/fixtures.d.ts.map +1 -1
  229. package/test/dist/fixtures.js +17 -10
  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/sql-store.test.d.ts.map +1 -0
  243. package/test/dist/workflow-engine-cosmos.test.d.ts.map +1 -0
  244. package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
  245. package/test/fixtures.ts +16 -9
  246. package/test/json-lower.test.ts +129 -0
  247. package/test/layerUtils.test.ts +2 -2
  248. package/test/query.test.ts +1382 -44
  249. package/test/rawQuery.test.ts +433 -8
  250. package/test/repository-ext.test.ts +456 -0
  251. package/test/repository-legacy-document.test.ts +394 -0
  252. package/test/requires.test.ts +10 -5
  253. package/test/router-generator.test.ts +187 -0
  254. package/test/routing-interruptibility.test.ts +66 -0
  255. package/test/rpc-context-map-streaming.test.ts +265 -0
  256. package/test/rpc-e2e-invalidation.test.ts +421 -0
  257. package/test/rpc-multi-middleware.test.ts +85 -10
  258. package/test/rpc-stream-fullstack.test.ts +307 -0
  259. package/test/sql-store.test.ts +1931 -0
  260. package/test/validateSample.test.ts +71 -43
  261. package/test/workflow-engine-cosmos.test.ts +345 -0
  262. package/test/workflow-engine-sqlite.test.ts +297 -0
  263. package/tsconfig.examples.json +1 -1
  264. package/tsconfig.json +9 -2
  265. package/tsconfig.src.json +35 -35
  266. package/tsconfig.test.json +2 -2
  267. package/dist/Emailer/service.d.ts +0 -55
  268. package/dist/Emailer/service.d.ts.map +0 -1
  269. package/dist/Emailer/service.js +0 -6
  270. package/dist/Emailer.d.ts +0 -2
  271. package/dist/Emailer.d.ts.map +0 -1
  272. package/dist/Emailer.js +0 -2
  273. package/dist/Model/Repository/ext.d.ts +0 -41
  274. package/dist/Model/Repository/ext.d.ts.map +0 -1
  275. package/dist/Model/Repository/ext.js +0 -65
  276. package/dist/Model/Repository/internal/internal.d.ts +0 -59
  277. package/dist/Model/Repository/internal/internal.d.ts.map +0 -1
  278. package/dist/Model/Repository/internal/internal.js +0 -316
  279. package/dist/Model/Repository/legacy.d.ts +0 -19
  280. package/dist/Model/Repository/legacy.d.ts.map +0 -1
  281. package/dist/Model/Repository/legacy.js +0 -2
  282. package/dist/Model/Repository/makeRepo.d.ts +0 -49
  283. package/dist/Model/Repository/makeRepo.d.ts.map +0 -1
  284. package/dist/Model/Repository/makeRepo.js +0 -24
  285. package/dist/Model/Repository/service.d.ts +0 -89
  286. package/dist/Model/Repository/service.d.ts.map +0 -1
  287. package/dist/Model/Repository/service.js +0 -2
  288. package/dist/Model/Repository/validation.d.ts +0 -42
  289. package/dist/Model/Repository/validation.d.ts.map +0 -1
  290. package/dist/Model/Repository/validation.js +0 -32
  291. package/dist/Model/Repository.d.ts +0 -6
  292. package/dist/Model/Repository.d.ts.map +0 -1
  293. package/dist/Model/Repository.js +0 -6
  294. package/dist/Model/dsl.d.ts +0 -32
  295. package/dist/Model/dsl.d.ts.map +0 -1
  296. package/dist/Model/dsl.js +0 -44
  297. package/dist/Model/filter/filterApi.d.ts +0 -30
  298. package/dist/Model/filter/filterApi.d.ts.map +0 -1
  299. package/dist/Model/filter/filterApi.js +0 -2
  300. package/dist/Model/filter/types/errors.d.ts +0 -29
  301. package/dist/Model/filter/types/errors.d.ts.map +0 -1
  302. package/dist/Model/filter/types/errors.js +0 -2
  303. package/dist/Model/filter/types/fields.d.ts +0 -15
  304. package/dist/Model/filter/types/fields.d.ts.map +0 -1
  305. package/dist/Model/filter/types/fields.js +0 -2
  306. package/dist/Model/filter/types/path/common.d.ts +0 -316
  307. package/dist/Model/filter/types/path/common.d.ts.map +0 -1
  308. package/dist/Model/filter/types/path/common.js +0 -2
  309. package/dist/Model/filter/types/path/eager.d.ts +0 -95
  310. package/dist/Model/filter/types/path/eager.d.ts.map +0 -1
  311. package/dist/Model/filter/types/path/eager.js +0 -31
  312. package/dist/Model/filter/types/path/index.d.ts +0 -4
  313. package/dist/Model/filter/types/path/index.d.ts.map +0 -1
  314. package/dist/Model/filter/types/path/index.js +0 -3
  315. package/dist/Model/filter/types/utils.d.ts +0 -79
  316. package/dist/Model/filter/types/utils.d.ts.map +0 -1
  317. package/dist/Model/filter/types/utils.js +0 -2
  318. package/dist/Model/filter/types/validator.d.ts +0 -30
  319. package/dist/Model/filter/types/validator.d.ts.map +0 -1
  320. package/dist/Model/filter/types/validator.js +0 -2
  321. package/dist/Model/filter/types.d.ts +0 -5
  322. package/dist/Model/filter/types.d.ts.map +0 -1
  323. package/dist/Model/filter/types.js +0 -7
  324. package/dist/Model/query/dsl.d.ts +0 -248
  325. package/dist/Model/query/dsl.d.ts.map +0 -1
  326. package/dist/Model/query/dsl.js +0 -104
  327. package/dist/Model/query/new-kid-interpreter.d.ts +0 -28
  328. package/dist/Model/query/new-kid-interpreter.d.ts.map +0 -1
  329. package/dist/Model/query/new-kid-interpreter.js +0 -165
  330. package/dist/Model/query.d.ts +0 -15
  331. package/dist/Model/query.d.ts.map +0 -1
  332. package/dist/Model/query.js +0 -3
  333. package/dist/Model.d.ts +0 -4
  334. package/dist/Model.d.ts.map +0 -1
  335. package/dist/Model.js +0 -4
  336. package/dist/Operations.d.ts +0 -55
  337. package/dist/Operations.d.ts.map +0 -1
  338. package/dist/Operations.js +0 -102
  339. package/dist/OperationsRepo.d.ts +0 -41
  340. package/dist/OperationsRepo.d.ts.map +0 -1
  341. package/dist/OperationsRepo.js +0 -14
  342. package/dist/QueueMaker/SQLQueue.d.ts +0 -16
  343. package/dist/QueueMaker/SQLQueue.d.ts.map +0 -1
  344. package/dist/QueueMaker/SQLQueue.js +0 -130
  345. package/dist/QueueMaker/memQueue.d.ts +0 -11
  346. package/dist/QueueMaker/memQueue.d.ts.map +0 -1
  347. package/dist/QueueMaker/memQueue.js +0 -76
  348. package/dist/QueueMaker/sbqueue.d.ts +0 -12
  349. package/dist/QueueMaker/sbqueue.d.ts.map +0 -1
  350. package/dist/QueueMaker/sbqueue.js +0 -91
  351. package/dist/QueueMaker/service.d.ts +0 -11
  352. package/dist/QueueMaker/service.d.ts.map +0 -1
  353. package/dist/QueueMaker/service.js +0 -4
  354. package/dist/RequestContext.d.ts +0 -63
  355. package/dist/RequestContext.d.ts.map +0 -1
  356. package/dist/RequestContext.js +0 -49
  357. package/dist/RequestFiberSet.d.ts +0 -36
  358. package/dist/RequestFiberSet.d.ts.map +0 -1
  359. package/dist/RequestFiberSet.js +0 -78
  360. package/dist/Store/ContextMapContainer.d.ts +0 -14
  361. package/dist/Store/ContextMapContainer.d.ts.map +0 -1
  362. package/dist/Store/ContextMapContainer.js +0 -16
  363. package/dist/Store/service.d.ts +0 -108
  364. package/dist/Store/service.d.ts.map +0 -1
  365. package/dist/Store/service.js +0 -71
  366. package/dist/Store.d.ts +0 -2
  367. package/dist/Store.d.ts.map +0 -1
  368. package/dist/Store.js +0 -2
  369. package/dist/adapters/SQL/Model.d.ts.map +0 -1
  370. package/dist/adapters/SQL/Model.js +0 -478
  371. package/dist/adapters/SQL.d.ts.map +0 -1
  372. package/dist/adapters/ServiceBus.d.ts +0 -58
  373. package/dist/adapters/ServiceBus.d.ts.map +0 -1
  374. package/dist/adapters/ServiceBus.js +0 -99
  375. package/dist/adapters/cosmos-client.d.ts +0 -14
  376. package/dist/adapters/cosmos-client.d.ts.map +0 -1
  377. package/dist/adapters/cosmos-client.js +0 -9
  378. package/dist/adapters/index.d.ts +0 -2
  379. package/dist/adapters/index.d.ts.map +0 -1
  380. package/dist/adapters/index.js +0 -2
  381. package/dist/adapters/logger.d.ts +0 -9
  382. package/dist/adapters/logger.d.ts.map +0 -1
  383. package/dist/adapters/logger.js +0 -3
  384. package/dist/adapters/memQueue.d.ts +0 -13
  385. package/dist/adapters/memQueue.d.ts.map +0 -1
  386. package/dist/adapters/memQueue.js +0 -20
  387. package/dist/adapters/mongo-client.d.ts.map +0 -1
  388. package/dist/adapters/mongo-client.js +0 -13
  389. package/dist/adapters/redis-client.d.ts.map +0 -1
  390. package/dist/adapters/redis-client.js +0 -94
  391. package/dist/api/ContextProvider.d.ts +0 -31
  392. package/dist/api/ContextProvider.d.ts.map +0 -1
  393. package/dist/api/ContextProvider.js +0 -38
  394. package/dist/api/codec.d.ts.map +0 -1
  395. package/dist/api/codec.js +0 -5
  396. package/dist/api/internal/RequestContextMiddleware.d.ts +0 -5
  397. package/dist/api/internal/RequestContextMiddleware.d.ts.map +0 -1
  398. package/dist/api/internal/RequestContextMiddleware.js +0 -35
  399. package/dist/api/internal/auth.d.ts +0 -15
  400. package/dist/api/internal/auth.d.ts.map +0 -1
  401. package/dist/api/internal/auth.js +0 -47
  402. package/dist/api/internal/events.d.ts +0 -9
  403. package/dist/api/internal/events.d.ts.map +0 -1
  404. package/dist/api/internal/events.js +0 -42
  405. package/dist/api/internal/health.d.ts.map +0 -1
  406. package/dist/api/internal/health.js +0 -5
  407. package/dist/api/layerUtils.d.ts.map +0 -1
  408. package/dist/api/layerUtils.js +0 -16
  409. package/dist/api/middlewares.d.ts.map +0 -1
  410. package/dist/api/reportError.d.ts.map +0 -1
  411. package/dist/api/reportError.js +0 -27
  412. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +0 -1
  413. package/dist/api/routing/middleware/middleware.d.ts +0 -9
  414. package/dist/api/routing/middleware/middleware.d.ts.map +0 -1
  415. package/dist/api/routing/middleware/middleware.js +0 -92
  416. package/dist/api/routing/middleware.d.ts.map +0 -1
  417. package/dist/api/routing/schema/jwt.d.ts.map +0 -1
  418. package/dist/api/routing/schema/jwt.js +0 -12
  419. package/dist/api/routing/tsort.d.ts.map +0 -1
  420. package/dist/api/routing/tsort.js +0 -51
  421. package/dist/api/routing/utils.d.ts.map +0 -1
  422. package/dist/api/routing/utils.js +0 -44
  423. package/dist/api/routing.d.ts +0 -138
  424. package/dist/api/routing.d.ts.map +0 -1
  425. package/dist/api/routing.js +0 -166
  426. package/dist/api/setupRequest.d.ts +0 -12
  427. package/dist/api/setupRequest.d.ts.map +0 -1
  428. package/dist/api/setupRequest.js +0 -44
  429. package/dist/api/util.d.ts.map +0 -1
  430. package/dist/api/util.js +0 -14
  431. package/eslint.config.mjs +0 -24
  432. package/src/Emailer/service.ts +0 -52
  433. package/src/Emailer.ts +0 -1
  434. package/src/Model/Repository/ext.ts +0 -283
  435. package/src/Model/Repository/internal/internal.ts +0 -577
  436. package/src/Model/Repository/legacy.ts +0 -27
  437. package/src/Model/Repository/makeRepo.ts +0 -139
  438. package/src/Model/Repository/service.ts +0 -627
  439. package/src/Model/Repository/validation.ts +0 -31
  440. package/src/Model/Repository.ts +0 -5
  441. package/src/Model/dsl.ts +0 -128
  442. package/src/Model/filter/filterApi.ts +0 -60
  443. package/src/Model/filter/types/errors.ts +0 -47
  444. package/src/Model/filter/types/fields.ts +0 -50
  445. package/src/Model/filter/types/path/common.ts +0 -404
  446. package/src/Model/filter/types/path/eager.ts +0 -298
  447. package/src/Model/filter/types/path/index.ts +0 -4
  448. package/src/Model/filter/types/utils.ts +0 -128
  449. package/src/Model/filter/types/validator.ts +0 -46
  450. package/src/Model/filter/types.ts +0 -6
  451. package/src/Model/query/dsl.ts +0 -2110
  452. package/src/Model/query/new-kid-interpreter.ts +0 -210
  453. package/src/Model/query.ts +0 -13
  454. package/src/Model.ts +0 -3
  455. package/src/Operations.ts +0 -235
  456. package/src/OperationsRepo.ts +0 -16
  457. package/src/QueueMaker/SQLQueue.ts +0 -189
  458. package/src/QueueMaker/memQueue.ts +0 -130
  459. package/src/QueueMaker/sbqueue.ts +0 -137
  460. package/src/QueueMaker/service.ts +0 -17
  461. package/src/RequestContext.ts +0 -63
  462. package/src/RequestFiberSet.ts +0 -105
  463. package/src/Store/ContextMapContainer.ts +0 -20
  464. package/src/Store/index.test.ts.bak +0 -112
  465. package/src/Store/service.ts +0 -184
  466. package/src/Store.ts +0 -1
  467. package/src/adapters/SQL.ts +0 -1
  468. package/src/adapters/ServiceBus.ts +0 -209
  469. package/src/adapters/cosmos-client.ts +0 -16
  470. package/src/adapters/index.ts +0 -0
  471. package/src/adapters/logger.ts +0 -3
  472. package/src/adapters/memQueue.ts +0 -21
  473. package/src/api/internal/auth.ts +0 -68
  474. package/src/api/middlewares.ts +0 -10
  475. package/src/api/routing/middleware/middleware.ts +0 -128
  476. package/src/api/routing/middleware.ts +0 -6
  477. package/src/api/routing.ts +0 -598
  478. package/src/api/setupRequest.ts +0 -84
  479. package/tsconfig.json.bak +0 -12
  480. /package/dist/{adapters/SQL.d.ts → SQL.d.ts} +0 -0
  481. /package/dist/{api/codec.d.ts → codec.d.ts} +0 -0
  482. /package/dist/{api/internal → internal}/health.d.ts +0 -0
  483. /package/dist/{api/middlewares.d.ts → middlewares.d.ts} +0 -0
  484. /package/dist/{api/routing → routing}/schema/jwt.d.ts +0 -0
  485. /package/dist/{api/routing → routing}/tsort.d.ts +0 -0
  486. /package/dist/{api/util.d.ts → util.d.ts} +0 -0
  487. /package/src/{api/codec.ts → codec.ts} +0 -0
  488. /package/src/{api/internal → internal}/health.ts +0 -0
  489. /package/src/{api/routing → routing}/tsort.ts +0 -0
  490. /package/src/{api/util.ts → util.ts} +0 -0
@@ -1,25 +1,41 @@
1
1
  /* eslint-disable unused-imports/no-unused-vars */
2
2
  /* eslint-disable @typescript-eslint/no-empty-object-type */
3
3
  /* eslint-disable @typescript-eslint/no-explicit-any */
4
- import { Effect, flow, Layer, Option, pipe, S, ServiceMap, Struct } from "effect-app"
4
+ import * as Context from "effect-app/Context"
5
+ import * as Effect from "effect-app/Effect"
6
+ import * as Layer from "effect-app/Layer"
7
+ import { and, computed, count, expr, make, one, or, order, page, project, type ProjectableFromDomain, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "effect-app/Model/query"
8
+ import { makeRepo } from "effect-app/Model/Repository"
9
+ import { RepositoryRegistryLive } from "effect-app/Model/Repository/Registry"
10
+ import * as Option from "effect-app/Option"
11
+ import * as S from "effect-app/Schema"
12
+ import { setupRequestContextFromCurrent } from "effect-app/setupRequest"
13
+ import { flow, pipe } from "effect/Function"
14
+ import * as Redacted from "effect/Redacted"
15
+ import * as Getter from "effect/SchemaGetter"
16
+ import * as SchemaTransformation from "effect/SchemaTransformation"
17
+ import * as Struct from "effect/Struct"
18
+ import * as fs from "fs"
19
+ import * as os from "os"
20
+ import * as path from "path"
5
21
  import { inspect } from "util"
6
22
  import { expect, expectTypeOf, it } from "vitest"
7
- import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
8
- import { and, count, make, one, or, order, page, project, type QueryEnd, type QueryProjection, type QueryWhere, toFilter, where } from "../src/Model/query.js"
9
- import { makeRepo } from "../src/Model/Repository.js"
23
+ import { DiskStoreLayer } from "../src/Store/Disk.js"
10
24
  import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
11
25
  import { SomeService } from "./fixtures.js"
12
26
 
27
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
28
+
13
29
  const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
14
30
  const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
15
31
  const someUnion = S.Union([str, num])
16
32
 
17
33
  export class Something extends S.Class<Something>("Something")({
18
- id: S.StringId.withDefault,
34
+ id: S.StringId.withConstructorDefault,
19
35
  displayName: S.NonEmptyString255,
20
- name: S.NullOr(S.NonEmptyString255).withDefault,
21
- n: S.Date.withDefault,
22
- union: someUnion.pipe(S.withDefaultConstructor(() => ({ _tag: "string" as const, value: "hi" })))
36
+ name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
37
+ n: S.Date.withConstructorDefault,
38
+ union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
23
39
  }) {}
24
40
  export declare namespace Something {
25
41
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -31,7 +47,7 @@ const q = make<Something.Encoded>()
31
47
  where("displayName", "Verona"),
32
48
  or(
33
49
  where("displayName", "Riley"),
34
- and("n", "gt", "2021-01-01T00:00:00Z") // TODO: work with To type translation, so Date?
50
+ and("n", "gt", new Date("2021-01-01T00:00:00Z"))
35
51
  ),
36
52
  order("displayName"),
37
53
  page({ take: 10 }),
@@ -90,8 +106,8 @@ it("works", () => {
90
106
 
91
107
  const processed = memFilter(interpreted)(items.map((_) =>
92
108
  S.encodeUnknownSync(S.Struct({
93
- ...Something.omit("displayName"),
94
- displayName: S.Literal("Verona", "Riley")
109
+ ...Struct.omit(Something.fields, ["displayName"]),
110
+ displayName: S.Literals(["Verona", "Riley"])
95
111
  }))(_)
96
112
  ))
97
113
 
@@ -99,7 +115,7 @@ it("works", () => {
99
115
  })
100
116
 
101
117
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
102
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo", {
118
+ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
103
119
  make: Effect.gen(function*() {
104
120
  return yield* makeRepo("Something", Something, {})
105
121
  })
@@ -112,7 +128,7 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo",
112
128
  })
113
129
  )
114
130
  .pipe(
115
- Layer.provide(MemoryStoreLive)
131
+ Layer.provide(TestStoreLive)
116
132
  )
117
133
  }
118
134
 
@@ -131,7 +147,7 @@ it("works with repo", () =>
131
147
  where("displayName", "Verona"),
132
148
  or(
133
149
  where("displayName", "Riley"),
134
- and("n", "gt", "2021-01-01T00:00:00Z") // TODO: work with To type translation, so Date?
150
+ and("n", "gt", new Date("2021-01-01T00:00:00Z"))
135
151
  ),
136
152
  order("displayName"),
137
153
  page({ take: 10 }),
@@ -158,13 +174,163 @@ it("works with repo", () =>
158
174
 
159
175
  expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
160
176
  expect(q2).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["displayName"])))
177
+
178
+ const byDate = yield* somethingRepo.query(
179
+ where("n", new Date("2020-01-01T00:00:00.000Z"))
180
+ )
181
+ expect(byDate.map((_) => _.displayName)).toEqual(["Verona", "Riley"])
161
182
  })
162
183
  .pipe(
163
184
  Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
164
185
  setupRequestContextFromCurrent(),
186
+ Effect.scoped,
165
187
  Effect.runPromise
166
188
  ))
167
189
 
190
+ it("memory store round-trips Date/Set/Map via JSON codecs", () =>
191
+ Effect
192
+ .gen(function*() {
193
+ class Doc extends S.Class<Doc>("JsonCodecDoc")({
194
+ id: S.String,
195
+ at: S.Date,
196
+ tags: S.ReadonlySet(S.String),
197
+ meta: S.ReadonlyMap({ key: S.String, value: S.Finite })
198
+ }) {}
199
+ const at = new Date("2024-06-01T00:00:00.000Z")
200
+ const saved = new Doc({
201
+ id: "d1",
202
+ at,
203
+ tags: new Set(["a", "b"]),
204
+ meta: new Map([["n", 1]])
205
+ })
206
+ const repo = yield* makeRepo("JsonCodecDoc", Doc, { makeInitial: Effect.succeed([saved]) })
207
+ const found = yield* repo.find("d1")
208
+ expect(Option.isSome(found)).toBe(true)
209
+ if (Option.isSome(found)) {
210
+ expect(found.value.at.toISOString()).toBe(at.toISOString())
211
+ expect(found.value.tags).toEqual(new Set(["a", "b"]))
212
+ expect(found.value.meta).toEqual(new Map([["n", 1]]))
213
+ }
214
+ const byDate = yield* repo.query(where("at", at))
215
+ expect(byDate.map((_) => _.id)).toEqual(["d1"])
216
+ const byTag = yield* repo.query(where("tags", "includes", "b"))
217
+ expect(byTag.map((_) => _.id)).toEqual(["d1"])
218
+ const byKey = yield* repo.query(where("meta", "hasKey", "n"))
219
+ expect(byKey.map((_) => _.id)).toEqual(["d1"])
220
+ const byPair = yield* repo.query(where("meta", "hasKeyValue", ["n", 1]))
221
+ expect(byPair.map((_) => _.id)).toEqual(["d1"])
222
+ })
223
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
224
+
225
+ class Day {
226
+ readonly ymd: string
227
+ constructor(ymd: string) {
228
+ this.ymd = ymd
229
+ }
230
+ }
231
+
232
+ const DayFromSelf = S.declare((u): u is Day => u instanceof Day, {
233
+ expected: "Day",
234
+ toCodecJson: () =>
235
+ S.link<Day>()(
236
+ S.String,
237
+ {
238
+ decode: Getter.transform((s: string) => new Day(s)),
239
+ encode: Getter.transform((d: Day) => d.ymd)
240
+ }
241
+ )
242
+ })
243
+
244
+ it("memory store round-trips app native Encoded values via the document schema", () =>
245
+ Effect
246
+ .gen(function*() {
247
+ class Doc extends S.Class<Doc>("JsonCodecDayDoc")({
248
+ id: S.String,
249
+ day: DayFromSelf
250
+ }) {}
251
+ const day = new Day("2024-06-01")
252
+ const saved = new Doc({ id: "d1", day })
253
+ const repo = yield* makeRepo("JsonCodecDayDoc", Doc, { makeInitial: Effect.succeed([saved]) })
254
+ const found = yield* repo.find("d1")
255
+ expect(Option.isSome(found)).toBe(true)
256
+ if (Option.isSome(found)) {
257
+ expect(found.value.day).toBeInstanceOf(Day)
258
+ expect(found.value.day.ymd).toBe("2024-06-01")
259
+ }
260
+ expect(S.encodeSync(DayFromSelf)(day)).toBe(day)
261
+ expect(S.encodeSync(S.toCodecJson(DayFromSelf))(day)).toBe("2024-06-01")
262
+ const byDay = yield* repo.query(where("day", day))
263
+ expect(byDay.map((_) => _.id)).toEqual(["d1"])
264
+ })
265
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
266
+
267
+ it("memory store queries encodeKeys-renamed native Encoded fields", () =>
268
+ Effect
269
+ .gen(function*() {
270
+ class Doc extends S.Class<Doc>("JsonCodecEncodeKeysDayDoc")({
271
+ id: S.String,
272
+ day: DayFromSelf
273
+ }) {}
274
+ const schema = Doc.pipe(S.encodeKeys({ day: "the_day" }))
275
+ const day = new Day("2024-06-01")
276
+ const saved = new Doc({ id: "d1", day })
277
+ const repo = yield* makeRepo("JsonCodecEncodeKeysDayDoc", schema, { makeInitial: Effect.succeed([saved]) })
278
+ const byDay = yield* repo.query(where("the_day", day))
279
+ expect(byDay.map((_) => _.id)).toEqual(["d1"])
280
+ const byId = yield* repo.query(where("id", "in", ["d1"]))
281
+ expect(byId.map((_) => _.id)).toEqual(["d1"])
282
+ })
283
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
284
+
285
+ it("disk store round-trips Date/Set/Map via JSON codecs", () => {
286
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "effect-app-disk-json-"))
287
+ const diskLive = Layer.merge(
288
+ DiskStoreLayer({ url: Redacted.make(`disk://${dir}`), prefix: "", dbName: "test" }, dir),
289
+ RepositoryRegistryLive
290
+ )
291
+ return Effect
292
+ .gen(function*() {
293
+ class Doc extends S.Class<Doc>("JsonCodecDiskDoc")({
294
+ id: S.String,
295
+ at: S.Date,
296
+ tags: S.ReadonlySet(S.String),
297
+ meta: S.ReadonlyMap({ key: S.String, value: S.Finite })
298
+ }) {}
299
+ const at = new Date("2024-06-01T00:00:00.000Z")
300
+ const saved = new Doc({
301
+ id: "d1",
302
+ at,
303
+ tags: new Set(["a", "b"]),
304
+ meta: new Map([["n", 1]])
305
+ })
306
+ const repo = yield* makeRepo("JsonCodecDiskDoc", Doc, { makeInitial: Effect.succeed([saved]) })
307
+ const found = yield* repo.find("d1")
308
+ expect(Option.isSome(found)).toBe(true)
309
+ if (Option.isSome(found)) {
310
+ expect(found.value.at.toISOString()).toBe(at.toISOString())
311
+ expect(found.value.tags).toEqual(new Set(["a", "b"]))
312
+ expect(found.value.meta).toEqual(new Map([["n", 1]]))
313
+ }
314
+ const jsonFile = fs.readdirSync(dir).find((f) => f.endsWith(".json"))
315
+ expect(jsonFile).toBeDefined()
316
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, jsonFile!), "utf8")) as Array<{
317
+ at: unknown
318
+ tags: unknown
319
+ meta: unknown
320
+ }>
321
+ expect(raw[0]?.at).toBe(at.toISOString())
322
+ expect(raw[0]?.tags).toEqual(["a", "b"])
323
+ expect(raw[0]?.meta).toEqual([["n", 1]])
324
+ })
325
+ .pipe(
326
+ Effect.provide(diskLive),
327
+ setupRequestContextFromCurrent(),
328
+ Effect.scoped,
329
+ Effect.runPromise
330
+ )
331
+ .finally(() => fs.rmSync(dir, { recursive: true, force: true }))
332
+ })
333
+
168
334
  it("collect", () =>
169
335
  Effect
170
336
  .gen(function*() {
@@ -185,8 +351,8 @@ it("collect", () =>
185
351
  })),
186
352
  S.toType(S.Option(S.String)),
187
353
  (_) =>
188
- _.displayName === "Riley" && _.n === "2020-01-01T00:00:00.000Z"
189
- ? Option.some(`${_.displayName}-${_.n}`)
354
+ _.displayName === "Riley" && _.n.toISOString() === "2020-01-01T00:00:00.000Z"
355
+ ? Option.some(`${_.displayName}-${_.n.toISOString()}`)
190
356
  : Option.none()
191
357
  ),
192
358
  "collect"
@@ -204,7 +370,7 @@ it("collect", () =>
204
370
  QueryEnd<{
205
371
  readonly id: string
206
372
  readonly displayName: string
207
- readonly n: string
373
+ readonly n: Date
208
374
  readonly union: {
209
375
  readonly _tag: "string"
210
376
  readonly value: string
@@ -233,18 +399,19 @@ it("collect", () =>
233
399
  .pipe(
234
400
  Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
235
401
  setupRequestContextFromCurrent(),
402
+ Effect.scoped,
236
403
  Effect.runPromise
237
404
  ))
238
405
 
239
- class Person extends S.ExtendedTaggedClass<Person, Person.Encoded>()("person", {
406
+ class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
240
407
  id: S.String,
241
408
  surname: S.String
242
409
  }) {}
243
- class Animal extends S.ExtendedTaggedClass<Animal, Animal.Encoded>()("animal", {
410
+ class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
244
411
  id: S.String,
245
412
  surname: S.String
246
413
  }) {}
247
- class Test extends S.ExtendedTaggedClass<Test, Test.Encoded>()("test", {
414
+ class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
248
415
  id: S.String
249
416
  }) {}
250
417
 
@@ -279,7 +446,7 @@ it(
279
446
  expect(result).toEqual([])
280
447
  expect(result2).toEqual([])
281
448
  })
282
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
449
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
283
450
  )
284
451
 
285
452
  it(
@@ -377,7 +544,6 @@ it(
377
544
  // eslint-disable-next-line unused-imports/no-unused-vars
378
545
  const query5 = make<Union>().pipe(
379
546
  where("id", "bla"),
380
- // @ts-expect-error cannot project over fields that are not in common between the union members (you must refine the union first)
381
547
  project(S.Struct({ id: S.String, a: S.Unknown }))
382
548
  )
383
549
  console.log(query5)
@@ -465,7 +631,7 @@ it(
465
631
 
466
632
  expect([]).toEqual([])
467
633
  })
468
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
634
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
469
635
  )
470
636
 
471
637
  it(
@@ -508,7 +674,7 @@ it(
508
674
 
509
675
  expect([]).toEqual([])
510
676
  })
511
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
677
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
512
678
  )
513
679
 
514
680
  it(
@@ -519,8 +685,8 @@ it(
519
685
  const schema = S.Struct({
520
686
  id: S.String,
521
687
  createdAt: S.Date.pipe(
522
- S.withDecodingDefault(() => new Date().toISOString()),
523
- S.withConstructorDefault(() => Option.some(new Date()))
688
+ S.withDecodingDefault(Effect.sync(() => new Date())),
689
+ S.withConstructorDefault(Effect.sync(() => new Date()))
524
690
  )
525
691
  })
526
692
  const repo = yield* makeRepo(
@@ -532,8 +698,8 @@ it(
532
698
  const outputSchema = S.Struct({
533
699
  id: S.Literal("123"),
534
700
  createdAt: S.Date.pipe(
535
- S.withDecodingDefault(() => new Date().toISOString()),
536
- S.withConstructorDefault(() => Option.some(new Date()))
701
+ S.withDecodingDefault(Effect.sync(() => new Date())),
702
+ S.withConstructorDefault(Effect.sync(() => new Date()))
537
703
  )
538
704
  })
539
705
 
@@ -541,9 +707,470 @@ it(
541
707
 
542
708
  expect(result).toEqual([])
543
709
  })
544
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
710
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
545
711
  )
546
712
 
713
+ it(
714
+ "project with encodeKeys in projection maps encoded keys",
715
+ () =>
716
+ Effect
717
+ .gen(function*() {
718
+ const schema = S.Struct({
719
+ id: S.String,
720
+ a: S.Number
721
+ })
722
+
723
+ const repo = yield* makeRepo(
724
+ "test",
725
+ schema,
726
+ {
727
+ makeInitial: Effect.sync(() => [{ id: "1", a: 1 }])
728
+ }
729
+ )
730
+
731
+ const outputSchema = S.Struct({ b: S.Number }).pipe(S.encodeKeys({ b: "a" }))
732
+
733
+ const result = yield* repo.query(project(outputSchema))
734
+
735
+ expect(result).toStrictEqual([{ b: 1 }])
736
+ })
737
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
738
+ )
739
+
740
+ it("projectComputed sets computed IR and forces project mode", () => {
741
+ const baseSchema = S.Struct({
742
+ id: S.String,
743
+ items: S.Array(S.Struct({
744
+ state: S.Struct({
745
+ _tag: S.String
746
+ })
747
+ }))
748
+ })
749
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
750
+ projectComputed(
751
+ S.Struct({
752
+ pickedCount: S.NonNegativeInt
753
+ }),
754
+ computed({
755
+ pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count(where("state._tag", "Picked"))
756
+ })
757
+ )
758
+ )
759
+ const interpreted = toFilter(query, baseSchema)
760
+ expect(interpreted.mode).toBe("project")
761
+ expect(interpreted.select).toEqual([
762
+ {
763
+ key: "pickedCount",
764
+ computed: {
765
+ _tag: "relation-count",
766
+ path: "items",
767
+ filter: [{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }]
768
+ }
769
+ }
770
+ ])
771
+ expect(interpreted.computed?.["pickedCount"]?._tag).toBe("relation-count")
772
+ expect(interpreted.computed?.["pickedCount"]?.path).toBe("items")
773
+ expect((interpreted.computed?.["pickedCount"] as { filter: unknown } | undefined)?.filter).toEqual([
774
+ { t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
775
+ ])
776
+ })
777
+
778
+ it("projectComputed validates extra computed keys", () => {
779
+ const baseSchema = S.Struct({
780
+ id: S.String,
781
+ items: S.Array(S.Struct({ value: S.Number }))
782
+ })
783
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
784
+ projectComputed(
785
+ S.Struct({ id: S.String }),
786
+ // @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
787
+ computed({
788
+ pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count()
789
+ })
790
+ )
791
+ )
792
+ expect(() => toFilter(query, baseSchema)).toThrowError("Computed projection keys must exist in projection schema")
793
+ })
794
+
795
+ it("projectComputed constrains projection schema to encoded repo fields and computed outputs", () => {
796
+ const baseSchema = S.Struct({
797
+ id: S.String,
798
+ name: S.String,
799
+ items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
800
+ })
801
+ type Encoded = S.Codec.Encoded<typeof baseSchema>
802
+
803
+ make<Encoded>().pipe(
804
+ projectComputed(
805
+ S.Struct({
806
+ id: S.String,
807
+ itemCount: S.Number,
808
+ hasItems: S.Boolean,
809
+ articleIds: S.Array(S.String)
810
+ }),
811
+ ({ relation }) => ({
812
+ itemCount: relation("items").count(),
813
+ hasItems: relation("items").any(),
814
+ articleIds: relation("items").collect("articleId")
815
+ })
816
+ )
817
+ )
818
+
819
+ make<Encoded>().pipe(
820
+ // @ts-expect-error missingField is neither an encoded repo field nor a computed projection
821
+ projectComputed(S.Struct({ missingField: S.String }), computed({}))
822
+ )
823
+
824
+ // Key presence only (not Encoded value types) — name: number is still projectable.
825
+ make<Encoded>().pipe(
826
+ projectComputed(
827
+ S.Struct({ name: S.Number }),
828
+ computed({})
829
+ )
830
+ )
831
+
832
+ // Computed field *value* types are not yet constrained against the expression
833
+ // result (only key presence). Keeping this as a documentation call site.
834
+ make<Encoded>().pipe(
835
+ projectComputed(
836
+ S.Struct({ itemCount: S.String }),
837
+ computed({ itemCount: relation<Encoded>("items").count() })
838
+ )
839
+ )
840
+
841
+ make<Encoded>().pipe(
842
+ projectComputed(
843
+ S.Struct({ itemCount: S.Number }),
844
+ // @ts-expect-error extra computed keys must exist in the projection schema
845
+ computed({
846
+ itemCount: relation<Encoded>("items").count(),
847
+ extraCount: relation<Encoded>("items").count()
848
+ })
849
+ )
850
+ )
851
+ })
852
+
853
+ it("projectComputed supports union projection schemas", () => {
854
+ const baseSchema = S.Union([
855
+ S.Struct({
856
+ _tag: S.Literal("open"),
857
+ id: S.String,
858
+ items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
859
+ }),
860
+ S.Struct({
861
+ _tag: S.Literal("closed"),
862
+ id: S.String,
863
+ closedAt: S.String
864
+ })
865
+ ])
866
+ type Encoded = S.Codec.Encoded<typeof baseSchema>
867
+ const query = make<Encoded>().pipe(
868
+ projectComputed(
869
+ S.Union([
870
+ S.Struct({
871
+ _tag: S.Literal("open"),
872
+ id: S.String,
873
+ articleCount: S.Number,
874
+ weight: S.Number,
875
+ articleIds: S.Array(S.String)
876
+ }),
877
+ S.Struct({
878
+ _tag: S.Literal("closed"),
879
+ id: S.String,
880
+ closedAt: S.String
881
+ })
882
+ ]),
883
+ computed({
884
+ articleCount: relation<Encoded>("items").count(),
885
+ weight: relation<Encoded>("items").sum("weight"),
886
+ articleIds: relation<Encoded>("items").collect("articleId")
887
+ })
888
+ )
889
+ )
890
+
891
+ const interpreted = toFilter(query, baseSchema)
892
+ expect(interpreted.computed && Object.keys(interpreted.computed).toSorted()).toEqual([
893
+ "articleCount",
894
+ "articleIds",
895
+ "weight"
896
+ ])
897
+ })
898
+
899
+ /**
900
+ * MACS-SCANNER-API-KS class of bug: domain cancel omits pack/print ownership
901
+ * (`activeRequest`); a hand-built overview projection that still requires it on
902
+ * every branch must fail at the `projectComputed` call site, not in prod decode.
903
+ */
904
+ it("projectComputed rejects state-owned fields required on the wrong tagged branch", () => {
905
+ const domain = S.Union([
906
+ S.Struct({
907
+ _tag: S.Literal("packing"),
908
+ id: S.String,
909
+ activeRequest: S.NullOr(S.String),
910
+ items: S.Array(S.Struct({ articleId: S.String }))
911
+ }),
912
+ S.Struct({
913
+ _tag: S.Literal("cancelled"),
914
+ id: S.String,
915
+ items: S.Array(S.Struct({ articleId: S.String }))
916
+ })
917
+ ])
918
+ type DomainEnc = S.Codec.Encoded<typeof domain>
919
+
920
+ // Good: activeRequest only on packing; cancel omits it; articleCount is computed.
921
+ make<DomainEnc>().pipe(
922
+ projectComputed(
923
+ S.Union([
924
+ S.Struct({
925
+ _tag: S.Literal("packing"),
926
+ id: S.String,
927
+ activeRequest: S.NullOr(S.String),
928
+ articleCount: S.Number
929
+ }),
930
+ S.Struct({
931
+ _tag: S.Literal("cancelled"),
932
+ id: S.String,
933
+ articleCount: S.Number
934
+ })
935
+ ]),
936
+ computed({
937
+ articleCount: relation<DomainEnc>("items").count()
938
+ })
939
+ )
940
+ )
941
+
942
+ make<DomainEnc>().pipe(
943
+ projectComputed(
944
+ S.Union([
945
+ S.Struct({
946
+ _tag: S.Literal("packing"),
947
+ id: S.String,
948
+ activeRequest: S.NullOr(S.String),
949
+ articleCount: S.Number
950
+ }),
951
+ S.Struct({
952
+ _tag: S.Literal("cancelled"),
953
+ id: S.String,
954
+ activeRequest: S.NullOr(S.String),
955
+ articleCount: S.Number
956
+ })
957
+ ]),
958
+ // @ts-expect-error activeRequest is not on domain cancelled — cannot project it there
959
+ computed({
960
+ articleCount: relation<DomainEnc>("items").count()
961
+ })
962
+ )
963
+ )
964
+ })
965
+
966
+ it("ProjectableFromDomain distributes over tagged union Encoded", () => {
967
+ type DomainEnc =
968
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
969
+ | { readonly _tag: "cancelled"; readonly id: string }
970
+
971
+ type Good =
972
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null; readonly n: number }
973
+ | { readonly _tag: "cancelled"; readonly id: string; readonly n: number }
974
+
975
+ type Bad =
976
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
977
+ | { readonly _tag: "cancelled"; readonly id: string; readonly activeRequest: string | null }
978
+
979
+ type GoodCheck = ProjectableFromDomain<Good, DomainEnc, "n">
980
+ type BadCheck = ProjectableFromDomain<Bad, DomainEnc>
981
+
982
+ const _good: GoodCheck = undefined
983
+ // @ts-expect-error cancelled branch requires activeRequest not present on domain cancelled
984
+ const _bad: BadCheck = undefined as unknown
985
+ void _good
986
+ void _bad
987
+ })
988
+
989
+ it("ProjectableFromDomain allows dual same-tag domain variants", () => {
990
+ // EasyLife packing/closed: two domain shapes share `_tag` with different fields.
991
+ type DomainEnc =
992
+ | { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
993
+ | { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
994
+ | { readonly _tag: "initial"; readonly id: string }
995
+
996
+ type Good =
997
+ | { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
998
+ | { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
999
+
1000
+ // Flat multi-tag + state-only field must still fail (batchId not on initial).
1001
+ type BadFlat = {
1002
+ readonly _tag: "packing" | "initial"
1003
+ readonly id: string
1004
+ readonly packages: readonly string[]
1005
+ }
1006
+
1007
+ type GoodCheck = ProjectableFromDomain<Good, DomainEnc>
1008
+ type BadFlatCheck = ProjectableFromDomain<BadFlat, DomainEnc>
1009
+
1010
+ const _good: GoodCheck = undefined
1011
+ // @ts-expect-error packages is not on domain initial; multi-tag flat intersection rejects it
1012
+ const _badFlat: BadFlatCheck = undefined as unknown
1013
+ void _good
1014
+ void _badFlat
1015
+ })
1016
+
1017
+ it("projection schema with computed fields fails without computed map", () => {
1018
+ const baseSchema = S.Struct({
1019
+ id: S.String,
1020
+ items: S.Array(S.Struct({ value: S.Number }))
1021
+ })
1022
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1023
+ // @ts-expect-error missing computed keys are rejected; also not projectable from domain alone
1024
+ projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
1025
+ )
1026
+ expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
1027
+ })
1028
+
1029
+ it("projectComputed.every emits relation-every IR", () => {
1030
+ const baseSchema = S.Struct({
1031
+ id: S.String,
1032
+ items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
1033
+ })
1034
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1035
+ projectComputed(
1036
+ S.Struct({ allPicked: S.Boolean }),
1037
+ computed({
1038
+ allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
1039
+ })
1040
+ )
1041
+ )
1042
+ const interpreted = toFilter(query, baseSchema)
1043
+ expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
1044
+ expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
1045
+ expect((interpreted.computed?.["allPicked"] as { filter: unknown } | undefined)?.filter).toEqual([
1046
+ { t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
1047
+ ])
1048
+ })
1049
+
1050
+ it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
1051
+ const baseSchema = S.Struct({
1052
+ id: S.String,
1053
+ items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
1054
+ })
1055
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1056
+ projectComputed(
1057
+ S.Struct({ positionCount: S.NonNegativeInt }),
1058
+ computed({
1059
+ positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
1060
+ "rowId",
1061
+ where("state._tag", "neq", "cancelled")
1062
+ )
1063
+ })
1064
+ )
1065
+ )
1066
+ const interpreted = toFilter(query, baseSchema)
1067
+ const ir = interpreted.computed?.["positionCount"]
1068
+ expect(ir?._tag).toBe("relation-distinct-count")
1069
+ expect((ir as { field: string } | undefined)?.field).toBe("rowId")
1070
+ expect((ir as { filter: unknown } | undefined)?.filter).toEqual([
1071
+ { t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
1072
+ ])
1073
+ })
1074
+
1075
+ it("projectComputed.sum emits relation-sum IR with field", () => {
1076
+ const baseSchema = S.Struct({
1077
+ id: S.String,
1078
+ items: S.Array(S.Struct({ weight: S.Number }))
1079
+ })
1080
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1081
+ projectComputed(
1082
+ S.Struct({ totalWeight: S.Number }),
1083
+ computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
1084
+ )
1085
+ )
1086
+ const interpreted = toFilter(query, baseSchema)
1087
+ const ir = interpreted.computed?.["totalWeight"]
1088
+ expect(ir?._tag).toBe("relation-sum")
1089
+ expect((ir as { field: string } | undefined)?.field).toBe("weight")
1090
+ expect((ir as { filter: unknown } | undefined)?.filter).toEqual([])
1091
+ })
1092
+
1093
+ it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
1094
+ const baseSchema = S.Struct({
1095
+ id: S.String,
1096
+ items: S.Array(S.Struct({ articleId: S.String }))
1097
+ })
1098
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1099
+ projectComputed(
1100
+ S.Struct({
1101
+ all: S.Array(S.String),
1102
+ distinct: S.Array(S.String)
1103
+ }),
1104
+ computed({
1105
+ all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
1106
+ distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
1107
+ })
1108
+ )
1109
+ )
1110
+ const interpreted = toFilter(query, baseSchema)
1111
+ const all = interpreted.computed?.["all"]
1112
+ const distinct = interpreted.computed?.["distinct"]
1113
+ expect(all?._tag).toBe("relation-collect")
1114
+ expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
1115
+ expect(distinct?._tag).toBe("relation-collect")
1116
+ expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
1117
+ })
1118
+
1119
+ it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
1120
+ const baseSchema = S.Struct({
1121
+ id: S.String,
1122
+ items: S.Array(S.Struct({
1123
+ weight: S.Number,
1124
+ tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
1125
+ }))
1126
+ })
1127
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1128
+ projectComputed(
1129
+ S.Struct({ total: S.Number }),
1130
+ computed({
1131
+ total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
1132
+ expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
1133
+ )
1134
+ })
1135
+ )
1136
+ )
1137
+ const interpreted = toFilter(query, baseSchema)
1138
+ const ir = interpreted.computed?.["total"]
1139
+ expect(ir?._tag).toBe("relation-sum-expr")
1140
+ expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
1141
+ _tag: "mul",
1142
+ left: { _tag: "field", field: "weight" },
1143
+ right: { _tag: "field", field: "tradeUnit.amount" }
1144
+ })
1145
+ })
1146
+
1147
+ it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
1148
+ const baseSchema = S.Struct({
1149
+ id: S.String,
1150
+ items: S.Array(S.Struct({
1151
+ weight: S.Number,
1152
+ tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
1153
+ }))
1154
+ })
1155
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
1156
+ projectComputed(
1157
+ S.Struct({
1158
+ totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
1159
+ }),
1160
+ computed({
1161
+ totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
1162
+ expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
1163
+ { unit: "tradeUnit.unit" }
1164
+ )
1165
+ })
1166
+ )
1167
+ )
1168
+ const interpreted = toFilter(query, baseSchema)
1169
+ const ir = interpreted.computed?.["totals"]
1170
+ expect(ir?._tag).toBe("relation-sum-expr-by")
1171
+ expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
1172
+ })
1173
+
547
1174
  it(
548
1175
  "doesn't mess when refining fields",
549
1176
  () =>
@@ -551,7 +1178,7 @@ it(
551
1178
  .gen(function*() {
552
1179
  const schema = S.Struct({
553
1180
  id: S.String,
554
- literals: S.Literal("a", "b", "c")
1181
+ literals: S.Literals(["a", "b", "c"])
555
1182
  })
556
1183
 
557
1184
  type Schema = typeof schema.Type
@@ -571,7 +1198,7 @@ it(
571
1198
 
572
1199
  expect(result).toEqual([])
573
1200
  })
574
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1201
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
575
1202
  )
576
1203
 
577
1204
  it(
@@ -581,7 +1208,7 @@ it(
581
1208
  .gen(function*() {
582
1209
  const schema = S.Struct({
583
1210
  id: S.String,
584
- literals: S.Union([S.Literal("a", "b", "c"), S.Null])
1211
+ literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
585
1212
  })
586
1213
 
587
1214
  type Schema = typeof schema.Type
@@ -615,7 +1242,7 @@ it(
615
1242
 
616
1243
  expect(result).toEqual([])
617
1244
  })
618
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1245
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
619
1246
  )
620
1247
 
621
1248
  it(
@@ -659,7 +1286,7 @@ it(
659
1286
 
660
1287
  expect(result).toEqual([])
661
1288
  })
662
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1289
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
663
1290
  )
664
1291
 
665
1292
  it("remove null from one constituent of a tagged union", () =>
@@ -708,7 +1335,7 @@ it("remove null from one constituent of a tagged union", () =>
708
1335
  })[]
709
1336
  >()
710
1337
  })
711
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1338
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
712
1339
 
713
1340
  it("refine 3", () =>
714
1341
  Effect
@@ -746,7 +1373,7 @@ it("refine 3", () =>
746
1373
  const resQuer1 = yield* repo.query(where("id", "AA"))
747
1374
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
748
1375
  })
749
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1376
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
750
1377
 
751
1378
  it("my test", () =>
752
1379
  Effect
@@ -764,7 +1391,7 @@ it("my test", () =>
764
1391
  )
765
1392
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
766
1393
  })
767
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1394
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
768
1395
 
769
1396
  it("refine inner without imposing a projection", () =>
770
1397
  Effect
@@ -808,7 +1435,7 @@ it("refine inner without imposing a projection", () =>
808
1435
  where("union._tag", "AA"),
809
1436
  // But if I wanna the whole Data as output ignoring the inner refinement
810
1437
  // I wanna be able to do so
811
- project(S.Struct(Data.pick("union")))
1438
+ project(Data.mapFields(Struct.pick(["union"])))
812
1439
  )
813
1440
 
814
1441
  expectTypeOf(query2).toEqualTypeOf<
@@ -839,7 +1466,7 @@ it("refine inner without imposing a projection", () =>
839
1466
  }[]
840
1467
  >()
841
1468
  })
842
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1469
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
843
1470
 
844
1471
  it("does not allow string queries on arrays", () =>
845
1472
  Effect
@@ -873,8 +1500,51 @@ it("does not allow string queries on arrays", () =>
873
1500
  expectTypeOf(good2).toEqualTypeOf<QueryWhere<Some, Some>>()
874
1501
  expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
875
1502
  expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
1503
+
1504
+ type Native = {
1505
+ readonly id: string
1506
+ readonly dates: Date[]
1507
+ readonly dateSet: ReadonlySet<Date>
1508
+ readonly tags: ReadonlySet<string>
1509
+ }
1510
+ const native = make<Native>()
1511
+ const d = new Date("2020-01-01T00:00:00.000Z")
1512
+ const n1 = native.pipe(where("dates", "includes", d))
1513
+ const n2 = native.pipe(where("dateSet", "includes", d))
1514
+ const n3 = native.pipe(where("tags", "includes", "a"))
1515
+ const n4 = native.pipe(where("dates", "includes-any", [d]))
1516
+ const n5 = native.pipe(where("dateSet", "includes-any", new Set([d])))
1517
+ const n6 = native.pipe(where("id", "in", new Set(["x"])))
1518
+ expectTypeOf(n1).toEqualTypeOf<QueryWhere<Native, Native>>()
1519
+ expectTypeOf(n2).toEqualTypeOf<QueryWhere<Native, Native>>()
1520
+ expectTypeOf(n3).toEqualTypeOf<QueryWhere<Native, Native>>()
1521
+ expectTypeOf(n4).toEqualTypeOf<QueryWhere<Native, Native>>()
1522
+ expectTypeOf(n5).toEqualTypeOf<QueryWhere<Native, Native>>()
1523
+ expectTypeOf(n6).toEqualTypeOf<QueryWhere<Native, Native>>()
1524
+
1525
+ type WithMap = {
1526
+ readonly id: string
1527
+ readonly meta: ReadonlyMap<string, number>
1528
+ }
1529
+ const mapped = make<WithMap>()
1530
+ const m1 = mapped.pipe(where("meta", "hasKey", "n"))
1531
+ const m2 = mapped.pipe(where("meta", "hasValue", 1))
1532
+ const m3 = mapped.pipe(where("meta", "hasKeyValue", ["n", 1] as const))
1533
+ const m4 = mapped.pipe(where("meta", "hasKey-any", ["n", "x"]))
1534
+ const m5 = mapped.pipe(where("meta", "hasValue-all", new Set([1, 2])))
1535
+ const m6 = mapped.pipe(where("meta", "hasKeyValue-any", [["n", 1] as const, ["x", 2] as const]))
1536
+ expectTypeOf(m1).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1537
+ expectTypeOf(m2).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1538
+ expectTypeOf(m3).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1539
+ expectTypeOf(m4).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1540
+ expectTypeOf(m5).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1541
+ expectTypeOf(m6).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
1542
+ // @ts-expect-error cannot hasKey on a string field
1543
+ mapped.pipe(where("id", "hasKey", "n"))
1544
+ // @ts-expect-error hasKey value must be the map key type
1545
+ mapped.pipe(where("meta", "hasKey", 1))
876
1546
  })
877
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1547
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
878
1548
 
879
1549
  it("test array.length", () =>
880
1550
  Effect
@@ -915,7 +1585,7 @@ it("test array.length", () =>
915
1585
  QueryWhere<Something, Something>
916
1586
  >()
917
1587
  })
918
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1588
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
919
1589
 
920
1590
  it("distribution over union", () =>
921
1591
  Effect
@@ -939,7 +1609,7 @@ it("distribution over union", () =>
939
1609
  })[]
940
1610
  >()
941
1611
  })
942
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1612
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
943
1613
 
944
1614
  it("refine nested union", () =>
945
1615
  Effect
@@ -980,7 +1650,158 @@ it("refine nested union", () =>
980
1650
  }[]
981
1651
  >()
982
1652
  })
983
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1653
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
1654
+
1655
+ it("find with transformed id", () =>
1656
+ Effect
1657
+ .gen(function*() {
1658
+ const ConfiguratorId = S.NonEmptyString255
1659
+
1660
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1661
+ configuratorId: ConfiguratorId,
1662
+ label: S.NonEmptyString50
1663
+ }) {}
1664
+
1665
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1666
+ S.decodeTo(
1667
+ S.toType(PreconfigurationId),
1668
+ SchemaTransformation.transformEffect({
1669
+ decode: Effect.fnUntraced(function*(value) {
1670
+ const values = value.split("_")
1671
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1672
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1673
+ values.join("_")
1674
+ )
1675
+ return new PreconfigurationId({ configuratorId, label })
1676
+ }),
1677
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1678
+ })
1679
+ ),
1680
+ S.revealCodec
1681
+ )
1682
+
1683
+ const Preconfiguration = S.Struct({
1684
+ id: PreconfigurationIdFromString,
1685
+ name: S.String
1686
+ })
1687
+
1688
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
1689
+
1690
+ const id = new PreconfigurationId({
1691
+ configuratorId: S.NonEmptyString255("myConfigurator"),
1692
+ label: S.NonEmptyString50("myLabel")
1693
+ })
1694
+ const item = { id, name: "test preconfig" }
1695
+
1696
+ yield* repo.saveAndPublish([item])
1697
+
1698
+ const found = yield* repo.find(id)
1699
+ expect(Option.isSome(found)).toBe(true)
1700
+ expect(Option.getOrThrow(found).name).toBe("test preconfig")
1701
+ expect(Option.getOrThrow(found).id).toEqual(id)
1702
+
1703
+ const notFound = yield* repo.find(
1704
+ new PreconfigurationId({
1705
+ configuratorId: S.NonEmptyString255("other"),
1706
+ label: S.NonEmptyString50("nope")
1707
+ })
1708
+ )
1709
+ expect(Option.isNone(notFound)).toBe(true)
1710
+ })
1711
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
1712
+
1713
+ it("find with transformed id in tagged union", () =>
1714
+ Effect
1715
+ .gen(function*() {
1716
+ const ConfiguratorId = S.NonEmptyString255
1717
+
1718
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1719
+ configuratorId: ConfiguratorId,
1720
+ label: S.NonEmptyString50
1721
+ }) {}
1722
+
1723
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1724
+ S.decodeTo(
1725
+ S.toType(PreconfigurationId),
1726
+ SchemaTransformation.transformEffect({
1727
+ decode: Effect.fnUntraced(function*(value) {
1728
+ const values = value.split("_")
1729
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1730
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1731
+ values.join("_")
1732
+ )
1733
+ return new PreconfigurationId({ configuratorId, label })
1734
+ }),
1735
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1736
+ })
1737
+ ),
1738
+ S.revealCodec
1739
+ )
1740
+
1741
+ class Draft extends S.TaggedClass<Draft>()("Draft", {
1742
+ id: PreconfigurationIdFromString,
1743
+ name: S.String
1744
+ }) {}
1745
+
1746
+ class Published extends S.TaggedClass<Published>()("Published", {
1747
+ id: PreconfigurationIdFromString,
1748
+ name: S.String,
1749
+ publishedAt: S.String
1750
+ }) {}
1751
+
1752
+ class Archived extends S.TaggedClass<Archived>()("Archived", {
1753
+ id: PreconfigurationIdFromString,
1754
+ name: S.String,
1755
+ archivedAt: S.String
1756
+ }) {}
1757
+
1758
+ const Preconfiguration = S.Union([Draft, Published, Archived])
1759
+
1760
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
1761
+
1762
+ const id1 = new PreconfigurationId({
1763
+ configuratorId: S.NonEmptyString255("conf1"),
1764
+ label: S.NonEmptyString50("draft1")
1765
+ })
1766
+ const id2 = new PreconfigurationId({
1767
+ configuratorId: S.NonEmptyString255("conf2"),
1768
+ label: S.NonEmptyString50("pub1")
1769
+ })
1770
+ const id3 = new PreconfigurationId({
1771
+ configuratorId: S.NonEmptyString255("conf3"),
1772
+ label: S.NonEmptyString50("arch1")
1773
+ })
1774
+
1775
+ const draft = new Draft({ id: id1, name: "my draft" })
1776
+ const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
1777
+ const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
1778
+
1779
+ yield* repo.saveAndPublish([draft, published, archived])
1780
+
1781
+ // find each by their PreconfigurationId instance
1782
+ const foundDraft = yield* repo.find(id1)
1783
+ expect(Option.isSome(foundDraft)).toBe(true)
1784
+ expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
1785
+ expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
1786
+
1787
+ const foundPublished = yield* repo.find(id2)
1788
+ expect(Option.isSome(foundPublished)).toBe(true)
1789
+ expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
1790
+
1791
+ const foundArchived = yield* repo.find(id3)
1792
+ expect(Option.isSome(foundArchived)).toBe(true)
1793
+ expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
1794
+
1795
+ // not found
1796
+ const notFound = yield* repo.find(
1797
+ new PreconfigurationId({
1798
+ configuratorId: S.NonEmptyString255("nope"),
1799
+ label: S.NonEmptyString50("nope")
1800
+ })
1801
+ )
1802
+ expect(Option.isNone(notFound)).toBe(true)
1803
+ })
1804
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
984
1805
 
985
1806
  it("refine union with nested union", () =>
986
1807
  Effect
@@ -1095,4 +1916,521 @@ it("refine union with nested union", () =>
1095
1916
  })[]
1096
1917
  >()
1097
1918
  })
1098
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1919
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
1920
+
1921
+ // ---------------------------------------------------------------------------
1922
+ // memFilter: computed projection execution (in-memory) and code filter coverage
1923
+ // ---------------------------------------------------------------------------
1924
+
1925
+ const computedBaseSchema = S.Struct({
1926
+ id: S.String,
1927
+ status: S.Literals(["active", "archived"]),
1928
+ items: S.Array(S.Struct({
1929
+ id: S.String,
1930
+ tag: S.Literals(["a", "b", "c"]),
1931
+ qty: S.Finite,
1932
+ note: S.String
1933
+ }))
1934
+ })
1935
+ type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
1936
+
1937
+ const computedRows: ComputedBase[] = [
1938
+ {
1939
+ id: "r1",
1940
+ status: "active",
1941
+ items: [
1942
+ { id: "i1", tag: "a", qty: 10, note: "alpha" },
1943
+ { id: "i2", tag: "a", qty: 20, note: "alpha" },
1944
+ { id: "i3", tag: "b", qty: 5, note: "beta" }
1945
+ ]
1946
+ },
1947
+ { id: "r2", status: "active", items: [] },
1948
+ {
1949
+ id: "r3",
1950
+ status: "archived",
1951
+ items: [
1952
+ { id: "i4", tag: "b", qty: 7, note: "gamma" },
1953
+ { id: "i5", tag: "b", qty: 7, note: "gamma" },
1954
+ { id: "i6", tag: "c", qty: 3, note: "delta" }
1955
+ ]
1956
+ }
1957
+ ]
1958
+
1959
+ it("memFilter: relation-count with filter", () => {
1960
+ const q = make<ComputedBase>().pipe(
1961
+ projectComputed(
1962
+ S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
1963
+ computed({
1964
+ aCount: relation<ComputedBase>("items").count(where("tag", "a"))
1965
+ })
1966
+ )
1967
+ )
1968
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1969
+ { id: "r1", aCount: 2 },
1970
+ { id: "r2", aCount: 0 },
1971
+ { id: "r3", aCount: 0 }
1972
+ ])
1973
+ })
1974
+
1975
+ it("memFilter: relation-any / every with filter", () => {
1976
+ const q = make<ComputedBase>().pipe(
1977
+ projectComputed(
1978
+ S.Struct({
1979
+ id: S.String,
1980
+ hasA: S.Boolean,
1981
+ allB: S.Boolean
1982
+ }),
1983
+ computed({
1984
+ hasA: relation<ComputedBase>("items").any(where("tag", "a")),
1985
+ allB: relation<ComputedBase>("items").every(where("tag", "b"))
1986
+ })
1987
+ )
1988
+ )
1989
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1990
+ { id: "r1", hasA: true, allB: false },
1991
+ // empty array: any → false, every → true (JS Array.every on [] is true)
1992
+ { id: "r2", hasA: false, allB: true },
1993
+ { id: "r3", hasA: false, allB: false }
1994
+ ])
1995
+ })
1996
+
1997
+ it("memFilter: relation-distinct-count with filter", () => {
1998
+ const q = make<ComputedBase>().pipe(
1999
+ projectComputed(
2000
+ S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
2001
+ computed({
2002
+ distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
2003
+ })
2004
+ )
2005
+ )
2006
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2007
+ { id: "r1", distinctNotes: 2 }, // alpha, beta
2008
+ { id: "r2", distinctNotes: 0 },
2009
+ { id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
2010
+ ])
2011
+ })
2012
+
2013
+ it("memFilter: relation-sum with filter", () => {
2014
+ const q = make<ComputedBase>().pipe(
2015
+ projectComputed(
2016
+ S.Struct({ id: S.String, totalQty: S.Finite }),
2017
+ computed({
2018
+ totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
2019
+ })
2020
+ )
2021
+ )
2022
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2023
+ { id: "r1", totalQty: 35 },
2024
+ { id: "r2", totalQty: 0 },
2025
+ { id: "r3", totalQty: 14 }
2026
+ ])
2027
+ })
2028
+
2029
+ it("memFilter: relation-collect / collectDistinct with filter", () => {
2030
+ const q = make<ComputedBase>().pipe(
2031
+ projectComputed(
2032
+ S.Struct({
2033
+ id: S.String,
2034
+ notes: S.Array(S.String),
2035
+ distinctNotes: S.Array(S.String)
2036
+ }),
2037
+ computed({
2038
+ notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
2039
+ distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
2040
+ })
2041
+ )
2042
+ )
2043
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2044
+ { id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
2045
+ { id: "r2", notes: [], distinctNotes: [] },
2046
+ { id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
2047
+ ])
2048
+ })
2049
+
2050
+ it("memFilter: computed projection with multi-statement relation filter", () => {
2051
+ const q = make<ComputedBase>().pipe(
2052
+ projectComputed(
2053
+ S.Struct({ id: S.String, hits: S.NonNegativeInt }),
2054
+ computed({
2055
+ hits: relation<ComputedBase>("items").count(
2056
+ flow(
2057
+ where("tag", "a"),
2058
+ and("qty", "gt", 10)
2059
+ )
2060
+ )
2061
+ })
2062
+ )
2063
+ )
2064
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2065
+ { id: "r1", hits: 1 }, // only i2 (a, qty 20)
2066
+ { id: "r2", hits: 0 },
2067
+ { id: "r3", hits: 0 }
2068
+ ])
2069
+ })
2070
+
2071
+ it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
2072
+ const schema = S.Struct({
2073
+ id: S.String,
2074
+ items: S.Array(S.Struct({
2075
+ weight: S.Finite,
2076
+ tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
2077
+ }))
2078
+ })
2079
+ type Row = S.Codec.Encoded<typeof schema>
2080
+ const rows: Row[] = [
2081
+ {
2082
+ id: "r1",
2083
+ items: [
2084
+ { weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
2085
+ { weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
2086
+ { weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
2087
+ ]
2088
+ },
2089
+ { id: "r2", items: [] }
2090
+ ]
2091
+ const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
2092
+ const q = make<Row>().pipe(
2093
+ projectComputed(
2094
+ S.Struct({
2095
+ id: S.String,
2096
+ totalRaw: S.Finite,
2097
+ totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
2098
+ totalKg: S.Finite
2099
+ }),
2100
+ computed({
2101
+ totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
2102
+ totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
2103
+ totalKg: relation<Row>("items").sumExprNormalized(weighted, {
2104
+ unit: "tradeUnit.unit",
2105
+ toBase: "kg",
2106
+ factors: { g: 0.001 }
2107
+ }, where("weight", "gte", 0))
2108
+ })
2109
+ )
2110
+ )
2111
+ expect(memFilter(toFilter(q, schema))(rows)).toEqual([
2112
+ {
2113
+ id: "r1",
2114
+ totalRaw: 4013,
2115
+ totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
2116
+ totalKg: 17
2117
+ },
2118
+ { id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
2119
+ ])
2120
+ })
2121
+
2122
+ it("memFilter: computed projection combined with root where filter", () => {
2123
+ const q = make<ComputedBase>().pipe(
2124
+ where("id", "neq", "r3"),
2125
+ projectComputed(
2126
+ S.Struct({ id: S.String, totalQty: S.Finite }),
2127
+ computed({
2128
+ totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
2129
+ })
2130
+ )
2131
+ )
2132
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2133
+ { id: "r1", totalQty: 35 },
2134
+ { id: "r2", totalQty: 0 }
2135
+ ])
2136
+ })
2137
+
2138
+ it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
2139
+ const q = make<ComputedBase>().pipe(
2140
+ order("id", "DESC"),
2141
+ page({ skip: 1, take: 1 }),
2142
+ projectComputed(
2143
+ S.Struct({ id: S.String, total: S.NonNegativeInt }),
2144
+ computed({
2145
+ total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
2146
+ })
2147
+ )
2148
+ )
2149
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
2150
+ { id: "r2", total: 0 }
2151
+ ])
2152
+ })
2153
+
2154
+ it("memFilter: computed projection - relation missing on row returns empty value", () => {
2155
+ const partial: ComputedBase[] = [
2156
+ { id: "x1" } as ComputedBase,
2157
+ { id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
2158
+ ]
2159
+ const q = make<ComputedBase>().pipe(
2160
+ projectComputed(
2161
+ S.Struct({
2162
+ id: S.String,
2163
+ c: S.NonNegativeInt,
2164
+ s: S.Finite,
2165
+ any_: S.Boolean,
2166
+ every_: S.Boolean,
2167
+ coll: S.Array(S.String)
2168
+ }),
2169
+ computed({
2170
+ c: relation<ComputedBase>("items").count(where("tag", "a")),
2171
+ s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
2172
+ any_: relation<ComputedBase>("items").any(where("tag", "a")),
2173
+ every_: relation<ComputedBase>("items").every(where("tag", "a")),
2174
+ coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
2175
+ })
2176
+ )
2177
+ )
2178
+ expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
2179
+ { id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
2180
+ { id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
2181
+ ])
2182
+ })
2183
+
2184
+ it("memFilter: rejects extra computed keys not in projection schema", () => {
2185
+ const q = make<ComputedBase>().pipe(
2186
+ projectComputed(
2187
+ S.Struct({ id: S.String }),
2188
+ // @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
2189
+ computed({
2190
+ bogus: relation<ComputedBase>("items").count(where("tag", "a"))
2191
+ })
2192
+ )
2193
+ )
2194
+ expect(() => toFilter(q, computedBaseSchema)).toThrowError(
2195
+ "Computed projection keys must exist in projection schema"
2196
+ )
2197
+ })
2198
+
2199
+ // ---------------------------------------------------------------------------
2200
+ // memFilter: code filter (where/and/or/scopes) execution coverage
2201
+ // ---------------------------------------------------------------------------
2202
+
2203
+ type CFRow = {
2204
+ readonly id: string
2205
+ readonly tag: "x" | "y" | "z"
2206
+ readonly qty: number
2207
+ readonly desc: string
2208
+ readonly tags: ReadonlyArray<string>
2209
+ readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
2210
+ }
2211
+
2212
+ const cfRows: CFRow[] = [
2213
+ { id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
2214
+ { id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
2215
+ { id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
2216
+ { id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
2217
+ ]
2218
+
2219
+ const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
2220
+
2221
+ it("codeFilter: where + and chain", () => {
2222
+ const q = make<CFRow>().pipe(
2223
+ where("tag", "y"),
2224
+ and("qty", "gt", 25)
2225
+ )
2226
+ expect(runCF(q)).toEqual(["4"])
2227
+ })
2228
+
2229
+ it("codeFilter: where + or chain", () => {
2230
+ const q = make<CFRow>().pipe(
2231
+ where("tag", "x"),
2232
+ or("tag", "z")
2233
+ )
2234
+ expect(runCF(q).sort()).toEqual(["1", "3"])
2235
+ })
2236
+
2237
+ it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
2238
+ const q = make<CFRow>().pipe(
2239
+ where("tag", "y"),
2240
+ and(
2241
+ where("qty", "gt", 25),
2242
+ or("desc", "contains", "good")
2243
+ )
2244
+ )
2245
+ // tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
2246
+ expect(runCF(q).sort()).toEqual(["2", "4"])
2247
+ })
2248
+
2249
+ it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
2250
+ expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
2251
+ expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
2252
+ expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
2253
+ })
2254
+
2255
+ it("codeFilter: array includes / includes-any / includes-all", () => {
2256
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
2257
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
2258
+ "1",
2259
+ "2",
2260
+ "3"
2261
+ ])
2262
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
2263
+ })
2264
+
2265
+ it("codeFilter: Date array / Set includes and in", () => {
2266
+ const d0 = new Date("2020-01-01T00:00:00.000Z")
2267
+ const d1 = new Date("2021-01-01T00:00:00.000Z")
2268
+ type DateRow = {
2269
+ readonly id: string
2270
+ readonly dates: Date[]
2271
+ readonly dateSet: ReadonlySet<Date>
2272
+ readonly tag: string
2273
+ }
2274
+ const rows: DateRow[] = [
2275
+ { id: "1", dates: [d0], dateSet: new Set([d0]), tag: "a" },
2276
+ { id: "2", dates: [d1, d0], dateSet: new Set([d1]), tag: "b" }
2277
+ ]
2278
+ const run = (q: any) => (memFilter(toFilter(q))(rows) as DateRow[]).map((_) => _.id)
2279
+ expect(run(make<DateRow>().pipe(where("dates", "includes", d0))).sort()).toEqual(["1", "2"])
2280
+ expect(run(make<DateRow>().pipe(where("dates", "includes", d1)))).toEqual(["2"])
2281
+ expect(run(make<DateRow>().pipe(where("dateSet", "includes", d0)))).toEqual(["1"])
2282
+ expect(run(make<DateRow>().pipe(where("dates", "includes-any", [d1])))).toEqual(["2"])
2283
+ expect(run(make<DateRow>().pipe(where("dateSet", "includes-any", new Set([d0, d1])))).sort()).toEqual([
2284
+ "1",
2285
+ "2"
2286
+ ])
2287
+ expect(run(make<DateRow>().pipe(where("tag", "in", new Set(["a"]))))).toEqual(["1"])
2288
+ })
2289
+
2290
+ it("codeFilter: Map hasKey / hasValue / hasKeyValue", () => {
2291
+ type MapRow = {
2292
+ readonly id: string
2293
+ readonly meta: ReadonlyMap<string, number>
2294
+ }
2295
+ const rows: MapRow[] = [
2296
+ { id: "1", meta: new Map([["n", 1], ["x", 2]]) },
2297
+ { id: "2", meta: new Map([["n", 9]]) },
2298
+ { id: "3", meta: new Map([["z", 2]]) }
2299
+ ]
2300
+ const run = (q: any) => (memFilter(toFilter(q))(rows) as MapRow[]).map((_) => _.id)
2301
+ expect(run(make<MapRow>().pipe(where("meta", "hasKey", "n"))).sort()).toEqual(["1", "2"])
2302
+ expect(run(make<MapRow>().pipe(where("meta", "notHasKey", "n")))).toEqual(["3"])
2303
+ expect(run(make<MapRow>().pipe(where("meta", "hasValue", 2))).sort()).toEqual(["1", "3"])
2304
+ expect(run(make<MapRow>().pipe(where("meta", "notHasValue", 2)))).toEqual(["2"])
2305
+ expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue", ["n", 1])))).toEqual(["1"])
2306
+ expect(run(make<MapRow>().pipe(where("meta", "notHasKeyValue", ["n", 1]))).sort()).toEqual(["2", "3"])
2307
+ expect(run(make<MapRow>().pipe(where("meta", "hasKey-any", ["z", "missing"])))).toEqual(["3"])
2308
+ expect(run(make<MapRow>().pipe(where("meta", "hasKey-all", ["n", "x"])))).toEqual(["1"])
2309
+ expect(run(make<MapRow>().pipe(where("meta", "notHasKey-all", ["n", "x"]))).sort()).toEqual(["2", "3"])
2310
+ expect(run(make<MapRow>().pipe(where("meta", "hasValue-any", [9, 99])))).toEqual(["2"])
2311
+ expect(run(make<MapRow>().pipe(where("meta", "hasValue-all", [1, 2])))).toEqual(["1"])
2312
+ expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue-any", [["n", 9], ["missing", 0]])))).toEqual(["2"])
2313
+ expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue-all", [["n", 1], ["x", 2]])))).toEqual(["1"])
2314
+ })
2315
+
2316
+ it("codeFilter: in / notIn", () => {
2317
+ expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
2318
+ expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
2319
+ })
2320
+
2321
+ it("codeFilter: gt / gte / lt / lte / neq", () => {
2322
+ expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
2323
+ expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
2324
+ expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
2325
+ expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
2326
+ expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
2327
+ })
2328
+
2329
+ it("codeFilter: nested path access through dot notation", () => {
2330
+ expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
2331
+ expect(
2332
+ runCF(
2333
+ make<CFRow>().pipe(
2334
+ where("nested.kind", "k2"),
2335
+ and("nested.v", "gt", 0)
2336
+ )
2337
+ )
2338
+ )
2339
+ .toEqual(["2"])
2340
+ })
2341
+
2342
+ it("codeFilter: array length predicates", () => {
2343
+ expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
2344
+ expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
2345
+ })
2346
+
2347
+ it("codeFilter: order + skip + limit applied after filter", () => {
2348
+ const q = make<CFRow>().pipe(
2349
+ where("tag", "neq", "z"),
2350
+ order("qty", "DESC"),
2351
+ page({ skip: 1, take: 2 })
2352
+ )
2353
+ expect(runCF(q)).toEqual(["2", "1"])
2354
+ })
2355
+
2356
+ // ─────────────────────────────────────────────────────────────────────────────
2357
+ // memFilter: aggregate (GROUP BY) support
2358
+ // ─────────────────────────────────────────────────────────────────────────────
2359
+
2360
+ it("memFilter: agg-count-when groups rows and counts conditionally", () => {
2361
+ type Row = { city: string; status: string }
2362
+ const rows: Row[] = [
2363
+ { city: "NYC", status: "active" },
2364
+ { city: "NYC", status: "inactive" },
2365
+ { city: "NYC", status: "active" },
2366
+ { city: "LA", status: "active" }
2367
+ ]
2368
+ const result = memFilter<Row>({
2369
+ t: {} as Row,
2370
+ select: [
2371
+ { key: "city", path: "city" },
2372
+ {
2373
+ key: "activeCount",
2374
+ aggregate: {
2375
+ _tag: "agg-count-when",
2376
+ filter: [{ t: "where", path: "status", op: "eq", value: "active" }]
2377
+ }
2378
+ },
2379
+ { key: "total", aggregate: { _tag: "agg-count" } }
2380
+ ] as any
2381
+ })(rows) as any[]
2382
+
2383
+ expect(result.length).toBe(2)
2384
+ const nyc = result.find((r: any) => r.city === "NYC")!
2385
+ expect(nyc.activeCount).toBe(2)
2386
+ expect(nyc.total).toBe(3)
2387
+ const la = result.find((r: any) => r.city === "LA")!
2388
+ expect(la.activeCount).toBe(1)
2389
+ expect(la.total).toBe(1)
2390
+ })
2391
+
2392
+ it("memFilter: agg-sum / agg-min / agg-max aggregate numerics", () => {
2393
+ type Row = { dept: string; salary: number }
2394
+ const rows: Row[] = [
2395
+ { dept: "eng", salary: 100 },
2396
+ { dept: "eng", salary: 200 },
2397
+ { dept: "hr", salary: 50 }
2398
+ ]
2399
+ const result = memFilter<Row>({
2400
+ t: {} as Row,
2401
+ select: [
2402
+ { key: "dept", path: "dept" },
2403
+ { key: "total", aggregate: { _tag: "agg-sum", field: "salary" } },
2404
+ { key: "min", aggregate: { _tag: "agg-min", field: "salary" } },
2405
+ { key: "max", aggregate: { _tag: "agg-max", field: "salary" } }
2406
+ ] as any
2407
+ })(rows) as any[]
2408
+
2409
+ expect(result.length).toBe(2)
2410
+ const eng = result.find((r: any) => r.dept === "eng")!
2411
+ expect(eng.total).toBe(300)
2412
+ expect(eng.min).toBe(100)
2413
+ expect(eng.max).toBe(200)
2414
+ const hr = result.find((r: any) => r.dept === "hr")!
2415
+ expect(hr.total).toBe(50)
2416
+ })
2417
+
2418
+ it("memFilter: aggregate with nested path grouping", () => {
2419
+ type Row = { address: { city: string }; value: number }
2420
+ const rows: Row[] = [
2421
+ { address: { city: "NYC" }, value: 10 },
2422
+ { address: { city: "NYC" }, value: 20 },
2423
+ { address: { city: "LA" }, value: 5 }
2424
+ ]
2425
+ const result = memFilter<Row>({
2426
+ t: {} as Row,
2427
+ select: [
2428
+ { key: "city", path: "address.city" },
2429
+ { key: "count", aggregate: { _tag: "agg-count" } }
2430
+ ] as any
2431
+ })(rows) as any[]
2432
+
2433
+ expect(result.length).toBe(2)
2434
+ expect(result.find((r: any) => r.city === "NYC")!.count).toBe(2)
2435
+ expect(result.find((r: any) => r.city === "LA")!.count).toBe(1)
2436
+ })