@effect-app/infra 4.0.0-beta.99 → 4.0.1

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 +2901 -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 +240 -44
  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 -320
  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 +347 -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 +230 -155
  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 +308 -14
  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 +370 -99
  53. package/dist/Store/codeFilter.d.ts +5 -5
  54. package/dist/Store/codeFilter.d.ts.map +1 -1
  55. package/dist/Store/codeFilter.js +72 -23
  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 +257 -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 +277 -57
  169. package/src/Store/Cosmos.ts +597 -512
  170. package/src/Store/Disk.ts +145 -71
  171. package/src/Store/Memory.ts +419 -115
  172. package/src/Store/SQL/Pg.ts +352 -269
  173. package/src/Store/SQL/query.ts +368 -22
  174. package/src/Store/SQL.ts +742 -294
  175. package/src/Store/codeFilter.ts +79 -29
  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 +70 -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 +827 -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
@@ -1,25 +1,40 @@
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 { SchemaTransformation } from "effect"
5
- import { Context, Effect, flow, Layer, Option, pipe, S, 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"
6
21
  import { inspect } from "util"
7
22
  import { expect, expectTypeOf, it } from "vitest"
8
- import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
9
- import { and, count, make, one, or, order, page, project, type QueryEnd, type QueryProjection, type QueryWhere, toFilter, where } from "../src/Model/query.js"
10
- import { makeRepo } from "../src/Model/Repository.js"
23
+ import { DiskStoreLayer } from "../src/Store/Disk.js"
11
24
  import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
12
25
  import { SomeService } from "./fixtures.js"
13
26
 
27
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
28
+
14
29
  const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
15
30
  const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
16
31
  const someUnion = S.Union([str, num])
17
32
 
18
33
  export class Something extends S.Class<Something>("Something")({
19
- id: S.StringId.withDefault,
34
+ id: S.StringId.withConstructorDefault,
20
35
  displayName: S.NonEmptyString255,
21
- name: S.NullOr(S.NonEmptyString255).withDefault,
22
- n: S.Date.withDefault,
36
+ name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
37
+ n: S.Date.withConstructorDefault,
23
38
  union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
24
39
  }) {}
25
40
  export declare namespace Something {
@@ -32,7 +47,7 @@ const q = make<Something.Encoded>()
32
47
  where("displayName", "Verona"),
33
48
  or(
34
49
  where("displayName", "Riley"),
35
- 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"))
36
51
  ),
37
52
  order("displayName"),
38
53
  page({ take: 10 }),
@@ -113,7 +128,7 @@ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
113
128
  })
114
129
  )
115
130
  .pipe(
116
- Layer.provide(MemoryStoreLive)
131
+ Layer.provide(TestStoreLive)
117
132
  )
118
133
  }
119
134
 
@@ -132,7 +147,7 @@ it("works with repo", () =>
132
147
  where("displayName", "Verona"),
133
148
  or(
134
149
  where("displayName", "Riley"),
135
- 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"))
136
151
  ),
137
152
  order("displayName"),
138
153
  page({ take: 10 }),
@@ -159,13 +174,163 @@ it("works with repo", () =>
159
174
 
160
175
  expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
161
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"])
162
182
  })
163
183
  .pipe(
164
184
  Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
165
185
  setupRequestContextFromCurrent(),
186
+ Effect.scoped,
166
187
  Effect.runPromise
167
188
  ))
168
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
+
169
334
  it("collect", () =>
170
335
  Effect
171
336
  .gen(function*() {
@@ -186,8 +351,8 @@ it("collect", () =>
186
351
  })),
187
352
  S.toType(S.Option(S.String)),
188
353
  (_) =>
189
- _.displayName === "Riley" && _.n === "2020-01-01T00:00:00.000Z"
190
- ? Option.some(`${_.displayName}-${_.n}`)
354
+ _.displayName === "Riley" && _.n.toISOString() === "2020-01-01T00:00:00.000Z"
355
+ ? Option.some(`${_.displayName}-${_.n.toISOString()}`)
191
356
  : Option.none()
192
357
  ),
193
358
  "collect"
