@effect-app/infra 4.0.0-beta.19 → 4.0.0-beta.190

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 (300) hide show
  1. package/CHANGELOG.md +1277 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +15 -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 +9 -3
  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 +5 -5
  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 +43 -32
  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 +142 -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/index.d.ts +1 -1
  48. package/dist/Model/filter/types/utils.d.ts +1 -1
  49. package/dist/Model/filter/types/validator.d.ts +1 -1
  50. package/dist/Model/filter/types.d.ts +1 -1
  51. package/dist/Model/query/dsl.d.ts +1 -1
  52. package/dist/Model/query/dsl.d.ts.map +1 -1
  53. package/dist/Model/query/new-kid-interpreter.d.ts +6 -6
  54. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  55. package/dist/Model/query/new-kid-interpreter.js +3 -3
  56. package/dist/Model/query.d.ts +1 -1
  57. package/dist/Model.d.ts +2 -1
  58. package/dist/Model.d.ts.map +1 -1
  59. package/dist/Model.js +2 -1
  60. package/dist/Operations.d.ts +6 -6
  61. package/dist/Operations.d.ts.map +1 -1
  62. package/dist/Operations.js +56 -59
  63. package/dist/OperationsRepo.d.ts +11 -29
  64. package/dist/OperationsRepo.d.ts.map +1 -1
  65. package/dist/OperationsRepo.js +3 -3
  66. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  67. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  68. package/dist/QueueMaker/SQLQueue.js +105 -114
  69. package/dist/QueueMaker/errors.d.ts +2 -2
  70. package/dist/QueueMaker/errors.d.ts.map +1 -1
  71. package/dist/QueueMaker/memQueue.d.ts +7 -4
  72. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  73. package/dist/QueueMaker/memQueue.js +51 -62
  74. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  75. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  76. package/dist/QueueMaker/sbqueue.js +37 -53
  77. package/dist/QueueMaker/service.d.ts +1 -1
  78. package/dist/RequestContext.d.ts +114 -26
  79. package/dist/RequestContext.d.ts.map +1 -1
  80. package/dist/RequestContext.js +7 -7
  81. package/dist/RequestFiberSet.d.ts +7 -7
  82. package/dist/RequestFiberSet.d.ts.map +1 -1
  83. package/dist/RequestFiberSet.js +5 -5
  84. package/dist/Store/ContextMapContainer.d.ts +19 -3
  85. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  86. package/dist/Store/ContextMapContainer.js +13 -3
  87. package/dist/Store/Cosmos/query.d.ts +1 -1
  88. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  89. package/dist/Store/Cosmos/query.js +8 -10
  90. package/dist/Store/Cosmos.d.ts +1 -1
  91. package/dist/Store/Cosmos.d.ts.map +1 -1
  92. package/dist/Store/Cosmos.js +308 -242
  93. package/dist/Store/Disk.d.ts +2 -2
  94. package/dist/Store/Disk.d.ts.map +1 -1
  95. package/dist/Store/Disk.js +25 -22
  96. package/dist/Store/Memory.d.ts +4 -4
  97. package/dist/Store/Memory.d.ts.map +1 -1
  98. package/dist/Store/Memory.js +27 -22
  99. package/dist/Store/SQL/Pg.d.ts +4 -0
  100. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  101. package/dist/Store/SQL/Pg.js +189 -0
  102. package/dist/Store/SQL/query.d.ts +38 -0
  103. package/dist/Store/SQL/query.d.ts.map +1 -0
  104. package/dist/Store/SQL/query.js +367 -0
  105. package/dist/Store/SQL.d.ts +20 -0
  106. package/dist/Store/SQL.d.ts.map +1 -0
  107. package/dist/Store/SQL.js +381 -0
  108. package/dist/Store/codeFilter.d.ts +1 -1
  109. package/dist/Store/codeFilter.d.ts.map +1 -1
  110. package/dist/Store/codeFilter.js +2 -1
  111. package/dist/Store/index.d.ts +5 -2
  112. package/dist/Store/index.d.ts.map +1 -1
  113. package/dist/Store/index.js +15 -3
  114. package/dist/Store/service.d.ts +17 -6
  115. package/dist/Store/service.d.ts.map +1 -1
  116. package/dist/Store/service.js +24 -6
  117. package/dist/Store/utils.d.ts +1 -1
  118. package/dist/Store/utils.d.ts.map +1 -1
  119. package/dist/Store/utils.js +3 -4
  120. package/dist/Store.d.ts +1 -1
  121. package/dist/adapters/SQL/Model.d.ts +28 -42
  122. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  123. package/dist/adapters/SQL/Model.js +2 -2
  124. package/dist/adapters/SQL.d.ts +1 -1
  125. package/dist/adapters/ServiceBus.d.ts +9 -9
  126. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  127. package/dist/adapters/ServiceBus.js +13 -15
  128. package/dist/adapters/cosmos-client.d.ts +3 -3
  129. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  130. package/dist/adapters/cosmos-client.js +3 -3
  131. package/dist/adapters/index.d.ts +8 -2
  132. package/dist/adapters/index.d.ts.map +1 -1
  133. package/dist/adapters/index.js +8 -2
  134. package/dist/adapters/logger.d.ts +1 -1
  135. package/dist/adapters/logger.d.ts.map +1 -1
  136. package/dist/adapters/memQueue.d.ts +3 -3
  137. package/dist/adapters/memQueue.d.ts.map +1 -1
  138. package/dist/adapters/memQueue.js +3 -3
  139. package/dist/adapters/mongo-client.d.ts +3 -3
  140. package/dist/adapters/mongo-client.d.ts.map +1 -1
  141. package/dist/adapters/mongo-client.js +3 -3
  142. package/dist/adapters/redis-client.d.ts +3 -3
  143. package/dist/adapters/redis-client.d.ts.map +1 -1
  144. package/dist/adapters/redis-client.js +3 -3
  145. package/dist/api/ContextProvider.d.ts +7 -7
  146. package/dist/api/ContextProvider.d.ts.map +1 -1
  147. package/dist/api/ContextProvider.js +6 -6
  148. package/dist/api/codec.d.ts +1 -1
  149. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  150. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  151. package/dist/api/internal/RequestContextMiddleware.js +2 -2
  152. package/dist/api/internal/auth.d.ts +44 -6
  153. package/dist/api/internal/auth.d.ts.map +1 -1
  154. package/dist/api/internal/auth.js +160 -29
  155. package/dist/api/internal/events.d.ts +3 -3
  156. package/dist/api/internal/events.d.ts.map +1 -1
  157. package/dist/api/internal/events.js +9 -7
  158. package/dist/api/internal/health.d.ts +1 -1
  159. package/dist/api/layerUtils.d.ts +6 -6
  160. package/dist/api/layerUtils.d.ts.map +1 -1
  161. package/dist/api/layerUtils.js +5 -5
  162. package/dist/api/middlewares.d.ts +1 -1
  163. package/dist/api/reportError.d.ts +1 -1
  164. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  165. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  166. package/dist/api/routing/middleware/middleware.d.ts +50 -4
  167. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  168. package/dist/api/routing/middleware/middleware.js +75 -17
  169. package/dist/api/routing/middleware.d.ts +1 -2
  170. package/dist/api/routing/middleware.d.ts.map +1 -1
  171. package/dist/api/routing/middleware.js +1 -2
  172. package/dist/api/routing/schema/jwt.d.ts +1 -1
  173. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  174. package/dist/api/routing/tsort.d.ts +1 -1
  175. package/dist/api/routing/tsort.d.ts.map +1 -1
  176. package/dist/api/routing/utils.d.ts +3 -3
  177. package/dist/api/routing/utils.d.ts.map +1 -1
  178. package/dist/api/routing.d.ts +26 -18
  179. package/dist/api/routing.d.ts.map +1 -1
  180. package/dist/api/routing.js +70 -11
  181. package/dist/api/setupRequest.d.ts +8 -5
  182. package/dist/api/setupRequest.d.ts.map +1 -1
  183. package/dist/api/setupRequest.js +12 -7
  184. package/dist/api/util.d.ts +1 -1
  185. package/dist/arbs.d.ts +1 -1
  186. package/dist/arbs.d.ts.map +1 -1
  187. package/dist/arbs.js +5 -3
  188. package/dist/errorReporter.d.ts +4 -4
  189. package/dist/errorReporter.d.ts.map +1 -1
  190. package/dist/errorReporter.js +20 -25
  191. package/dist/errors.d.ts +1 -1
  192. package/dist/fileUtil.d.ts +1 -1
  193. package/dist/fileUtil.d.ts.map +1 -1
  194. package/dist/index.d.ts +1 -1
  195. package/dist/logger/jsonLogger.d.ts +1 -1
  196. package/dist/logger/logFmtLogger.d.ts +1 -1
  197. package/dist/logger/shared.d.ts +1 -1
  198. package/dist/logger/shared.js +2 -2
  199. package/dist/logger.d.ts +1 -1
  200. package/dist/logger.d.ts.map +1 -1
  201. package/dist/rateLimit.d.ts +9 -3
  202. package/dist/rateLimit.d.ts.map +1 -1
  203. package/dist/rateLimit.js +5 -11
  204. package/dist/test.d.ts +1 -1
  205. package/dist/test.d.ts.map +1 -1
  206. package/dist/vitest.d.ts +1 -1
  207. package/eslint.config.mjs +3 -3
  208. package/examples/query.ts +39 -35
  209. package/package.json +42 -36
  210. package/src/CUPS.ts +9 -11
  211. package/src/Emailer/Sendgrid.ts +17 -14
  212. package/src/Emailer/service.ts +8 -2
  213. package/src/MainFiberSet.ts +3 -3
  214. package/src/Model/Repository/Registry.ts +33 -0
  215. package/src/Model/Repository/ext.ts +93 -6
  216. package/src/Model/Repository/internal/internal.ts +97 -88
  217. package/src/Model/Repository/makeRepo.ts +12 -10
  218. package/src/Model/Repository/service.ts +31 -22
  219. package/src/Model/Repository/validation.ts +4 -4
  220. package/src/Model/Repository.ts +1 -0
  221. package/src/Model/dsl.ts +3 -3
  222. package/src/Model/query/new-kid-interpreter.ts +2 -2
  223. package/src/Model.ts +1 -0
  224. package/src/QueueMaker/SQLQueue.ts +121 -151
  225. package/src/QueueMaker/memQueue.ts +82 -103
  226. package/src/QueueMaker/sbqueue.ts +56 -86
  227. package/src/RequestContext.ts +8 -8
  228. package/src/RequestFiberSet.ts +4 -4
  229. package/src/Store/ContextMapContainer.ts +41 -2
  230. package/src/Store/Cosmos/query.ts +9 -11
  231. package/src/Store/Cosmos.ts +437 -343
  232. package/src/Store/Disk.ts +52 -49
  233. package/src/Store/Memory.ts +54 -48
  234. package/src/Store/SQL/Pg.ts +318 -0
  235. package/src/Store/SQL/query.ts +409 -0
  236. package/src/Store/SQL.ts +668 -0
  237. package/src/Store/codeFilter.ts +1 -0
  238. package/src/Store/index.ts +17 -2
  239. package/src/Store/service.ts +31 -7
  240. package/src/Store/utils.ts +23 -22
  241. package/src/adapters/SQL/Model.ts +10 -4
  242. package/src/adapters/ServiceBus.ts +111 -115
  243. package/src/adapters/cosmos-client.ts +2 -2
  244. package/src/adapters/index.ts +7 -0
  245. package/src/adapters/memQueue.ts +2 -2
  246. package/src/adapters/mongo-client.ts +2 -2
  247. package/src/adapters/redis-client.ts +2 -2
  248. package/src/api/ContextProvider.ts +11 -11
  249. package/src/api/internal/RequestContextMiddleware.ts +1 -1
  250. package/src/api/internal/auth.ts +246 -44
  251. package/src/api/internal/events.ts +12 -8
  252. package/src/api/layerUtils.ts +8 -8
  253. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  254. package/src/api/routing/middleware/middleware.ts +109 -15
  255. package/src/api/routing/middleware.ts +0 -2
  256. package/src/api/routing.ts +154 -13
  257. package/src/api/setupRequest.ts +28 -8
  258. package/src/arbs.ts +4 -2
  259. package/src/errorReporter.ts +62 -74
  260. package/src/logger/shared.ts +1 -1
  261. package/src/rateLimit.ts +30 -22
  262. package/test/auth.test.ts +101 -0
  263. package/test/contextProvider.test.ts +11 -11
  264. package/test/controller.test.ts +18 -14
  265. package/test/dist/auth.test.d.ts.map +1 -0
  266. package/test/dist/contextProvider.test.d.ts.map +1 -1
  267. package/test/dist/controller.test.d.ts.map +1 -1
  268. package/test/dist/date-query.test.d.ts.map +1 -0
  269. package/test/dist/fixtures.d.ts +26 -12
  270. package/test/dist/fixtures.d.ts.map +1 -1
  271. package/test/dist/fixtures.js +12 -10
  272. package/test/dist/query.test.d.ts.map +1 -1
  273. package/test/dist/rawQuery.test.d.ts.map +1 -1
  274. package/test/dist/repository-ext.test.d.ts.map +1 -0
  275. package/test/dist/requires.test.d.ts.map +1 -1
  276. package/test/dist/router-generator.test.d.ts.map +1 -0
  277. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  278. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  279. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  280. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  281. package/test/dist/sql-store.test.d.ts.map +1 -0
  282. package/test/fixtures.ts +11 -9
  283. package/test/query.test.ts +216 -34
  284. package/test/rawQuery.test.ts +23 -19
  285. package/test/repository-ext.test.ts +60 -0
  286. package/test/requires.test.ts +6 -6
  287. package/test/router-generator.test.ts +180 -0
  288. package/test/routing-interruptibility.test.ts +63 -0
  289. package/test/rpc-e2e-invalidation.test.ts +507 -0
  290. package/test/rpc-multi-middleware.test.ts +78 -9
  291. package/test/rpc-stream-fullstack.test.ts +181 -0
  292. package/test/sql-store.test.ts +1064 -0
  293. package/test/validateSample.test.ts +15 -12
  294. package/tsconfig.examples.json +1 -1
  295. package/tsconfig.json +0 -1
  296. package/tsconfig.json.bak +2 -2
  297. package/tsconfig.src.json +35 -35
  298. package/tsconfig.test.json +2 -2
  299. package/src/Operations.ts +0 -235
  300. package/src/OperationsRepo.ts +0 -16
