@effect-app/infra 2.70.2 → 2.72.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.
@@ -1,99 +1,208 @@
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 { type Array, type Context, Effect, type Layer, type Request, type S, type Scope } from "effect-app"
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<CTXMap extends Record<string, RPCContextMap.Any>> = <
11
+ export type RPCHandlerFactory<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR> = <
10
12
  T extends {
11
- config?: Partial<Record<keyof CTXMap, any>>
13
+ config?: Partial<Record<keyof RequestContextMap, any>>
12
14
  },
13
15
  Req extends S.TaggedRequest.All,
14
- R
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<EffectRequest.Request.Success<Req>, EffectRequest.Request.Error<Req>, R>,
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,
24
30
  headers: any
25
31
  ) => Effect.Effect<
26
32
  Request.Request.Success<Req>,
27
- Request.Request.Error<Req>,
28
- any // smd
33
+ Request.Request.Error<Req> | RequestContextMapErrors<RequestContextMap>,
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
- export type ContextProviderOut<RRet> = Effect<Context.Context<RRet>, never, Scope>
32
- export type ContextProviderShape<RRet> = ContextProviderOut<RRet>
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
+ const middlewareLayer = Layer
68
+ .effect(
69
+ MiddlewareMaker,
70
+ middleware.execute!(makeRpcHandler<RequestContextMap, MiddlewareR>())
71
+ )
72
+ .pipe(middleware.dependencies ? Layer.provide(middleware.dependencies) as any : (_) => _)
73
+
74
+ return middlewareLayer as Layer.Layer<
75
+ "MiddlewareMaker",
76
+ never,
77
+ Exclude<MiddlewareR, LayersUtils.GetLayersSuccess<MiddlewareDependencies>>
78
+ >
79
+ }
80
+ export type ContextProviderShape<ContextProviderA> = Effect<Context.Context<ContextProviderA>, never, Scope>
33
81
 
34
82
  export interface Middleware<
35
- MiddlewareContext,
36
- CTXMap extends Record<string, RPCContextMap.Any>,
37
- R,
38
- Layers extends Array<Layer.Layer.Any>,
39
- CtxId,
40
- CtxTag extends string,
41
- RRet,
42
- RErr,
43
- RCtx
83
+ MiddlewareR, // what the middlware requires to execute
84
+ RequestContextMap extends Record<string, RPCContextMap.Any>, // what services will the middlware provide dynamically to the handler, or raise errors.
85
+ MakeMiddlewareR, // what the middlware requires to be constructed
86
+ MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>, // layers provided for the middlware to be constructed
87
+ //
88
+ // ContextProvider is a service that builds additional context for each request.
89
+ ContextProviderId, // it is the context provider itself
90
+ ContextProviderKey extends string, // tag for the context provider
91
+ ContextProviderA, // what the context provider provides
92
+ MakeContextProviderE, // what the context provider construction can fail with
93
+ MakeContextProviderR // what the context provider construction requires
44
94
  > {
45
- dependencies?: Layers
46
- contextMap: CTXMap
47
- context: MiddlewareContext
48
- contextProvider: Context.Tag<CtxId, CtxId & ContextProviderShape<RRet> & { _tag: CtxTag }> & {
49
- Default: Layer.Layer<CtxId, RErr, RCtx>
50
- }
51
- execute: Effect<
52
- RPCHandlerFactory<CTXMap>,
95
+ dependencies?: MiddlewareDependencies
96
+ contextProvider:
97
+ & Context.Tag<
98
+ ContextProviderId,
99
+ ContextProviderId & ContextProviderShape<ContextProviderA> & { _tag: ContextProviderKey }
100
+ >
101
+ & {
102
+ Default: Layer.Layer<ContextProviderId, MakeContextProviderE, MakeContextProviderR>
103
+ }
104
+ execute: (
105
+ maker: (cb: RPCHandlerFactory<RequestContextMap, MiddlewareR>) => RPCHandlerFactory<RequestContextMap, MiddlewareR>
106
+ ) => Effect<
107
+ RPCHandlerFactory<RequestContextMap, MiddlewareR>,
53
108
  never,
54
- R
109
+ MakeMiddlewareR
55
110
  >
56
111
  }
57
112
 
