@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.
@@ -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 { 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,
@@ -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
- any // smd
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
+ 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
- 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
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?: 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>,
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
- R
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
- 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
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<Context, CTXMap, R, Layers, CtxId, CtxTag, RRet, RErr, RCtx>
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
- execute: middleware.execute,
74
- contextProvider: middleware.contextProvider
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, 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.
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
  })))
@@ -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"