@effect-app/infra 2.93.0 → 3.0.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 +22 -0
- package/dist/Emailer/service.d.ts +1 -1
- package/dist/MainFiberSet.d.ts +1 -1
- package/dist/Operations.d.ts +1 -1
- package/dist/RequestFiberSet.d.ts +1 -1
- package/dist/Store/service.d.ts +2 -2
- package/dist/adapters/SQL/Model.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts +2 -2
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +13 -10
- package/dist/adapters/memQueue.d.ts +1 -1
- package/dist/api/ContextProvider.d.ts +3 -3
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +1 -1
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +6 -6
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/RpcMiddleware.d.ts +4 -15
- package/dist/api/routing/middleware/RpcMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/RpcMiddleware.js +3 -4
- package/dist/api/routing/middleware/RpcMiddlewareX.d.ts +18 -0
- package/dist/api/routing/middleware/RpcMiddlewareX.d.ts.map +1 -0
- package/dist/api/routing/middleware/RpcMiddlewareX.js +16 -0
- package/dist/api/routing/middleware/generic-middleware.d.ts +22 -8
- package/dist/api/routing/middleware/generic-middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/generic-middleware.js +59 -62
- package/dist/api/routing/middleware/middleware-api.d.ts +51 -32
- package/dist/api/routing/middleware/middleware-api.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware-api.js +54 -24
- package/dist/api/routing/middleware/middleware-native.d.ts +23 -0
- package/dist/api/routing/middleware/middleware-native.d.ts.map +1 -0
- package/dist/api/routing/middleware/middleware-native.js +19 -0
- package/dist/api/routing/middleware/middleware.d.ts +7 -27
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +73 -88
- package/dist/api/routing/middleware.d.ts +1 -0
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +2 -1
- package/dist/api/routing.d.ts +1 -4
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +8 -8
- package/package.json +11 -3
- package/src/adapters/ServiceBus.ts +7 -5
- package/src/api/ContextProvider.ts +9 -7
- package/src/api/routing/middleware/RouterMiddleware.ts +9 -8
- package/src/api/routing/middleware/RpcMiddleware.ts +4 -51
- package/src/api/routing/middleware/RpcMiddlewareX.ts +70 -0
- package/src/api/routing/middleware/generic-middleware.ts +142 -107
- package/src/api/routing/middleware/middleware-api.ts +239 -61
- package/src/api/routing/middleware/middleware-native.ts +23 -0
- package/src/api/routing/middleware/middleware.ts +37 -40
- package/src/api/routing/middleware.ts +1 -0
- package/src/api/routing.ts +7 -13
- package/test/contextProvider.test.ts +4 -4
- package/test/controller.test.ts +41 -16
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/fixtures.d.ts +48 -32
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +76 -47
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -0
- package/test/fixtures.ts +71 -17
- package/test/requires.test.ts +15 -50
- package/test/rpc-multi-middleware.test.ts +134 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Rpc, type RpcGroup, type RpcMiddleware, type RpcSchema } from "@effect/rpc"
|
|
2
3
|
import { type AnyWithProps } from "@effect/rpc/Rpc"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { type HandlersFrom } from "@effect/rpc/RpcGroup"
|
|
5
|
+
import { Tag } from "@effect/rpc/RpcMiddleware"
|
|
6
|
+
import { Context, type Effect, Layer, type NonEmptyArray, type NonEmptyReadonlyArray, S, type Schema, type Scope, type Stream } from "effect-app"
|
|
7
|
+
import { type GetContextConfig, type GetEffectContext, type RPCContextMap } from "effect-app/client"
|
|
8
|
+
import { typedValuesOf } from "effect-app/utils"
|
|
9
|
+
import { type ReadonlyMailbox } from "effect/Mailbox"
|
|
5
10
|
import { type TypeTestId } from "../../routing.js"
|
|
6
|
-
import { type MiddlewareMaker, middlewareMaker } from "./generic-middleware.js"
|
|
7
|
-
import { type AnyDynamic, type RpcDynamic,
|
|
11
|
+
import { type MiddlewareMaker, middlewareMaker, type RequestContextTag } from "./generic-middleware.js"
|
|
12
|
+
import { type AnyDynamic, type RpcDynamic, type TagClassAny } from "./RpcMiddleware.js"
|
|
8
13
|
|
|
9
14
|
/** Adapter used when setting the dynamic prop on a middleware implementation */
|
|
10
15
|
export const contextMap = <
|
|
@@ -15,26 +20,27 @@ export const contextMap = <
|
|
|
15
20
|
settings: { service: rcm[key]!["service"] } as RequestContextMap[Key]
|
|
16
21
|
})
|
|
17
22
|
|
|
23
|
+
const tag = Context.GenericTag("RequestContextConfig")
|
|
18
24
|
/** Retrieves RequestContextConfig out of the RPC annotations */
|
|
19
25
|
export const getConfig = <
|
|
20
26
|
RequestContextMap extends Record<string, RPCContextMap.Any>
|
|
21
27
|
>() =>
|
|
22
28
|
(rpc: AnyWithProps): GetContextConfig<RequestContextMap> => {
|
|
23
|
-
return Context.
|
|
29
|
+
return Context.getOrElse(rpc.annotations, tag as any, () => ({}))
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
// the following implements sort of builder pattern
|
|
27
33
|
// we support both sideways and upwards elimination of dependencies
|
|
28
34
|
|
|
29
35
|
// it's for dynamic middlewares
|
|
30
|
-
type GetDependsOnKeys<MW extends MiddlewareMaker> = MW extends { dependsOn: NonEmptyReadonlyArray<TagClassAny> } ? {
|
|
36
|
+
type GetDependsOnKeys<MW extends MiddlewareMaker.Any> = MW extends { dependsOn: NonEmptyReadonlyArray<TagClassAny> } ? {
|
|
31
37
|
[K in keyof MW["dependsOn"]]: MW["dependsOn"][K] extends AnyDynamic ? MW["dependsOn"][K]["dynamic"]["key"]
|
|
32
38
|
: never
|
|
33
39
|
}[keyof MW["dependsOn"]]
|
|
34
40
|
: never
|
|
35
41
|
|
|
36
42
|
type FilterInDynamicMiddlewares<
|
|
37
|
-
MWs extends ReadonlyArray<MiddlewareMaker>,
|
|
43
|
+
MWs extends ReadonlyArray<MiddlewareMaker.Any>,
|
|
38
44
|
RequestContextMap extends Record<string, RPCContextMap.Any>
|
|
39
45
|
> = {
|
|
40
46
|
[K in keyof MWs]: MWs[K] extends { dynamic: RpcDynamic<any, RequestContextMap[keyof RequestContextMap]> } ? MWs[K]
|
|
@@ -46,13 +52,13 @@ type RecursiveHandleMWsSideways<
|
|
|
46
52
|
R extends {
|
|
47
53
|
rcm: Record<string, RPCContextMap.Any>
|
|
48
54
|
provided: keyof R["rcm"] // that's fine
|
|
49
|
-
middlewares: ReadonlyArray<MiddlewareMaker>
|
|
55
|
+
middlewares: ReadonlyArray<MiddlewareMaker.Any>
|
|
50
56
|
dmp: any
|
|
51
57
|
middlewareR: any
|
|
52
58
|
}
|
|
53
59
|
> = MWs extends [
|
|
54
|
-
infer F extends MiddlewareMaker,
|
|
55
|
-
...infer Rest extends ReadonlyArray<MiddlewareMaker>
|
|
60
|
+
infer F extends MiddlewareMaker.Any,
|
|
61
|
+
...infer Rest extends ReadonlyArray<MiddlewareMaker.Any>
|
|
56
62
|
] ? RecursiveHandleMWsSideways<Rest, {
|
|
57
63
|
rcm: R["rcm"]
|
|
58
64
|
// when one dynamic middleware depends on another, subtract the key to enforce the dependency to be provided after
|
|
@@ -76,11 +82,36 @@ type RecursiveHandleMWsSideways<
|
|
|
76
82
|
export interface BuildingMiddleware<
|
|
77
83
|
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
78
84
|
Provided extends keyof RequestContextMap,
|
|
79
|
-
Middlewares extends ReadonlyArray<MiddlewareMaker>,
|
|
85
|
+
Middlewares extends ReadonlyArray<MiddlewareMaker.Any>,
|
|
80
86
|
DynamicMiddlewareProviders,
|
|
81
87
|
out MiddlewareR extends { _tag: string } = never
|
|
82
88
|
> {
|
|
83
|
-
|
|
89
|
+
rpc: <
|
|
90
|
+
const Tag extends string,
|
|
91
|
+
Payload extends Schema.Schema.Any | Schema.Struct.Fields = typeof Schema.Void,
|
|
92
|
+
Success extends Schema.Schema.Any = typeof Schema.Void,
|
|
93
|
+
Error extends Schema.Schema.All = typeof Schema.Never,
|
|
94
|
+
const Stream extends boolean = false,
|
|
95
|
+
Config extends GetContextConfig<RequestContextMap> = {}
|
|
96
|
+
>(tag: Tag, options?: {
|
|
97
|
+
readonly payload?: Payload
|
|
98
|
+
readonly success?: Success
|
|
99
|
+
readonly error?: Error
|
|
100
|
+
readonly stream?: Stream
|
|
101
|
+
readonly config?: Config
|
|
102
|
+
readonly primaryKey?: [Payload] extends [Schema.Struct.Fields]
|
|
103
|
+
? ((payload: Schema.Simplify<Schema.Struct.Type<NoInfer<Payload>>>) => string)
|
|
104
|
+
: never
|
|
105
|
+
}) =>
|
|
106
|
+
& Rpc.Rpc<
|
|
107
|
+
Tag,
|
|
108
|
+
Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
|
|
109
|
+
Stream extends true ? RpcSchema.Stream<Success, Error> : Success,
|
|
110
|
+
Stream extends true ? typeof Schema.Never : Error
|
|
111
|
+
>
|
|
112
|
+
& { readonly config: Config }
|
|
113
|
+
|
|
114
|
+
middleware<MWs extends NonEmptyArray<MiddlewareMaker.Any>>(
|
|
84
115
|
...mw: MWs
|
|
85
116
|
): RecursiveHandleMWsSideways<MWs, {
|
|
86
117
|
rcm: RequestContextMap
|
|
@@ -91,7 +122,7 @@ export interface BuildingMiddleware<
|
|
|
91
122
|
}> extends infer Res extends {
|
|
92
123
|
rcm: RequestContextMap
|
|
93
124
|
provided: keyof RequestContextMap
|
|
94
|
-
middlewares: ReadonlyArray<MiddlewareMaker>
|
|
125
|
+
middlewares: ReadonlyArray<MiddlewareMaker.Any>
|
|
95
126
|
dmp: any
|
|
96
127
|
middlewareR: any
|
|
97
128
|
} ? MiddlewaresBuilder<
|
|
@@ -113,7 +144,7 @@ export interface BuildingMiddleware<
|
|
|
113
144
|
export type MiddlewaresBuilder<
|
|
114
145
|
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
115
146
|
Provided extends keyof RequestContextMap = never,
|
|
116
|
-
Middlewares extends ReadonlyArray<MiddlewareMaker> = [],
|
|
147
|
+
Middlewares extends ReadonlyArray<MiddlewareMaker.Any> = [],
|
|
117
148
|
DynamicMiddlewareProviders = unknown,
|
|
118
149
|
MiddlewareR extends { _tag: string } = never
|
|
119
150
|
> =
|
|
@@ -126,50 +157,20 @@ export type MiddlewaresBuilder<
|
|
|
126
157
|
>
|
|
127
158
|
& // keyof Omit<RequestContextMap, Provided> extends never is true when all the dynamic middlewares are provided
|
|
128
159
|
// MiddlewareR is never when all the required services from generic & dynamic middlewares are provided
|
|
129
|
-
(keyof Omit<RequestContextMap, Provided> extends never ? [MiddlewareR] extends [never] ?
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Middlewares
|
|
133
|
-
>
|
|
160
|
+
(keyof Omit<RequestContextMap, Provided> extends never ? [MiddlewareR] extends [never] ? MiddlewareMaker<
|
|
161
|
+
RequestContextMap,
|
|
162
|
+
Middlewares
|
|
134
163
|
>
|
|
135
164
|
: {}
|
|
136
165
|
: {})
|
|
137
166
|
|
|
138
|
-
export const makeMiddleware: <
|
|
139
|
-
RequestContextMap extends Record<string, RPCContextMap.Any>
|
|
140
|
-
>(rcm: RequestContextMap) => MiddlewaresBuilder<RequestContextMap> = (rcm) => {
|
|
141
|
-
let allMiddleware: MiddlewareMaker[] = []
|
|
142
|
-
const it = {
|
|
143
|
-
middleware: (...middlewares: any[]) => {
|
|
144
|
-
for (const mw of middlewares) {
|
|
145
|
-
// recall that we run middlewares in reverse order
|
|
146
|
-
allMiddleware = [mw, ...allMiddleware]
|
|
147
|
-
}
|
|
148
|
-
return allMiddleware.filter((m) => !!m.dynamic).length !== Object.keys(rcm).length
|
|
149
|
-
// for sure, until all the dynamic middlewares are provided it's non sensical to call makeMiddlewareBasic
|
|
150
|
-
? it
|
|
151
|
-
// actually, we don't know yet if MiddlewareR is never, but we can't easily check it at runtime
|
|
152
|
-
: Object.assign(makeMiddlewareBasic<any, any>(rcm, ...allMiddleware), it)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return it as any
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
export interface MiddlewareMakerId {
|
|
160
|
-
readonly _id: unique symbol
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// TODO: actually end up with [Tag<A, A>, Tag<B, B>, ...]
|
|
164
|
-
type MakeTags<A> = Context.Tag<A, A>
|
|
165
|
-
|
|
166
167
|
const makeMiddlewareBasic =
|
|
167
168
|
// by setting RequestContextMap beforehand, execute contextual typing does not fuck up itself to anys
|
|
168
169
|
<
|
|
169
170
|
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
170
|
-
MiddlewareProviders extends ReadonlyArray<MiddlewareMaker>
|
|
171
|
+
MiddlewareProviders extends ReadonlyArray<MiddlewareMaker.Any>
|
|
171
172
|
>(
|
|
172
|
-
|
|
173
|
+
rcm: RequestContextMap,
|
|
173
174
|
...make: MiddlewareProviders
|
|
174
175
|
) => {
|
|
175
176
|
// reverse middlewares and wrap one after the other
|
|
@@ -201,26 +202,203 @@ const makeMiddlewareBasic =
|
|
|
201
202
|
],
|
|
202
203
|
provides: (provides.length > 0
|
|
203
204
|
? provides
|
|
204
|
-
: undefined) as unknown as MiddlewareMaker.ManyProvided<MiddlewareProviders> extends never ? never
|
|
205
|
-
MakeTags<MiddlewareMaker.ManyProvided<MiddlewareProviders
|
|
206
|
-
],
|
|
205
|
+
: undefined) as unknown as MiddlewareMaker.ManyProvided<MiddlewareProviders> extends never ? never
|
|
206
|
+
: MakeTags<MiddlewareMaker.ManyProvided<MiddlewareProviders>>,
|
|
207
207
|
wrap: true
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const layer = Layer
|
|
211
|
+
.scoped(
|
|
212
|
+
MiddlewareMaker,
|
|
213
|
+
middleware as Effect<
|
|
212
214
|
any, // TODO: why ?
|
|
213
|
-
Effect.Error<typeof middleware
|
|
214
|
-
Effect.Context<typeof middleware
|
|
215
|
+
Effect.Error<typeof middleware>,
|
|
216
|
+
Effect.Context<typeof middleware>
|
|
215
217
|
>
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
+
)
|
|
218
219
|
|
|
219
220
|
// add to the tag a default implementation
|
|
220
221
|
return Object.assign(MiddlewareMaker, {
|
|
222
|
+
layer,
|
|
221
223
|
// tag to be used to retrieve the RequestContextConfig from RPC annotations
|
|
222
224
|
requestContext: Context.GenericTag<"RequestContextConfig", GetContextConfig<RequestContextMap>>(
|
|
223
225
|
"RequestContextConfig"
|
|
224
|
-
)
|
|
226
|
+
),
|
|
227
|
+
requestContextMap: rcm
|
|
225
228
|
})
|
|
226
229
|
}
|
|
230
|
+
|
|
231
|
+
export const makeMiddleware = <
|
|
232
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>
|
|
233
|
+
>(rcm: RequestContextMap): MiddlewaresBuilder<RequestContextMap> => {
|
|
234
|
+
let allMiddleware: MiddlewareMaker.Any[] = []
|
|
235
|
+
const requestContext = Context.GenericTag<"RequestContextConfig", GetContextConfig<RequestContextMap>>(
|
|
236
|
+
"RequestContextConfig"
|
|
237
|
+
)
|
|
238
|
+
const it = {
|
|
239
|
+
// rpc with config
|
|
240
|
+
rpc: <
|
|
241
|
+
const Tag extends string,
|
|
242
|
+
Payload extends Schema.Schema.Any | Schema.Struct.Fields = typeof Schema.Void,
|
|
243
|
+
Success extends Schema.Schema.Any = typeof Schema.Void,
|
|
244
|
+
Error extends Schema.Schema.All = typeof Schema.Never,
|
|
245
|
+
const Stream extends boolean = false,
|
|
246
|
+
Config extends GetContextConfig<RequestContextMap> = {}
|
|
247
|
+
>(tag: Tag, options?: {
|
|
248
|
+
readonly payload?: Payload
|
|
249
|
+
readonly success?: Success
|
|
250
|
+
readonly error?: Error
|
|
251
|
+
readonly stream?: Stream
|
|
252
|
+
readonly config?: Config
|
|
253
|
+
readonly primaryKey?: [Payload] extends [Schema.Struct.Fields]
|
|
254
|
+
? ((payload: Schema.Simplify<Schema.Struct.Type<NoInfer<Payload>>>) => string)
|
|
255
|
+
: never
|
|
256
|
+
}):
|
|
257
|
+
& Rpc.Rpc<
|
|
258
|
+
Tag,
|
|
259
|
+
Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
|
|
260
|
+
// TODO: enhance `Error`. type based on middleware config.
|
|
261
|
+
Stream extends true ? RpcSchema.Stream<Success, Error> : Success,
|
|
262
|
+
Stream extends true ? typeof Schema.Never : Error
|
|
263
|
+
>
|
|
264
|
+
& { config: Config } =>
|
|
265
|
+
{
|
|
266
|
+
const config = options?.config ?? {} as Config
|
|
267
|
+
|
|
268
|
+
// based on the config, we must enhance (union) or set failures.
|
|
269
|
+
// TODO: we should only include errors that are relevant based on the middleware config.ks
|
|
270
|
+
const error = options?.error
|
|
271
|
+
const errors = typedValuesOf(rcm).map((_) => _.error).filter((_) => _ && _ !== S.Never) // TODO: only the errors relevant based on config
|
|
272
|
+
const newError = error ? S.Union(error, ...errors) : S.Union(...errors)
|
|
273
|
+
|
|
274
|
+
const rpc = Rpc.make(tag, { ...options, error: newError }) as any
|
|
275
|
+
|
|
276
|
+
return Object.assign(rpc.annotate(requestContext, config), { config })
|
|
277
|
+
},
|
|
278
|
+
middleware: (...middlewares: any[]) => {
|
|
279
|
+
for (const mw of middlewares) {
|
|
280
|
+
// recall that we run middlewares in reverse order
|
|
281
|
+
allMiddleware = [mw, ...allMiddleware]
|
|
282
|
+
}
|
|
283
|
+
return allMiddleware.filter((m) => !!m.dynamic).length !== Object.keys(rcm).length
|
|
284
|
+
// for sure, until all the dynamic middlewares are provided it's non sensical to call makeMiddlewareBasic
|
|
285
|
+
? it
|
|
286
|
+
// actually, we don't know yet if MiddlewareR is never, but we can't easily check it at runtime
|
|
287
|
+
: Object.assign(makeMiddlewareBasic<any, any>(rcm, ...allMiddleware), it)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return it as any
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// customised version of Rpc.AddMiddleware, so that we don't loose the `config`...
|
|
294
|
+
// not needed if there's official support in Rpc.Rpc.
|
|
295
|
+
export type AddMiddleware<R extends Rpc.Any, Middleware extends RpcMiddleware.TagClassAny> = R extends Rpc.Rpc<
|
|
296
|
+
infer _Tag,
|
|
297
|
+
infer _Payload,
|
|
298
|
+
infer _Success,
|
|
299
|
+
infer _Error,
|
|
300
|
+
infer _Middleware
|
|
301
|
+
> ?
|
|
302
|
+
& Rpc.Rpc<
|
|
303
|
+
_Tag,
|
|
304
|
+
_Payload,
|
|
305
|
+
_Success,
|
|
306
|
+
_Error,
|
|
307
|
+
_Middleware | Middleware
|
|
308
|
+
>
|
|
309
|
+
& { readonly config: R extends { readonly config: infer _C } ? _C : never }
|
|
310
|
+
: never
|
|
311
|
+
|
|
312
|
+
// alternatively consider group.serverMiddleware? hmmm
|
|
313
|
+
export const middlewareGroup = <
|
|
314
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
315
|
+
Middleware extends Context.Tag<MiddlewareMakerId, any> & RpcMiddleware.TagClassAny & {
|
|
316
|
+
readonly requestContext: RequestContextTag<RequestContextMap>
|
|
317
|
+
readonly requestContextMap: RequestContextMap
|
|
318
|
+
}
|
|
319
|
+
>(
|
|
320
|
+
middleware: Middleware
|
|
321
|
+
) =>
|
|
322
|
+
<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>) => {
|
|
323
|
+
type RN = AddMiddleware<R, typeof middleware>
|
|
324
|
+
const middlewaredGroup = group.middleware(middleware) as unknown as RpcGroup.RpcGroup<RN>
|
|
325
|
+
const toLayerOriginal = middlewaredGroup.toLayer.bind(middlewaredGroup)
|
|
326
|
+
return Object.assign(middlewaredGroup, {
|
|
327
|
+
toLayerDynamic: <
|
|
328
|
+
Handlers extends HandlersFrom<RN>,
|
|
329
|
+
EX = never,
|
|
330
|
+
RX = never
|
|
331
|
+
>(
|
|
332
|
+
build:
|
|
333
|
+
| Handlers
|
|
334
|
+
| Effect.Effect<Handlers, EX, RX>
|
|
335
|
+
): Layer.Layer<
|
|
336
|
+
Rpc.ToHandler<RN>,
|
|
337
|
+
EX,
|
|
338
|
+
| Exclude<RX, Scope>
|
|
339
|
+
| HandlersContext<RN, Handlers>
|
|
340
|
+
> => {
|
|
341
|
+
return toLayerOriginal(build as any) as any // ??
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// customized versions to handle dynamically eliminated context.
|
|
347
|
+
export type HandlersContext<Rpcs extends Rpc.Any, Handlers> = keyof Handlers extends infer K
|
|
348
|
+
? K extends keyof Handlers & string ? HandlerContext<Rpcs, K, Handlers[K]> : never
|
|
349
|
+
: never
|
|
350
|
+
|
|
351
|
+
export type HandlerContext<Rpcs extends Rpc.Any, K extends Rpcs["_tag"], Handler> = [Rpc.IsStream<Rpcs, K>] extends
|
|
352
|
+
[true] ? Handler extends (...args: any) =>
|
|
353
|
+
| Stream.Stream<infer _A, infer _E, infer _R>
|
|
354
|
+
| Rpc.Fork<Stream.Stream<infer _A, infer _E, infer _R>>
|
|
355
|
+
| Effect.Effect<
|
|
356
|
+
ReadonlyMailbox<infer _A, infer _E>,
|
|
357
|
+
infer _EX,
|
|
358
|
+
infer _R
|
|
359
|
+
>
|
|
360
|
+
| Rpc.Fork<
|
|
361
|
+
Effect.Effect<
|
|
362
|
+
ReadonlyMailbox<infer _A, infer _E>,
|
|
363
|
+
infer _EX,
|
|
364
|
+
infer _R
|
|
365
|
+
>
|
|
366
|
+
> ? Exclude<ExcludeProvides<_R, Rpcs, K>, Scope>
|
|
367
|
+
: never
|
|
368
|
+
: Handler extends (
|
|
369
|
+
...args: any
|
|
370
|
+
) => Effect.Effect<infer _A, infer _E, infer _R> | Rpc.Fork<Effect.Effect<infer _A, infer _E, infer _R>>
|
|
371
|
+
? ExcludeProvides<_R, Rpcs, K>
|
|
372
|
+
: never
|
|
373
|
+
|
|
374
|
+
// new
|
|
375
|
+
export type ExtractDynamicallyProvides<R extends Rpc.Any, Tag extends string> = R extends
|
|
376
|
+
Rpc.Rpc<Tag, infer _Payload, infer _Success, infer _Error, infer _Middleware> ? _Middleware extends {
|
|
377
|
+
readonly requestContextMap: infer _RC
|
|
378
|
+
} ? _RC extends Record<string, RPCContextMap.Any> // ? GetEffectContext<_RC, { allowAnonymous: false }>
|
|
379
|
+
? R extends { readonly config: infer _C } ? GetEffectContext<_RC, _C>
|
|
380
|
+
: GetEffectContext<_RC, {}>
|
|
381
|
+
: never
|
|
382
|
+
: never
|
|
383
|
+
: never
|
|
384
|
+
|
|
385
|
+
export type ExtractProvides<R extends Rpc.Any, Tag extends string> = R extends
|
|
386
|
+
Rpc.Rpc<Tag, infer _Payload, infer _Success, infer _Error, infer _Middleware> ? _Middleware extends {
|
|
387
|
+
readonly provides: Context.Tag<infer _I, infer _S>
|
|
388
|
+
} ? _I
|
|
389
|
+
: never
|
|
390
|
+
: never
|
|
391
|
+
|
|
392
|
+
export type ExcludeProvides<Env, R extends Rpc.Any, Tag extends string> = Exclude<
|
|
393
|
+
Env,
|
|
394
|
+
// customisation is down here.
|
|
395
|
+
ExtractProvides<R, Tag> | ExtractDynamicallyProvides<R, Tag>
|
|
396
|
+
>
|
|
397
|
+
|
|
398
|
+
//
|
|
399
|
+
export interface MiddlewareMakerId {
|
|
400
|
+
readonly _id: unique symbol
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// TODO: actually end up with [Tag<A, A>, Tag<B, B>, ...] once `provides: []` is implemented
|
|
404
|
+
export type MakeTags<A> = Context.Tag<A, A>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { RpcMiddleware } from "@effect/rpc"
|
|
3
|
+
import { Context } from "effect-app"
|
|
4
|
+
|
|
5
|
+
export class DevMode extends Context.Reference<DevMode>()("DevMode", { defaultValue: () => false }) {}
|
|
6
|
+
|
|
7
|
+
export class RequestCacheMiddleware
|
|
8
|
+
extends RpcMiddleware.Tag<RequestCacheMiddleware>()("RequestCacheMiddleware", { wrap: true })
|
|
9
|
+
{}
|
|
10
|
+
|
|
11
|
+
export class ConfigureInterruptibilityMiddleware
|
|
12
|
+
extends RpcMiddleware.Tag<ConfigureInterruptibilityMiddleware>()("ConfigureInterruptibilityMiddleware", {
|
|
13
|
+
wrap: true
|
|
14
|
+
})
|
|
15
|
+
{}
|
|
16
|
+
|
|
17
|
+
export class LoggerMiddleware extends RpcMiddleware.Tag<LoggerMiddleware>()("LoggerMiddleware", { wrap: true }) {}
|
|
18
|
+
|
|
19
|
+
export const DefaultGenericMiddlewares = [
|
|
20
|
+
RequestCacheMiddleware,
|
|
21
|
+
ConfigureInterruptibilityMiddleware,
|
|
22
|
+
LoggerMiddleware
|
|
23
|
+
] as const
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
import { Cause, Context, Duration, Effect, Layer, ParseResult, Request, Schedule, type Schema } from "effect-app"
|
|
1
|
+
import { Cause, Duration, Effect, Layer, ParseResult, Request, Schedule, type Schema } from "effect"
|
|
3
2
|
import { pretty } from "effect-app/utils"
|
|
4
3
|
import { logError, reportError } from "../../../errorReporter.js"
|
|
5
4
|
import { InfraLogger } from "../../../logger.js"
|
|
6
5
|
import { determineMethod, isCommand } from "../utils.js"
|
|
7
|
-
import
|
|
6
|
+
import * as MiddlewareNative from "./middleware-native.js"
|
|
8
7
|
|
|
9
8
|
const logRequestError = logError("Request")
|
|
10
9
|
const reportRequestError = reportError("Request")
|
|
11
10
|
|
|
11
|
+
export { DevMode } from "./middleware-native.js"
|
|
12
|
+
|
|
12
13
|
export const RequestCacheLayers = Layer.mergeAll(
|
|
13
14
|
Layer.setRequestCache(
|
|
14
15
|
Request.makeCache({ capacity: 500, timeToLive: Duration.hours(8) })
|
|
@@ -17,46 +18,43 @@ export const RequestCacheLayers = Layer.mergeAll(
|
|
|
17
18
|
Layer.setRequestBatching(true)
|
|
18
19
|
)
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}) {
|
|
25
|
-
}
|
|
21
|
+
export const RequestCacheMiddlewareLive = Layer.succeed(
|
|
22
|
+
MiddlewareNative.RequestCacheMiddleware,
|
|
23
|
+
({ next }) => next.pipe(Effect.provide(RequestCacheLayers))
|
|
24
|
+
)
|
|
26
25
|
|
|
27
26
|
// retry just once on optimistic concurrency exceptions
|
|
28
27
|
const optimisticConcurrencySchedule = Schedule.once.pipe(
|
|
29
28
|
Schedule.intersect(Schedule.recurWhile<any>((a) => a?._tag === "OptimisticConcurrencyException"))
|
|
30
29
|
)
|
|
31
30
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
export const ConfigureInterruptibilityMiddlewareLive = Layer.effect(
|
|
32
|
+
MiddlewareNative.ConfigureInterruptibilityMiddleware,
|
|
33
|
+
Effect.gen(function*() {
|
|
34
|
+
const cache = new Map()
|
|
35
|
+
const getCached = (key: string, schema: Schema.Schema.Any) => {
|
|
36
|
+
const existing = cache.get(key)
|
|
37
|
+
if (existing) return existing
|
|
38
|
+
const n = determineMethod(key, schema)
|
|
39
|
+
cache.set(key, n)
|
|
40
|
+
return n
|
|
41
|
+
}
|
|
42
|
+
return ({ next, rpc }) => {
|
|
43
|
+
const method = getCached(rpc._tag, rpc.payloadSchema)
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
next = isCommand(method)
|
|
46
|
+
? Effect.retry(Effect.uninterruptible(next), optimisticConcurrencySchedule)
|
|
47
|
+
: Effect.interruptible(next)
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
49
|
+
return next
|
|
50
|
+
}
|
|
53
51
|
})
|
|
54
|
-
|
|
55
|
-
}
|
|
52
|
+
)
|
|
56
53
|
|
|
57
|
-
export
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
export const LoggerMiddlewareLive = Layer.effect(
|
|
55
|
+
MiddlewareNative.LoggerMiddleware,
|
|
56
|
+
Effect.gen(function*() {
|
|
57
|
+
const devMode = yield* MiddlewareNative.DevMode
|
|
60
58
|
return ({ headers, next, payload, rpc }) =>
|
|
61
59
|
Effect
|
|
62
60
|
.annotateCurrentSpan({
|
|
@@ -112,11 +110,10 @@ export class LoggerMiddleware extends Tag<LoggerMiddleware>()("LoggerMiddleware"
|
|
|
112
110
|
devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error"))
|
|
113
111
|
)
|
|
114
112
|
})
|
|
115
|
-
|
|
116
|
-
}
|
|
113
|
+
)
|
|
117
114
|
|
|
118
|
-
export const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
export const DefaultGenericMiddlewaresLive = Layer.mergeAll(
|
|
116
|
+
RequestCacheMiddlewareLive,
|
|
117
|
+
ConfigureInterruptibilityMiddlewareLive,
|
|
118
|
+
LoggerMiddlewareLive
|
|
119
|
+
)
|
|
@@ -4,6 +4,7 @@ export * from "./middleware/middleware-api.js"
|
|
|
4
4
|
export * from "./middleware/middleware.js"
|
|
5
5
|
export * from "./middleware/RouterMiddleware.js"
|
|
6
6
|
export * from "./middleware/RpcMiddleware.js"
|
|
7
|
+
export * from "./middleware/RpcMiddlewareX.js"
|
|
7
8
|
// codegen:end
|
|
8
9
|
|
|
9
10
|
export * as Middleware from "./middleware.js"
|
package/src/api/routing.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Rpc, RpcGroup, type RpcSerialization, RpcServer } from "@effect/rpc"
|
|
6
6
|
import { type Array, Effect, Layer, type NonEmptyReadonlyArray, Predicate, S, Schema, type Scope } from "effect-app"
|
|
7
7
|
import type { GetEffectContext, GetEffectError, RPCContextMap } from "effect-app/client/req"
|
|
8
|
-
import { type HttpHeaders
|
|
8
|
+
import { type HttpHeaders } from "effect-app/http"
|
|
9
9
|
import { typedKeysOf, typedValuesOf } from "effect-app/utils"
|
|
10
10
|
import { type Service } from "effect/Effect"
|
|
11
11
|
import type { Contravariant } from "effect/Types"
|
|
@@ -158,8 +158,6 @@ export type RouteMatcher<
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
export class Router extends HttpRouter.Tag("@effect-app/Rpc")<Router>() {}
|
|
162
|
-
|
|
163
161
|
export const makeRouter = <
|
|
164
162
|
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
165
163
|
MakeMiddlewareE,
|
|
@@ -439,15 +437,13 @@ export const makeRouter = <
|
|
|
439
437
|
>
|
|
440
438
|
|
|
441
439
|
return RpcServer
|
|
442
|
-
.
|
|
440
|
+
.layerHttpRouter({
|
|
441
|
+
spanPrefix: "RpcServer." + meta.moduleName,
|
|
442
|
+
group: rpcs,
|
|
443
|
+
path: ("/rpc/" + meta.moduleName) as `/${typeof meta.moduleName}`,
|
|
444
|
+
protocol: "http"
|
|
445
|
+
})
|
|
443
446
|
.pipe(Layer.provide(rpc))
|
|
444
|
-
.pipe(
|
|
445
|
-
Layer.provideMerge(
|
|
446
|
-
RpcServer.layerProtocolHttp(
|
|
447
|
-
{ path: ("/" + meta.moduleName) as `/${typeof meta.moduleName}`, routerTag: Router }
|
|
448
|
-
)
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
447
|
})
|
|
452
448
|
.pipe(Layer.unwrapEffect)
|
|
453
449
|
|
|
@@ -459,8 +455,6 @@ export const makeRouter = <
|
|
|
459
455
|
Layer.provide(Layer.succeed(DevMode, devMode))
|
|
460
456
|
)
|
|
461
457
|
|
|
462
|
-
// Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
|
|
463
|
-
|
|
464
458
|
return {
|
|
465
459
|
moduleName: meta.moduleName,
|
|
466
460
|
routes
|
|
@@ -12,7 +12,7 @@ class MyContextProvider extends Effect.Service<MyContextProvider>()("MyContextPr
|
|
|
12
12
|
if (Math.random() > 0.5) return yield* new CustomError1()
|
|
13
13
|
|
|
14
14
|
return Effect.gen(function*() {
|
|
15
|
-
// the only requirements you can have are the one provided by
|
|
15
|
+
// the only requirements you can have are the one provided by HttpLayerRouter.Provided
|
|
16
16
|
yield* Scope.Scope
|
|
17
17
|
|
|
18
18
|
yield* Effect.logInfo("MyContextProviderGen", "this is a generator")
|
|
@@ -63,7 +63,7 @@ class MyContextProviderGen extends Effect.Service<MyContextProviderGen>()("MyCon
|
|
|
63
63
|
if (Math.random() > 0.5) return yield* new CustomError1()
|
|
64
64
|
|
|
65
65
|
return function*() {
|
|
66
|
-
// the only requirements you can have are the one provided by
|
|
66
|
+
// the only requirements you can have are the one provided by HttpLayerRouter.Provided
|
|
67
67
|
yield* Scope.Scope
|
|
68
68
|
|
|
69
69
|
yield* Effect.logInfo("MyContextProviderGen", "this is a generator")
|
|
@@ -87,7 +87,7 @@ export const someContextProvider = ContextProvider({
|
|
|
87
87
|
if (Math.random() > 0.5) return yield* new CustomError1()
|
|
88
88
|
|
|
89
89
|
return Effect.gen(function*() {
|
|
90
|
-
// the only requirements you can have are the one provided by
|
|
90
|
+
// the only requirements you can have are the one provided by HttpLayerRouter.Provided
|
|
91
91
|
yield* Scope.Scope
|
|
92
92
|
|
|
93
93
|
// not allowed
|
|
@@ -106,7 +106,7 @@ export const someContextProviderGen = ContextProvider({
|
|
|
106
106
|
if (Math.random() > 0.5) return yield* new CustomError1()
|
|
107
107
|
|
|
108
108
|
return function*() {
|
|
109
|
-
// the only requirements you can have are the one provided by
|
|
109
|
+
// the only requirements you can have are the one provided by HttpLayerRouter.Provided
|
|
110
110
|
yield* Scope.Scope
|
|
111
111
|
|
|
112
112
|
// not allowed
|