@effect-app/infra 2.70.2 → 2.71.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 +12 -0
- package/dist/Store/ContextMapContainer.d.ts +1 -1
- package/dist/api/internal/middlewares.js +1 -1
- package/dist/api/routing/DynamicMiddleware.d.ts +29 -19
- package/dist/api/routing/DynamicMiddleware.d.ts.map +1 -1
- package/dist/api/routing/DynamicMiddleware.js +37 -13
- package/dist/api/routing/utils.d.ts +7 -0
- package/dist/api/routing/utils.d.ts.map +1 -1
- package/dist/api/routing/utils.js +2 -1
- package/dist/api/routing.d.ts +47 -48
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +54 -67
- package/package.json +2 -2
- package/src/api/internal/middlewares.ts +1 -1
- package/src/api/routing/DynamicMiddleware.ts +178 -62
- package/src/api/routing/utils.ts +2 -0
- package/src/api/routing.ts +208 -169
- package/test/controller.test.ts +63 -108
- package/test/dist/controller.test.d.ts.map +1 -1
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
import {
|
|
5
|
-
import type { RPCContextMap } from "effect-app/client/req"
|
|
6
|
-
|
|
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"
|
|
7
6
|
import type * as EffectRequest from "effect/Request"
|
|
7
|
+
import { type LayersUtils } from "../routing.js"
|
|
8
|
+
|
|
9
|
+
export const MiddlewareMaker = Context.GenericTag<"MiddlewareMaker", any>("MiddlewareMaker")
|
|
8
10
|
|
|
9
|
-
export type RPCHandlerFactory<
|
|
11
|
+
export type RPCHandlerFactory<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR> = <
|
|
10
12
|
T extends {
|
|
11
|
-
config?: Partial<Record<keyof
|
|
13
|
+
config?: Partial<Record<keyof RequestContextMap, any>>
|
|
12
14
|
},
|
|
13
15
|
Req extends S.TaggedRequest.All,
|
|
14
|
-
|
|
16
|
+
HandlerR
|
|
15
17
|
>(
|
|
16
18
|
schema: T & S.Schema<Req, any, never>,
|
|
17
19
|
handler: (
|
|
18
20
|
request: Req,
|
|
19
21
|
headers: any
|
|
20
|
-
) => Effect.Effect<
|
|
22
|
+
) => Effect.Effect<
|
|
23
|
+
EffectRequest.Request.Success<Req>,
|
|
24
|
+
EffectRequest.Request.Error<Req>,
|
|
25
|
+
HandlerR
|
|
26
|
+
>,
|
|
21
27
|
moduleName?: string
|
|
22
28
|
) => (
|
|
23
29
|
req: Req,
|
|
@@ -25,75 +31,185 @@ export type RPCHandlerFactory<CTXMap extends Record<string, RPCContextMap.Any>>
|
|
|
25
31
|
) => Effect.Effect<
|
|
26
32
|
Request.Request.Success<Req>,
|
|
27
33
|
Request.Request.Error<Req>,
|
|
28
|
-
|
|
34
|
+
// the middleware will remove from HandlerR the dynamic context, but will also add the MiddlewareR
|
|
35
|
+
| MiddlewareR
|
|
36
|
+
// & S.Schema<Req, any, never> is useless here but useful when creating the middleware
|
|
37
|
+
| Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>
|
|
29
38
|
>
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
function makeRpcHandler<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR>() {
|
|
41
|
+
return (cb: RPCHandlerFactory<RequestContextMap, MiddlewareR>) => cb
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const makeMiddlewareLayer = <
|
|
45
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
46
|
+
MiddlewareR,
|
|
47
|
+
MakeMiddlewareR,
|
|
48
|
+
MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>,
|
|
49
|
+
ContextProviderId,
|
|
50
|
+
ContextProviderKey extends string,
|
|
51
|
+
ContextProviderA,
|
|
52
|
+
MakeContextProviderE,
|
|
53
|
+
MakeContextProviderR
|
|
54
|
+
>(
|
|
55
|
+
middleware: Middleware<
|
|
56
|
+
MiddlewareR,
|
|
57
|
+
RequestContextMap,
|
|
58
|
+
MakeMiddlewareR,
|
|
59
|
+
MiddlewareDependencies,
|
|
60
|
+
ContextProviderId,
|
|
61
|
+
ContextProviderKey,
|
|
62
|
+
ContextProviderA,
|
|
63
|
+
MakeContextProviderE,
|
|
64
|
+
MakeContextProviderR
|
|
65
|
+
>
|
|
66
|
+
) => {
|
|
67
|
+
if (!middleware.execute && !middleware.executeContextual) {
|
|
68
|
+
throw new Error("No execute or executeContextual provided in middleware")
|
|
69
|
+
}
|
|
70
|
+
const middlewareLayer = Layer
|
|
71
|
+
.effect(
|
|
72
|
+
MiddlewareMaker,
|
|
73
|
+
middleware.execute
|
|
74
|
+
? middleware.execute
|
|
75
|
+
: middleware.executeContextual!(makeRpcHandler<RequestContextMap, MiddlewareR>())
|
|
76
|
+
)
|
|
77
|
+
.pipe(middleware.dependencies ? Layer.provide(middleware.dependencies) as any : (_) => _)
|
|
78
|
+
|
|
79
|
+
return middlewareLayer as Layer.Layer<
|
|
80
|
+
"MiddlewareMaker",
|
|
81
|
+
never,
|
|
82
|
+
Exclude<MiddlewareR, LayersUtils.GetLayersSuccess<MiddlewareDependencies>>
|
|
83
|
+
>
|
|
84
|
+
}
|
|
85
|
+
export type ContextProviderShape<ContextProviderA> = Effect<Context.Context<ContextProviderA>, never, Scope>
|
|
33
86
|
|
|
34
87
|
export interface Middleware<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
88
|
+
MiddlewareR, // what the middlware requires to execute
|
|
89
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>, // what services will the middlware provide dynamically to the handler, or raise errors.
|
|
90
|
+
MakeMiddlewareR, // what the middlware requires to be constructed
|
|
91
|
+
MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>, // layers provided for the middlware to be constructed
|
|
92
|
+
//
|
|
93
|
+
// ContextProvider is a service that builds additional context for each request.
|
|
94
|
+
ContextProviderId, // it is the context provider itself
|
|
95
|
+
ContextProviderKey extends string, // tag for the context provider
|
|
96
|
+
ContextProviderA, // what the context provider provides
|
|
97
|
+
MakeContextProviderE, // what the context provider construction can fail with
|
|
98
|
+
MakeContextProviderR // what the context provider construction requires
|
|
44
99
|
> {
|
|
45
|
-
dependencies?:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
100
|
+
dependencies?: MiddlewareDependencies
|
|
101
|
+
contextProvider:
|
|
102
|
+
& Context.Tag<
|
|
103
|
+
ContextProviderId,
|
|
104
|
+
ContextProviderId & ContextProviderShape<ContextProviderA> & { _tag: ContextProviderKey }
|
|
105
|
+
>
|
|
106
|
+
& {
|
|
107
|
+
Default: Layer.Layer<ContextProviderId, MakeContextProviderE, MakeContextProviderR>
|
|
108
|
+
}
|
|
109
|
+
execute?: Effect<
|
|
110
|
+
RPCHandlerFactory<RequestContextMap, MiddlewareR>,
|
|
111
|
+
never,
|
|
112
|
+
MakeMiddlewareR
|
|
113
|
+
>
|
|
114
|
+
// better DX because types are contextually provided
|
|
115
|
+
executeContextual?: (
|
|
116
|
+
maker: (cb: RPCHandlerFactory<RequestContextMap, MiddlewareR>) => RPCHandlerFactory<RequestContextMap, MiddlewareR>
|
|
117
|
+
) => Effect<
|
|
118
|
+
RPCHandlerFactory<RequestContextMap, MiddlewareR>,
|
|
53
119
|
never,
|
|
54
|
-
|
|
120
|
+
MakeMiddlewareR
|
|
55
121
|
>
|
|
56
122
|
}
|
|
57
123
|
|
|
124
|
+
// identity factory for Middleware
|
|
125
|
+
export const makeMiddleware =
|
|
126
|
+
// by setting MiddlewareR and RequestContextMap beforehand, executeContextual contextual typing does not fuck up itself to anys
|
|
127
|
+
<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR>() =>
|
|
128
|
+
<M extends Middleware<MiddlewareR, RequestContextMap, any, NonEmptyArray<Layer.Layer.Any>, any, any, any, any, any>>(
|
|
129
|
+
content: M
|
|
130
|
+
): M => content
|
|
131
|
+
|
|
132
|
+
// it just provides the right types without cluttering the implementation with them
|
|
133
|
+
function makeRpcEffect<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR, ContextProviderA>() {
|
|
134
|
+
return (
|
|
135
|
+
cb: <
|
|
136
|
+
T extends {
|
|
137
|
+
config?: Partial<Record<keyof RequestContextMap, any>>
|
|
138
|
+
},
|
|
139
|
+
Req extends S.TaggedRequest.All,
|
|
140
|
+
HandlerR
|
|
141
|
+
>(
|
|
142
|
+
schema: T & S.Schema<Req, any, never>,
|
|
143
|
+
handler: (
|
|
144
|
+
request: Req,
|
|
145
|
+
headers: any
|
|
146
|
+
) => Effect.Effect<
|
|
147
|
+
EffectRequest.Request.Success<Req>,
|
|
148
|
+
EffectRequest.Request.Error<Req>,
|
|
149
|
+
HandlerR
|
|
150
|
+
>,
|
|
151
|
+
moduleName?: string
|
|
152
|
+
) => (
|
|
153
|
+
req: Req,
|
|
154
|
+
headers: any
|
|
155
|
+
) => Effect.Effect<
|
|
156
|
+
Request.Request.Success<Req>,
|
|
157
|
+
Request.Request.Error<Req>,
|
|
158
|
+
| Scope.Scope // the context provider may require a Scope to run
|
|
159
|
+
| Exclude<MiddlewareR, ContextProviderA> // for sure ContextProviderA is provided, so it can be removed from the MiddlewareR
|
|
160
|
+
| Exclude<
|
|
161
|
+
Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>,
|
|
162
|
+
ContextProviderA
|
|
163
|
+
> // it can also be removed from HandlerR
|
|
164
|
+
>
|
|
165
|
+
) => cb
|
|
166
|
+
}
|
|
167
|
+
|
|
58
168
|
export const makeRpc = <
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
169
|
+
MiddlewareR,
|
|
170
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
171
|
+
MakeMiddlewareR,
|
|
172
|
+
MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>,
|
|
173
|
+
ContextProviderId,
|
|
174
|
+
ContextProviderKey extends string,
|
|
175
|
+
ContextProviderA,
|
|
176
|
+
MakeContextProviderE,
|
|
177
|
+
MakeContextProviderR
|
|
68
178
|
>(
|
|
69
|
-
middleware: Middleware<
|
|
179
|
+
middleware: Middleware<
|
|
180
|
+
MiddlewareR,
|
|
181
|
+
RequestContextMap,
|
|
182
|
+
MakeMiddlewareR,
|
|
183
|
+
MiddlewareDependencies,
|
|
184
|
+
ContextProviderId,
|
|
185
|
+
ContextProviderKey,
|
|
186
|
+
ContextProviderA,
|
|
187
|
+
MakeContextProviderE,
|
|
188
|
+
MakeContextProviderR
|
|
189
|
+
>
|
|
70
190
|
) =>
|
|
71
191
|
Effect
|
|
72
192
|
.all({
|
|
73
|
-
|
|
74
|
-
|
|
193
|
+
middleware: MiddlewareMaker as Context.Tag<
|
|
194
|
+
"MiddlewareMaker",
|
|
195
|
+
RPCHandlerFactory<RequestContextMap, MiddlewareR>
|
|
196
|
+
>,
|
|
197
|
+
contextProvider: middleware.contextProvider // uses the middleware.contextProvider tag to get the context provider service
|
|
75
198
|
})
|
|
76
|
-
.pipe(Effect.map(({ contextProvider,
|
|
77
|
-
effect: <
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
request
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const h = execute(schema, handler, moduleName)
|
|
90
|
-
return (req: Req, headers: any) =>
|
|
91
|
-
Effect.gen(function*() {
|
|
92
|
-
const ctx = yield* contextProvider
|
|
93
|
-
return yield* h(req, headers).pipe(
|
|
94
|
-
Effect.provide(ctx),
|
|
95
|
-
Effect.uninterruptible // TODO: make this depend on query/command, and consider if middleware also should be affected or not.
|
|
199
|
+
.pipe(Effect.map(({ contextProvider, middleware }) => ({
|
|
200
|
+
effect: makeRpcEffect<RequestContextMap, MiddlewareR, ContextProviderA>()((schema, handler, moduleName) => {
|
|
201
|
+
const h = middleware(schema, handler, moduleName)
|
|
202
|
+
return (req, headers) =>
|
|
203
|
+
// the contextProvider is an Effect that builds the context for the request
|
|
204
|
+
contextProvider.pipe(
|
|
205
|
+
Effect.flatMap((ctx) =>
|
|
206
|
+
h(req, headers)
|
|
207
|
+
.pipe(
|
|
208
|
+
Effect.provide(ctx),
|
|
209
|
+
// TODO: make this depend on query/command, and consider if middleware also should be affected or not.
|
|
210
|
+
Effect.uninterruptible
|
|
211
|
+
)
|
|
96
212
|
)
|
|
97
|
-
|
|
98
|
-
}
|
|
213
|
+
)
|
|
214
|
+
})
|
|
99
215
|
})))
|
package/src/api/routing/utils.ts
CHANGED
|
@@ -38,3 +38,5 @@ export const determineMethod = (actionName: string, schema: Schema<any, any, any
|
|
|
38
38
|
if (patch.some((_) => actionName.startsWith(_))) return { _tag: "command", method: "PATCH" } as const
|
|
39
39
|
return { _tag: "command", method: "POST" } as const
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
export const isCommand = (method: ReturnType<typeof determineMethod>) => method._tag === "command"
|