@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221

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 (337) hide show
  1. package/CHANGELOG.md +1648 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +12 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +16 -12
  6. package/dist/Emailer/Sendgrid.d.ts +15 -15
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +20 -16
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/fake.js +2 -2
  11. package/dist/Emailer/service.d.ts +13 -4
  12. package/dist/Emailer/service.d.ts.map +1 -1
  13. package/dist/Emailer/service.js +4 -3
  14. package/dist/Emailer.d.ts +1 -1
  15. package/dist/MainFiberSet.d.ts +12 -9
  16. package/dist/MainFiberSet.d.ts.map +1 -1
  17. package/dist/MainFiberSet.js +7 -3
  18. package/dist/Model/Repository/Registry.d.ts +21 -0
  19. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  20. package/dist/Model/Repository/Registry.js +18 -0
  21. package/dist/Model/Repository/ext.d.ts +35 -16
  22. package/dist/Model/Repository/ext.d.ts.map +1 -1
  23. package/dist/Model/Repository/ext.js +60 -3
  24. package/dist/Model/Repository/internal/internal.d.ts +9 -6
  25. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  26. package/dist/Model/Repository/internal/internal.js +115 -51
  27. package/dist/Model/Repository/legacy.d.ts +4 -2
  28. package/dist/Model/Repository/legacy.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.d.ts +10 -6
  30. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  31. package/dist/Model/Repository/makeRepo.js +5 -2
  32. package/dist/Model/Repository/service.d.ts +32 -24
  33. package/dist/Model/Repository/service.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.d.ts +47 -18
  35. package/dist/Model/Repository/validation.d.ts.map +1 -1
  36. package/dist/Model/Repository/validation.js +6 -6
  37. package/dist/Model/Repository.d.ts +2 -1
  38. package/dist/Model/Repository.d.ts.map +1 -1
  39. package/dist/Model/Repository.js +2 -1
  40. package/dist/Model/dsl.d.ts +6 -5
  41. package/dist/Model/dsl.d.ts.map +1 -1
  42. package/dist/Model/dsl.js +2 -3
  43. package/dist/Model/filter/filterApi.d.ts +5 -5
  44. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  45. package/dist/Model/filter/types/errors.d.ts +1 -1
  46. package/dist/Model/filter/types/fields.d.ts +1 -1
  47. package/dist/Model/filter/types/path/common.d.ts +1 -1
  48. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  49. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  50. package/dist/Model/filter/types/path/eager.js +1 -1
  51. package/dist/Model/filter/types/path/index.d.ts +1 -1
  52. package/dist/Model/filter/types/utils.d.ts +1 -1
  53. package/dist/Model/filter/types/validator.d.ts +1 -1
  54. package/dist/Model/filter/types.d.ts +1 -1
  55. package/dist/Model/query/dsl.d.ts +142 -17
  56. package/dist/Model/query/dsl.d.ts.map +1 -1
  57. package/dist/Model/query/dsl.js +190 -5
  58. package/dist/Model/query/new-kid-interpreter.d.ts +77 -8
  59. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  60. package/dist/Model/query/new-kid-interpreter.js +127 -6
  61. package/dist/Model/query.d.ts +1 -1
  62. package/dist/Model.d.ts +2 -1
  63. package/dist/Model.d.ts.map +1 -1
  64. package/dist/Model.js +2 -1
  65. package/dist/QueueMaker/SQLQueue.d.ts +7 -8
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +135 -117
  68. package/dist/QueueMaker/errors.d.ts +5 -3
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/errors.js +4 -2
  71. package/dist/QueueMaker/memQueue.d.ts +9 -5
  72. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  73. package/dist/QueueMaker/memQueue.js +81 -65
  74. package/dist/QueueMaker/sbqueue.d.ts +8 -4
  75. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  76. package/dist/QueueMaker/sbqueue.js +57 -55
  77. package/dist/QueueMaker/service.d.ts +4 -2
  78. package/dist/QueueMaker/service.d.ts.map +1 -1
  79. package/dist/QueueMaker/service.js +1 -1
  80. package/dist/RequestContext.d.ts +75 -35
  81. package/dist/RequestContext.d.ts.map +1 -1
  82. package/dist/RequestContext.js +14 -14
  83. package/dist/RequestFiberSet.d.ts +10 -7
  84. package/dist/RequestFiberSet.d.ts.map +1 -1
  85. package/dist/RequestFiberSet.js +8 -3
  86. package/dist/Store/ContextMapContainer.d.ts +22 -3
  87. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  88. package/dist/Store/ContextMapContainer.js +17 -3
  89. package/dist/Store/Cosmos/query.d.ts +7 -2
  90. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  91. package/dist/Store/Cosmos/query.js +115 -35
  92. package/dist/Store/Cosmos.d.ts +2 -2
  93. package/dist/Store/Cosmos.d.ts.map +1 -1
  94. package/dist/Store/Cosmos.js +343 -244
  95. package/dist/Store/Disk.d.ts +3 -3
  96. package/dist/Store/Disk.d.ts.map +1 -1
  97. package/dist/Store/Disk.js +76 -36
  98. package/dist/Store/Memory.d.ts +7 -4
  99. package/dist/Store/Memory.d.ts.map +1 -1
  100. package/dist/Store/Memory.js +251 -58
  101. package/dist/Store/SQL/Pg.d.ts +4 -0
  102. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  103. package/dist/Store/SQL/Pg.js +233 -0
  104. package/dist/Store/SQL/query.d.ts +43 -0
  105. package/dist/Store/SQL/query.d.ts.map +1 -0
  106. package/dist/Store/SQL/query.js +478 -0
  107. package/dist/Store/SQL.d.ts +21 -0
  108. package/dist/Store/SQL.d.ts.map +1 -0
  109. package/dist/Store/SQL.js +450 -0
  110. package/dist/Store/codeFilter.d.ts +2 -2
  111. package/dist/Store/codeFilter.d.ts.map +1 -1
  112. package/dist/Store/codeFilter.js +6 -3
  113. package/dist/Store/index.d.ts +6 -3
  114. package/dist/Store/index.d.ts.map +1 -1
  115. package/dist/Store/index.js +18 -4
  116. package/dist/Store/service.d.ts +26 -8
  117. package/dist/Store/service.d.ts.map +1 -1
  118. package/dist/Store/service.js +25 -6
  119. package/dist/Store/utils.d.ts +3 -2
  120. package/dist/Store/utils.d.ts.map +1 -1
  121. package/dist/Store/utils.js +5 -5
  122. package/dist/Store.d.ts +1 -1
  123. package/dist/adapters/SQL/Model.d.ts +32 -43
  124. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  125. package/dist/adapters/SQL/Model.js +30 -39
  126. package/dist/adapters/SQL.d.ts +1 -1
  127. package/dist/adapters/ServiceBus.d.ts +14 -11
  128. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  129. package/dist/adapters/ServiceBus.js +30 -21
  130. package/dist/adapters/cosmos-client.d.ts +5 -3
  131. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  132. package/dist/adapters/cosmos-client.js +5 -3
  133. package/dist/adapters/index.d.ts +8 -2
  134. package/dist/adapters/index.d.ts.map +1 -1
  135. package/dist/adapters/index.js +8 -2
  136. package/dist/adapters/logger.d.ts +2 -2
  137. package/dist/adapters/logger.d.ts.map +1 -1
  138. package/dist/adapters/memQueue.d.ts +5 -3
  139. package/dist/adapters/memQueue.d.ts.map +1 -1
  140. package/dist/adapters/memQueue.js +6 -5
  141. package/dist/adapters/mongo-client.d.ts +4 -3
  142. package/dist/adapters/mongo-client.d.ts.map +1 -1
  143. package/dist/adapters/mongo-client.js +5 -3
  144. package/dist/adapters/redis-client.d.ts +6 -3
  145. package/dist/adapters/redis-client.d.ts.map +1 -1
  146. package/dist/adapters/redis-client.js +7 -3
  147. package/dist/api/ContextProvider.d.ts +12 -8
  148. package/dist/api/ContextProvider.d.ts.map +1 -1
  149. package/dist/api/ContextProvider.js +9 -7
  150. package/dist/api/codec.d.ts +1 -1
  151. package/dist/api/internal/RequestContextMiddleware.d.ts +3 -3
  152. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  153. package/dist/api/internal/RequestContextMiddleware.js +10 -6
  154. package/dist/api/internal/auth.d.ts +45 -7
  155. package/dist/api/internal/auth.d.ts.map +1 -1
  156. package/dist/api/internal/auth.js +162 -29
  157. package/dist/api/internal/events.d.ts +6 -4
  158. package/dist/api/internal/events.d.ts.map +1 -1
  159. package/dist/api/internal/events.js +16 -9
  160. package/dist/api/internal/health.d.ts +1 -1
  161. package/dist/api/layerUtils.d.ts +10 -6
  162. package/dist/api/layerUtils.d.ts.map +1 -1
  163. package/dist/api/layerUtils.js +7 -6
  164. package/dist/api/middlewares.d.ts +1 -1
  165. package/dist/api/reportError.d.ts +2 -2
  166. package/dist/api/reportError.d.ts.map +1 -1
  167. package/dist/api/reportError.js +3 -2
  168. package/dist/api/routing/middleware/RouterMiddleware.d.ts +5 -4
  169. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware/middleware.d.ts +42 -3
  171. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  172. package/dist/api/routing/middleware/middleware.js +53 -17
  173. package/dist/api/routing/middleware.d.ts +1 -2
  174. package/dist/api/routing/middleware.d.ts.map +1 -1
  175. package/dist/api/routing/middleware.js +1 -2
  176. package/dist/api/routing/schema/jwt.d.ts +1 -1
  177. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  178. package/dist/api/routing/schema/jwt.js +3 -2
  179. package/dist/api/routing/tsort.d.ts +1 -1
  180. package/dist/api/routing/tsort.d.ts.map +1 -1
  181. package/dist/api/routing/utils.d.ts +4 -4
  182. package/dist/api/routing/utils.d.ts.map +1 -1
  183. package/dist/api/routing/utils.js +3 -2
  184. package/dist/api/routing.d.ts +84 -37
  185. package/dist/api/routing.d.ts.map +1 -1
  186. package/dist/api/routing.js +115 -45
  187. package/dist/api/setupRequest.d.ts +10 -6
  188. package/dist/api/setupRequest.d.ts.map +1 -1
  189. package/dist/api/setupRequest.js +15 -7
  190. package/dist/api/util.d.ts +1 -1
  191. package/dist/arbs.d.ts +2 -2
  192. package/dist/arbs.d.ts.map +1 -1
  193. package/dist/arbs.js +5 -3
  194. package/dist/errorReporter.d.ts +7 -5
  195. package/dist/errorReporter.d.ts.map +1 -1
  196. package/dist/errorReporter.js +22 -26
  197. package/dist/errors.d.ts +1 -1
  198. package/dist/fileUtil.d.ts +2 -2
  199. package/dist/fileUtil.d.ts.map +1 -1
  200. package/dist/fileUtil.js +2 -2
  201. package/dist/index.d.ts +1 -1
  202. package/dist/logger/jsonLogger.d.ts +2 -2
  203. package/dist/logger/jsonLogger.d.ts.map +1 -1
  204. package/dist/logger/jsonLogger.js +4 -2
  205. package/dist/logger/logFmtLogger.d.ts +2 -2
  206. package/dist/logger/logFmtLogger.d.ts.map +1 -1
  207. package/dist/logger/logFmtLogger.js +2 -2
  208. package/dist/logger/shared.d.ts +2 -2
  209. package/dist/logger/shared.d.ts.map +1 -1
  210. package/dist/logger/shared.js +3 -3
  211. package/dist/logger.d.ts +1 -1
  212. package/dist/logger.d.ts.map +1 -1
  213. package/dist/otel.d.ts +75 -0
  214. package/dist/otel.d.ts.map +1 -0
  215. package/dist/otel.js +65 -0
  216. package/dist/rateLimit.d.ts +12 -4
  217. package/dist/rateLimit.d.ts.map +1 -1
  218. package/dist/rateLimit.js +7 -12
  219. package/dist/test.d.ts +3 -3
  220. package/dist/test.d.ts.map +1 -1
  221. package/dist/test.js +2 -2
  222. package/dist/vitest.d.ts +1 -1
  223. package/examples/query.ts +46 -38
  224. package/package.json +46 -37
  225. package/src/CUPS.ts +15 -11
  226. package/src/Emailer/Sendgrid.ts +21 -15
  227. package/src/Emailer/fake.ts +1 -1
  228. package/src/Emailer/service.ts +13 -3
  229. package/src/MainFiberSet.ts +9 -6
  230. package/src/Model/Repository/Registry.ts +34 -0
  231. package/src/Model/Repository/ext.ts +103 -11
  232. package/src/Model/Repository/internal/internal.ts +231 -149
  233. package/src/Model/Repository/legacy.ts +3 -1
  234. package/src/Model/Repository/makeRepo.ts +15 -10
  235. package/src/Model/Repository/service.ts +35 -23
  236. package/src/Model/Repository/validation.ts +5 -5
  237. package/src/Model/Repository.ts +1 -0
  238. package/src/Model/dsl.ts +5 -4
  239. package/src/Model/filter/types/path/eager.ts +1 -2
  240. package/src/Model/query/dsl.ts +353 -19
  241. package/src/Model/query/new-kid-interpreter.ts +211 -6
  242. package/src/Model.ts +1 -0
  243. package/src/QueueMaker/SQLQueue.ts +150 -153
  244. package/src/QueueMaker/errors.ts +3 -1
  245. package/src/QueueMaker/memQueue.ts +111 -105
  246. package/src/QueueMaker/sbqueue.ts +76 -88
  247. package/src/QueueMaker/service.ts +3 -1
  248. package/src/RequestContext.ts +15 -16
  249. package/src/RequestFiberSet.ts +8 -2
  250. package/src/Store/ContextMapContainer.ts +45 -2
  251. package/src/Store/Cosmos/query.ts +143 -44
  252. package/src/Store/Cosmos.ts +491 -350
  253. package/src/Store/Disk.ts +106 -66
  254. package/src/Store/Memory.ts +285 -87
  255. package/src/Store/SQL/Pg.ts +364 -0
  256. package/src/Store/SQL/query.ts +540 -0
  257. package/src/Store/SQL.ts +736 -0
  258. package/src/Store/codeFilter.ts +5 -2
  259. package/src/Store/index.ts +20 -3
  260. package/src/Store/service.ts +45 -10
  261. package/src/Store/utils.ts +25 -23
  262. package/src/adapters/SQL/Model.ts +42 -41
  263. package/src/adapters/ServiceBus.ts +131 -121
  264. package/src/adapters/cosmos-client.ts +4 -2
  265. package/src/adapters/index.ts +7 -0
  266. package/src/adapters/memQueue.ts +5 -4
  267. package/src/adapters/mongo-client.ts +4 -2
  268. package/src/adapters/redis-client.ts +6 -2
  269. package/src/api/ContextProvider.ts +17 -13
  270. package/src/api/internal/RequestContextMiddleware.ts +16 -5
  271. package/src/api/internal/auth.ts +248 -44
  272. package/src/api/internal/events.ts +19 -10
  273. package/src/api/layerUtils.ts +12 -8
  274. package/src/api/reportError.ts +2 -1
  275. package/src/api/routing/middleware/RouterMiddleware.ts +5 -4
  276. package/src/api/routing/middleware/middleware.ts +60 -15
  277. package/src/api/routing/middleware.ts +0 -2
  278. package/src/api/routing/schema/jwt.ts +2 -1
  279. package/src/api/routing/utils.ts +2 -1
  280. package/src/api/routing.ts +304 -131
  281. package/src/api/setupRequest.ts +31 -8
  282. package/src/arbs.ts +5 -3
  283. package/src/errorReporter.ts +65 -75
  284. package/src/fileUtil.ts +1 -1
  285. package/src/logger/jsonLogger.ts +3 -1
  286. package/src/logger/logFmtLogger.ts +1 -1
  287. package/src/logger/shared.ts +3 -2
  288. package/src/otel.ts +152 -0
  289. package/src/rateLimit.ts +34 -23
  290. package/src/test.ts +2 -2
  291. package/test/auth.test.ts +101 -0
  292. package/test/contextProvider.test.ts +14 -11
  293. package/test/controller.test.ts +25 -29
  294. package/test/dist/auth.test.d.ts.map +1 -0
  295. package/test/dist/contextProvider.test.d.ts.map +1 -1
  296. package/test/dist/controller.test.d.ts.map +1 -1
  297. package/test/dist/date-query.test.d.ts.map +1 -0
  298. package/test/dist/fixtures.d.ts +30 -12
  299. package/test/dist/fixtures.d.ts.map +1 -1
  300. package/test/dist/fixtures.js +17 -10
  301. package/test/dist/query.test.d.ts.map +1 -1
  302. package/test/dist/rawQuery.test.d.ts.map +1 -1
  303. package/test/dist/repository-ext.test.d.ts.map +1 -0
  304. package/test/dist/requires.test.d.ts.map +1 -1
  305. package/test/dist/router-generator.test.d.ts.map +1 -0
  306. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  307. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  308. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  309. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  310. package/test/dist/sql-store.test.d.ts.map +1 -0
  311. package/test/fixtures.ts +16 -9
  312. package/test/layerUtils.test.ts +1 -1
  313. package/test/query.test.ts +819 -38
  314. package/test/rawQuery.test.ts +312 -20
  315. package/test/repository-ext.test.ts +62 -0
  316. package/test/requires.test.ts +10 -5
  317. package/test/router-generator.test.ts +187 -0
  318. package/test/routing-interruptibility.test.ts +66 -0
  319. package/test/rpc-e2e-invalidation.test.ts +256 -0
  320. package/test/rpc-multi-middleware.test.ts +84 -9
  321. package/test/rpc-stream-fullstack.test.ts +304 -0
  322. package/test/sql-store.test.ts +1592 -0
  323. package/test/validateSample.test.ts +17 -12
  324. package/tsconfig.examples.json +1 -1
  325. package/tsconfig.json +0 -1
  326. package/tsconfig.json.bak +2 -2
  327. package/tsconfig.src.json +35 -35
  328. package/tsconfig.test.json +2 -2
  329. package/dist/Operations.d.ts +0 -55
  330. package/dist/Operations.d.ts.map +0 -1
  331. package/dist/Operations.js +0 -102
  332. package/dist/OperationsRepo.d.ts +0 -41
  333. package/dist/OperationsRepo.d.ts.map +0 -1
  334. package/dist/OperationsRepo.js +0 -14
  335. package/eslint.config.mjs +0 -24
  336. package/src/Operations.ts +0 -235
  337. package/src/OperationsRepo.ts +0 -16