@@ -2,22 +2,30 @@
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
3
  /* eslint-disable @typescript-eslint/no-empty-object-type */
4
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
5
- import { Config, Effect, Layer, type NonEmptyReadonlyArray, Predicate, S, type Scope } from "effect-app"
5
+ import { Config, Effect, Layer, type NonEmptyReadonlyArray, Predicate, Ref, S, type Scope, Stream } from "effect-app"
6
+ import { getMeta } from "effect-app/client"
6
7
  import { type HttpHeaders } from "effect-app/http"
8
+ import { Invalidation } from "effect-app/rpc"
7
9
  import { type GetEffectContext, type GetEffectError, type RpcContextMap } from "effect-app/rpc/RpcContextMap"
8
10
  import { type TypeTestId } from "effect-app/TypeTest"
9
11
  import { typedKeysOf, typedValuesOf } from "effect-app/utils"
10
12
  import { type Yieldable } from "effect/Effect"
11
13
  import { Rpc, RpcGroup, type RpcSerialization, RpcServer } from "effect/unstable/rpc"
12
14
  import { type LayerUtils } from "./layerUtils.js"
13
- import { type RouterMiddleware } from "./routing/middleware.js"
15
+ import { RequestType as RequestTypeAnnotation, type RouterMiddleware } from "./routing/middleware.js"
14
16
 
