@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.
- package/CHANGELOG.md +17 -0
- package/dist/api/layerUtils.d.ts +22 -0
- package/dist/api/layerUtils.d.ts.map +1 -0
- package/dist/api/layerUtils.js +2 -0
- package/dist/api/routing/middleware/ContextProvider.d.ts +41 -0
- package/dist/api/routing/middleware/ContextProvider.d.ts.map +1 -0
- package/dist/api/routing/middleware/ContextProvider.js +27 -0
- package/dist/api/routing/middleware/DynamicMiddleware.d.ts +61 -0
- package/dist/api/routing/middleware/DynamicMiddleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/DynamicMiddleware.js +45 -0
- package/dist/api/routing/middleware/dynamic-middleware.d.ts +25 -0
- package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/dynamic-middleware.js +39 -0
- package/dist/api/routing/middleware/generic-middleware.d.ts +9 -0
- package/dist/api/routing/middleware/generic-middleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/generic-middleware.js +20 -0
- package/dist/api/routing/middleware/middleware.d.ts +28 -0
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/middleware.js +101 -0
- package/dist/api/routing/middleware.d.ts +6 -0
- package/dist/api/routing/middleware.d.ts.map +1 -0
- package/dist/api/routing/middleware.js +8 -0
- package/dist/api/routing.d.ts +18 -28
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +3 -3
- package/package.json +27 -7
- package/src/api/layerUtils.ts +33 -0
- package/src/api/routing/middleware/ContextProvider.ts +136 -0
- package/src/api/routing/middleware/DynamicMiddleware.ts +317 -0
- package/src/api/routing/{dynamic-middleware.ts → middleware/dynamic-middleware.ts} +29 -63
- package/src/api/routing/middleware/generic-middleware.ts +38 -0
- package/src/api/routing/middleware/middleware.ts +134 -0
- package/src/api/routing/middleware.ts +7 -0
- package/src/api/routing.ts +37 -56
- package/test/controller.test.ts +132 -15
- package/test/dist/controller.test.d.ts.map +1 -1
- package/dist/api/routing/DynamicMiddleware.d.ts +0 -104
- package/dist/api/routing/DynamicMiddleware.d.ts.map +0 -1
- package/dist/api/routing/DynamicMiddleware.js +0 -122
- package/dist/api/routing/dynamic-middleware.d.ts +0 -24
- package/dist/api/routing/dynamic-middleware.d.ts.map +0 -1
- package/dist/api/routing/dynamic-middleware.js +0 -39
- 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,
|
|
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 "
|
|
7
|
-
import {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
31
|
-
|
|
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]:
|
|
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
|
-
)
|
|
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
|