@@ -1,25 +1,35 @@
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 * as Option from "effect-app/Option"
8
+ import * as S from "effect-app/Schema"
9
+ import { flow, pipe } from "effect/Function"
10
+ import * as SchemaTransformation from "effect/SchemaTransformation"
11
+ import * as Struct from "effect/Struct"
5
12
  import { inspect } from "util"
6
13
  import { expect, expectTypeOf, it } from "vitest"
7
14
  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"
15
+ import { and, computed, count, expr, make, one, or, order, page, project, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "../src/Model/query.js"
9
16
  import { makeRepo } from "../src/Model/Repository.js"
17
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
10
18
  import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
11
19
  import { SomeService } from "./fixtures.js"
12
20
 
21
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
22
+
13
23
  const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
14
- const num = S.Struct({ _tag: S.Literal("number"), value: S.Number })
24
+ const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
15
25
  const someUnion = S.Union([str, num])
16
26
 
17
27
  export class Something extends S.Class<Something>("Something")({
18
- id: S.StringId.withDefault,
28
+ id: S.StringId.withConstructorDefault,
19
29
  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" })))
30
+ name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
31
+ n: S.Date.withConstructorDefault,
32
+ union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
23
33
  }) {}
24
34
  export declare namespace Something {
25
35
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -90,8 +100,8 @@ it("works", () => {
90
100
 
91
101
  const processed = memFilter(interpreted)(items.map((_) =>
92
102
  S.encodeUnknownSync(S.Struct({
93
- ...Something.omit("displayName"),
94
- displayName: S.Literal("Verona", "Riley")
103
+ ...Struct.omit(Something.fields, ["displayName"]),
104
+ displayName: S.Literals(["Verona", "Riley"])
95
105
  }))(_)
96
106
  ))
97
107
 
@@ -99,7 +109,7 @@ it("works", () => {
99
109
  })
100
110
 
101
111
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
102
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo", {
112
+ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
103
113
  make: Effect.gen(function*() {
104
114
  return yield* makeRepo("Something", Something, {})
105
115
  })
@@ -112,7 +122,7 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo",
112
122
  })
113
123
  )
114
124
  .pipe(
115
- Layer.provide(MemoryStoreLive)
125
+ Layer.provide(TestStoreLive)
116
126
  )
117
127
  }