15
17
  export * from "./routing/middleware.js"
16
18
 
19
+ export const applyRequestTypeInterruptibility = <A, E, R>(
20
+ requestType: "command" | "query",
21
+ effect: Effect.Effect<A, E, R>
22
+ ) => requestType === "command" ? Rpc.uninterruptible(effect) : effect
23
+
17
24
  // it's the result of extending S.Req setting success, config
18
25
  // it's a schema plus some metadata
19
26
  export type AnyRequestModule = S.Top & {
20
27
  _tag: string // unique identifier for the request module
28
+ type: "command" | "query" | "stream"
21
29
  config: any // ?
22
30
  success: S.Top // validates the success response
23
31
  error: S.Top // validates the failure response
@@ -65,7 +73,10 @@ interface HandlerBase<Action extends AnyRequestModule, RT extends RequestType, A
65
73
  new(): {}
66
74
  _tag: RT
67
75
  stack: string
68
- handler: (req: S.Schema.Type<Action>, headers: HttpHeaders.Headers) => Effect.Effect<A, E, R>
76
+ handler: (
77
+ req: S.Schema.Type<Action>,
78
+ headers: HttpHeaders.Headers
79
+ ) => Effect.Effect<A, E, R> | Stream.Stream<A, E, R>
69
80
  }
