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

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