118
128
 
@@ -236,15 +246,15 @@ it("collect", () =>
236
246
  Effect.runPromise
237
247
  ))
238
248
 
239
- class Person extends S.ExtendedTaggedClass<Person, Person.Encoded>()("person", {
249
+ class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
240
250
  id: S.String,
241
251
  surname: S.String
242
252
  }) {}
243
- class Animal extends S.ExtendedTaggedClass<Animal, Animal.Encoded>()("animal", {
253
+ class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
244
254
  id: S.String,
245
255
  surname: S.String
246
256
  }) {}
247
- class Test extends S.ExtendedTaggedClass<Test, Test.Encoded>()("test", {
257
+ class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
248
258
  id: S.String
249
259
  }) {}
250
260
 
@@ -279,7 +289,7 @@ it(
279
289
  expect(result).toEqual([])
280
290
  expect(result2).toEqual([])
281
291
  })
282
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
292
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
283
293
  )
284
294
 
285
295
  it(
@@ -465,7 +475,7 @@ it(
465
475
 
466
476
  expect([]).toEqual([])
467
477
  })
468
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
478
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
469
479
  )
470
480
 
471
481
  it(
@@ -508,7 +518,7 @@ it(
508
518
 
509
519
  expect([]).toEqual([])
510
520
  })