70
81
 
71
82
  export interface Handler<Action extends AnyRequestModule, RT extends RequestType, R> extends
@@ -126,6 +137,28 @@ type Match<
126
137
  Scope.Scope
127
138
  >
128
139
  >
140
+
141
+ <A extends GetSuccessShape<Resource[Key], RT>, R2 = never, E = never>(
142
+ f: Stream.Stream<A, E, R2>
143
+ ): Handler<
144
+ Resource[Key],
145
+ RT,
146
+ Exclude<
147
+ Exclude<R2, GetEffectContext<RequestContextMap, Resource[Key]["config"]>>,
148
+ Scope.Scope
149
+ >
150
+ >
151
+
152
+ <A extends GetSuccessShape<Resource[Key], RT>, R2 = never, E = never>(
153
+ f: (req: S.Schema.Type<Resource[Key]>) => Stream.Stream<A, E, R2>
154
+ ): Handler<
155
+ Resource[Key],
156
+ RT,
157
+ Exclude<
158
+ Exclude<R2, GetEffectContext<RequestContextMap, Resource[Key]["config"]>>,
159
+ Scope.Scope
160
+ >
161
+ >
129
162
  }
130
163
 
131
164
  export type RouteMatcher<
@@ -182,10 +215,9 @@ export const makeRouter = <
182
215
  * if `check` is provided, the router will only be created if the effect succeeds with true
183
216
  */
184
217
  function matchFor<
185
- const ModuleName extends string,
186
218
  const Resource extends Record<string, any>
