@effect-app/infra 4.0.0-beta.14 → 4.0.0-beta.141

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 (283) hide show
  1. package/CHANGELOG.md +927 -0
  2. package/dist/CUPS.d.ts +15 -7
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +10 -12
  5. package/dist/Emailer/Sendgrid.d.ts +14 -14
  6. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  7. package/dist/Emailer/Sendgrid.js +16 -15
  8. package/dist/Emailer/fake.d.ts +1 -1
  9. package/dist/Emailer/service.d.ts +9 -3
  10. package/dist/Emailer/service.d.ts.map +1 -1
  11. package/dist/Emailer/service.js +3 -3
  12. package/dist/Emailer.d.ts +1 -1
  13. package/dist/MainFiberSet.d.ts +5 -5
  14. package/dist/MainFiberSet.d.ts.map +1 -1
  15. package/dist/MainFiberSet.js +3 -3
  16. package/dist/Model/Repository/Registry.d.ts +20 -0
  17. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  18. package/dist/Model/Repository/Registry.js +17 -0
  19. package/dist/Model/Repository/ext.d.ts +33 -15
  20. package/dist/Model/Repository/ext.d.ts.map +1 -1
  21. package/dist/Model/Repository/ext.js +54 -2
  22. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  23. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  24. package/dist/Model/Repository/internal/internal.js +33 -22
  25. package/dist/Model/Repository/legacy.d.ts +1 -1
  26. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  27. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  28. package/dist/Model/Repository/makeRepo.js +5 -1
  29. package/dist/Model/Repository/service.d.ts +28 -23
  30. package/dist/Model/Repository/service.d.ts.map +1 -1
  31. package/dist/Model/Repository/validation.d.ts +60 -10
  32. package/dist/Model/Repository/validation.d.ts.map +1 -1
  33. package/dist/Model/Repository.d.ts +2 -1
  34. package/dist/Model/Repository.d.ts.map +1 -1
  35. package/dist/Model/Repository.js +2 -1
  36. package/dist/Model/dsl.d.ts +4 -4
  37. package/dist/Model/dsl.d.ts.map +1 -1
  38. package/dist/Model/filter/filterApi.d.ts +5 -5
  39. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  40. package/dist/Model/filter/types/errors.d.ts +1 -1
  41. package/dist/Model/filter/types/fields.d.ts +1 -1
  42. package/dist/Model/filter/types/path/common.d.ts +1 -1
  43. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  44. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  45. package/dist/Model/filter/types/path/index.d.ts +1 -1
  46. package/dist/Model/filter/types/utils.d.ts +1 -1
  47. package/dist/Model/filter/types/validator.d.ts +1 -1
  48. package/dist/Model/filter/types.d.ts +1 -1
  49. package/dist/Model/query/dsl.d.ts +1 -1
  50. package/dist/Model/query/dsl.d.ts.map +1 -1
  51. package/dist/Model/query/new-kid-interpreter.d.ts +6 -6
  52. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  53. package/dist/Model/query/new-kid-interpreter.js +3 -3
  54. package/dist/Model/query.d.ts +1 -1
  55. package/dist/Model.d.ts +2 -1
  56. package/dist/Model.d.ts.map +1 -1
  57. package/dist/Model.js +2 -1
  58. package/dist/Operations.d.ts +6 -6
  59. package/dist/Operations.d.ts.map +1 -1
  60. package/dist/Operations.js +56 -59
  61. package/dist/OperationsRepo.d.ts +19 -19
  62. package/dist/OperationsRepo.d.ts.map +1 -1
  63. package/dist/OperationsRepo.js +3 -3
  64. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  65. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  66. package/dist/QueueMaker/SQLQueue.js +105 -114
  67. package/dist/QueueMaker/errors.d.ts +2 -2
  68. package/dist/QueueMaker/errors.d.ts.map +1 -1
  69. package/dist/QueueMaker/memQueue.d.ts +7 -4
  70. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  71. package/dist/QueueMaker/memQueue.js +51 -62
  72. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  73. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  74. package/dist/QueueMaker/sbqueue.js +36 -52
  75. package/dist/QueueMaker/service.d.ts +1 -1
  76. package/dist/RequestContext.d.ts +55 -25
  77. package/dist/RequestContext.d.ts.map +1 -1
  78. package/dist/RequestContext.js +5 -5
  79. package/dist/RequestFiberSet.d.ts +7 -7
  80. package/dist/RequestFiberSet.d.ts.map +1 -1
  81. package/dist/RequestFiberSet.js +5 -5
  82. package/dist/Store/ContextMapContainer.d.ts +19 -3
  83. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  84. package/dist/Store/ContextMapContainer.js +13 -3
  85. package/dist/Store/Cosmos/query.d.ts +1 -1
  86. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  87. package/dist/Store/Cosmos/query.js +8 -10
  88. package/dist/Store/Cosmos.d.ts +1 -1
  89. package/dist/Store/Cosmos.d.ts.map +1 -1
  90. package/dist/Store/Cosmos.js +308 -242
  91. package/dist/Store/Disk.d.ts +2 -2
  92. package/dist/Store/Disk.d.ts.map +1 -1
  93. package/dist/Store/Disk.js +25 -22
  94. package/dist/Store/Memory.d.ts +4 -4
  95. package/dist/Store/Memory.d.ts.map +1 -1
  96. package/dist/Store/Memory.js +27 -22
  97. package/dist/Store/SQL/Pg.d.ts +4 -0
  98. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  99. package/dist/Store/SQL/Pg.js +189 -0
  100. package/dist/Store/SQL/query.d.ts +38 -0
  101. package/dist/Store/SQL/query.d.ts.map +1 -0
  102. package/dist/Store/SQL/query.js +367 -0
  103. package/dist/Store/SQL.d.ts +20 -0
  104. package/dist/Store/SQL.d.ts.map +1 -0
  105. package/dist/Store/SQL.js +381 -0
  106. package/dist/Store/codeFilter.d.ts +1 -1
  107. package/dist/Store/codeFilter.d.ts.map +1 -1
  108. package/dist/Store/codeFilter.js +2 -1
  109. package/dist/Store/index.d.ts +5 -2
  110. package/dist/Store/index.d.ts.map +1 -1
  111. package/dist/Store/index.js +15 -3
  112. package/dist/Store/service.d.ts +17 -6
  113. package/dist/Store/service.d.ts.map +1 -1
  114. package/dist/Store/service.js +24 -6
  115. package/dist/Store/utils.d.ts +1 -1
  116. package/dist/Store/utils.d.ts.map +1 -1
  117. package/dist/Store/utils.js +3 -4
  118. package/dist/Store.d.ts +1 -1
  119. package/dist/adapters/SQL/Model.d.ts +28 -42
  120. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  121. package/dist/adapters/SQL/Model.js +2 -2
  122. package/dist/adapters/SQL.d.ts +1 -1
  123. package/dist/adapters/ServiceBus.d.ts +9 -9
  124. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  125. package/dist/adapters/ServiceBus.js +13 -15
  126. package/dist/adapters/cosmos-client.d.ts +3 -3
  127. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  128. package/dist/adapters/cosmos-client.js +3 -3
  129. package/dist/adapters/index.d.ts +8 -2
  130. package/dist/adapters/index.d.ts.map +1 -1
  131. package/dist/adapters/index.js +8 -2
  132. package/dist/adapters/logger.d.ts +1 -1
  133. package/dist/adapters/logger.d.ts.map +1 -1
  134. package/dist/adapters/memQueue.d.ts +3 -3
  135. package/dist/adapters/memQueue.d.ts.map +1 -1
  136. package/dist/adapters/memQueue.js +3 -3
  137. package/dist/adapters/mongo-client.d.ts +3 -3
  138. package/dist/adapters/mongo-client.d.ts.map +1 -1
  139. package/dist/adapters/mongo-client.js +3 -3
  140. package/dist/adapters/redis-client.d.ts +3 -3
  141. package/dist/adapters/redis-client.d.ts.map +1 -1
  142. package/dist/adapters/redis-client.js +3 -3
  143. package/dist/api/ContextProvider.d.ts +7 -7
  144. package/dist/api/ContextProvider.d.ts.map +1 -1
  145. package/dist/api/ContextProvider.js +6 -6
  146. package/dist/api/codec.d.ts +1 -1
  147. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  148. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  149. package/dist/api/internal/RequestContextMiddleware.js +1 -1
  150. package/dist/api/internal/auth.d.ts +44 -6
  151. package/dist/api/internal/auth.d.ts.map +1 -1
  152. package/dist/api/internal/auth.js +160 -29
  153. package/dist/api/internal/events.d.ts +3 -3
  154. package/dist/api/internal/events.d.ts.map +1 -1
  155. package/dist/api/internal/events.js +11 -7
  156. package/dist/api/internal/health.d.ts +1 -1
  157. package/dist/api/layerUtils.d.ts +6 -6
  158. package/dist/api/layerUtils.d.ts.map +1 -1
  159. package/dist/api/layerUtils.js +5 -5
  160. package/dist/api/middlewares.d.ts +1 -1
  161. package/dist/api/reportError.d.ts +1 -1
  162. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  163. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  164. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  165. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  166. package/dist/api/routing/middleware/middleware.js +45 -14
  167. package/dist/api/routing/middleware.d.ts +1 -2
  168. package/dist/api/routing/middleware.d.ts.map +1 -1
  169. package/dist/api/routing/middleware.js +1 -2
  170. package/dist/api/routing/schema/jwt.d.ts +1 -1
  171. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  172. package/dist/api/routing/tsort.d.ts +1 -1
  173. package/dist/api/routing/tsort.d.ts.map +1 -1
  174. package/dist/api/routing/utils.d.ts +3 -3
  175. package/dist/api/routing/utils.d.ts.map +1 -1
  176. package/dist/api/routing.d.ts +12 -14
  177. package/dist/api/routing.d.ts.map +1 -1
  178. package/dist/api/routing.js +17 -6
  179. package/dist/api/setupRequest.d.ts +8 -5
  180. package/dist/api/setupRequest.d.ts.map +1 -1
  181. package/dist/api/setupRequest.js +11 -6
  182. package/dist/api/util.d.ts +1 -1
  183. package/dist/arbs.d.ts +1 -1
  184. package/dist/errorReporter.d.ts +4 -4
  185. package/dist/errorReporter.d.ts.map +1 -1
  186. package/dist/errorReporter.js +16 -23
  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.d.ts +1 -1
  195. package/dist/logger.d.ts.map +1 -1
  196. package/dist/rateLimit.d.ts +9 -3
  197. package/dist/rateLimit.d.ts.map +1 -1
  198. package/dist/rateLimit.js +5 -11
  199. package/dist/test.d.ts +1 -1
  200. package/dist/test.d.ts.map +1 -1
  201. package/dist/vitest.d.ts +1 -1
  202. package/eslint.config.mjs +1 -1
  203. package/examples/query.ts +30 -26
  204. package/package.json +41 -27
  205. package/src/CUPS.ts +9 -11
  206. package/src/Emailer/Sendgrid.ts +17 -14
  207. package/src/Emailer/service.ts +8 -2
  208. package/src/MainFiberSet.ts +3 -3
  209. package/src/Model/Repository/Registry.ts +33 -0
  210. package/src/Model/Repository/ext.ts +93 -6
  211. package/src/Model/Repository/internal/internal.ts +85 -78
  212. package/src/Model/Repository/makeRepo.ts +12 -10
  213. package/src/Model/Repository/service.ts +31 -22
  214. package/src/Model/Repository.ts +1 -0
  215. package/src/Model/dsl.ts +3 -3
  216. package/src/Model/query/new-kid-interpreter.ts +2 -2
  217. package/src/Model.ts +1 -0
  218. package/src/Operations.ts +78 -113
  219. package/src/OperationsRepo.ts +2 -2
  220. package/src/QueueMaker/SQLQueue.ts +121 -151
  221. package/src/QueueMaker/memQueue.ts +82 -103
  222. package/src/QueueMaker/sbqueue.ts +55 -85
  223. package/src/RequestContext.ts +4 -4
  224. package/src/RequestFiberSet.ts +4 -4
  225. package/src/Store/ContextMapContainer.ts +41 -2
  226. package/src/Store/Cosmos/query.ts +9 -11
  227. package/src/Store/Cosmos.ts +437 -343
  228. package/src/Store/Disk.ts +52 -49
  229. package/src/Store/Memory.ts +54 -48
  230. package/src/Store/SQL/Pg.ts +318 -0
  231. package/src/Store/SQL/query.ts +409 -0
  232. package/src/Store/SQL.ts +668 -0
  233. package/src/Store/codeFilter.ts +1 -0
  234. package/src/Store/index.ts +17 -2
  235. package/src/Store/service.ts +31 -7
  236. package/src/Store/utils.ts +23 -22
  237. package/src/adapters/SQL/Model.ts +10 -4
  238. package/src/adapters/ServiceBus.ts +111 -115
  239. package/src/adapters/cosmos-client.ts +2 -2
  240. package/src/adapters/index.ts +7 -0
  241. package/src/adapters/memQueue.ts +2 -2
  242. package/src/adapters/mongo-client.ts +2 -2
  243. package/src/adapters/redis-client.ts +2 -2
  244. package/src/api/ContextProvider.ts +11 -11
  245. package/src/api/internal/auth.ts +246 -44
  246. package/src/api/internal/events.ts +14 -9
  247. package/src/api/layerUtils.ts +8 -8
  248. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  249. package/src/api/routing/middleware/middleware.ts +52 -12
  250. package/src/api/routing/middleware.ts +0 -2
  251. package/src/api/routing.ts +21 -7
  252. package/src/api/setupRequest.ts +27 -7
  253. package/src/errorReporter.ts +58 -72
  254. package/src/rateLimit.ts +30 -22
  255. package/test/auth.test.ts +101 -0
  256. package/test/contextProvider.test.ts +11 -11
  257. package/test/controller.test.ts +18 -14
  258. package/test/dist/auth.test.d.ts.map +1 -0
  259. package/test/dist/contextProvider.test.d.ts.map +1 -1
  260. package/test/dist/controller.test.d.ts.map +1 -1
  261. package/test/dist/date-query.test.d.ts.map +1 -0
  262. package/test/dist/fixtures.d.ts +22 -12
  263. package/test/dist/fixtures.d.ts.map +1 -1
  264. package/test/dist/fixtures.js +12 -10
  265. package/test/dist/query.test.d.ts.map +1 -1
  266. package/test/dist/rawQuery.test.d.ts.map +1 -1
  267. package/test/dist/repository-ext.test.d.ts.map +1 -0
  268. package/test/dist/requires.test.d.ts.map +1 -1
  269. package/test/dist/router-generator.test.d.ts.map +1 -0
  270. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  271. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  272. package/test/dist/sql-store.test.d.ts.map +1 -0
  273. package/test/fixtures.ts +11 -9
  274. package/test/query.test.ts +209 -31
  275. package/test/rawQuery.test.ts +23 -19
  276. package/test/repository-ext.test.ts +58 -0
  277. package/test/requires.test.ts +6 -6
  278. package/test/router-generator.test.ts +180 -0
  279. package/test/routing-interruptibility.test.ts +63 -0
  280. package/test/rpc-multi-middleware.test.ts +78 -9
  281. package/test/sql-store.test.ts +1064 -0
  282. package/test/validateSample.test.ts +12 -9
  283. package/tsconfig.json +0 -1
