@effect-app/infra 2.74.0 → 2.76.0

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 (43) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/layerUtils.d.ts +22 -0
  3. package/dist/api/layerUtils.d.ts.map +1 -0
  4. package/dist/api/layerUtils.js +2 -0
  5. package/dist/api/routing/middleware/ContextProvider.d.ts +41 -0
  6. package/dist/api/routing/middleware/ContextProvider.d.ts.map +1 -0
  7. package/dist/api/routing/middleware/ContextProvider.js +27 -0
  8. package/dist/api/routing/middleware/DynamicMiddleware.d.ts +61 -0
  9. package/dist/api/routing/middleware/DynamicMiddleware.d.ts.map +1 -0
  10. package/dist/api/routing/middleware/DynamicMiddleware.js +45 -0
  11. package/dist/api/routing/middleware/dynamic-middleware.d.ts +25 -0
  12. package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +1 -0
  13. package/dist/api/routing/middleware/dynamic-middleware.js +39 -0
  14. package/dist/api/routing/middleware/generic-middleware.d.ts +9 -0
  15. package/dist/api/routing/middleware/generic-middleware.d.ts.map +1 -0
  16. package/dist/api/routing/middleware/generic-middleware.js +20 -0
  17. package/dist/api/routing/middleware/middleware.d.ts +28 -0
  18. package/dist/api/routing/middleware/middleware.d.ts.map +1 -0
  19. package/dist/api/routing/middleware/middleware.js +101 -0
  20. package/dist/api/routing/middleware.d.ts +6 -0
  21. package/dist/api/routing/middleware.d.ts.map +1 -0
  22. package/dist/api/routing/middleware.js +8 -0
  23. package/dist/api/routing.d.ts +18 -28
  24. package/dist/api/routing.d.ts.map +1 -1
  25. package/dist/api/routing.js +3 -3
  26. package/package.json +27 -7
  27. package/src/api/layerUtils.ts +33 -0
  28. package/src/api/routing/middleware/ContextProvider.ts +136 -0
  29. package/src/api/routing/middleware/DynamicMiddleware.ts +317 -0
  30. package/src/api/routing/{dynamic-middleware.ts → middleware/dynamic-middleware.ts} +29 -63
  31. package/src/api/routing/middleware/generic-middleware.ts +38 -0
  32. package/src/api/routing/middleware/middleware.ts +134 -0
  33. package/src/api/routing/middleware.ts +7 -0
  34. package/src/api/routing.ts +37 -56
  35. package/test/controller.test.ts +132 -15
  36. package/test/dist/controller.test.d.ts.map +1 -1
  37. package/dist/api/routing/DynamicMiddleware.d.ts +0 -104
  38. package/dist/api/routing/DynamicMiddleware.d.ts.map +0 -1
  39. package/dist/api/routing/DynamicMiddleware.js +0 -122
  40. package/dist/api/routing/dynamic-middleware.d.ts +0 -24
  41. package/dist/api/routing/dynamic-middleware.d.ts.map +0 -1
  42. package/dist/api/routing/dynamic-middleware.js +0 -39
  43. package/src/api/routing/DynamicMiddleware.ts +0 -527