187
219
  >(
188
- rsc: Resource & { meta: { moduleName: ModuleName } },
220
+ rsc: Resource,
189
221
  options?: { check?: Effect.Effect<boolean> }
190
222
  ) {
191
223
  type HandlerWithInputGen<
@@ -230,10 +262,32 @@ export const makeRouter = <
230
262
  GetEffectContext<RequestContextMap, Action["config"]> | ContextProviderA
231
263
  >
232
264
 
265
+ type HandlerWithInputStream<
266
+ Action extends AnyRequestModule,
267
+ RT extends RequestType
268
+ > = (
269
+ req: S.Schema.Type<Action>
270
+ ) => Stream.Stream<
271
+ GetSuccessShape<Action, RT>,
272
+ S.Schema.Type<GetFailure<Action>> | S.SchemaError,
273
+ GetEffectContext<RequestContextMap, Action["config"]> | ContextProviderA
274
+ >
275
+
276
+ type HandlerStream<
277
+ Action extends AnyRequestModule,
278
+ RT extends RequestType
279
+ > = Stream.Stream<
280
+ GetSuccessShape<Action, RT>,
281
+ S.Schema.Type<GetFailure<Action>> | S.SchemaError,
282
+ GetEffectContext<RequestContextMap, Action["config"]> | ContextProviderA
283
+ >
284
+
233
285
  type Handlers<Action extends AnyRequestModule, RT extends RequestType> =
234
286
  | HandlerWithInputGen<Action, RT>
235
287
  | HandlerWithInputEff<Action, RT>
236
288
  | HandlerEff<Action, RT>
289
+ | HandlerWithInputStream<Action, RT>
290
+ | HandlerStream<Action, RT>
237
291
 
238
292
  type HandlersDecoded<Action extends AnyRequestModule> = Handlers<Action, RequestTypes.DECODED>
239
293
 
@@ -241,15 +295,17 @@ export const makeRouter = <
241
295
  | { raw: HandlerWithInputGen<Action, RequestTypes.RAW> }
242
296
  | { raw: HandlerWithInputEff<Action, RequestTypes.RAW> }
243
297
  | { raw: HandlerEff<Action, RequestTypes.RAW> }
298
+ | { raw: HandlerWithInputStream<Action, RequestTypes.RAW> }
299
+ | { raw: HandlerStream<Action, RequestTypes.RAW> }
244
300
 
245
301
  type AnyHandlers<Action extends AnyRequestModule> = HandlersRaw<Action> | HandlersDecoded<Action>
246
302
 
247
- const { meta } = rsc
303
+ const meta = getMeta(rsc)
248
304
 
249
305
  type RequestModules = FilterRequestModules<Resource>
250
306
  const requestModules = typedKeysOf(rsc).reduce((acc, cur) => {
251
307
  if (Predicate.isObjectKeyword(rsc[cur]) && rsc[cur]["success"]) {
252
- acc[cur as keyof RequestModules] = rsc[cur] as RequestModules[keyof RequestModules]
308
+ acc[cur as keyof RequestModules] = rsc[cur]
253
309
  }
254
310
  return acc
255
311
  }, {} as RequestModules)
@@ -260,13 +316,16 @@ export const makeRouter = <
260
316
  // handlerImpl is the actual handler implementation
261
317
  if (handlerImpl[Symbol.toStringTag] === "GeneratorFunction") handlerImpl = Effect.fnUntraced(handlerImpl)
262
318
  const stack = new Error().stack?.split("\n").slice(2).join("\n")
263
- return Effect.isEffect(handlerImpl)
319
+ const isValueShape = Effect.isEffect(handlerImpl) || Stream.isStream(handlerImpl)
320
+ return isValueShape
321
+ // oxlint-disable-next-line typescript/no-extraneous-class
264
322
  ? class {
265
323
  static request = rsc[cur]
266
324
  static stack = stack
267
325
  static _tag = RequestTypes.DECODED
268
326
  static handler = () => handlerImpl
269
327
  }
328
+ // oxlint-disable-next-line typescript/no-extraneous-class
270
329
  : class {
271
330
  static request = rsc[cur]
272
331
  static stack = stack
@@ -283,13 +342,16 @@ export const makeRouter = <
283
342
  (handlerImpl: any) => {
284
343
  if (handlerImpl[Symbol.toStringTag] === "GeneratorFunction") handlerImpl = Effect.fnUntraced(handlerImpl)
285
344
  const stack = new Error().stack?.split("\n").slice(2).join("\n")
286
- return Effect.isEffect(handlerImpl)
345
+ const isValueShape = Effect.isEffect(handlerImpl) || Stream.isStream(handlerImpl)
346
+ return isValueShape
347
+ // oxlint-disable-next-line typescript/no-extraneous-class
287
348
  ? class {
288
349
  static request = rsc[cur]
289
350
  static stack = stack
290
351
  static _tag = RequestTypes.RAW
291
352
  static handler = () => handlerImpl
292
353
  }
354
+ // oxlint-disable-next-line typescript/no-extraneous-class
293
355
  : class {
294
356
  static request = rsc[cur]
295
357
  static stack = stack
@@ -319,6 +381,8 @@ export const makeRouter = <
319
381
  Impl[K] extends { raw: any }
320
382
  ? Impl[K]["raw"] extends (...args: any[]) => Effect.Effect<any, any, infer R> ? R
321
383
  : Impl[K]["raw"] extends Effect.Effect<any, any, infer R> ? R
384
+ : Impl[K]["raw"] extends (...args: any[]) => Stream.Stream<any, any, infer R> ? R
385
+ : Impl[K]["raw"] extends Stream.Stream<any, any, infer R> ? R
322
386
  : Impl[K]["raw"] extends (...args: any[]) => Generator<
323
387
  Yieldable<any, any, any, infer R>,
324
388
  any,
@@ -327,6 +391,8 @@ export const makeRouter = <
327
391
  : never
328
392
  : Impl[K] extends (...args: any[]) => Effect.Effect<any, any, infer R> ? R
329
393
  : Impl[K] extends Effect.Effect<any, any, infer R> ? R
394
+ : Impl[K] extends (...args: any[]) => Stream.Stream<any, any, infer R> ? R
395
+ : Impl[K] extends Stream.Stream<any, any, infer R> ? R
330
396
  : Impl[K] extends (...args: any[]) => Generator<
331
397
  Yieldable<any, any, any, infer R>,
332
398
  any,
@@ -386,12 +452,68 @@ export const makeRouter = <
386
452
  static success = S.toEncoded(resource.success)
387
453
  } as any
388
454
  : resource,
389
- (payload: any, headers: any) =>
390
- (handler.handler(payload, headers) as Effect.Effect<unknown, unknown, unknown>).pipe(
455
+ (payload: any, headers: any) => {
456
+ const result = handler.handler(payload, headers)
457
+ if (Stream.isStream(result)) {
458
+ // Wrap stream items as { _tag: "value", value } and append a final
459
+ // { _tag: "done", metadata } chunk carrying accumulated invalidation keys.
460
+ // V2: on failure, convert to { _tag: "error", error, metadata } chunk so
461
+ // clients can invalidate queries even when the stream fails.
462
+ const keysRef = Ref.makeUnsafe<ReadonlyArray<Invalidation.InvalidationKey>>([])
463
+ const invalidationSet = Invalidation.makeInvalidationSet(keysRef)
464
+ return Stream.concat(
465
+ (result as Stream.Stream<any, any, any>).pipe(
466
+ Stream.map((item: any) => ({ _tag: "value" as const, value: item })),
467
+ Stream.provideService(Invalidation.InvalidationSet, invalidationSet),
468
+ // V3: after each value chunk, drain accumulated keys and emit a "metadata"
469
+ // chunk if any keys were collected since the last drain. This lets clients
470
+ // invalidate queries mid-stream without waiting for the "done" chunk.
471
+ Stream.flatMap((valueChunk: any) =>
472
+ Stream
473
+ .fromEffect(
474
+ Ref.getAndSet(keysRef, []).pipe(
475
+ Effect.map((keys) =>
476
+ keys.length > 0
477
+ ? [
478
+ valueChunk,
479
+ { _tag: "metadata" as const, metadata: { invalidateQueries: keys } }
480
+ ]
481
+ : [valueChunk]
482
+ )
483
+ )
484
+ )
485
+ .pipe(Stream.flatMap(Stream.fromIterable))
486
+ ),
487
+ // V2: catch stream failures and embed them in the stream as an error chunk
488
+ Stream.catch((err: any) =>
489
+ Stream.fromEffect(
490
+ Ref.get(keysRef).pipe(
491
+ Effect.flatMap((keys) =>
492
+ Effect.fail({
493
+ _tag: "error" as const,
494
+ error: err,
495
+ metadata: { invalidateQueries: keys }
496
+ })
497
+ )
498
+ )
499
+ )
500
+ )
501
+ ),
502
+ Stream.fromEffect(
503
+ Ref.get(keysRef).pipe(
504
+ Effect.map((keys) => ({ _tag: "done" as const, metadata: { invalidateQueries: keys } }))
505
+ )
506
+ )
507
+ )
508
+ }
509
+ const effect = (result as Effect.Effect<unknown, unknown, unknown>).pipe(
391
510
  Effect.withSpan(`Request.${meta.moduleName}.${resource._tag}`, {}, {
392
511
  captureStackTrace: () => handler.stack // capturing the handler stack is the main reason why we are doing the span here
393
512
  })
394
513
  )
514
+
515
+ return applyRequestTypeInterruptibility(resource.type, effect)
516
+ }
395
517
  ] as const
396
518
  return acc
397
519
  }, {} as any) as {
@@ -415,9 +537,28 @@ export const makeRouter = <
415
537
  const rpcs = RpcGroup
416
538
  .make(
417
539
  ...typedValuesOf(mapped).map(([resource]) => {
418
- return Rpc
419
- .make(resource._tag, { payload: resource, success: resource.success, error: resource.error })
540
+ const isStream = resource.type === "stream"
541
+ const isCommand = resource.type === "command"
542
+ return (isCommand
543
+ ? Invalidation.makeCommandRpc(resource._tag, {
544
+ payload: resource,
545
+ success: resource.success,
546
+ error: resource.error
547
+ })
548
+ : isStream
549
+ ? Invalidation.makeStreamRpc(resource._tag, {
550
+ payload: resource,
551
+ success: resource.success,
552
+ error: resource.error,
553
+ stream: true as const
554
+ })
555
+ : Rpc.make(resource._tag, {
556
+ payload: resource,
557
+ success: resource.success,
558
+ error: resource.error
559
+ }))
420
560
  .annotate(middleware.requestContext, resource.config ?? {})
561
+ .annotate(RequestTypeAnnotation, resource.type)
421
562
  })
422
563
  )
423
564
  .prefix(`${meta.moduleName}.`)
@@ -1,9 +1,18 @@
1
- import { Effect, Layer, Tracer } from "effect-app"
1
+ import { Effect, Layer, Option, Tracer } from "effect-app"
2
2
  import { NonEmptyString255 } from "effect-app/Schema"
3
+ import { SqlClient } from "effect/unstable/sql"
3
4
  import { LocaleRef, RequestContext, spanAttributes } from "../RequestContext.js"
4
5
  import { ContextMapContainer } from "../Store/ContextMapContainer.js"
5
6
  import { storeId } from "../Store/Memory.js"
6
7
 
8
+ const withSqlTransaction = <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
9
+ Effect.serviceOption(SqlClient.SqlClient).pipe(
10
+ Effect.flatMap(Option.match({
11
+ onNone: () => self,
12
+ onSome: (sql) => sql.withTransaction(self).pipe(Effect.orDie)
13
+ }))
14
+ )
15
+
7
16
  export const getRequestContext = Effect
8
17
  .all({
9
18
  span: Effect.currentSpan.pipe(Effect.orDie),
@@ -12,7 +21,7 @@ export const getRequestContext = Effect
12
21
  })
13
22
  .pipe(
14
23
  Effect.map(({ locale, namespace, span }) =>
15
- new RequestContext({
24
+ RequestContext.make({
16
25
  span: Tracer.externalSpan(span),
17
26
  locale,
18
27
  namespace,
@@ -43,16 +52,25 @@ const withRequestSpan = (name = "request", options?: Tracer.SpanOptions) => <R,
43
52
  )
44
53
  )
45
54
 
55
+ export interface SetupRequestOptions {
56
+ readonly withTransaction?: boolean
57
+ }
58
+
46
59
  export const setupRequestContextFromCurrent =
47
- (name = "request", options?: Tracer.SpanOptions) => <R, E, A>(self: Effect.Effect<A, E, R>) =>
60
+ (name = "request", options?: Tracer.SpanOptions & SetupRequestOptions) => <R, E, A>(self: Effect.Effect<A, E, R>) =>
48
61
  self
49
62
  .pipe(
63
+ options?.withTransaction === true ? withSqlTransaction : (_) => _,
50
64
  withRequestSpan(name, options),
51
- Effect.provide(ContextMapContainer.layer)
65
+ Effect.provide(ContextMapContainer.layer, { local: true })
52
66
  )
53
67
 
54
68
  // TODO: consider integrating Effect.withParentSpan
55
- export function setupRequestContext<R, E, A>(self: Effect.Effect<A, E, R>, requestContext: RequestContext) {
69
+ export function setupRequestContext<R, E, A>(
70
+ self: Effect.Effect<A, E, R>,
71
+ requestContext: RequestContext,
72
+ options?: SetupRequestOptions
73
+ ) {
56
74
  const layer = Layer.mergeAll(
57
75
  ContextMapContainer.layer,
58
76
  Layer.succeed(LocaleRef, requestContext.locale),
@@ -60,8 +78,9 @@ export function setupRequestContext<R, E, A>(self: Effect.Effect<A, E, R>, reque
60
78
  )
61
79
  return self
62
80
  .pipe(
81
+ options?.withTransaction === true ? withSqlTransaction : (_) => _,
63
82
  withRequestSpan(requestContext.name),
64
- Effect.provide(layer)
83
+ Effect.provide(layer, { local: true })
65
84
  )
66
85
  }
67
86
 
@@ -69,7 +88,7 @@ export function setupRequestContextWithCustomSpan<R, E, A>(
69
88
  self: Effect.Effect<A, E, R>,
70
89
  requestContext: RequestContext,
71
90
  name: string,
72
- options?: Tracer.SpanOptions
91
+ options?: Tracer.SpanOptions & SetupRequestOptions
73
92
  ) {
74
93
  const layer = Layer.mergeAll(
75
94
  ContextMapContainer.layer,
@@ -78,7 +97,8 @@ export function setupRequestContextWithCustomSpan<R, E, A>(
78
97
  )
79
98
  return self
80
99
  .pipe(
100
+ options?.withTransaction === true ? withSqlTransaction : (_) => _,
81
101
  withRequestSpan(name, options),
82
- Effect.provide(layer)
102
+ Effect.provide(layer, { local: true })
83
103
  )
84
104
  }
package/src/arbs.ts CHANGED
@@ -5,9 +5,11 @@ import { type S } from "effect-app"
5
5
  import { setFaker } from "effect-app/faker"
6
6
  import * as FastCheck from "effect/testing/FastCheck"
7
7
  import { Random } from "fast-check"
8
- import * as rand from "pure-rand"
8
+ import { congruential32 } from "pure-rand/generator/congruential32"
9
9
 
10
- const rnd = new Random(rand.congruential32(5))
10
+ const seed = 5
11
+ const rng = congruential32(seed)
12
+ const rnd = new Random(rng)
11
13
 
12
14
  setFaker(faker)
13
15
 
@@ -13,47 +13,41 @@ const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): CauseExcepti
13
13
  }
14
14
  }
15
15
 
16
- export function reportError(
17
- name: string
18
- ) {
19
- return (
20
- cause: Cause.Cause<unknown>,
21
- extras?: Record<string, unknown>,
22
- level: LogLevel.Severity = "Error"
23
- ) =>
24
- Effect
25
- .gen(function*() {
26
- if (Cause.hasInterruptsOnly(cause)) {
27
- yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
28
- return
29
- }
30
- const error = tryCauseException(cause, name)
31
-
32
- yield* reportSentry(error, extras, LogLevelToSentry(level))
33
- yield* InfraLogger
34
- .logWithLevel(level, "Reporting error", cause)
35
- .pipe(
36
- Effect.annotateLogs(dropUndefined({
37
- extras,
38
- error: tryToReport(error),
39
- cause: tryToJson(cause),
40
- __error_name__: name
41
- }))
42
- )
43
- .pipe(
44
- Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
45
- Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
46
- )
16
+ export function reportError(name: string) {
17
+ return Effect.fnUntraced(
18
+ function*(
19
+ cause: Cause.Cause<unknown>,
20
+ extras?: Record<string, unknown>,
21
+ level: LogLevel.Severity = "Error"
22
+ ) {
23
+ if (Cause.hasInterruptsOnly(cause)) {
24
+ yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
25
+ return
26
+ }
27
+ const error = tryCauseException(cause, name)
47
28
 
48
- return error
49
- })
50
- .pipe(
51
- Effect.tapCause((cause) =>
52
- InfraLogger.logError("Failed to report error", cause).pipe(
53
- Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
54
- )
29
+ yield* reportSentry(error, extras, LogLevelToSentry(level))
30
+ yield* InfraLogger
31
+ .logWithLevel(level, "Reporting error", cause)
32
+ .pipe(
33
+ Effect.annotateLogs(dropUndefined({
34
+ extras,
35
+ error: tryToReport(error),
36
+ cause: tryToJson(cause),
37
+ __error_name__: name
38
+ })),
39
+ Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
40
+ Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
55
41
  )
56
- )
42
+
43
+ return error
44
+ },
45
+ (effect) =>
46
+ Effect.tapCause(effect, (cause) =>
47
+ InfraLogger.logError("Failed to report error", cause).pipe(
48
+ Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
49
+ ))
50
+ )
57
51
  }
58
52
 
59
53
  function reportSentry(
@@ -66,45 +60,39 @@ function reportSentry(
66
60
  scope.setLevel(level)
67
61
  if (context) scope.setContext("context", { ...context })
68
62
  if (extras) scope.setContext("extras", extras)
69
- scope.setContext("error", { data: tryToReport(error) })
70
- scope.setContext("cause", { data: tryToJson(error.originalCause) })
63
+ const squashed = Cause.squash(error.originalCause)
64
+ scope.setContext("mainError", tryToJson(squashed))
65
+ scope.setContext("error", tryToReport(error))
66
+ scope.setContext("cause", tryToJson(error.originalCause))
71
67
  Sentry.captureException(error, scope)
72
68
  }))
73
69
  }
74
70
 
75
- export function logError<E>(
76
- name: string
77
- ) {
78
- return (cause: Cause.Cause<E>, extras?: Record<string, unknown>) =>
79
- Effect
80
- .gen(function*() {
81
- if (Cause.hasInterruptsOnly(cause)) {
82
- yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
83
- return
84
- }
85
- yield* InfraLogger
86
- .logWarning("Logging error", cause)
87
- .pipe(
88
- Effect.annotateLogs(dropUndefined({
89
- extras,
90
- cause: tryToJson(cause),
91
- __error_name__: name
92
- }))
93
- )
94
- })
95
- .pipe(
96
- Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
97
- )
71
+ export function logError<E>(name: string) {
72
+ return Effect.fnUntraced(
73
+ function*(cause: Cause.Cause<E>, extras?: Record<string, unknown>) {
74
+ if (Cause.hasInterruptsOnly(cause)) {
75
+ yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
76
+ return
77
+ }
78
+ yield* InfraLogger
79
+ .logWarning("Logging error", cause)
80
+ .pipe(Effect.annotateLogs(dropUndefined({
81
+ extras,
82
+ cause: tryToJson(cause),
83
+ __error_name__: name
84
+ })))
85
+ },
86
+ (effect) => Effect.tapCause(effect, () => InfraLogger.logFatal("Failed to log error cause"))
87
+ )
98
88
  }
99
89
 
100
- export function reportMessage(message: string, extras?: Record<string, unknown>) {
101
- return Effect.gen(function*() {
102
- const context = yield* getRC
103
- const scope = new Sentry.Scope()
104
- if (context) scope.setContext("context", { ...context })
105
- if (extras) scope.setContext("extras", extras)
106
- Sentry.captureMessage(message, scope)
90
+ export const reportMessage = Effect.fnUntraced(function*(message: string, extras?: Record<string, unknown>) {
91
+ const context = yield* getRC
92
+ const scope = new Sentry.Scope()
93
+ if (context) scope.setContext("context", { ...context })
94
+ if (extras) scope.setContext("extras", extras)
95
+ Sentry.captureMessage(message, scope)
107
96
 
108
- console.warn(message, extras)
109
- })
110
- }
97
+ console.warn(message, extras)
98
+ })
@@ -7,7 +7,7 @@ export function getRequestContextFromFiber(fiber: Fiber.Fiber<unknown, unknown>)
7
7
  const span = Option.fromNullishOr(fiber.currentSpan)
8
8
  const locale = fiber.getRef(LocaleRef)
9
9
  const namespace = fiber.getRef(storeId)
10
- return new RequestContext({
10
+ return RequestContext.make({
11
11
  span: Option.map(span, (s) => ({ spanId: s.spanId, traceId: s.traceId, sampled: s.sampled })).pipe(
12
12
  Option.getOrElse(() => ({ spanId: "bogus", sampled: true, traceId: "bogus" }))
13
13
  ),
package/src/rateLimit.ts CHANGED
@@ -21,7 +21,9 @@
21
21
  // }
22
22
 
23
23
  import { Array, type Duration, Effect, type NonEmptyArray } from "effect-app"
24
+ import { dual } from "effect-app/Function"
24
25
  import type { Semaphore } from "effect/Semaphore"
26
+ import type { Concurrency } from "effect/Types"
25
27
 
26
28
  /**
27
29
  * Executes the specified effect, acquiring the specified number of permits
@@ -45,36 +47,42 @@ export function SEM_withPermitsDuration(permits: number, duration: Duration.Dura
45
47
  }
46
48
  }
47
49
 
48
- export function batchPar<R, E, A, R2, E2, A2, T>(
49
- n: number,
50
- forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
51
- forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>
52
- ) {
53
- return (items: Iterable<T>) =>
54
- Effect.forEach(
55
- Array.chunksOf(items, n),
56
- (_, i) =>
57
- Effect
58
- .forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: "inherit" })
59
- .pipe(Effect.flatMap((_) => forEachBatch(_, i))),
60
- { concurrency: "inherit" }
61
- )
50
+ export interface BatchOptions {
51
+ readonly concurrency?: Concurrency | undefined
62
52
  }
63
53
 
64
- export function batch<R, E, A, R2, E2, A2, T>(
65
- n: number,
66
- forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
67
- forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>
68
- ) {
69
- return (items: Iterable<T>) =>
54
+ export const batch: {
55
+ <T, A, E, R, A2, E2, R2>(
56
+ n: number,
57
+ forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
58
+ forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
59
+ options?: BatchOptions
60
+ ): (items: Iterable<T>) => Effect.Effect<Array<A2>, E | E2, R | R2>
61
+ <T, A, E, R, A2, E2, R2>(
62
+ items: Iterable<T>,
63
+ n: number,
64
+ forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
65
+ forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
66
+ options?: BatchOptions
67
+ ): Effect.Effect<Array<A2>, E | E2, R | R2>
68
+ } = dual(
69
+ (args) => typeof args[0] !== "number",
70
+ <T, A, E, R, A2, E2, R2>(
71
+ items: Iterable<T>,
72
+ n: number,
73
+ forEachItem: (item: T, iWithinBatch: number, batchI: number) => Effect.Effect<A, E, R>,
74
+ forEachBatch: (a: NonEmptyArray<A>, i: number) => Effect.Effect<A2, E2, R2>,
75
+ options?: BatchOptions
76
+ ) =>
70
77
  Effect.forEach(
71
78
  Array.chunksOf(items, n),
72
79
  (_, i) =>
73
80
  Effect
74
81
  .forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: "inherit" })
75
- .pipe(Effect.flatMap((_) => forEachBatch(_, i)))
82
+ .pipe(Effect.flatMap((_) => forEachBatch(_, i))),
83
+ { concurrency: options?.concurrency }
76
84
  )
77
- }
85
+ )
78
86
 
79
87
  // export function rateLimit(
80
88
  // n: number,