511
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
521
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
512
522
  )
513
523
 
514
524
  it(
@@ -519,8 +529,8 @@ it(
519
529
  const schema = S.Struct({
520
530
  id: S.String,
521
531
  createdAt: S.Date.pipe(
522
- S.withDecodingDefault(() => new Date().toISOString()),
523
- S.withConstructorDefault(() => Option.some(new Date()))
532
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
533
+ S.withConstructorDefault(Effect.sync(() => new Date()))
524
534
  )
525
535
  })
526
536
  const repo = yield* makeRepo(
@@ -532,8 +542,8 @@ it(
532
542
  const outputSchema = S.Struct({
533
543
  id: S.Literal("123"),
534
544
  createdAt: S.Date.pipe(
535
- S.withDecodingDefault(() => new Date().toISOString()),
536
- S.withConstructorDefault(() => Option.some(new Date()))
545
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
546
+ S.withConstructorDefault(Effect.sync(() => new Date()))
537
547
  )
538
548
  })
539
549
 
@@ -541,9 +551,246 @@ it(
541
551
 
542
552
  expect(result).toEqual([])
543
553
  })
544
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
554
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
545
555
  )
546
556
 
557
+ it(
558
+ "project with encodeKeys in projection maps encoded keys",
559
+ () =>
560
+ Effect
561
+ .gen(function*() {
562
+ const schema = S.Struct({
563
+ id: S.String,
564
+ a: S.Number
565
+ })
566
+
567
+ const repo = yield* makeRepo(
568
+ "test",
569
+ schema,
570
+ {
571
+ makeInitial: Effect.sync(() => [{ id: "1", a: 1 }])
572
+ }
573
+ )
574
+
575
+ const outputSchema = S.Struct({ b: S.Number }).pipe(S.encodeKeys({ b: "a" }))
576
+
577
+ const result = yield* repo.query(project(outputSchema))
578
+
579
+ expect(result).toStrictEqual([{ b: 1 }])
580
+ })
581
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
582
+ )
583
+
584
+ it("projectComputed sets computed IR and forces project mode", () => {
585
+ const baseSchema = S.Struct({
586
+ id: S.String,
587
+ items: S.Array(S.Struct({
588
+ state: S.Struct({
589
+ _tag: S.String
590
+ })
591
+ }))
592
+ })
593
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
594
+ projectComputed(
595
+ S.Struct({
596
+ pickedCount: S.NonNegativeInt
597
+ }),
598
+ computed({
599
+ pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count(where("state._tag", "Picked"))
600
+ })
601
+ )
602
+ )
603
+ const interpreted = toFilter(query, baseSchema)
604
+ expect(interpreted.mode).toBe("project")
605
+ expect(interpreted.select).toEqual([
606
+ {
607
+ key: "pickedCount",
608
+ computed: {
609
+ _tag: "relation-count",
610
+ path: "items",
611
+ filter: [{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }]
612
+ }
613
+ }
614
+ ])
615
+ expect(interpreted.computed?.["pickedCount"]?._tag).toBe("relation-count")
616
+ expect(interpreted.computed?.["pickedCount"]?.path).toBe("items")
617
+ expect(interpreted.computed?.["pickedCount"]?.filter).toEqual([
618
+ { t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
619
+ ])
620
+ })
621
+
622
+ it("projectComputed validates extra computed keys", () => {
623
+ const baseSchema = S.Struct({
624
+ id: S.String,
625
+ items: S.Array(S.Struct({ value: S.Number }))
626
+ })
627
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
628
+ projectComputed(
629
+ S.Struct({ id: S.String }),
630
+ computed({
631
+ pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count()
632
+ })
633
+ )
634
+ )
635
+ expect(() => toFilter(query, baseSchema)).toThrowError("Computed projection keys must exist in projection schema")
636
+ })
637
+
638
+ it("projection schema with computed fields fails without computed map", () => {
639
+ const baseSchema = S.Struct({
640
+ id: S.String,
641
+ items: S.Array(S.Struct({ value: S.Number }))
642
+ })
643
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
644
+ projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
645
+ )
646
+ expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
647
+ })
648
+
649
+ it("projectComputed.every emits relation-every IR", () => {
650
+ const baseSchema = S.Struct({
651
+ id: S.String,
652
+ items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
653
+ })
654
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
655
+ projectComputed(
656
+ S.Struct({ allPicked: S.Boolean }),
657
+ computed({
658
+ allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
659
+ })
660
+ )
661
+ )
662
+ const interpreted = toFilter(query, baseSchema)
663
+ expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
664
+ expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
665
+ expect(interpreted.computed?.["allPicked"]?.filter).toEqual([
666
+ { t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
667
+ ])
668
+ })
669
+
670
+ it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
671
+ const baseSchema = S.Struct({
672
+ id: S.String,
673
+ items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
674
+ })
675
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
676
+ projectComputed(
677
+ S.Struct({ positionCount: S.NonNegativeInt }),
678
+ computed({
679
+ positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
680
+ "rowId",
681
+ where("state._tag", "neq", "cancelled")
682
+ )
683
+ })
684
+ )
685
+ )
686
+ const interpreted = toFilter(query, baseSchema)
687
+ const ir = interpreted.computed?.["positionCount"]
688
+ expect(ir?._tag).toBe("relation-distinct-count")
689
+ expect((ir as { field: string } | undefined)?.field).toBe("rowId")
690
+ expect(ir?.filter).toEqual([
691
+ { t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
692
+ ])
693
+ })
694
+
695
+ it("projectComputed.sum emits relation-sum IR with field", () => {
696
+ const baseSchema = S.Struct({
697
+ id: S.String,
698
+ items: S.Array(S.Struct({ weight: S.Number }))
699
+ })
700
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
701
+ projectComputed(
702
+ S.Struct({ totalWeight: S.Number }),
703
+ computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
704
+ )
705
+ )
706
+ const interpreted = toFilter(query, baseSchema)
707
+ const ir = interpreted.computed?.["totalWeight"]
708
+ expect(ir?._tag).toBe("relation-sum")
709
+ expect((ir as { field: string } | undefined)?.field).toBe("weight")
710
+ expect(ir?.filter).toEqual([])
711
+ })
712
+
713
+ it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
714
+ const baseSchema = S.Struct({
715
+ id: S.String,
716
+ items: S.Array(S.Struct({ articleId: S.String }))
717
+ })
718
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
719
+ projectComputed(
720
+ S.Struct({
721
+ all: S.Array(S.String),
722
+ distinct: S.Array(S.String)
723
+ }),
724
+ computed({
725
+ all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
726
+ distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
727
+ })
728
+ )
729
+ )
730
+ const interpreted = toFilter(query, baseSchema)
731
+ const all = interpreted.computed?.["all"]
732
+ const distinct = interpreted.computed?.["distinct"]
733
+ expect(all?._tag).toBe("relation-collect")
734
+ expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
735
+ expect(distinct?._tag).toBe("relation-collect")
736
+ expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
737
+ })
738
+
739
+ it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
740
+ const baseSchema = S.Struct({
741
+ id: S.String,
742
+ items: S.Array(S.Struct({
743
+ weight: S.Number,
744
+ tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
745
+ }))
746
+ })
747
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
748
+ projectComputed(
749
+ S.Struct({ total: S.Number }),
750
+ computed({
751
+ total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
752
+ expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
753
+ )
754
+ })
755
+ )
756
+ )
757
+ const interpreted = toFilter(query, baseSchema)
758
+ const ir = interpreted.computed?.["total"]
759
+ expect(ir?._tag).toBe("relation-sum-expr")
760
+ expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
761
+ _tag: "mul",
762
+ left: { _tag: "field", field: "weight" },
763
+ right: { _tag: "field", field: "tradeUnit.amount" }
764
+ })
765
+ })
766
+
767
+ it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
768
+ const baseSchema = S.Struct({
769
+ id: S.String,
770
+ items: S.Array(S.Struct({
771
+ weight: S.Number,
772
+ tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
773
+ }))
774
+ })
775
+ const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
776
+ projectComputed(
777
+ S.Struct({
778
+ totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
779
+ }),
780
+ computed({
781
+ totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
782
+ expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
783
+ { unit: "tradeUnit.unit" }
784
+ )
785
+ })
786
+ )
787
+ )
788
+ const interpreted = toFilter(query, baseSchema)
789
+ const ir = interpreted.computed?.["totals"]
790
+ expect(ir?._tag).toBe("relation-sum-expr-by")
791
+ expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
792
+ })
793
+
547
794
  it(
548
795
  "doesn't mess when refining fields",
549
796
  () =>
@@ -551,7 +798,7 @@ it(
551
798
  .gen(function*() {
552
799
  const schema = S.Struct({
553
800
  id: S.String,
554
- literals: S.Literal("a", "b", "c")
801
+ literals: S.Literals(["a", "b", "c"])
555
802
  })
556
803
 
557
804
  type Schema = typeof schema.Type
@@ -571,7 +818,7 @@ it(
571
818
 
572
819
  expect(result).toEqual([])
573
820
  })
574
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
821
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
575
822
  )
576
823
 
577
824
  it(
@@ -581,7 +828,7 @@ it(
581
828
  .gen(function*() {
582
829
  const schema = S.Struct({
583
830
  id: S.String,
584
- literals: S.Union([S.Literal("a", "b", "c"), S.Null])
831
+ literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
585
832
  })
586
833
 
587
834
  type Schema = typeof schema.Type
@@ -615,7 +862,7 @@ it(
615
862
 
616
863
  expect(result).toEqual([])
617
864
  })
618
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
865
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
619
866
  )
620
867
 
621
868
  it(
@@ -659,7 +906,7 @@ it(
659
906
 
660
907
  expect(result).toEqual([])
661
908
  })
662
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
909
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
663
910
  )
664
911
 
665
912
  it("remove null from one constituent of a tagged union", () =>
@@ -672,7 +919,7 @@ it("remove null from one constituent of a tagged union", () =>
672
919
 
673
920
  class BB extends S.Class<BB>("BB")({
674
921
  id: S.Literal("BB"),
675
- b: S.NullOr(S.Number)
922
+ b: S.NullOr(S.Finite)
676
923
  }) {}
677
924
 
678
925
  type Union = AA | BB
@@ -708,7 +955,7 @@ it("remove null from one constituent of a tagged union", () =>
708
955
  })[]
709
956
  >()
710
957
  })
711
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
958
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
712
959
 
713
960
  it("refine 3", () =>
714
961
  Effect
@@ -746,7 +993,7 @@ it("refine 3", () =>
746
993
  const resQuer1 = yield* repo.query(where("id", "AA"))
747
994
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
748
995
  })
749
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
996
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
750
997
 
751
998
  it("my test", () =>
752
999
  Effect
@@ -764,7 +1011,7 @@ it("my test", () =>
764
1011
  )
765
1012
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
766
1013
  })
767
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1014
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
768
1015
 
769
1016
  it("refine inner without imposing a projection", () =>
770
1017
  Effect
@@ -808,7 +1055,7 @@ it("refine inner without imposing a projection", () =>
808
1055
  where("union._tag", "AA"),
809
1056
  // But if I wanna the whole Data as output ignoring the inner refinement
810
1057
  // I wanna be able to do so
811
- project(S.Struct(Data.pick("union")))
1058
+ project(Data.mapFields(Struct.pick(["union"])))
812
1059
  )
813
1060
 
814
1061
  expectTypeOf(query2).toEqualTypeOf<
@@ -839,7 +1086,7 @@ it("refine inner without imposing a projection", () =>
839
1086
  }[]
840
1087
  >()
841
1088
  })