@@ -0,0 +1,317 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ import { Context, Effect, Layer, type NonEmptyArray, type Request, type S, type Scope } from "effect-app"
5
+ import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
6
+ import { type HttpRouter } from "effect-app/http"
7
+ import type * as EffectRequest from "effect/Request"
8
+ import { type ContextTagWithDefault, type LayerUtils } from "../../layerUtils.js"
9
+ import { type ContextProviderId, type ContextProviderShape } from "./ContextProvider.js"
10
+ import { type ContextWithLayer, implementMiddleware } from "./dynamic-middleware.js"
11
+ import { type GenericMiddlewareMaker, genericMiddlewareMaker } from "./generic-middleware.js"
12
+
13
+ // module:
14
+ //
15
+ export type MakeRPCHandlerFactory<
16
+ RequestContextMap extends Record<string, RPCContextMap.Any>,
17
+ MiddlewareR
18
+ > = <
19
+ T extends {
20
+ config?: Partial<Record<keyof RequestContextMap, any>>
21
+ },
22
+ Req extends S.TaggedRequest.All,
23
+ HandlerR
24
+ >(
25
+ schema: T & S.Schema<Req, any, never>,
26
+ handler: (
27
+ request: Req,
28
+ headers: any
29
+ ) => Effect.Effect<
30
+ EffectRequest.Request.Success<Req>,
31
+ EffectRequest.Request.Error<Req>,
32
+ // dynamic middlewares removes the dynamic context from HandlerR
33
+ Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>
34
+ >,
35
+ moduleName: string
36
+ ) => (
37
+ req: Req,
38
+ headers: any
39
+ ) => Effect.Effect<
40
+ Request.Request.Success<Req>,
41
+ Request.Request.Error<Req> | RequestContextMapErrors<RequestContextMap>,
42
+ // the middleware will remove from HandlerR the dynamic context, but will also add some requirements
43
+ | MiddlewareR
44
+ // & S.Schema<Req, any, never> is useless here but useful when creating the middleware
45
+ | Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>
46
+ >
47
+
48
+ export type RPCHandlerFactory<
49
+ RequestContextMap extends Record<string, RPCContextMap.Any>,
50
+ ContextProviderA
51
+ > = <
52
+ T extends {
53
+ config?: Partial<Record<keyof RequestContextMap, any>>
54
+ },
55
+ Req extends S.TaggedRequest.All,
56
+ HandlerR
57
+ >(
58
+ schema: T & S.Schema<Req, any, never>,
59
+ handler: (
60
+ request: Req,
61
+ headers: any
62
+ ) => Effect.Effect<
63
+ EffectRequest.Request.Success<Req>,
64
+ EffectRequest.Request.Error<Req>,
65
+ HandlerR
66
+ >,
67
+ moduleName: string
68
+ ) => (
69
+ req: Req,
70
+ headers: any
71
+ ) => Effect.Effect<
72
+ Request.Request.Success<Req>,
73
+ Request.Request.Error<Req> | RequestContextMapErrors<RequestContextMap>,
74
+ | HttpRouter.HttpRouter.Provided // because of the context provider and the middleware (Middleware)
75
+ | Exclude<
76
+ // the middleware will remove from HandlerR the dynamic context
77
+ // & S.Schema<Req, any, never> is useless here but useful when creating the middleware
78
+ Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>,
79
+ // the context provider provides additional stuff both to the middleware and the handler
80
+ ContextProviderA
81
+ >
82
+ >
83
+
84
+ type RequestContextMapProvider<RequestContextMap extends Record<string, RPCContextMap.Any>> = {
85
+ [K in keyof RequestContextMap]: ContextWithLayer.Base<
86
+ { [K in keyof RequestContextMap]?: RequestContextMap[K]["contextActivation"] },
87
+ RequestContextMap[K]["service"],
88
+ S.Schema.Type<RequestContextMap[K]["error"]>
89
+ >
90
+ }
91
+
92
+ export interface MiddlewareMake<
93
+ RequestContextMap extends Record<string, RPCContextMap.Any>, // what services will the middleware provide dynamically to the handler, or raise errors.
94
+ //
95
+ // ContextProvider is a service that builds additional context for each request.
96
+ ContextProviderA, // what the context provider provides
97
+ ContextProviderR extends HttpRouter.HttpRouter.Provided, // what the context provider requires
98
+ MakeContextProviderE, // what the context provider construction can fail with
99
+ MakeContextProviderR, // what the context provider construction requires
100
+ DynamicMiddlewareProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
101
+ GenericMiddlewareProviders extends Array<
102
+ ContextTagWithDefault.Base<GenericMiddlewareMaker>
103
+ >,
104
+ MakeMiddlewareE, // what the middleware construction can fail with
105
+ MakeMiddlewareR, // what the middleware requires to be constructed
106
+ MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any> // layers provided for the middleware to be constructed
107
+ > {
108
+ /* dynamic middlewares to be applied based on Request Configuration */
109
+ dynamicMiddlewares: DynamicMiddlewareProviders
110
+ /** generic middlewares are those which follow the (next) => (input, headers) => pattern */
111
+ genericMiddlewares: GenericMiddlewareProviders
112
+ /** static context providers */
113
+ contextProvider: ContextTagWithDefault<
114
+ ContextProviderId,
115
+ ContextProviderShape<ContextProviderA, ContextProviderR>,
116
+ MakeContextProviderE,
117
+ MakeContextProviderR
118
+ >
119
+
120
+ /* dependencies for the main middleware running just before the handler is called */
121
+ dependencies?: MiddlewareDependencies
122
+ // this actually builds "the middleware", i.e. returns the augmented handler factory when yielded...
123
+ execute?: (
124
+ maker: (
125
+ // MiddlewareR is set to ContextProviderA | HttpRouter.HttpRouter.Provided because that's what, at most
126
+ // a middleware can additionally require to get executed
127
+ cb: MakeRPCHandlerFactory<RequestContextMap, ContextProviderA | HttpRouter.HttpRouter.Provided>
128
+ ) => MakeRPCHandlerFactory<RequestContextMap, ContextProviderA | HttpRouter.HttpRouter.Provided>
129
+ ) => Effect<
130
+ MakeRPCHandlerFactory<RequestContextMap, ContextProviderA | HttpRouter.HttpRouter.Provided>,
131
+ MakeMiddlewareE,
132
+ MakeMiddlewareR | Scope // ...that's why MakeMiddlewareR is here
133
+ >
134
+ }
135
+
136
+ export interface MiddlewareMakerId {
137
+ _tag: "MiddlewareMaker"
138
+ }
139
+
140
+ export type Middleware<
141
+ RequestContextMap extends Record<string, RPCContextMap.Any>, // what services will the middlware provide dynamically to the handler, or raise errors.
142
+ MakeMiddlewareE, // what the middleware construction can fail with
143
+ MakeMiddlewareR, // what the middlware requires to be constructed
144
+ ContextProviderA // what the context provider provides
145
+ > = ContextTagWithDefault<
146
+ MiddlewareMakerId,
147
+ {
148
+ effect: RPCHandlerFactory<RequestContextMap, ContextProviderA>
149
+ },
150
+ MakeMiddlewareE,
151
+ MakeMiddlewareR,
152
+ "MiddlewareMaker"
153
+ >
154
+
155
+ export type RequestContextMapErrors<RequestContextMap extends Record<string, RPCContextMap.Any>> = S.Schema.Type<
156
+ RequestContextMap[keyof RequestContextMap]["error"]
157
+ >
158
+
159
+ // factory for middlewares
160
+ export const makeMiddleware =
161
+ // by setting RequestContextMap beforehand, execute contextual typing does not fuck up itself to anys
162
+ <
163
+ RequestContextMap extends Record<string, RPCContextMap.Any>
164
+ >() =>
165
+ <
166
+ //
167
+ // ContextProvider is a service that builds additional context for each request.
168
+ ContextProviderA, // what the context provider provides
169
+ ContextProviderR extends HttpRouter.HttpRouter.Provided, // what the context provider requires
170
+ MakeContextProviderE, // what the context provider construction can fail with
171
+ MakeContextProviderR, // what the context provider construction requires
172
+ RequestContextProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
173
+ GenericMiddlewareProviders extends Array<
174
+ ContextTagWithDefault.Base<GenericMiddlewareMaker>
175
+ >,
176
+ MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>, // layers provided for the middlware to be constructed
177
+ MakeMiddlewareE = never, // what the middleware construction can fail with
178
+ MakeMiddlewareR = never // what the middlware requires to be constructed
179
+ >(
180
+ make: MiddlewareMake<
181
+ RequestContextMap,
182
+ ContextProviderA,
183
+ ContextProviderR,
184
+ MakeContextProviderE,
185
+ MakeContextProviderR,
186
+ RequestContextProviders,
187
+ GenericMiddlewareProviders,
188
+ MakeMiddlewareE,
189
+ MakeMiddlewareR,
190
+ MiddlewareDependencies
191
+ >
192
+ ) => {
193
+ // type Id = MiddlewareMakerId &
194
+ const MiddlewareMaker = Context.GenericTag<
195
+ MiddlewareMakerId,
196
+ {
197
+ effect: RPCHandlerFactory<RequestContextMap, ContextProviderA>
198
+ _tag: "MiddlewareMaker"
199
+ }
200
+ >(
201
+ "MiddlewareMaker"
202
+ )
203
+
204
+ const dynamicMiddlewares = implementMiddleware<RequestContextMap>()(make.dynamicMiddlewares)
205
+ const middlewares = genericMiddlewareMaker(...make.genericMiddlewares)
206
+
207
+ const l = Layer.scoped(
208
+ MiddlewareMaker,
209
+ Effect
210
+ .all({
211
+ dynamicMiddlewares: dynamicMiddlewares.effect,
212
+ generic: middlewares.effect,
213
+ middleware: make.execute
214
+ ? make.execute((
215
+ cb: MakeRPCHandlerFactory<RequestContextMap, HttpRouter.HttpRouter.Provided | ContextProviderA>
216
+ ) => cb)
217
+ : Effect.succeed<
218
+ MakeRPCHandlerFactory<RequestContextMap, ContextProviderA | HttpRouter.HttpRouter.Provided>
219
+ >((_schema, handle) => (req, headers) => handle(req, headers)),
220
+ contextProvider: make.contextProvider // uses the middleware.contextProvider tag to get the context provider service
221
+ })
222
+ .pipe(
223
+ Effect.map(({ contextProvider, dynamicMiddlewares, generic, middleware }) => ({
224
+ _tag: "MiddlewareMaker" as const,
225
+ effect: makeRpcEffect<RequestContextMap, ContextProviderA>()(
226
+ (schema, handler, moduleName) => {
227
+ const h = middleware(schema, handler as any, moduleName)
228
+ return generic(
229
+ Effect.fnUntraced(function*(req, headers) {
230
+ yield* Effect.annotateCurrentSpan(
231
+ "request.name",
232
+ moduleName ? `${moduleName}.${req._tag}` : req._tag
233
+ )
234
+
235
+ // the contextProvider is an Effect that builds the context for the request
236
+ return yield* contextProvider.pipe(
237
+ Effect.flatMap((contextProviderContext) =>
238
+ // the dynamicMiddlewares is an Effect that builds the dynamiuc context for the request
239
+ dynamicMiddlewares(schema.config ?? {}, headers).pipe(
240
+ Effect.flatMap((dynamicContext) => h(req, headers).pipe(Effect.provide(dynamicContext))),
241
+ Effect.provide(contextProviderContext)
242
+ )
243
+ )
244
+ )
245
+ }) as any,
246
+ moduleName
247
+ )
248
+ }
249
+ )
250
+ }))
251
+ )
252
+ )
253
+
254
+ const middlewareLayer = l
255
+ .pipe(
256
+ Layer.provide(
257
+ Layer.mergeAll(
258
+ make.dependencies ? make.dependencies as any : Layer.empty,
259
+ ...(dynamicMiddlewares.dependencies as any),
260
+ make.contextProvider.Default,
261
+ ...middlewares.dependencies
262
+ )
263
+ )
264
+ ) as Layer.Layer<
265
+ MiddlewareMakerId,
266
+ | MakeMiddlewareE // what the middleware construction can fail with
267
+ | LayerUtils.GetLayersContext<typeof dynamicMiddlewares.dependencies>
268
+ | LayerUtils.GetLayersContext<typeof middlewares.dependencies> // what could go wrong when building the dynamic middleware provider
269
+ | Layer.Error<typeof make.contextProvider.Default>, // what could go wrong when building the context provider
270
+ | LayerUtils.GetLayersContext<MiddlewareDependencies> // what's needed to build layers
271
+ | LayerUtils.GetLayersContext<typeof middlewares.dependencies>
272
+ | LayerUtils.GetLayersContext<typeof dynamicMiddlewares.dependencies> // what's needed to build dynamic middleware layers
273
+ | Exclude<MakeMiddlewareR, LayerUtils.GetLayersSuccess<MiddlewareDependencies>> // what layers provides
274
+ | Layer.Context<typeof make.contextProvider.Default> // what's needed to build the contextProvider
275
+ >
276
+
277
+ return Object.assign(MiddlewareMaker, { Default: middlewareLayer })
278
+ }
279
+
280
+ // it just provides the right types without cluttering the implementation with them
281
+ function makeRpcEffect<
282
+ RequestContextMap extends Record<string, RPCContextMap.Any>,
283
+ ContextProviderA
284
+ >() {
285
+ return (
286
+ cb: <
287
+ T extends {
288
+ config?: Partial<Record<keyof RequestContextMap, any>>
289
+ },
290
+ Req extends S.TaggedRequest.All,
291
+ HandlerR
292
+ >(
293
+ schema: T & S.Schema<Req, any, never>,
294
+ handler: (
295
+ request: Req,
296
+ headers: any
297
+ ) => Effect.Effect<
298
+ EffectRequest.Request.Success<Req>,
299
+ EffectRequest.Request.Error<Req>,
300
+ HandlerR
301
+ >,
302
+ moduleName: string
303
+ ) => (
304
+ req: Req,
305
+ headers: any
306
+ ) => Effect.Effect<
307
+ Request.Request.Success<Req>,
308
+ Request.Request.Error<Req> | RequestContextMapErrors<RequestContextMap>,
309
+ | HttpRouter.HttpRouter.Provided // the context provider may require HttpRouter.Provided to run
310
+ | Exclude<
311
+ // it can also be removed from HandlerR
312
+ Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>,
313
+ ContextProviderA
314
+ >
315
+ >
316
+ ) => cb
317
+ }
@@ -1,81 +1,43 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Array, Context, Effect, type Layer, Option, type S } from "effect-app"
2
+ import { Array, Context, Effect, Option, type S } from "effect-app"
3
3
  import { type GetEffectContext, type RPCContextMap } from "effect-app/client"