@@ -1,9 +1,10 @@
1
1
  import { describe, expect, it } from "@effect/vitest"
2
- import { Array, Config, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S, ServiceMap } from "effect-app"
2
+ import { Array, Config, Context, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S } from "effect-app"
3
3
  import { LogLevels } from "effect-app/utils"
4
4
  import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
5
5
  import { and, or, project, where, whereEvery, whereSome } from "../src/Model/query.js"
6
6
  import { makeRepo } from "../src/Model/Repository/makeRepo.js"
7
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
7
8
  import { CosmosStoreLayer } from "../src/Store/Cosmos.js"
8
9
  import { MemoryStoreLive } from "../src/Store/Memory.js"
9
10
 
@@ -24,7 +25,7 @@ class Something extends S.Class<Something>("Something")({
24
25
  id: S.String,
25
26
  name: S.String,
26
27
  description: S.String,
27
- items: S.Array(S.Struct({ id: S.String, value: S.Number, description: S.String }))
28
+ items: S.Array(S.Struct({ id: S.String, value: S.Finite, description: S.String }))
28
29
  }) {}
29
30
 
30
31
  const items = [
@@ -49,7 +50,7 @@ const items = [
49
50
  ]
50
51
 
51
52
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
52
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
53
+ class SomethingRepo extends Context.Service<SomethingRepo>()(
53
54
  "SomethingRepo",
54
55
  {
55
56
  make: Effect.gen(function*() {
@@ -76,28 +77,31 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
76
77
  static readonly Test = this
77
78
  .layer
78
79
  .pipe(
79
- Layer.provide(MemoryStoreLive)
80
+ Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
80
81
  )
81
82
 
82
83
  static readonly TestCosmos = this
83
84
  .layer
84
85
  .pipe(
85
86
  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=="
87
+ Effect
88
+ .gen(function*() {
89
+ const url = yield* Config.redacted("STORAGE_URL").pipe(
90
+ Config.withDefault(
91
+ Redacted.make(
92
+ // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
93
+ "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
94
+ )
92
95
  )
93
96
  )
94
- )
95
- return CosmosStoreLayer({
96
- dbName: "test",
97
- prefix: "",
98
- url
97
+ return CosmosStoreLayer({
98
+ dbName: "test",
99
+ prefix: "",
100
+ url
101
+ })
102
+ .pipe(Layer.merge(RepositoryRegistryLive))
99
103
  })
100
- }).pipe(Layer.unwrap)
104
+ .pipe(Layer.unwrap)
101
105
  )
102
106
  )
103
107
  }
@@ -107,7 +111,7 @@ describe("select first-level array fields", () => {
107
111
  .gen(function*() {
108
112
  const repo = yield* SomethingRepo
109
113
 
110
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
114
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
111
115
 
112
116
  // 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
117
  const items = yield* repo.queryRaw(projected, {
@@ -159,7 +163,7 @@ describe("select first-level array fields", () => {
159
163
  .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
160
164
  })
161
165
 
162
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
166
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
163
167
 
164
168
  const expected = [
165
169
  {
@@ -405,7 +409,7 @@ describe("removeByIds", () => {
405
409
 
406
410
  yield* repo.saveAndPublish(items)
407
411
  const itemsAfterSave = yield* repo.all
408
- yield* repo.removeById(...items.slice(0, 2).map((_) => _.id))
412
+ yield* repo.removeById([items[0]!.id, items[1]!.id])
409
413
 
410
414
  const items2 = yield* repo.all
411
415
 
@@ -0,0 +1,58 @@
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.gen(function*() {
18
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
19
+ const items = [
20
+ new BatchItem({ id: "1", label: "one" }),
21
+ new BatchItem({ id: "2", label: "two" }),
22
+ new BatchItem({ id: "3", label: "three" }),
23
+ new BatchItem({ id: "4", label: "four" })
24
+ ] as const
25
+
26
+ yield* repo.save(items, { batch: 2 })
27
+
28
+ const all = yield* repo.all
29
+ expect(all).toHaveLength(4)
30
+ expect(all.map((_) => _.id).toSorted()).toEqual(["1", "2", "3", "4"])
31
+ }).pipe(
32
+ setupRequestContextFromCurrent(),
33
+ Effect.provide(TestStoreLive)
34
+ )
35
+ )
36
+
37
+ it.effect("supports remove batching overload", () =>
38
+ Effect.gen(function*() {
39
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
40
+ const items = [
41
+ new BatchItem({ id: "1", label: "one" }),
42
+ new BatchItem({ id: "2", label: "two" }),
43
+ new BatchItem({ id: "3", label: "three" }),
44
+ new BatchItem({ id: "4", label: "four" })
45
+ ] as const
46
+
47
+ yield* repo.save(items)
48
+ yield* repo.remove([items[0], items[1], items[2]], { batch: true })
49
+
50
+ const all = yield* repo.all
51
+ expect(all).toHaveLength(1)
52
+ expect(all[0]?.id).toBe("4")
53
+ }).pipe(
54
+ setupRequestContextFromCurrent(),
55
+ Effect.provide(TestStoreLive)
56
+ )
57
+ )
58
+ })
@@ -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,180 @@
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(RequestContextMap)
59
+ const Req = TaggedRequestFor("GenRouter")
60
+
61
+ class GetThing extends Req.Query<GetThing>()("GetThing", { id: S.String }, { success: S.String }) {}
62
+ class DoThing extends Req.Command<DoThing>()("DoThing", { id: S.String }, { success: S.Void }) {}
63
+
64
+ const Resource = { GetThing, DoThing }
65
+
66
+ const { Router, matchAll } = makeRouter(mw)
67
+
68
+ class ThingRepo extends Context.Service<ThingRepo>()("ThingRepo", {
69
+ make: Effect.succeed({ get: (id: string) => Effect.succeed(id + "!") })
70
+ }) {
71
+ static Default = Layer.effect(this, this.make)
72
+ }
73
+
74
+ // Case under test:
75
+ // `match({})` is given handlers as **shorthand generator methods** (`*GetThing(req) { ... }`).
76
+ // tsgo (>= 7 dev) infers `TNext = unknown` for these shorthand generators while TS6 infers `never`.
77
+ // `HandlerWithInputGen` in routing.ts must accept both — see the structural fix.
78
+ const router = Router(Resource)({
79
+ dependencies: [ThingRepo.Default],
80
+ *effect(match) {
81
+ const repo = yield* ThingRepo
82
+
83
+ if (Math.random() > 0.5) return yield* new InvalidStateError("nope")
84
+
85
+ return match({
86
+ *GetThing(req) {
87
+ const some = yield* Some
88
+ if (req.id === "boom") {
89
+ return yield* Effect.fail(new UnauthorizedError())
90
+ }
91
+ return yield* repo.get(req.id + String(some.a))
92
+ },
93
+ *DoThing(_req) {
94
+ yield* Effect.succeed(1)
95
+ }
96
+ })
97
+ }
98
+ })
99
+
100
+ // Same scenario but using the `raw:` variant — exercises the `raw` path of `HandlerWithInputGen`.
101
+ const routerRaw = Router({ GetThing })({
102
+ *effect(match) {
103
+ return match({
104
+ GetThing: {
105
+ *raw(req) {
106
+ const some = yield* Some
107
+ return yield* Effect.succeed(req.id + String(some.a))
108
+ }
109
+ }
110
+ })
111
+ }
112
+ })
113
+
114
+ it("router with generator-method handlers compiles", () => {
115
+ expectTypeOf(router).toMatchTypeOf<
116
+ Layer.Layer<never, ConfigError | InvalidStateError, SomeService | RpcSerialization>
117
+ >()
118
+ expectTypeOf(routerRaw).toMatchTypeOf<Layer.Layer<never, ConfigError, SomeService | RpcSerialization>>()
119
+ })
120
+
121
+ // Type-level assertions: verify generator yields propagate to MakeErrors / MakeContext
122
+ type Errors = MakeErrors<typeof router[TypeTestId]>
123
+ type Ctx = MakeContext<typeof router[TypeTestId]>
124
+ expectTypeOf<Errors>().toEqualTypeOf<InvalidStateError>()
125
+ expectTypeOf<Ctx>().toEqualTypeOf<ThingRepo>()
126
+
127
+ const matched = matchAll({ router })
128
+ expectTypeOf(matched).toMatchTypeOf<
129
+ Layer.Layer<never, ConfigError | InvalidStateError, SomeService | RpcSerialization>
130
+ >()
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // DSL R-inference regression
134
+ // ---------------------------------------------------------------------------
135
+ // `OneDSL`/`OneDSLExt.update`/`.modify` previously annotated the callback's
136
+ // effect R as `FixEnv<R, Evt, S1, S2>`. That deadlocked inference of `R`
137
+ // (TS6 → `never`, tsgo → `unknown`), causing yielded effects to leak
138
+ // `unknown` in the R slot when consumed by generator handlers.
139
+ // The fix uses bare `R` in the callback and `FixEnv<R, …>` only on the return.
140
+ class Item extends S.Class<Item>("Item")({ id: S.String, label: S.String }) {}
141
+ class Dep extends Context.Service<Dep>()("Dep", { make: Effect.succeed({ tag: "dep" as const }) }) {}
142
+
143
+ type Evt = { _tag: "Updated"; id: string }
144
+
145
+ const Items$ = makeAllDSL<Item, Evt>()
146
+ const Item$ = makeOneDSL<Item, Evt>()
147
+
148
+ // Callback body uses generator syntax (TNext = unknown under tsgo) and yields
149
+ // a service-dependent effect — R must be inferred as `Dep` (plus the
150
+ // canonical PureEnvEnv contributed by FixEnv on the return).
151
+ const oneUpdate = Item$.update((item) =>
152
+ Effect.gen(function*() {
153
+ const dep = yield* Dep
154
+ return new Item({ id: item.id, label: item.label + dep.tag })
155
+ })
156
+ )
157
+
158
+ const allUpdate = Items$.update((items) =>
159
+ Effect.gen(function*() {
160
+ const dep = yield* Dep
161
+ return items.map((_) => new Item({ id: _.id, label: _.label + dep.tag }))
162
+ })
163
+ )
164
+
165
+ const oneModify = Item$.modify((item, _dsl) =>
166
+ Effect.gen(function*() {
167
+ const dep = yield* Dep
168
+ return { ...item, tag: dep.tag }
169
+ })
170
+ )
171
+
172
+ // `R` should be `FixEnv<Dep, Evt, …>` — never collapsed to `unknown`/`never`.
173
+ // The regression manifested as `unknown` here, breaking `Dep` assignability.
174
+ expectTypeOf(oneUpdate).toMatchTypeOf<Effect.Effect<Item, never, FixEnv<Dep, Evt, Item, Item>>>()
175
+ expectTypeOf(allUpdate).toMatchTypeOf<
176
+ Effect.Effect<readonly Item[], never, FixEnv<Dep, Evt, readonly Item[], readonly Item[]>>
177
+ >()
178
+ expectTypeOf(oneModify).toMatchTypeOf<
179
+ Effect.Effect<{ tag: "dep"; id: string; label: string }, never, FixEnv<Dep, Evt, Item, Item>>
180
+ >()
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Fiber, Layer, Ref } from "effect"
3
+ import { S } from "effect-app"
4
+ import { ConfigureInterruptibilityMiddleware } from "effect-app/middleware"
5
+ import { Rpc, RpcGroup, RpcTest } from "effect/unstable/rpc"
6
+ import { applyRequestTypeInterruptibility } from "../src/api/routing.js"
7
+ import { ConfigureInterruptibilityMiddlewareLive, RequestType } from "../src/api/routing/middleware.js"
8
+
9
+ const InterruptibilityRpcs = RpcGroup.make(
10
+ Rpc
11
+ .make("doCommand", { success: S.Void })
12
+ .annotate(RequestType, "command")
13
+ .middleware(ConfigureInterruptibilityMiddleware),
14
+ Rpc
15
+ .make("doQuery", { success: S.Void })
16
+ .annotate(RequestType, "query")
17
+ .middleware(ConfigureInterruptibilityMiddleware)
18
+ )
19
+
20
+ const makeImplLayer = (commandDone: Ref.Ref<boolean>, queryDone: Ref.Ref<boolean>) =>
21
+ InterruptibilityRpcs.toLayer({
22
+ doCommand: () =>
23
+ applyRequestTypeInterruptibility(
24
+ "command",
25
+ Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(commandDone, true)))
26
+ ),
27
+ doQuery: () =>
28
+ applyRequestTypeInterruptibility(
29
+ "query",
30
+ Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(queryDone, true)))
31
+ )
32
+ })
33
+
34
+ describe("routing interruptibility", () => {
35
+ it.live(
36
+ "e2e: command continues after client interrupt, query does not",
37
+ () =>
38
+ Effect.gen(function*() {
39
+ const commandDone = yield* Ref.make(false)
40
+ const queryDone = yield* Ref.make(false)
41
+
42
+ const client = yield* RpcTest
43
+ .makeClient(InterruptibilityRpcs)
44
+ .pipe(
45
+ Effect.provide(
46
+ Layer.mergeAll(makeImplLayer(commandDone, queryDone), ConfigureInterruptibilityMiddlewareLive)
47
+ )
48
+ )
49
+
50
+ const commandFiber = yield* Effect.forkDetach(client.doCommand())
51
+ yield* Effect.sleep("20 millis")
52
+ yield* Fiber.interrupt(commandFiber)
53
+ yield* Effect.sleep("180 millis")
54
+ expect(yield* Ref.get(commandDone)).toBe(true)
55
+
56
+ const queryFiber = yield* Effect.forkDetach(client.doQuery())
57
+ yield* Effect.sleep("20 millis")
58
+ yield* Fiber.interrupt(queryFiber)
59
+ yield* Effect.sleep("180 millis")
60
+ expect(yield* Ref.get(queryDone)).toBe(false)
61
+ })
62
+ )
63
+ })
@@ -1,19 +1,18 @@
1
1
  import { NodeHttpServer } from "@effect/platform-node"
2
2
  import { expect, expectTypeOf, it } from "@effect/vitest"
3
- import { Console, Effect, Layer, Result } from "effect"
4
- import { S } from "effect-app"
3
+ import { Console, Effect, Layer, Ref, Result } from "effect"
4
+ import { Context, RpcX, S } from "effect-app"
5
5
  import { NotLoggedInError } from "effect-app/client"
6
6
  import { HttpRouter } from "effect-app/http"
7
7
  import { DefaultGenericMiddlewares } from "effect-app/middleware"
8
- import { MiddlewareMaker } from "effect-app/rpc"
9
- import { middlewareGroup } from "effect-app/rpc/MiddlewareMaker"
10
8
  import { FetchHttpClient } from "effect/unstable/http"
11
- import { RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
9
+ import { Rpc, RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
12
10
  import { createServer } from "http"
13
11
  import { DefaultGenericMiddlewaresLive } from "../src/api/routing.js"
14
12
  import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, Some, SomeElseMiddleware, SomeElseMiddlewareLive, SomeMiddleware, SomeMiddlewareLive, SomeService, Test, TestLive, UserProfile } from "./fixtures.js"
15
13
 
16
- const incomplete = MiddlewareMaker
14
+ const incomplete = RpcX
15
+ .MiddlewareMaker
17
16
  .Tag<middleware>()("MiddlewareMaker", RequestContextMap)
18
17
  .middleware(RequireRoles)
19
18
  .middleware(AllowAnonymous, Test)
@@ -21,7 +20,8 @@ const incomplete = MiddlewareMaker
21
20
  // this extension is allowed otherwise the error is quite obscure
22
21
  export class incompleteMiddleware extends incomplete {}
23
22
 
24
- class middleware extends MiddlewareMaker
23
+ class middleware extends RpcX
24
+ .MiddlewareMaker
25
25
  .Tag<middleware>()("MiddlewareMaker", RequestContextMap)
26
26
  .middleware(RequireRoles)
27
27
  .middleware(AllowAnonymous, Test)
@@ -29,7 +29,7 @@ class middleware extends MiddlewareMaker
29
29
  .middleware(...DefaultGenericMiddlewares)
30
30
  {}
31
31
 
32
- const UserRpcs = middlewareGroup(middleware)(
32
+ const UserRpcs = RpcX.MiddlewareMaker.middlewareGroup(middleware)(
33
33
  RpcGroup.make(
34
34
  middleware.rpc("getUser", {
35
35
  success: S.Literal("awesome")
@@ -56,7 +56,7 @@ const impl = UserRpcs
56
56
 
57
57
  expectTypeOf<Layer.Services<typeof impl>>().toEqualTypeOf<never>()
58
58
 
59
- const UserRpcsBad = middlewareGroup(middleware)(
59
+ const UserRpcsBad = RpcX.MiddlewareMaker.middlewareGroup(middleware)(
60
60
  RpcGroup.make(
61
61
  middleware.rpc("doSomethingElse", {
62
62
  success: S.Literal("also-awesome2"),
@@ -136,3 +136,72 @@ it.live(
136
136
  Effect.provide(RpcTestLayer)
137
137
  )
138
138
  )
139
+
140
+ // Per-request service isolation test
141
+
142
+ class PerRequestCounter extends Context.Service<PerRequestCounter>()(
143
+ "PerRequestCounter",
144
+ { make: Effect.sync(() => ({ a: 0 })) }
145
+ ) {
146
+ static Default = Layer.effect(this, this.make)
147
+ }
148
+
149
+ class GlobalCounter extends Context.Service<GlobalCounter, {
150
+ readonly ref: Ref.Ref<number>
151
+ }>()("GlobalCounter") {}
152
+
153
+ const CounterRpcs = RpcGroup.make(
154
+ Rpc.make("incrementA", {
155
+ success: S.Number
156
+ }),
157
+ Rpc.make("incrementB", {
158
+ success: S.Number
159
+ })
160
+ )
161
+
162
+ const counterImpl = CounterRpcs
163
+ .toLayer({
164
+ incrementA: Effect.fn(function*() {
165
+ const counter = yield* PerRequestCounter
166
+ counter.a++
167
+ const global = yield* GlobalCounter
168
+ yield* Ref.update(global.ref, (n) => n + 1)
169
+ return counter.a
170
+ }, Effect.provide(PerRequestCounter.Default)),
171
+ incrementB: Effect.fn(function*() {
172
+ const counter = yield* PerRequestCounter
173
+ counter.a++
174
+ const global = yield* GlobalCounter
175
+ yield* Ref.update(global.ref, (n) => n + 1)
176
+ return counter.a
177
+ }, Effect.provide(PerRequestCounter.Default))
178
+ })
179
+
180
+ const GlobalCounterLive = Layer.effect(
181
+ GlobalCounter,
182
+ Ref.make(0).pipe(Effect.map((ref) => ({ ref })))
183
+ )
184
+
185
+ const CounterTestLayer = counterImpl.pipe(Layer.provideMerge(GlobalCounterLive))
186
+
187
+ it.live(
188
+ "per-request service isolation with shared global counter",
189
+ Effect.fnUntraced(
190
+ function*() {
191
+ const client = yield* RpcTest.makeClient(CounterRpcs)
192
+ const global = yield* GlobalCounter
193
+
194
+ const r1 = yield* client.incrementA()
195
+ const r2 = yield* client.incrementB()
196
+
197
+ // per-request counter is fresh each time → both return 1
198
+ expect(r1).toBe(1)
199
+ expect(r2).toBe(1)
200
+
201
+ // global counter is shared across requests → accumulates to 2
202
+ const globalCount = yield* Ref.get(global.ref)
203
+ expect(globalCount).toBe(2)
204
+ },
205
+ Effect.provide(CounterTestLayer)
206
+ )
207
+ )