842
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1089
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
843
1090
 
844
1091
  it("does not allow string queries on arrays", () =>
845
1092
  Effect
@@ -874,7 +1121,7 @@ it("does not allow string queries on arrays", () =>
874
1121
  expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
875
1122
  expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
876
1123
  })
877
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1124
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
878
1125
 
879
1126
  it("test array.length", () =>
880
1127
  Effect
@@ -915,7 +1162,7 @@ it("test array.length", () =>
915
1162
  QueryWhere<Something, Something>
916
1163
  >()
917
1164
  })
918
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1165
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
919
1166
 
920
1167
  it("distribution over union", () =>
921
1168
  Effect
@@ -939,7 +1186,7 @@ it("distribution over union", () =>
939
1186
  })[]
940
1187
  >()
941
1188
  })
942
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1189
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
943
1190
 
944
1191
  it("refine nested union", () =>
945
1192
  Effect
@@ -980,7 +1227,158 @@ it("refine nested union", () =>
980
1227
  }[]
981
1228
  >()
982
1229
  })
983
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1230
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1231
+
1232
+ it("find with transformed id", () =>
1233
+ Effect
1234
+ .gen(function*() {
1235
+ const ConfiguratorId = S.NonEmptyString255
1236
+
1237
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1238
+ configuratorId: ConfiguratorId,
1239
+ label: S.NonEmptyString50
1240
+ }) {}
1241
+
1242
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1243
+ S.decodeTo(
1244
+ S.toType(PreconfigurationId),
1245
+ SchemaTransformation.transformOrFail({
1246
+ decode: Effect.fnUntraced(function*(value) {
1247
+ const values = value.split("_")
1248
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1249
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1250
+ values.join("_")
1251
+ )
1252
+ return new PreconfigurationId({ configuratorId, label })
1253
+ }),
1254
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1255
+ })
1256
+ ),
1257
+ S.revealCodec
1258
+ )
1259
+
1260
+ const Preconfiguration = S.Struct({
1261
+ id: PreconfigurationIdFromString,
1262
+ name: S.String
1263
+ })
1264
+
1265
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
1266
+
1267
+ const id = new PreconfigurationId({
1268
+ configuratorId: S.NonEmptyString255("myConfigurator"),
1269
+ label: S.NonEmptyString50("myLabel")
1270
+ })
1271
+ const item = { id, name: "test preconfig" }
1272
+
1273
+ yield* repo.saveAndPublish([item])
1274
+
1275
+ const found = yield* repo.find(id)
1276
+ expect(Option.isSome(found)).toBe(true)
1277
+ expect(Option.getOrThrow(found).name).toBe("test preconfig")
1278
+ expect(Option.getOrThrow(found).id).toEqual(id)
1279
+
1280
+ const notFound = yield* repo.find(
1281
+ new PreconfigurationId({
1282
+ configuratorId: S.NonEmptyString255("other"),
1283
+ label: S.NonEmptyString50("nope")
1284
+ })
1285
+ )
1286
+ expect(Option.isNone(notFound)).toBe(true)
1287
+ })
1288
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1289
+
1290
+ it("find with transformed id in tagged union", () =>
1291
+ Effect
1292
+ .gen(function*() {
1293
+ const ConfiguratorId = S.NonEmptyString255
1294
+
1295
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1296
+ configuratorId: ConfiguratorId,
1297
+ label: S.NonEmptyString50
1298
+ }) {}
1299
+
1300
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1301
+ S.decodeTo(
1302
+ S.toType(PreconfigurationId),
1303
+ SchemaTransformation.transformOrFail({
1304
+ decode: Effect.fnUntraced(function*(value) {
1305
+ const values = value.split("_")
1306
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1307
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
1308
+ values.join("_")
1309
+ )
1310
+ return new PreconfigurationId({ configuratorId, label })
1311
+ }),
1312
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1313
+ })
1314
+ ),
1315
+ S.revealCodec
1316
+ )
1317
+
1318
+ class Draft extends S.TaggedClass<Draft>()("Draft", {
1319
+ id: PreconfigurationIdFromString,
1320
+ name: S.String
1321
+ }) {}
1322
+
1323
+ class Published extends S.TaggedClass<Published>()("Published", {
1324
+ id: PreconfigurationIdFromString,
1325
+ name: S.String,
1326
+ publishedAt: S.String
1327
+ }) {}
1328
+
1329
+ class Archived extends S.TaggedClass<Archived>()("Archived", {
1330
+ id: PreconfigurationIdFromString,
1331
+ name: S.String,
1332
+ archivedAt: S.String
1333
+ }) {}
1334
+
1335
+ const Preconfiguration = S.Union([Draft, Published, Archived])
1336
+
1337
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
1338
+
1339
+ const id1 = new PreconfigurationId({
1340
+ configuratorId: S.NonEmptyString255("conf1"),
1341
+ label: S.NonEmptyString50("draft1")
1342
+ })
1343
+ const id2 = new PreconfigurationId({
1344
+ configuratorId: S.NonEmptyString255("conf2"),
1345
+ label: S.NonEmptyString50("pub1")
1346
+ })
1347
+ const id3 = new PreconfigurationId({
1348
+ configuratorId: S.NonEmptyString255("conf3"),
1349
+ label: S.NonEmptyString50("arch1")
1350
+ })
1351
+
1352
+ const draft = new Draft({ id: id1, name: "my draft" })
1353
+ const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
1354
+ const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
1355
+
1356
+ yield* repo.saveAndPublish([draft, published, archived])
1357
+
1358
+ // find each by their PreconfigurationId instance
1359
+ const foundDraft = yield* repo.find(id1)
1360
+ expect(Option.isSome(foundDraft)).toBe(true)
1361
+ expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
1362
+ expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
1363
+
1364
+ const foundPublished = yield* repo.find(id2)
1365
+ expect(Option.isSome(foundPublished)).toBe(true)
1366
+ expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
1367
+
1368
+ const foundArchived = yield* repo.find(id3)
1369
+ expect(Option.isSome(foundArchived)).toBe(true)
1370
+ expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
1371
+
1372
+ // not found
1373
+ const notFound = yield* repo.find(
1374
+ new PreconfigurationId({
1375
+ configuratorId: S.NonEmptyString255("nope"),
1376
+ label: S.NonEmptyString50("nope")
1377
+ })
1378
+ )
1379
+ expect(Option.isNone(notFound)).toBe(true)
1380
+ })
1381
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
984
1382
 