4
4
  import { type Tag } from "effect-app/Context"
5
5
  import { typedValuesOf } from "effect-app/utils"
6
- import { InfraLogger } from "../../logger.js"
7
- import { sort } from "./tsort.js"
6
+ import { InfraLogger } from "../../../logger.js"
7
+ import { type ContextTagWithDefault } from "../../layerUtils.js"
8
+ import { sort } from "../tsort.js"
8
9
 
9
10
  export type ContextWithLayer<
10
11
  Config,
11
- Id,
12
12
  Service,
13
- E,
14
- R,
15
- MakeE,
16
- MakeR,
17
- Tag extends string,
18
- Args extends [config: Config, headers: Record<string, string>],
19
- Dependencies extends any[]
20
- > =
21
- & Context.Tag<
22
- Id,
23
- { handle: (...args: Args) => Effect<Option<Context<Service>>, E, R>; _tag: Tag }
13
+ Error,
14
+ Dependencies,
15
+ Thing extends ContextTagWithDefault<
16
+ any,
17
+ {
18
+ handle: (
19
+ ...args: [config: Config, headers: Record<string, string>]
20
+ ) => Effect<Option<Context<Service>>, Error, unknown>
21
+ },
22
+ any,
23
+ unknown,
24
+ any
24
25
  >
