@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,11 +1,14 @@
1
+ import { SqliteClient } from "@effect/sql-sqlite-node"
1
2
  import { describe, expect, it } from "@effect/vitest"
2
- import { Array, Config, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S, ServiceMap } from "effect-app"
3
+ import { Array, Config, Context, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S, Struct } from "effect-app"
3
4
  import { LogLevels } from "effect-app/utils"
4
5
  import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
5
- import { and, or, project, where, whereEvery, whereSome } from "../src/Model/query.js"
6
+ import { and, computed, or, project, projectComputed, relation, where, whereEvery, whereSome } from "../src/Model/query.js"
6
7
  import { makeRepo } from "../src/Model/Repository/makeRepo.js"
8
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
7
9
  import { CosmosStoreLayer } from "../src/Store/Cosmos.js"
8
10
  import { MemoryStoreLive } from "../src/Store/Memory.js"
11
+ import { SQLiteStoreLayer } from "../src/Store/SQL.js"
9
12
 
10
13
  export const rt = ManagedRuntime.make(Layer.mergeAll(
11
14
  Layer.effect(
@@ -24,7 +27,7 @@ class Something extends S.Class<Something>("Something")({
24
27
  id: S.String,
25
28
  name: S.String,
26
29
  description: S.String,
27
- items: S.Array(S.Struct({ id: S.String, value: S.Number, description: S.String }))
30
+ items: S.Array(S.Struct({ id: S.String, value: S.Finite, description: S.String }))
28
31
  }) {}
29
32
 
30
33
  const items = [
@@ -49,7 +52,7 @@ const items = [
49
52
  ]
50
53
 
51
54
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
52
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
55
+ class SomethingRepo extends Context.Service<SomethingRepo>()(
53
56
  "SomethingRepo",
54
57
  {
55
58
  make: Effect.gen(function*() {
@@ -76,28 +79,31 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
76
79
  static readonly Test = this
77
80
  .layer
78
81
  .pipe(
79
- Layer.provide(MemoryStoreLive)
82
+ Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
80
83
  )
81
84
 
82
85
  static readonly TestCosmos = this
83
86
  .layer
84
87
  .pipe(
85
88
  Layer.provide(
86
- Effect.gen(function*() {
87
- const url = yield* Config.redacted("STORAGE_URL").pipe(
88
- Config.withDefault(
89
- Redacted.make(
90
- // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
91
- "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
89
+ Effect
90
+ .gen(function*() {
91
+ const url = yield* Config.redacted("STORAGE_URL").pipe(
92
+ Config.withDefault(
93
+ Redacted.make(
94
+ // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
95
+ "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
96
+ )
92
97
  )
93
98
  )
94
- )
95
- return CosmosStoreLayer({
96
- dbName: "test",
97
- prefix: "",
98
- url
99
+ return CosmosStoreLayer({
100
+ dbName: "test",
101
+ prefix: "",
102
+ url
103
+ })
104
+ .pipe(Layer.merge(RepositoryRegistryLive))
99
105
  })
100
- }).pipe(Layer.unwrap)
106
+ .pipe(Layer.unwrap)
101
107
  )
102
108
  )
103
109
  }
@@ -107,7 +113,7 @@ describe("select first-level array fields", () => {
107
113
  .gen(function*() {
108
114
  const repo = yield* SomethingRepo
109
115
 
110
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
116
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
111
117
 
112
118
  // ok crazy lol, "value" is a reserved word in CosmosDB, so we have to use t["value"] as a field name instead of t.value
113
119
  const items = yield* repo.queryRaw(projected, {
@@ -159,7 +165,7 @@ describe("select first-level array fields", () => {
159
165
  .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
160
166
  })
161
167
 
162
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
168
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
163
169
 
164
170
  const expected = [
165
171
  {
@@ -352,6 +358,41 @@ describe("multi-level", () => {
352
358
  .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
353
359
  })
354
360
 
361
+ describe("computed projections", () => {
362
+ const test = Effect
363
+ .gen(function*() {
364
+ const repo = yield* SomethingRepo
365
+ const output = S.Struct({
366
+ id: S.String,
367
+ pickedCount: S.NonNegativeInt,
368
+ hasPicked: S.Boolean
369
+ })
370
+ const pickedFilter = where("value", "gt", 20)
371
+ const items = yield* repo.query(
372
+ projectComputed(
373
+ output,
374
+ computed({
375
+ pickedCount: relation<S.Codec.Encoded<typeof Something>>("items").count(pickedFilter),
376
+ hasPicked: relation<S.Codec.Encoded<typeof Something>>("items").any(pickedFilter)
377
+ })
378
+ )
379
+ )
380
+ expect(items).toStrictEqual([
381
+ { id: "1", pickedCount: 0, hasPicked: false },
382
+ { id: "2", pickedCount: 2, hasPicked: true }
383
+ ])
384
+ })
385
+ .pipe(setupRequestContextFromCurrent())
386
+
387
+ it.skipIf(!process.env["STORAGE_URL"])("works well in CosmosDB", () =>
388
+ test
389
+ .pipe(Effect.provide(SomethingRepo.TestCosmos), rt.runPromise))
390
+
391
+ it("works well in Memory", () =>
392
+ test
393
+ .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
394
+ })
395
+
355
396
  // FUTURE: we need something like this instead:
356
397
  /*
357
398
  const subQuery = <T extends FieldValues>() => <TKey extends keyof T>(key: TKey, type: "some" | "every" = "some") => make<T[TKey][number]>() // todo: mark that this is sub query on field "items"
@@ -369,6 +410,246 @@ describe("multi-level", () => {
369
410
  ))
370
411
  */
371
412
 
413
+ // Mimic scanner MultiPick/EasyLife AllPickList shape:
414
+ // - parent has tagged-state with `at` timestamp
415
+ // - items is NonEmptyArray with state._tag + articleId/articleGTIN
416
+ // - controller filters `state.at gte X` and `state._tag neq closed`
417
+ // then projectComputed with: count, any(initial/picking/packed),
418
+ // every(picked/packed), collectDistinct(articleId)
419
+ const itemStateSchema = S.Union([
420
+ S.TaggedStruct("initial", { at: S.String }),
421
+ S.TaggedStruct("picking", { at: S.String }),
422
+ S.TaggedStruct("picked", { at: S.String }),
423
+ S.TaggedStruct("packed", { at: S.String })
424
+ ])
425
+
426
+ class ArticleLineItem extends S.Class<ArticleLineItem>("ArticleLineItem")({
427
+ articleId: S.String,
428
+ articleGTIN: S.String,
429
+ state: itemStateSchema
430
+ }) {}
431
+
432
+ const stOrderState = S.Union([
433
+ S.TaggedStruct("initial", { at: S.String }),
434
+ S.TaggedStruct("packed", { at: S.String }),
435
+ S.TaggedStruct("closed", { at: S.String })
436
+ ])
437
+
438
+ class Order extends S.Class<Order>("Order")({
439
+ id: S.String,
440
+ state: stOrderState,
441
+ items: S.NonEmptyArray(ArticleLineItem)
442
+ }) {}
443
+
444
+ const orderItems = [
445
+ new Order({
446
+ id: "o-open-1",
447
+ state: { _tag: "initial", at: "2026-05-08T08:00:00Z" },
448
+ items: [
449
+ new ArticleLineItem({
450
+ articleId: "A1",
451
+ articleGTIN: "G1",
452
+ state: { _tag: "picking", at: "2026-05-08T08:01:00Z" }
453
+ }),
454
+ new ArticleLineItem({
455
+ articleId: "A1",
456
+ articleGTIN: "G1",
457
+ state: { _tag: "picked", at: "2026-05-08T08:02:00Z" }
458
+ }),
459
+ new ArticleLineItem({
460
+ articleId: "A2",
461
+ articleGTIN: "G2",
462
+ state: { _tag: "initial", at: "2026-05-08T08:00:00Z" }
463
+ })
464
+ ]
465
+ }),
466
+ new Order({
467
+ id: "o-allpicked-2",
468
+ state: { _tag: "packed", at: "2026-05-07T10:00:00Z" },
469
+ items: [
470
+ new ArticleLineItem({
471
+ articleId: "B1",
472
+ articleGTIN: "GB1",
473
+ state: { _tag: "picked", at: "2026-05-07T09:50:00Z" }
474
+ }),
475
+ new ArticleLineItem({
476
+ articleId: "B2",
477
+ articleGTIN: "GB2",
478
+ state: { _tag: "picked", at: "2026-05-07T09:55:00Z" }
479
+ })
480
+ ]
481
+ }),
482
+ new Order({
483
+ id: "o-closed-3",
484
+ state: { _tag: "closed", at: "2026-05-04T10:00:00Z" },
485
+ items: [
486
+ new ArticleLineItem({
487
+ articleId: "C1",
488
+ articleGTIN: "GC1",
489
+ state: { _tag: "packed", at: "2026-05-04T09:00:00Z" }
490
+ })
491
+ ]
492
+ })
493
+ ]
494
+
495
+ // @effect-diagnostics-next-line missingEffectServiceDependency:off
496
+ class OrderRepo extends Context.Service<OrderRepo>()(
497
+ "OrderRepo",
498
+ {
499
+ make: Effect.gen(function*() {
500
+ const partitionKey = "orders-" + new Date().getTime()
501
+ return yield* makeRepo("Order", Order, { config: { partitionValue: () => partitionKey } })
502
+ })
503
+ }
504
+ ) {
505
+ static readonly layer = Layer
506
+ .effect(
507
+ OrderRepo,
508
+ Effect.gen(function*() {
509
+ const partitionKey = "orders-" + new Date().getTime()
510
+ const repo = OrderRepo.of(
511
+ yield* makeRepo("Order", Order, {
512
+ config: { partitionValue: () => partitionKey }
513
+ })
514
+ )
515
+ yield* repo.saveAndPublish(orderItems).pipe(setupRequestContextFromCurrent("init"))
516
+ return repo
517
+ })
518
+ )
519
+ static readonly Test = this
520
+ .layer
521
+ .pipe(Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive)))
522
+
523
+ static readonly TestSqlite = this
524
+ .layer
525
+ .pipe(
526
+ Layer.provide(
527
+ Layer.merge(
528
+ SQLiteStoreLayer({
529
+ url: Redacted.make("sqlite://"),
530
+ prefix: "test_",
531
+ dbName: "test"
532
+ }),
533
+ RepositoryRegistryLive
534
+ )
535
+ ),
536
+ Layer.provide(SqliteClient.layer({ filename: ":memory:" }))
537
+ )
538
+ }
539
+
540
+ describe("scanner-style AllPickList computed projections", () => {
541
+ const test = Effect
542
+ .gen(function*() {
543
+ const repo = yield* OrderRepo
544
+ type OrderEnc = S.Codec.Encoded<typeof Order>
545
+
546
+ const projection = S.Struct({
547
+ id: S.String,
548
+ state: stOrderState,
549
+ articleCount: S.NonNegativeInt,
550
+ hasInitialItem: S.Boolean,
551
+ hasPickingItem: S.Boolean,
552
+ hasPackedItem: S.Boolean,
553
+ allItemsPicked: S.Boolean,
554
+ allItemsPacked: S.Boolean,
555
+ articleIds: S.Array(S.String)
556
+ })
557
+
558
+ const result = yield* repo.query(
559
+ where("state.at", "gte", "2026-05-05T00:00:00Z"),
560
+ and("state._tag", "neq", "closed"),
561
+ projectComputed(
562
+ projection,
563
+ computed({
564
+ articleCount: relation<OrderEnc>("items").count(),
565
+ hasInitialItem: relation<OrderEnc>("items").any(where("state._tag", "initial")),
566
+ hasPickingItem: relation<OrderEnc>("items").any(where("state._tag", "picking")),
567
+ hasPackedItem: relation<OrderEnc>("items").any(where("state._tag", "packed")),
568
+ allItemsPicked: relation<OrderEnc>("items").every(where("state._tag", "picked")),
569
+ allItemsPacked: relation<OrderEnc>("items").every(where("state._tag", "packed")),
570
+ articleIds: relation<OrderEnc>("items").collectDistinct("articleId")
571
+ })
572
+ )
573
+ )
574
+
575
+ const byId = Object.fromEntries(result.map((r) => [r.id, r]))
576
+
577
+ expect(Object.keys(byId).sort()).toEqual(["o-allpicked-2", "o-open-1"])
578
+
579
+ const open = byId["o-open-1"]!
580
+ expect(open.articleCount).toBe(3)
581
+ expect(open.hasInitialItem).toBe(true)
582
+ expect(open.hasPickingItem).toBe(true)
583
+ expect(open.hasPackedItem).toBe(false)
584
+ expect(open.allItemsPicked).toBe(false)
585
+ expect(open.allItemsPacked).toBe(false)
586
+ expect([...open.articleIds].sort()).toEqual(["A1", "A2"])
587
+
588
+ const allp = byId["o-allpicked-2"]!
589
+ expect(allp.articleCount).toBe(2)
590
+ expect(allp.hasInitialItem).toBe(false)
591
+ expect(allp.hasPickingItem).toBe(false)
592
+ expect(allp.hasPackedItem).toBe(false)
593
+ expect(allp.allItemsPicked).toBe(true)
594
+ expect(allp.allItemsPacked).toBe(false)
595
+ expect([...allp.articleIds].sort()).toEqual(["B1", "B2"])
596
+ })
597
+ .pipe(setupRequestContextFromCurrent())
598
+
599
+ it("works well in Memory", () => test.pipe(Effect.provide(OrderRepo.Test), rt.runPromise))
600
+
601
+ it("works well in SQLite", () => test.pipe(Effect.provide(OrderRepo.TestSqlite), rt.runPromise))
602
+ })
603
+
604
+ // Same but mimics the FULL controller projection: includes `items` array
605
+ // (NonEmptyArray) alongside the computed scalars. This tests the
606
+ // memory-side select pipeline that combines subKeys (items) with
607
+ // computedKeys in one Project node.
608
+ describe("scanner-style AllPickList — items + computed combined", () => {
609
+ const test = Effect
610
+ .gen(function*() {
611
+ const repo = yield* OrderRepo
612
+ type OrderEnc = S.Codec.Encoded<typeof Order>
613
+
614
+ const projection = S.Struct({
615
+ id: S.String,
616
+ items: S.NonEmptyArray(ArticleLineItem.mapFields(Struct.pick(["articleId", "articleGTIN"]))),
617
+ articleCount: S.NonNegativeInt,
618
+ allItemsPicked: S.Boolean,
619
+ articleIds: S.Array(S.String)
620
+ })
621
+
622
+ const result = yield* repo.query(
623
+ where("state.at", "gte", "2026-05-05T00:00:00Z"),
624
+ and("state._tag", "neq", "closed"),
625
+ projectComputed(
626
+ projection,
627
+ computed({
628
+ articleCount: relation<OrderEnc>("items").count(),
629
+ allItemsPicked: relation<OrderEnc>("items").every(where("state._tag", "picked")),
630
+ articleIds: relation<OrderEnc>("items").collectDistinct("articleId")
631
+ })
632
+ )
633
+ )
634
+
635
+ expect(result.length).toBe(2)
636
+ const byId = Object.fromEntries(result.map((r) => [r.id, r]))
637
+ const open = byId["o-open-1"]!
638
+ expect(open.items.length).toBe(3)
639
+ expect(open.items[0]).toHaveProperty("articleId")
640
+ expect(open.items[0]).toHaveProperty("articleGTIN")
641
+ expect(open.allItemsPicked).toBe(false)
642
+ const allp = byId["o-allpicked-2"]!
643
+ expect(allp.items.length).toBe(2)
644
+ expect(allp.allItemsPicked).toBe(true)
645
+ })
646
+ .pipe(setupRequestContextFromCurrent())
647
+
648
+ it("works well in Memory", () => test.pipe(Effect.provide(OrderRepo.Test), rt.runPromise))
649
+
650
+ it("works well in SQLite", () => test.pipe(Effect.provide(OrderRepo.TestSqlite), rt.runPromise))
651
+ })
652
+
372
653
  describe("removeByIds", () => {
373
654
  const test = Effect
374
655
  .gen(function*() {
@@ -405,7 +686,7 @@ describe("removeByIds", () => {
405
686
 
406
687
  yield* repo.saveAndPublish(items)
407
688
  const itemsAfterSave = yield* repo.all
408
- yield* repo.removeById(...items.slice(0, 2).map((_) => _.id))
689
+ yield* repo.removeById([items[0]!.id, items[1]!.id])
409
690
 
410
691
  const items2 = yield* repo.all
411
692
 
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Layer, S } from "effect-app"
3
+ import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
4
+ import { makeRepo } from "../src/Model/Repository.js"
5
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
6
+ import { MemoryStoreLive } from "../src/Store/Memory.js"
7
+
8
+ class BatchItem extends S.Class<BatchItem>("BatchItem")({
9
+ id: S.String,
10
+ label: S.String
11
+ }) {}
12
+
13
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
14
+
15
+ describe("repository ext save/remove batching", () => {
16
+ it.effect("supports save batching overload", () =>
17
+ Effect
18
+ .gen(function*() {
19
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
20
+ const items = [
21
+ new BatchItem({ id: "1", label: "one" }),
22
+ new BatchItem({ id: "2", label: "two" }),
23
+ new BatchItem({ id: "3", label: "three" }),
24
+ new BatchItem({ id: "4", label: "four" })
25
+ ] as const
26
+
27
+ yield* repo.save(items, { batch: 2 })
28
+
29
+ const all = yield* repo.all
30
+ expect(all).toHaveLength(4)
31
+ expect(all.map((_) => _.id).toSorted()).toEqual(["1", "2", "3", "4"])
32
+ })
33
+ .pipe(
34
+ setupRequestContextFromCurrent(),
35
+ Effect.provide(TestStoreLive)
36
+ ))
37
+
38
+ it.effect("supports remove batching overload", () =>
39
+ Effect
40
+ .gen(function*() {
41
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
42
+ const items = [
43
+ new BatchItem({ id: "1", label: "one" }),
44
+ new BatchItem({ id: "2", label: "two" }),
45
+ new BatchItem({ id: "3", label: "three" }),
46
+ new BatchItem({ id: "4", label: "four" })
47
+ ] as const
48
+
49
+ yield* repo.save(items)
50
+ yield* repo.remove([items[0], items[1], items[2]], { batch: true })
51
+
52
+ const all = yield* repo.all
53
+ expect(all).toHaveLength(1)
54
+ expect(all[0]?.id).toBe("4")
55
+ })
56
+ .pipe(
57
+ setupRequestContextFromCurrent(),
58
+ Effect.provide(TestStoreLive)
59
+ ))
60
+ })
@@ -1,8 +1,7 @@
1
1
  import { describe, expect, expectTypeOf, it } from "@effect/vitest"
2
- import { Effect, Layer, Result, S, ServiceMap } from "effect-app"
2
+ import { Context, Effect, Layer, Result, RpcX, S } from "effect-app"
3
3
  import { NotLoggedInError, UnauthorizedError } from "effect-app/client"
4
4
  import { HttpHeaders } from "effect-app/http"
5
- import * as RpcX from "effect-app/rpc"
6
5
  import { MiddlewareMaker } from "effect-app/rpc"
7
6
  import type { unhandled } from "effect-app/Types"
8
7
  import { Rpc } from "effect/unstable/rpc"
@@ -63,11 +62,12 @@ const testSuite = (_mw: typeof middleware3) =>
63
62
  "works",
64
63
  Effect.fn(function*() {
65
64
  const defaultOpts = {
65
+ client: null as any, // TODO?
66
66
  headers: HttpHeaders.fromRecordUnsafe({}),
67
67
  payload: { _tag: "Test" },
68
68
  clientId: 0,
69
69
  requestId: "test-id" as any,
70
- rpc: { ...TestRpc, annotations: ServiceMap.make(_mw.requestContext, {}) }
70
+ rpc: { ...TestRpc, annotations: Context.make(_mw.requestContext, {}) }
71
71
  }
72
72
  const next = Effect.void as unknown as Effect.Effect<SuccessValue, unhandled, never>
73
73
  const layer = _mw.layer.pipe(
@@ -89,7 +89,7 @@ const testSuite = (_mw: typeof middleware3) =>
89
89
  headers: HttpHeaders.fromRecordUnsafe({ "x-user": "test-user", "x-is-manager": "true" }),
90
90
  rpc: {
91
91
  ...defaultOpts.rpc,
92
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
92
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
93
93
  }
94
94
  })
95
95
  )
@@ -127,7 +127,7 @@ const testSuite = (_mw: typeof middleware3) =>
127
127
  Object.assign({ ...defaultOpts }, {
128
128
  rpc: {
129
129
  ...defaultOpts.rpc,
130
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
130
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
131
131
  }
132
132
  })
133
133
  )
@@ -153,7 +153,7 @@ const testSuite = (_mw: typeof middleware3) =>
153
153
  {
154
154
  rpc: {
155
155
  ...defaultOpts.rpc,
156
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
156
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
157
157
  }
158
158
  }
159
159
  )
@@ -0,0 +1,183 @@
1
+ import { type MakeContext, type MakeErrors, makeRouter } from "@effect-app/infra/api/routing"
2
+ import { makeAllDSL, makeOneDSL } from "@effect-app/infra/Model"
3
+ import { expectTypeOf, it } from "@effect/vitest"
4
+ import { Context, Effect, Layer, RpcX, S } from "effect-app"
5
+ import { InvalidStateError, makeRpcClient, UnauthorizedError } from "effect-app/client"
6
+ import { DefaultGenericMiddlewares } from "effect-app/middleware"
7
+ import { type FixEnv } from "effect-app/Pure"
8
+ import { MiddlewareMaker } from "effect-app/rpc"
9
+ import { type TypeTestId } from "effect-app/TypeTest"
10
+ import { type ConfigError } from "effect/Config"
11
+ import { type RpcSerialization } from "effect/unstable/rpc/RpcSerialization"
12
+ import { DefaultGenericMiddlewaresLive, DevModeMiddlewareLive } from "../src/api/routing/middleware.js"
13
+ import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, Some, SomeElse, SomeService, Test, TestLive } from "./fixtures.js"
14
+
15
+ // Inline minimal context provider (provides `Some`)
16
+ class CtxProvider extends RpcX.RpcMiddleware.Tag<CtxProvider, { provides: Some }>()("CtxProvider") {
17
+ static Default = Layer.make(this, {
18
+ *make() {
19
+ return Effect.fnUntraced(function*(effect) {
20
+ return yield* Effect.provideService(effect, Some, Some.of({ a: 1 }))
21
+ })
22
+ }
23
+ })
24
+ }
25
+
26
+ // Provides `SomeElse` so AllowAnonymous's requirement is met.
27
+ class SomeElseProvider extends RpcX.RpcMiddleware.Tag<SomeElseProvider, { provides: SomeElse }>()("SomeElseProvider") {
28
+ static Default = Layer.make(this, {
29
+ *make() {
30
+ return Effect.fnUntraced(function*(effect) {
31
+ return yield* Effect.provideService(effect, SomeElse, SomeElse.of({ b: 2 }))
32
+ })
33
+ }
34
+ })
35
+ }
36
+
37
+ class mw extends MiddlewareMaker
38
+ .Tag<mw>()("mw", RequestContextMap)
39
+ .middleware(RequireRoles, Test)
40
+ .middleware(AllowAnonymous)
41
+ .middleware(CtxProvider)
42
+ .middleware(...DefaultGenericMiddlewares, SomeElseProvider)
43
+ {
44
+ static Default = this.layer.pipe(
45
+ Layer.provide([
46
+ RequireRolesLive,
47
+ TestLive,
48
+ AllowAnonymousLive,
49
+ CtxProvider.Default,
50
+ SomeElseProvider.Default,
51
+ DefaultGenericMiddlewaresLive,
52
+ DevModeMiddlewareLive,
53
+ SomeService.Default
54
+ ])
55
+ )
56
+ }
57
+
58
+ const { TaggedRequestFor } = makeRpcClient(mw)
59
+ const Req = TaggedRequestFor("GenRouter")
60
+
61
+ class GetThing extends Req.Query<GetThing>()("GetThing", { id: S.String }, {
62
+ success: S.String,
63
+ error: UnauthorizedError
64
+ }) {}
65
+ class DoThing extends Req.Command<DoThing>()("DoThing", { id: S.String }, { success: S.Void }) {}
66
+
67
+ const Resource = { GetThing, DoThing }
68
+
69
+ const { Router, matchAll } = makeRouter(mw.Default)
70
+
71
+ class ThingRepo extends Context.Service<ThingRepo>()("ThingRepo", {
72
+ make: Effect.succeed({ get: (id: string) => Effect.succeed(id + "!") })
73
+ }) {
74
+ static Default = Layer.effect(this, this.make)
75
+ }
76
+
77
+ // Case under test:
78
+ // `match({})` is given handlers as **shorthand generator methods** (`*GetThing(req) { ... }`).
79
+ // tsgo (>= 7 dev) infers `TNext = unknown` for these shorthand generators while TS6 infers `never`.
80
+ // `HandlerWithInputGen` in routing.ts must accept both — see the structural fix.
81
+ const router = Router(Resource)({
82
+ dependencies: [ThingRepo.Default],
83
+ *effect(match) {
84
+ const repo = yield* ThingRepo
85
+
86
+ if (Math.random() > 0.5) return yield* new InvalidStateError("nope")
87
+
88
+ return match({
89
+ *GetThing(req) {
90
+ const some = yield* Some
91
+ if (req.id === "boom") {
92
+ return yield* Effect.fail(new UnauthorizedError())
93
+ }
94
+ return yield* repo.get(req.id + String(some.a))
95
+ },
96
+ *DoThing(_req) {
97
+ yield* Effect.succeed(1)
98
+ }
99
+ })
100
+ }
101
+ })
102
+
103
+ // Same scenario but using the `raw:` variant — exercises the `raw` path of `HandlerWithInputGen`.
104
+ const routerRaw = Router({ GetThing })({
105
+ *effect(match) {
106
+ return match({
107
+ GetThing: {
108
+ *raw(req) {
109
+ const some = yield* Some
110
+ return yield* Effect.succeed(req.id + String(some.a))
111
+ }
112
+ }
113
+ })
114
+ }
115
+ })
116
+
117
+ it("router with generator-method handlers compiles", () => {
118
+ expectTypeOf(router).toMatchTypeOf<
119
+ Layer.Layer<never, ConfigError | InvalidStateError, SomeService | RpcSerialization>
120
+ >()
121
+ expectTypeOf(routerRaw).toMatchTypeOf<Layer.Layer<never, ConfigError, SomeService | RpcSerialization>>()
122
+ })
123
+
124
+ // Type-level assertions: verify generator yields propagate to MakeErrors / MakeContext
125
+ type Errors = MakeErrors<typeof router[TypeTestId]>
126
+ type Ctx = MakeContext<typeof router[TypeTestId]>
127
+ expectTypeOf<Errors>().toEqualTypeOf<InvalidStateError>()
128
+ expectTypeOf<Ctx>().toEqualTypeOf<ThingRepo>()
129
+
130
+ const matched = matchAll({ router })
131
+ expectTypeOf(matched).toMatchTypeOf<
132
+ Layer.Layer<never, ConfigError | InvalidStateError, SomeService | RpcSerialization>
133
+ >()
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // DSL R-inference regression
137
+ // ---------------------------------------------------------------------------
138
+ // `OneDSL`/`OneDSLExt.update`/`.modify` previously annotated the callback's
139
+ // effect R as `FixEnv<R, Evt, S1, S2>`. That deadlocked inference of `R`
140
+ // (TS6 → `never`, tsgo → `unknown`), causing yielded effects to leak
141
+ // `unknown` in the R slot when consumed by generator handlers.
142
+ // The fix uses bare `R` in the callback and `FixEnv<R, …>` only on the return.
143
+ class Item extends S.Class<Item>("Item")({ id: S.String, label: S.String }) {}
144
+ class Dep extends Context.Service<Dep>()("Dep", { make: Effect.succeed({ tag: "dep" as const }) }) {}
145
+
146
+ type Evt = { _tag: "Updated"; id: string }
147
+
148
+ const Items$ = makeAllDSL<Item, Evt>()
149
+ const Item$ = makeOneDSL<Item, Evt>()
150
+
151
+ // Callback body uses generator syntax (TNext = unknown under tsgo) and yields
152
+ // a service-dependent effect — R must be inferred as `Dep` (plus the
153
+ // canonical PureEnvEnv contributed by FixEnv on the return).
154
+ const oneUpdate = Item$.update((item) =>
155
+ Effect.gen(function*() {
156
+ const dep = yield* Dep
157
+ return new Item({ id: item.id, label: item.label + dep.tag })
158
+ })
159
+ )
160
+
161
+ const allUpdate = Items$.update((items) =>
162
+ Effect.gen(function*() {
163
+ const dep = yield* Dep
164
+ return items.map((_) => new Item({ id: _.id, label: _.label + dep.tag }))
165
+ })
166
+ )
167
+
168
+ const oneModify = Item$.modify((item, _dsl) =>
169
+ Effect.gen(function*() {
170
+ const dep = yield* Dep
171
+ return { ...item, tag: dep.tag }
172
+ })
173
+ )
174
+
175
+ // `R` should be `FixEnv<Dep, Evt, …>` — never collapsed to `unknown`/`never`.
176
+ // The regression manifested as `unknown` here, breaking `Dep` assignability.
177
+ expectTypeOf(oneUpdate).toMatchTypeOf<Effect.Effect<Item, never, FixEnv<Dep, Evt, Item, Item>>>()
178
+ expectTypeOf(allUpdate).toMatchTypeOf<
179
+ Effect.Effect<readonly Item[], never, FixEnv<Dep, Evt, readonly Item[], readonly Item[]>>
180
+ >()
181
+ expectTypeOf(oneModify).toMatchTypeOf<
182
+ Effect.Effect<{ tag: "dep"; id: string; label: string }, never, FixEnv<Dep, Evt, Item, Item>>
183
+ >()