985
1383
  it("refine union with nested union", () =>
986
1384
  Effect
@@ -1095,4 +1493,387 @@ it("refine union with nested union", () =>
1095
1493
  })[]
1096
1494
  >()
1097
1495
  })
1098
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1496
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1497
+
1498
+ // ---------------------------------------------------------------------------
1499
+ // memFilter: computed projection execution (in-memory) and code filter coverage
1500
+ // ---------------------------------------------------------------------------
1501
+
1502
+ const computedBaseSchema = S.Struct({
1503
+ id: S.String,
1504
+ status: S.Literals(["active", "archived"]),
1505
+ items: S.Array(S.Struct({
1506
+ id: S.String,
1507
+ tag: S.Literals(["a", "b", "c"]),
1508
+ qty: S.Finite,
1509
+ note: S.String
1510
+ }))
1511
+ })
1512
+ type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
1513
+
1514
+ const computedRows: ComputedBase[] = [
1515
+ {
1516
+ id: "r1",
1517
+ status: "active",
1518
+ items: [
1519
+ { id: "i1", tag: "a", qty: 10, note: "alpha" },
1520
+ { id: "i2", tag: "a", qty: 20, note: "alpha" },
1521
+ { id: "i3", tag: "b", qty: 5, note: "beta" }
1522
+ ]
1523
+ },
1524
+ { id: "r2", status: "active", items: [] },
1525
+ {
1526
+ id: "r3",
1527
+ status: "archived",
1528
+ items: [
1529
+ { id: "i4", tag: "b", qty: 7, note: "gamma" },
1530
+ { id: "i5", tag: "b", qty: 7, note: "gamma" },
1531
+ { id: "i6", tag: "c", qty: 3, note: "delta" }
1532
+ ]
1533
+ }
1534
+ ]
1535
+
1536
+ it("memFilter: relation-count with filter", () => {
1537
+ const q = make<ComputedBase>().pipe(
1538
+ projectComputed(
1539
+ S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
1540
+ computed({
1541
+ aCount: relation<ComputedBase>("items").count(where("tag", "a"))
1542
+ })
1543
+ )
1544
+ )
1545
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1546
+ { id: "r1", aCount: 2 },
1547
+ { id: "r2", aCount: 0 },
1548
+ { id: "r3", aCount: 0 }
1549
+ ])
1550
+ })
1551
+
1552
+ it("memFilter: relation-any / every with filter", () => {
1553
+ const q = make<ComputedBase>().pipe(
1554
+ projectComputed(
1555
+ S.Struct({
1556
+ id: S.String,
1557
+ hasA: S.Boolean,
1558
+ allB: S.Boolean
1559
+ }),
1560
+ computed({
1561
+ hasA: relation<ComputedBase>("items").any(where("tag", "a")),
1562
+ allB: relation<ComputedBase>("items").every(where("tag", "b"))
1563
+ })
1564
+ )
1565
+ )
1566
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1567
+ { id: "r1", hasA: true, allB: false },
1568
+ // empty array: any → false, every → true (JS Array.every on [] is true)
1569
+ { id: "r2", hasA: false, allB: true },
1570
+ { id: "r3", hasA: false, allB: false }
1571
+ ])
1572
+ })
1573
+
1574
+ it("memFilter: relation-distinct-count with filter", () => {
1575
+ const q = make<ComputedBase>().pipe(
1576
+ projectComputed(
1577
+ S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
1578
+ computed({
1579
+ distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
1580
+ })
1581
+ )
1582
+ )
1583
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1584
+ { id: "r1", distinctNotes: 2 }, // alpha, beta
1585
+ { id: "r2", distinctNotes: 0 },
1586
+ { id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
1587
+ ])
1588
+ })
1589
+
1590
+ it("memFilter: relation-sum with filter", () => {
1591
+ const q = make<ComputedBase>().pipe(
1592
+ projectComputed(
1593
+ S.Struct({ id: S.String, totalQty: S.Finite }),
1594
+ computed({
1595
+ totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
1596
+ })
1597
+ )
1598
+ )
1599
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1600
+ { id: "r1", totalQty: 35 },
1601
+ { id: "r2", totalQty: 0 },
1602
+ { id: "r3", totalQty: 14 }
1603
+ ])
1604
+ })
1605
+
1606
+ it("memFilter: relation-collect / collectDistinct with filter", () => {
1607
+ const q = make<ComputedBase>().pipe(
1608
+ projectComputed(
1609
+ S.Struct({
1610
+ id: S.String,
1611
+ notes: S.Array(S.String),
1612
+ distinctNotes: S.Array(S.String)
1613
+ }),
1614
+ computed({
1615
+ notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
1616
+ distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
1617
+ })
1618
+ )
1619
+ )
1620
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1621
+ { id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
1622
+ { id: "r2", notes: [], distinctNotes: [] },
1623
+ { id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
1624
+ ])
1625
+ })
1626
+
1627
+ it("memFilter: computed projection with multi-statement relation filter", () => {
1628
+ const q = make<ComputedBase>().pipe(
1629
+ projectComputed(
1630
+ S.Struct({ id: S.String, hits: S.NonNegativeInt }),
1631
+ computed({
1632
+ hits: relation<ComputedBase>("items").count(
1633
+ flow(
1634
+ where("tag", "a"),
1635
+ and("qty", "gt", 10)
1636
+ )
1637
+ )
1638
+ })
1639
+ )
1640
+ )
1641
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1642
+ { id: "r1", hits: 1 }, // only i2 (a, qty 20)
1643
+ { id: "r2", hits: 0 },
1644
+ { id: "r3", hits: 0 }
1645
+ ])
1646
+ })
1647
+
1648
+ it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
1649
+ const schema = S.Struct({
1650
+ id: S.String,
1651
+ items: S.Array(S.Struct({
1652
+ weight: S.Finite,
1653
+ tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
1654
+ }))
1655
+ })
1656
+ type Row = S.Codec.Encoded<typeof schema>
1657
+ const rows: Row[] = [
1658
+ {
1659
+ id: "r1",
1660
+ items: [
1661
+ { weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
1662
+ { weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
1663
+ { weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
1664
+ ]
1665
+ },
1666
+ { id: "r2", items: [] }
1667
+ ]
1668
+ const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
1669
+ const q = make<Row>().pipe(
1670
+ projectComputed(
1671
+ S.Struct({
1672
+ id: S.String,
1673
+ totalRaw: S.Finite,
1674
+ totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
1675
+ totalKg: S.Finite
1676
+ }),
1677
+ computed({
1678
+ totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
1679
+ totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
1680
+ totalKg: relation<Row>("items").sumExprNormalized(weighted, {
1681
+ unit: "tradeUnit.unit",
1682
+ toBase: "kg",
1683
+ factors: { g: 0.001 }
1684
+ }, where("weight", "gte", 0))
1685
+ })
1686
+ )
1687
+ )
1688
+ expect(memFilter(toFilter(q, schema))(rows)).toEqual([
1689
+ {
1690
+ id: "r1",
1691
+ totalRaw: 4013,
1692
+ totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
1693
+ totalKg: 17
1694
+ },
1695
+ { id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
1696
+ ])
1697
+ })
1698
+
1699
+ it("memFilter: computed projection combined with root where filter", () => {
1700
+ const q = make<ComputedBase>().pipe(
1701
+ where("id", "neq", "r3"),
1702
+ projectComputed(
1703
+ S.Struct({ id: S.String, totalQty: S.Finite }),
1704
+ computed({
1705
+ totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
1706
+ })
1707
+ )
1708
+ )
1709
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1710
+ { id: "r1", totalQty: 35 },
1711
+ { id: "r2", totalQty: 0 }
1712
+ ])
1713
+ })
1714
+
1715
+ it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
1716
+ const q = make<ComputedBase>().pipe(
1717
+ order("id", "DESC"),
1718
+ page({ skip: 1, take: 1 }),
1719
+ projectComputed(
1720
+ S.Struct({ id: S.String, total: S.NonNegativeInt }),
1721
+ computed({
1722
+ total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
1723
+ })
1724
+ )
1725
+ )
1726
+ expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
1727
+ { id: "r2", total: 0 }
1728
+ ])
1729
+ })
1730
+
1731
+ it("memFilter: computed projection - relation missing on row returns empty value", () => {
1732
+ const partial: ComputedBase[] = [
1733
+ { id: "x1" } as ComputedBase,
1734
+ { id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
1735
+ ]
1736
+ const q = make<ComputedBase>().pipe(
1737
+ projectComputed(
1738
+ S.Struct({
1739
+ id: S.String,
1740
+ c: S.NonNegativeInt,
1741
+ s: S.Finite,
1742
+ any_: S.Boolean,
1743
+ every_: S.Boolean,
1744
+ coll: S.Array(S.String)
1745
+ }),
1746
+ computed({
1747
+ c: relation<ComputedBase>("items").count(where("tag", "a")),
1748
+ s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
1749
+ any_: relation<ComputedBase>("items").any(where("tag", "a")),
1750
+ every_: relation<ComputedBase>("items").every(where("tag", "a")),
1751
+ coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
1752
+ })
1753
+ )
1754
+ )
1755
+ expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
1756
+ { id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
1757
+ { id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
1758
+ ])
1759
+ })
1760
+
1761
+ it("memFilter: rejects extra computed keys not in projection schema", () => {
1762
+ const q = make<ComputedBase>().pipe(
1763
+ projectComputed(
1764
+ S.Struct({ id: S.String }),
1765
+ computed({
1766
+ bogus: relation<ComputedBase>("items").count(where("tag", "a"))
1767
+ })
1768
+ )
1769
+ )
1770
+ expect(() => toFilter(q, computedBaseSchema)).toThrowError(
1771
+ "Computed projection keys must exist in projection schema"
1772
+ )
1773
+ })
1774
+
1775
+ // ---------------------------------------------------------------------------
1776
+ // memFilter: code filter (where/and/or/scopes) execution coverage
1777
+ // ---------------------------------------------------------------------------
1778
+
1779
+ type CFRow = {
1780
+ readonly id: string
1781
+ readonly tag: "x" | "y" | "z"
1782
+ readonly qty: number
1783
+ readonly desc: string
1784
+ readonly tags: ReadonlyArray<string>
1785
+ readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
1786
+ }
1787
+
1788
+ const cfRows: CFRow[] = [
1789
+ { id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
1790
+ { id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
1791
+ { id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
1792
+ { id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
1793
+ ]
1794
+
1795
+ const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
1796
+
1797
+ it("codeFilter: where + and chain", () => {
1798
+ const q = make<CFRow>().pipe(
1799
+ where("tag", "y"),
1800
+ and("qty", "gt", 25)
1801
+ )
1802
+ expect(runCF(q)).toEqual(["4"])
1803
+ })
1804
+
1805
+ it("codeFilter: where + or chain", () => {
1806
+ const q = make<CFRow>().pipe(
1807
+ where("tag", "x"),
1808
+ or("tag", "z")
1809
+ )
1810
+ expect(runCF(q).sort()).toEqual(["1", "3"])
1811
+ })
1812
+
1813
+ it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
1814
+ const q = make<CFRow>().pipe(
1815
+ where("tag", "y"),
1816
+ and(
1817
+ where("qty", "gt", 25),
1818
+ or("desc", "contains", "good")
1819
+ )
1820
+ )
1821
+ // tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
1822
+ expect(runCF(q).sort()).toEqual(["2", "4"])
1823
+ })
1824
+
1825
+ it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
1826
+ expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
1827
+ expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
1828
+ expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
1829
+ })
1830
+
1831
+ it("codeFilter: array includes / includes-any / includes-all", () => {
1832
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
1833
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
1834
+ "1",
1835
+ "2",
1836
+ "3"
1837
+ ])
1838
+ expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
1839
+ })
1840
+
1841
+ it("codeFilter: in / notIn", () => {
1842
+ expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
1843
+ expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
1844
+ })
1845
+
1846
+ it("codeFilter: gt / gte / lt / lte / neq", () => {
1847
+ expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
1848
+ expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
1849
+ expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
1850
+ expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
1851
+ expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
1852
+ })
1853
+
1854
+ it("codeFilter: nested path access through dot notation", () => {
1855
+ expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
1856
+ expect(
1857
+ runCF(
1858
+ make<CFRow>().pipe(
1859
+ where("nested.kind", "k2"),
1860
+ and("nested.v", "gt", 0)
1861
+ )
1862
+ )
1863
+ )
1864
+ .toEqual(["2"])
1865
+ })
1866
+
1867
+ it("codeFilter: array length predicates", () => {
1868
+ expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
1869
+ expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
1870
+ })
1871
+
1872
+ it("codeFilter: order + skip + limit applied after filter", () => {
1873
+ const q = make<CFRow>().pipe(
1874
+ where("tag", "neq", "z"),
1875
+ order("qty", "DESC"),
1876
+ page({ skip: 1, take: 2 })
1877
+ )
1878
+ expect(runCF(q)).toEqual(["2", "1"])
1879
+ })