26
+ > =
27
+ & Thing
25
28
  & {
26
- Default: Layer.Layer<Id, MakeE, MakeR>
27
29
  dependsOn?: Dependencies
28
30
  }
29
31
 
30
- export type AnyContextWithLayer<Config, Service, Error> =
31
- | ContextWithLayer<
32
+ export namespace ContextWithLayer {
33
+ export type Base<Config, Service, Error> = ContextWithLayer<
32
34
  Config,
33
- any,
34
35
  Service,
35
36
  Error,
36
37
  any,
37
- any,
38
- any,
39
- string,
40
- any,
41
- any
42
- >
43
- | ContextWithLayer<
44
- Config,
45
- any,
46
- Service,
47
- Error,
48
- never,
49
- any,
50
- never,
51
- any,
52
- any,
53
- any
54
- >
55
- | ContextWithLayer<
56
- Config,
57
- any,
58
- Service,
59
- Error,
60
- any,
61
- any,
62
- never,
63
- any,
64
- any,
65
- any
66
- >
67
- | ContextWithLayer<
68
- Config,
69
- any,
70
- Service,
71
- Error,
72
- never,
73
- any,
74
- any,
75
- any,
76
- any,
77
38
  any
78
39
  >
40
+ }
79
41
 
80
42
  export const mergeContexts = Effect.fnUntraced(
81
43
  function*<T extends readonly { maker: any; handle: Effect<Context<any>> }[]>(makers: T) {
@@ -116,7 +78,7 @@ export const mergeOptionContexts = Effect.fnUntraced(
116
78
  export const implementMiddleware = <T extends Record<string, RPCContextMap.Any>>() =>
117
79
  <
118
80
  TI extends {
119
- [K in keyof T]: AnyContextWithLayer<
81
+ [K in keyof T]: ContextWithLayer.Base<
120
82
  { [K in keyof T]?: T[K]["contextActivation"] },
121
83
  T[K]["service"],
122
84
  S.Schema.Type<T[K]["error"]>
@@ -142,13 +104,17 @@ export const implementMiddleware = <T extends Record<string, RPCContextMap.Any>>
142
104
  GetEffectContext<T, typeof config>
143
105
  >
144
106
  }
145
- ) as (
107
+ )
108
+ }) as unknown as Effect<
109
+ (
146
110
  config: { [K in keyof T]?: T[K]["contextActivation"] },
147
111
  headers: Record<string, string>
148
112
  ) => Effect.Effect<