113
+ export type RequestContextMapErrors<RequestContextMap extends Record<string, RPCContextMap.Any>> = S.Schema.Type<
114
+ RequestContextMap[keyof RequestContextMap]["error"]
115
+ >
116
+
117
+ // identity factory for Middleware
118
+ export const makeMiddleware =
119
+ // by setting MiddlewareR and RequestContextMap beforehand, execute contextual typing does not fuck up itself to anys
120
+ <RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR>() =>
121
+ <M extends Middleware<MiddlewareR, RequestContextMap, any, NonEmptyArray<Layer.Layer.Any>, any, any, any, any, any>>(
122
+ content: M
123
+ ): M => content
124
+
125
+ // it just provides the right types without cluttering the implementation with them
126
+ function makeRpcEffect<RequestContextMap extends Record<string, RPCContextMap.Any>, MiddlewareR, ContextProviderA>() {
127
+ return (
128
+ cb: <
129
+ T extends {
130
+ config?: Partial<Record<keyof RequestContextMap, any>>
131
+ },
132
+ Req extends S.TaggedRequest.All,
133
+ HandlerR
134
+ >(
135
+ schema: T & S.Schema<Req, any, never>,
136
+ handler: (
137
+ request: Req,
138
+ headers: any
139
+ ) => Effect.Effect<
140
+ EffectRequest.Request.Success<Req>,
141
+ EffectRequest.Request.Error<Req>,
142
+ HandlerR
143
+ >,
144
+ moduleName?: string
145
+ ) => (
146
+ req: Req,
147
+ headers: any
148
+ ) => Effect.Effect<
149
+ Request.Request.Success<Req>,
150
+ Request.Request.Error<Req> | RequestContextMapErrors<RequestContextMap>,
151
+ | Scope.Scope // the context provider may require a Scope to run
152
+ | Exclude<MiddlewareR, ContextProviderA> // for sure ContextProviderA is provided, so it can be removed from the MiddlewareR
153
+ | Exclude<
154
+ Exclude<HandlerR, GetEffectContext<RequestContextMap, (T & S.Schema<Req, any, never>)["config"]>>,
155
+ ContextProviderA
156
+ > // it can also be removed from HandlerR
157
+ >
158
+ ) => cb
159
+ }
160
+
58
161
  export const makeRpc = <
59
- Context,
60
- CTXMap extends Record<string, RPCContextMap.Any>,
61
- R,
62
- Layers extends Array<Layer.Layer.Any>,
63
- CtxId,
64
- CtxTag extends string,
65
- RRet,
66
- RErr,
67
- RCtx
162
+ MiddlewareR,
163
+ RequestContextMap extends Record<string, RPCContextMap.Any>,
164
+ MakeMiddlewareR,
165
+ MiddlewareDependencies extends NonEmptyArray<Layer.Layer.Any>,
166
+ ContextProviderId,
167
+ ContextProviderKey extends string,
168
+ ContextProviderA,
169
+ MakeContextProviderE,
170
+ MakeContextProviderR
68
171
  >(
69
- middleware: Middleware<Context, CTXMap, R, Layers, CtxId, CtxTag, RRet, RErr, RCtx>
172
+ middleware: Middleware<
173
+ MiddlewareR,
174
+ RequestContextMap,
175
+ MakeMiddlewareR,
176
+ MiddlewareDependencies,
177
+ ContextProviderId,
178
+ ContextProviderKey,
179
+ ContextProviderA,
180
+ MakeContextProviderE,
181
+ MakeContextProviderR
182
+ >
70
183
  ) =>
71
184
  Effect
72
185
  .all({
73
- execute: middleware.execute,
74
- contextProvider: middleware.contextProvider
186
+ middleware: MiddlewareMaker as Context.Tag<
187
+ "MiddlewareMaker",
188
+ RPCHandlerFactory<RequestContextMap, MiddlewareR>
189
+ >,
190
+ contextProvider: middleware.contextProvider // uses the middleware.contextProvider tag to get the context provider service
75
191
  })
76
- .pipe(Effect.map(({ contextProvider, execute }) => ({
77
- effect: <T extends { config?: Partial<Record<keyof CTXMap, any>> }, Req extends S.TaggedRequest.All, R>(
78
- schema: T & S.Schema<Req, any, never>,
79
- handler: (
80
- request: Req,
81
- headers: any
82
- ) => Effect.Effect<
83
- EffectRequest.Request.Success<Req>,
84
- EffectRequest.Request.Error<Req>,
85
- R
86
- >,
87
- moduleName?: string
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.
192
+ .pipe(Effect.map(({ contextProvider, middleware }) => ({
193
+ effect: makeRpcEffect<RequestContextMap, MiddlewareR, ContextProviderA>()((schema, handler, moduleName) => {
194
+ const h = middleware(schema, handler, moduleName)
195
+ return (req, headers) =>
196
+ // the contextProvider is an Effect that builds the context for the request
197
+ contextProvider.pipe(
198
+ Effect.flatMap((ctx) =>
199
+ h(req, headers)
200
+ .pipe(
201
+ Effect.provide(ctx),
202
+ // TODO: make this depend on query/command, and consider if middleware also should be affected or not.
203
+ Effect.uninterruptible
204
+ )
96
205
  )
97
- })
98
- }
206
+ )
207
+ })
99
208
  })))
@@ -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"