@@ -205,7 +370,7 @@ it("collect", () =>
205
370
  QueryEnd<{
206
371
  readonly id: string
207
372
  readonly displayName: string
208
- readonly n: string
373
+ readonly n: Date
209
374
  readonly union: {
210
375
  readonly _tag: "string"
211
376
  readonly value: string
@@ -234,18 +399,19 @@ it("collect", () =>
234
399
  .pipe(
235
400
  Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
236
401
  setupRequestContextFromCurrent(),
402
+ Effect.scoped,
237
403
  Effect.runPromise
238
404
  ))
239
405
 
240
- class Person extends S.ExtendedTaggedClass<Person, Person.Encoded>()("person", {
406
+ class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
241
407
  id: S.String,
242
408
  surname: S.String
243
409
  }) {}
244
- class Animal extends S.ExtendedTaggedClass<Animal, Animal.Encoded>()("animal", {
410
+ class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
245
411
  id: S.String,
246
412
  surname: S.String
247
413
  }) {}
248
- class Test extends S.ExtendedTaggedClass<Test, Test.Encoded>()("test", {
414
+ class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
249
415
  id: S.String
250
416
  }) {}
251
417
 
@@ -280,7 +446,7 @@ it(
280
446
  expect(result).toEqual([])
281
447
  expect(result2).toEqual([])
282
448
  })
283
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
449
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
284
450
  )
285
451
 
286
452
  it(
@@ -378,7 +544,6 @@ it(
378
544
  // eslint-disable-next-line unused-imports/no-unused-vars
379
545
  const query5 = make<Union>().pipe(
380
546
  where("id", "bla"),
381
- // @ts-expect-error cannot project over fields that are not in common between the union members (you must refine the union first)
382
547
  project(S.Struct({ id: S.String, a: S.Unknown }))
383
548
  )
384
549
  console.log(query5)
@@ -466,7 +631,7 @@ it(
466
631
 
467
632
  expect([]).toEqual([])
468
633
  })
469
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
634
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
470
635
  )
471
636
 
472
637
  it(
@@ -509,7 +674,7 @@ it(
509
674
 
510
675
  expect([]).toEqual([])
511
676
  })
512
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
677
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
513
678
  )
514
679
 
515
680
  it(
@@ -520,7 +685,7 @@ it(
520
685
  const schema = S.Struct({
521
686
  id: S.String,
522
687
  createdAt: S.Date.pipe(
523
- S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
688
+ S.withDecodingDefault(Effect.sync(() => new Date())),
524
689
  S.withConstructorDefault(Effect.sync(() => new Date()))
525
690
  )
526
691
  })
@@ -533,7 +698,7 @@ it(
533
698
  const outputSchema = S.Struct({
534
699
  id: S.Literal("123"),
535
700
  createdAt: S.Date.pipe(
536
- S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
701
+ S.withDecodingDefault(Effect.sync(() => new Date())),
537
702
  S.withConstructorDefault(Effect.sync(() => new Date()))
538
703
  )
539
704
  })
@@ -542,9 +707,470 @@ it(
542
707
 
543
708
  expect(result).toEqual([])
544
709
  })
545
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
710
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
546
711
  )
547
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
+
548
1174
  it(
549
1175
  "doesn't mess when refining fields",
550
1176
  () =>
@@ -572,7 +1198,7 @@ it(
572
1198
 
573
1199
  expect(result).toEqual([])
574
1200
  })
575
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1201
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
576
1202
  )
577
1203
 
578
1204
  it(
@@ -616,7 +1242,7 @@ it(
616
1242
 
617
1243
  expect(result).toEqual([])
618
1244
  })
619
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1245
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
620
1246
  )
621
1247
 
622
1248
  it(
@@ -660,7 +1286,7 @@ it(
660
1286
 
661
1287
  expect(result).toEqual([])
662
1288
  })
663
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
1289
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
664
1290
  )
665
1291
 
666
1292
  it("remove null from one constituent of a tagged union", () =>
@@ -709,7 +1335,7 @@ it("remove null from one constituent of a tagged union", () =>
709
1335
  })[]
710
1336
  >()
711
1337
  })
712
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1338
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
713
1339
 