149
113
  Context.Context<GetEffectContext<T, typeof config>>,
150
114
  Effect.Error<ReturnType<Tag.Service<TI[keyof TI]>["handle"]>>,
151
115
  Effect.Context<ReturnType<Tag.Service<TI[keyof TI]>["handle"]>>
152
- >
153
- })
116
+ >,
117
+ never,
118
+ Tag.Identifier<{ [K in keyof TI]: TI[K] }[keyof TI]>
119
+ >
154
120
  })
@@ -0,0 +1,38 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { type Array, Effect } from "effect-app"
3
+ import { type HttpHeaders, type HttpRouter } from "effect-app/http"
4
+ import { type ContextTagWithDefault } from "../../layerUtils.js"
5
+
6
+ export type GenericMiddlewareMaker = <A, E>(
7
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>,
8
+ moduleName: string
9
+ ) => (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>
10
+
11
+ export const genericMiddlewareMaker = <
12
+ T extends Array<
13
+ ContextTagWithDefault.Base<GenericMiddlewareMaker>
14
+ >
15
+ >(...middlewares: T): {
16
+ dependencies: { [K in keyof T]: T[K]["Default"] }
17
+ effect: Effect.Effect<GenericMiddlewareMaker>
18
+ } => {
19
+ return {
20
+ dependencies: middlewares.map((_) => _.Default),
21
+ effect: Effect.gen(function*() {
22
+ const middlewaresInstances = yield* Effect.all(middlewares)
23
+
24
+ return <A, E, R>(
25
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, R>,
26
+ moduleName: string
27
+ ) => {
28
+ return (input: any, headers: HttpHeaders.Headers) => {
29
+ let effect = handle
30
+ for (const middleware of (middlewaresInstances as any[]).toReversed()) {
31
+ effect = middleware(effect, moduleName)
32
+ }
33
+ return effect(input, headers)
34
+ }
35
+ }
36
+ })
37
+ } as any
38
+ }
@@ -0,0 +1,134 @@
1
+ import { Cause, Context, Effect, ParseResult } from "effect-app"
2
+ import { HttpHeaders, type HttpRouter, HttpServerRequest } from "effect-app/http"
3
+ import { pretty } from "effect-app/utils"
4
+ import { logError, reportError } from "../../../errorReporter.js"
5
+ import { InfraLogger } from "../../../logger.js"
6
+ import { RequestCacheLayers } from "../../routing.js"
7
+
8
+ const logRequestError = logError("Request")
9
+ const reportRequestError = reportError("Request")
10
+
11
+ export class DevMode extends Context.Reference<DevMode>()("DevMode", { defaultValue: () => false }) {}
12
+
13
+ export class RequestCacheMiddleware extends Effect.Service<RequestCacheMiddleware>()("RequestCacheMiddleware", {
14
+ effect: Effect.gen(function*() {
15
+ return <A, E>(
16
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>,
17
+ _moduleName: string
18
+ ) =>
19
+ Effect.fnUntraced(function*(input: any, headers: HttpHeaders.Headers) {
20
+ return yield* handle(input, headers).pipe(Effect.provide(RequestCacheLayers))
21
+ })
22
+ })
23
+ }) {}
24
+
25
+ export class ConfigureInterruptibility extends Effect.Service<ConfigureInterruptibility>()(
26
+ "ConfigureInterruptibility",
27
+ {
28
+ effect: Effect.gen(function*() {
29
+ return <A, E>(
30
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>,
31
+ _moduleName: string
32
+ ) =>
33
+ Effect.fnUntraced(function*(input: any, headers: HttpHeaders.Headers) {
34
+ return yield* handle(input, headers).pipe(
35
+ // TODO: make this depend on query/command, and consider if middleware also should be affected. right now it's not.
36
+ Effect.uninterruptible
37
+ )
38
+ })
39
+ })
40
+ }
41
+ ) {}
42
+
43
+ export class CaptureHttpHeadersAsRpcHeaders
44
+ extends Effect.Service<CaptureHttpHeadersAsRpcHeaders>()("CaptureHttpHeadersAsRpcHeaders", {
45
+ effect: Effect.gen(function*() {
46
+ return <A, E>(
47
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>,
48
+ _moduleName: string
49
+ ) =>
50
+ Effect.fnUntraced(function*(input: any, rpcHeaders: HttpHeaders.Headers) {
51
+ // merge in the request headers
52
+ // we should consider if we should merge them into rpc headers on the Protocol layer instead.
53
+ const httpReq = yield* HttpServerRequest.HttpServerRequest
54
+ const headers = HttpHeaders.merge(httpReq.headers, rpcHeaders)
55
+ return yield* handle(input, headers)
56
+ })
57
+ })
58
+ })
59
+ {}
60
+
61
+ export class MiddlewareLogger extends Effect.Service<MiddlewareLogger>()("MiddlewareLogger", {
62
+ effect: Effect.gen(function*() {
63
+ return <A, E>(
64
+ handle: (input: any, headers: HttpHeaders.Headers) => Effect.Effect<A, E, HttpRouter.HttpRouter.Provided>,
65
+ moduleName: string
66
+ ) =>
67
+ Effect.fnUntraced(function*(input: any, rpcHeaders: HttpHeaders.Headers) {
68
+ const devMode = yield* DevMode
69
+ // merge in the request headers
70
+ // we should consider if we should merge them into rpc headers on the Protocol layer instead.
71
+ const httpReq = yield* HttpServerRequest.HttpServerRequest
72
+ const headers = HttpHeaders.merge(httpReq.headers, rpcHeaders)
73
+
74
+ return yield* Effect
75
+ .annotateCurrentSpan(
76
+ "requestInput",
77
+ Object.entries(input).reduce((prev, [key, value]: [string, unknown]) => {
78
+ prev[key] = key === "password"
79
+ ? "<redacted>"
80
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
81
+ ? typeof value === "string" && value.length > 256
82
+ ? (value.substring(0, 253) + "...")
83
+ : value
84
+ : Array.isArray(value)
85
+ ? `Array[${value.length}]`
86
+ : value === null || value === undefined
87
+ ? `${value}`
88
+ : typeof value === "object" && value
89
+ ? `Object[${Object.keys(value).length}]`
90
+ : typeof value
91
+ return prev
92
+ }, {} as Record<string, string | number | boolean>)
93
+ )
94
+ .pipe(
95
+ // can't use andThen due to some being a function and effect
96
+ Effect.zipRight(handle(input, headers)),
97
+ // TODO: support ParseResult if the error channel of the request allows it.. but who would want that?
98
+ Effect.catchAll((_) => ParseResult.isParseError(_) ? Effect.die(_) : Effect.fail(_)),
99
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
100
+ Effect.tapDefect((cause) =>
101
+ Effect
102
+ .all([
103
+ reportRequestError(cause, {
104
+ action: `${moduleName}.${input._tag}`
105
+ }),
106
+ InfraLogger
107
+ .logError("Finished request", cause)
108
+ .pipe(Effect.annotateLogs({
109
+ action: `${moduleName}.${input._tag}`,
110
+ req: pretty(input),
111
+ headers: pretty(headers)
112
+ // resHeaders: pretty(
113
+ // Object
114
+ // .entries(headers)
115
+ // .reduce((prev, [key, value]) => {
116
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
117
+ // return prev
118
+ // }, {} as Record<string, any>)
119
+ // )
120
+ }))
121
+ ])
122
+ ),
123
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error"))
124
+ )
125
+ })
126
+ })
127
+ }) {}
128
+
129
+ export const DefaultGenericMiddlewares = [
130
+ RequestCacheMiddleware,
131
+ ConfigureInterruptibility,
132
+ CaptureHttpHeadersAsRpcHeaders,
133
+ MiddlewareLogger
134
+ ] as const
@@ -0,0 +1,7 @@
1
+ // codegen:start {preset: barrel, include: ./middleware/*.ts, nodir: false }
2
+ export * from "./middleware/ContextProvider.js"
3
+ export * from "./middleware/dynamic-middleware.js"
4
+ export * from "./middleware/DynamicMiddleware.js"
5
+ export * from "./middleware/generic-middleware.js"
6
+ export * from "./middleware/middleware.js"
7
+ // codegen:end