714
1340
  it("refine 3", () =>
715
1341
  Effect
@@ -747,7 +1373,7 @@ it("refine 3", () =>
747
1373
  const resQuer1 = yield* repo.query(where("id", "AA"))
748
1374
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
749
1375
  })
750
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1376
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
751
1377
 
752
1378
  it("my test", () =>
753
1379
  Effect
@@ -765,7 +1391,7 @@ it("my test", () =>
765
1391
  )
766
1392
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
767
1393
  })
768
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1394
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
769
1395
 
770
1396
  it("refine inner without imposing a projection", () =>
771
1397
  Effect
@@ -840,7 +1466,7 @@ it("refine inner without imposing a projection", () =>
840
1466
  }[]
841
1467
  >()
842
1468
  })
843
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1469
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
844
1470
 
845
1471
  it("does not allow string queries on arrays", () =>
846
1472
  Effect
@@ -874,8 +1500,51 @@ it("does not allow string queries on arrays", () =>
874
1500
  expectTypeOf(good2).toEqualTypeOf<QueryWhere<Some, Some>>()
875
1501
  expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
876
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))
877
1546
  })
878
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1547
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
879
1548
 
880
1549
  it("test array.length", () =>
881
1550
  Effect
@@ -916,7 +1585,7 @@ it("test array.length", () =>
916
1585
  QueryWhere<Something, Something>
917
1586
  >()
918
1587
  })
919
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1588
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
920
1589
 
921
1590
  it("distribution over union", () =>
922
1591
  Effect
@@ -940,7 +1609,7 @@ it("distribution over union", () =>
940
1609
  })[]
941
1610
  >()
942
1611
  })
943
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1612
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
944
1613
 
945
1614
  it("refine nested union", () =>
946
1615
  Effect
@@ -981,7 +1650,7 @@ it("refine nested union", () =>
981
1650
  }[]
982
1651
  >()
983
1652
  })
984
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1653
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
985
1654
 
986
1655
  it("find with transformed id", () =>
987
1656
  Effect
@@ -996,11 +1665,13 @@ it("find with transformed id", () =>
996
1665
  const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
997
1666
  S.decodeTo(
998
1667
  S.toType(PreconfigurationId),
999
- SchemaTransformation.transformOrFail({
1668
+ SchemaTransformation.transformEffect({
1000
1669
  decode: Effect.fnUntraced(function*(value) {
1001
1670
  const values = value.split("_")
1002
1671
  const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1003
- const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1672
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1673
+ values.join("_")
1674
+ )
1004
1675
  return new PreconfigurationId({ configuratorId, label })
1005
1676
  }),
1006
1677
  encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
@@ -1037,7 +1708,7 @@ it("find with transformed id", () =>
1037
1708
  )
1038
1709
  expect(Option.isNone(notFound)).toBe(true)
1039
1710
  })
1040
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1711
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
1041
1712
 
1042
1713
  it("find with transformed id in tagged union", () =>
1043
1714
  Effect
@@ -1052,11 +1723,13 @@ it("find with transformed id in tagged union", () =>
1052
1723
  const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1053
1724
  S.decodeTo(
1054
1725
  S.toType(PreconfigurationId),
1055
- SchemaTransformation.transformOrFail({
1726
+ SchemaTransformation.transformEffect({
1056
1727
  decode: Effect.fnUntraced(function*(value) {
1057
1728
  const values = value.split("_")
1058
1729
  const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1059
- const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1730
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1731
+ values.join("_")
1732
+ )
1060
1733
  return new PreconfigurationId({ configuratorId, label })
1061
1734
  }),
1062
1735
  encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
@@ -1128,7 +1801,7 @@ it("find with transformed id in tagged union", () =>
1128
1801
  )
1129
1802
  expect(Option.isNone(notFound)).toBe(true)
1130
1803
  })
1131
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1804
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
1132
1805
 
1133
1806
  it("refine union with nested union", () =>
1134
1807
  Effect
@@ -1243,4 +1916,521 @@ it("refine union with nested union", () =>
1243
1916
  })[]
1244
1917
  >()
1245
1918
  })
1246
- .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
+ })