@effect-app/infra 2.86.0 → 2.87.1
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 +1 -5
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +3 -11
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +33 -0
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/RouterMiddleware.js +5 -0
- package/dist/api/routing/middleware/RpcMiddleware.d.ts +199 -0
- package/dist/api/routing/middleware/RpcMiddleware.d.ts.map +1 -0
- package/dist/api/routing/middleware/RpcMiddleware.js +14 -0
- package/dist/api/routing/middleware/dynamic-middleware.d.ts +2 -12
- package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/dynamic-middleware.js +2 -18
- package/dist/api/routing/middleware/generic-middleware.d.ts +4 -23
- package/dist/api/routing/middleware/generic-middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/generic-middleware.js +13 -9
- package/dist/api/routing/middleware/middleware-api.d.ts +27 -7
- package/dist/api/routing/middleware/middleware-api.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware-api.js +47 -22
- package/dist/api/routing/middleware/middleware.d.ts +3 -3
- package/dist/api/routing/middleware/middleware.js +2 -2
- package/dist/api/routing/middleware.d.ts +2 -1
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +3 -2
- package/dist/api/routing/tsort.d.ts +2 -2
- package/dist/api/routing/tsort.d.ts.map +1 -1
- package/dist/api/routing/tsort.js +1 -1
- package/package.json +9 -5
- package/src/api/layerUtils.ts +4 -18
- package/src/api/routing/middleware/RouterMiddleware.ts +149 -0
- package/src/api/routing/middleware/RpcMiddleware.ts +287 -0
- package/src/api/routing/middleware/dynamic-middleware.ts +5 -52
- package/src/api/routing/middleware/generic-middleware.ts +23 -34
- package/src/api/routing/middleware/middleware-api.ts +107 -45
- package/src/api/routing/middleware/middleware.ts +1 -1
- package/src/api/routing/middleware.ts +2 -1
- package/src/api/routing/tsort.ts +2 -2
- package/test/controller.test.ts +22 -58
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/fixtures.d.ts +4 -2
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +21 -9
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/fixtures.ts +29 -12
- package/test/requires.test.ts +98 -17
- package/dist/api/routing/middleware/DynamicMiddleware.d.ts +0 -229
- package/dist/api/routing/middleware/DynamicMiddleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/DynamicMiddleware.js +0 -168
- package/src/api/routing/middleware/DynamicMiddleware.ts +0 -715
|
@@ -0,0 +1,287 @@
|
|
|
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 { type Rpc, RpcMiddleware } from "@effect/rpc"
|
|
5
|
+
import { type SuccessValue, type TypeId } from "@effect/rpc/RpcMiddleware"
|
|
6
|
+
import { type Context, type Effect, Layer, type NonEmptyReadonlyArray, type Option, type S, type Schema, type Scope, Unify } from "effect-app"
|
|
7
|
+
import type { AnyService, ContextRepr, RPCContextMap } from "effect-app/client/req"
|
|
8
|
+
import { type HttpHeaders } from "effect-app/http"
|
|
9
|
+
import { type TagUnify, type TagUnifyIgnore } from "effect/Context"
|
|
10
|
+
import { type LayerUtils } from "../../layerUtils.js"
|
|
11
|
+
|
|
12
|
+
// updated to support Scope.Scope
|
|
13
|
+
export interface RpcMiddleware<Provides, E, Requires> {
|
|
14
|
+
(options: {
|
|
15
|
+
readonly clientId: number
|
|
16
|
+
readonly rpc: Rpc.AnyWithProps
|
|
17
|
+
readonly payload: unknown
|
|
18
|
+
readonly headers: HttpHeaders.Headers
|
|
19
|
+
}): Effect.Effect<Provides, E, Scope.Scope | Requires>
|
|
20
|
+
}
|
|
21
|
+
export interface RpcMiddlewareWrap<Provides, E, Requires> {
|
|
22
|
+
(options: {
|
|
23
|
+
readonly clientId: number
|
|
24
|
+
readonly rpc: Rpc.AnyWithProps
|
|
25
|
+
readonly payload: unknown
|
|
26
|
+
readonly headers: HttpHeaders.Headers
|
|
27
|
+
readonly next: Effect.Effect<SuccessValue, E, Provides | Scope.Scope | Requires>
|
|
28
|
+
}): Effect.Effect<SuccessValue, E, Scope.Scope | Requires>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type RpcOptionsOriginal = {
|
|
32
|
+
readonly wrap?: boolean
|
|
33
|
+
readonly optional?: boolean
|
|
34
|
+
readonly failure?: Schema.Schema.All
|
|
35
|
+
readonly provides?: AnyService
|
|
36
|
+
readonly requiredForClient?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type RpcDynamic<Key extends string, A extends RPCContextMap.Any> = {
|
|
40
|
+
key: Key
|
|
41
|
+
settings: A
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type AnyDynamic = { dynamic: RpcDynamic<any, any> }
|
|
45
|
+
|
|
46
|
+
export type DependsOn = {
|
|
47
|
+
readonly dependsOn: NonEmptyReadonlyArray<AnyDynamic> | undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type RpcOptionsDynamic<Key extends string, A extends RPCContextMap.Any> = RpcOptionsOriginal & {
|
|
51
|
+
readonly dynamic: RpcDynamic<Key, A>
|
|
52
|
+
readonly dependsOn?: NonEmptyReadonlyArray<AnyDynamic> | undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type Dynamic<Options> = Options extends RpcOptionsDynamic<any, any> ? true : false
|
|
56
|
+
|
|
57
|
+
export interface RpcMiddlewareDynamicWrap<E, R, Config> {
|
|
58
|
+
(options: {
|
|
59
|
+
readonly next: Effect.Effect<SuccessValue, E, Scope.Scope | R>
|
|
60
|
+
readonly config: Config // todo
|
|
61
|
+
readonly clientId: number
|
|
62
|
+
readonly rpc: Rpc.AnyWithProps
|
|
63
|
+
readonly payload: unknown
|
|
64
|
+
readonly headers: HttpHeaders.Headers
|
|
65
|
+
}): Effect.Effect<
|
|
66
|
+
SuccessValue,
|
|
67
|
+
E,
|
|
68
|
+
Scope.Scope | R
|
|
69
|
+
>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RpcMiddlewareDynamicNormal<A, E, R, Config> {
|
|
73
|
+
(options: {
|
|
74
|
+
readonly config: Config // todo
|
|
75
|
+
readonly clientId: number
|
|
76
|
+
readonly rpc: Rpc.AnyWithProps
|
|
77
|
+
readonly payload: unknown
|
|
78
|
+
readonly headers: HttpHeaders.Headers
|
|
79
|
+
}): Effect.Effect<
|
|
80
|
+
Option.Option<A>,
|
|
81
|
+
E,
|
|
82
|
+
Scope.Scope | R
|
|
83
|
+
>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface TagClassAny extends Context.Tag<any, any> {
|
|
87
|
+
readonly [TypeId]: TypeId
|
|
88
|
+
readonly optional: boolean
|
|
89
|
+
readonly provides?: Context.Tag<any, any> | ContextRepr | undefined
|
|
90
|
+
readonly requires?: Context.Tag<any, any> | ContextRepr | undefined
|
|
91
|
+
readonly failure: Schema.Schema.All
|
|
92
|
+
readonly requiredForClient: boolean
|
|
93
|
+
readonly wrap: boolean
|
|
94
|
+
readonly dynamic?: RpcDynamic<any, any> | undefined
|
|
95
|
+
readonly dependsOn?: NonEmptyReadonlyArray<AnyDynamic> | undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export declare namespace TagClass {
|
|
99
|
+
/**
|
|
100
|
+
* @since 1.0.0
|
|
101
|
+
* @category models
|
|
102
|
+
*/
|
|
103
|
+
export type Provides<Options> = Options extends {
|
|
104
|
+
readonly provides: Context.Tag<any, any>
|
|
105
|
+
readonly optional?: false
|
|
106
|
+
} ? Context.Tag.Identifier<Options["provides"]>
|
|
107
|
+
: Options extends {
|
|
108
|
+
readonly provides: ContextRepr
|
|
109
|
+
readonly optional?: false
|
|
110
|
+
} ? ContextRepr.Identifier<Options["provides"]>
|
|
111
|
+
: never
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @since 1.0.0
|
|
115
|
+
* @category models
|
|
116
|
+
*/
|
|
117
|
+
export type Requires<Options> = Options extends {
|
|
118
|
+
readonly requires: Context.Tag<any, any>
|
|
119
|
+
} ? Context.Tag.Identifier<Options["requires"]>
|
|
120
|
+
: Options extends {
|
|
121
|
+
readonly requires: ContextRepr
|
|
122
|
+
} ? ContextRepr.Identifier<Options["requires"]>
|
|
123
|
+
: never
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @since 1.0.0
|
|
127
|
+
* @category models
|
|
128
|
+
*/
|
|
129
|
+
export type Service<Options> = Options extends { readonly provides: Context.Tag<any, any> }
|
|
130
|
+
? Context.Tag.Service<Options["provides"]>
|
|
131
|
+
: Options extends { readonly dynamic: RpcDynamic<any, infer A> }
|
|
132
|
+
? Options extends { wrap: true } ? void : AnyService.Bla<A["service"]>
|
|
133
|
+
: Options extends { readonly provides: ContextRepr } ? Context.Context<ContextRepr.Identifier<Options["provides"]>>
|
|
134
|
+
: void
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @since 1.0.0
|
|
138
|
+
* @category models
|
|
139
|
+
*/
|
|
140
|
+
export type FailureSchema<Options> = Options extends
|
|
141
|
+
{ readonly failure: Schema.Schema.All; readonly optional?: false } ? Options["failure"]
|
|
142
|
+
: Options extends { readonly dynamic: RpcDynamic<any, infer A> } ? A["error"]
|
|
143
|
+
: typeof Schema.Never
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @since 1.0.0
|
|
147
|
+
* @category models
|
|
148
|
+
*/
|
|
149
|
+
export type Failure<Options> = Options extends
|
|
150
|
+
{ readonly failure: Schema.Schema<infer _A, infer _I, infer _R>; readonly optional?: false } ? _A
|
|
151
|
+
: Options extends { readonly dynamic: RpcDynamic<any, infer A> } ? S.Schema.Type<A["error"]>
|
|
152
|
+
: never
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @since 1.0.0
|
|
156
|
+
* @category models
|
|
157
|
+
*/
|
|
158
|
+
export type FailureContext<Options> = Schema.Schema.Context<FailureSchema<Options>>
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @since 1.0.0
|
|
162
|
+
* @category models
|
|
163
|
+
*/
|
|
164
|
+
export type FailureService<Options> = Optional<Options> extends true ? unknown : Failure<Options>
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @since 1.0.0
|
|
168
|
+
* @category models
|
|
169
|
+
*/
|
|
170
|
+
export type Optional<Options> = Options extends { readonly optional: true } ? true : false
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @since 1.0.0
|
|
174
|
+
* @category models
|
|
175
|
+
*/
|
|
176
|
+
export type RequiredForClient<Options> = Options extends { readonly requiredForClient: true } ? true : false
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @since 1.0.0
|
|
180
|
+
* @category models
|
|
181
|
+
*/
|
|
182
|
+
export type Wrap<Options> = Options extends { readonly wrap: true } ? true : false
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @since 1.0.0
|
|
186
|
+
* @category models
|
|
187
|
+
*/
|
|
188
|
+
export interface Base<Self, Name extends string, Options, Service> extends Context.Tag<Self, Service> {
|
|
189
|
+
new(_: never): Context.TagClassShape<Name, Service>
|
|
190
|
+
readonly [TypeId]: TypeId
|
|
191
|
+
readonly optional: Optional<Options>
|
|
192
|
+
readonly failure: FailureSchema<Options>
|
|
193
|
+
readonly provides: Options extends { readonly provides: Context.Tag<any, any> } ? Options["provides"]
|
|
194
|
+
: Options extends { readonly provides: ContextRepr } ? Options["provides"]
|
|
195
|
+
: undefined
|
|
196
|
+
readonly requires: Options extends { readonly requires: Context.Tag<any, any> } ? Options["requires"]
|
|
197
|
+
: Options extends { readonly requires: ContextRepr } ? Options["requires"]
|
|
198
|
+
: undefined
|
|
199
|
+
readonly dynamic: Options extends RpcOptionsDynamic<any, any> ? Options["dynamic"]
|
|
200
|
+
: undefined
|
|
201
|
+
readonly dependsOn: Options extends DependsOn ? Options["dependsOn"] : undefined
|
|
202
|
+
readonly requiredForClient: RequiredForClient<Options>
|
|
203
|
+
readonly wrap: Wrap<Options>
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface TagClass<
|
|
208
|
+
Self,
|
|
209
|
+
Name extends string,
|
|
210
|
+
Options
|
|
211
|
+
> extends
|
|
212
|
+
TagClass.Base<
|
|
213
|
+
Self,
|
|
214
|
+
Name,
|
|
215
|
+
Options,
|
|
216
|
+
Options extends RpcOptionsDynamic<any, any> ? TagClass.Wrap<Options> extends true ? RpcMiddlewareDynamicWrap<
|
|
217
|
+
TagClass.FailureService<Options>,
|
|
218
|
+
TagClass.Requires<Options>,
|
|
219
|
+
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
|
|
220
|
+
>
|
|
221
|
+
: RpcMiddlewareDynamicNormal<
|
|
222
|
+
TagClass.Service<Options>,
|
|
223
|
+
TagClass.FailureService<Options>,
|
|
224
|
+
TagClass.Requires<Options>,
|
|
225
|
+
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
|
|
226
|
+
>
|
|
227
|
+
: TagClass.Wrap<Options> extends true ? RpcMiddlewareWrap<
|
|
228
|
+
TagClass.Provides<Options>,
|
|
229
|
+
TagClass.Requires<Options>,
|
|
230
|
+
TagClass.Failure<Options>
|
|
231
|
+
>
|
|
232
|
+
: RpcMiddleware<
|
|
233
|
+
TagClass.Service<Options>,
|
|
234
|
+
TagClass.FailureService<Options>,
|
|
235
|
+
TagClass.Requires<Options>
|
|
236
|
+
>
|
|
237
|
+
>
|
|
238
|
+
{}
|
|
239
|
+
|
|
240
|
+
export const Tag = <Self>() =>
|
|
241
|
+
<
|
|
242
|
+
const Name extends string,
|
|
243
|
+
const Options extends RpcOptionsOriginal | RpcOptionsDynamic<any, any>
|
|
244
|
+
>(
|
|
245
|
+
id: Name,
|
|
246
|
+
options?: Options | undefined
|
|
247
|
+
) =>
|
|
248
|
+
<E, R, L extends NonEmptyReadonlyArray<Layer.Layer.Any>>(opts: {
|
|
249
|
+
effect: Effect.Effect<
|
|
250
|
+
Options extends RpcOptionsDynamic<any, any> ? TagClass.Wrap<Options> extends true ? RpcMiddlewareDynamicWrap<
|
|
251
|
+
TagClass.FailureService<Options>,
|
|
252
|
+
TagClass.Requires<Options>,
|
|
253
|
+
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
|
|
254
|
+
>
|
|
255
|
+
: RpcMiddlewareDynamicNormal<
|
|
256
|
+
TagClass.Service<Options>,
|
|
257
|
+
TagClass.FailureService<Options>,
|
|
258
|
+
TagClass.Requires<Options>,
|
|
259
|
+
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
|
|
260
|
+
>
|
|
261
|
+
: TagClass.Wrap<Options> extends true ? RpcMiddlewareWrap<
|
|
262
|
+
TagClass.Provides<Options>,
|
|
263
|
+
TagClass.Failure<Options>,
|
|
264
|
+
TagClass.Requires<Options>
|
|
265
|
+
>
|
|
266
|
+
: RpcMiddleware<
|
|
267
|
+
TagClass.Service<Options>,
|
|
268
|
+
TagClass.FailureService<Options>,
|
|
269
|
+
TagClass.Requires<Options>
|
|
270
|
+
>,
|
|
271
|
+
E,
|
|
272
|
+
R
|
|
273
|
+
>
|
|
274
|
+
dependencies?: L
|
|
275
|
+
}): TagClass<Self, Name, Options> & {
|
|
276
|
+
Default: Layer.Layer<Self, E | LayerUtils.GetLayersError<L>, Exclude<R, LayerUtils.GetLayersSuccess<L>>>
|
|
277
|
+
} =>
|
|
278
|
+
class extends RpcMiddleware.Tag<Self>()(id, options as any) {
|
|
279
|
+
static readonly dynamic = options && "dynamic" in options ? options.dynamic : undefined
|
|
280
|
+
static readonly dependsOn = options && "dependsOn" in options ? options.dependsOn : undefined
|
|
281
|
+
static readonly Default = Layer.scoped(this, opts.effect as any).pipe(
|
|
282
|
+
Layer.provide([Layer.empty, ...opts.dependencies ?? []])
|
|
283
|
+
)
|
|
284
|
+
static override [Unify.typeSymbol]?: unknown
|
|
285
|
+
static override [Unify.unifySymbol]?: TagUnify<typeof this>
|
|
286
|
+
static override [Unify.ignoreSymbol]?: TagUnifyIgnore
|
|
287
|
+
} as any
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { type ContextTagWithDefault, mergeOptionContexts } from "../../layerUtils.js"
|
|
8
|
-
import { sort } from "../tsort.js"
|
|
9
|
-
import { type RpcMiddlewareDynamic } from "./DynamicMiddleware.js"
|
|
2
|
+
import { type ContextTagWithDefault } from "../../layerUtils.js"
|
|
3
|
+
import { type RpcMiddlewareDynamicNormal, type RpcMiddlewareDynamicWrap } from "./RpcMiddleware.js"
|
|
4
|
+
|
|
5
|
+
export type RpcMiddlewareDynamic<A, E, R, Config> = [A] extends [void] ? RpcMiddlewareDynamicWrap<E, R, Config>
|
|
6
|
+
: RpcMiddlewareDynamicNormal<A, E, R, Config>
|
|
10
7
|
|
|
11
8
|
export type ContextWithLayer<
|
|
12
9
|
Config,
|
|
@@ -48,47 +45,3 @@ export namespace ContextWithLayer {
|
|
|
48
45
|
any
|
|
49
46
|
>
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
// Effect Rpc Middleware: no substitute atm. though maybe something could be achieved with Wrap, we just don't have type safety on the Request input etc.
|
|
53
|
-
export const implementMiddleware = <T extends Record<string, RPCContextMap.Any>>() =>
|
|
54
|
-
<
|
|
55
|
-
TI extends {
|
|
56
|
-
[K in keyof T]: ContextWithLayer.Base<
|
|
57
|
-
{ [K in keyof T]?: T[K]["contextActivation"] },
|
|
58
|
-
T[K]["service"],
|
|
59
|
-
S.Schema.Type<T[K]["error"]>
|
|
60
|
-
>
|
|
61
|
-
}
|
|
62
|
-
>(implementations: TI) => ({
|
|
63
|
-
dependencies: typedValuesOf(implementations).map((_) => _.Default) as {
|
|
64
|
-
[K in keyof TI]: TI[K]["Default"]
|
|
65
|
-
}[keyof TI][],
|
|
66
|
-
effect: Effect.gen(function*() {
|
|
67
|
-
const sorted = sort(typedValuesOf(implementations))
|
|
68
|
-
|
|
69
|
-
const makers = yield* Effect.all(sorted)
|
|
70
|
-
return Effect.fnUntraced(
|
|
71
|
-
function*(options: { config: { [K in keyof T]?: T[K]["contextActivation"] }; headers: HttpHeaders.Headers }) {
|
|
72
|
-
const ctx = yield* mergeOptionContexts(
|
|
73
|
-
Array.map(
|
|
74
|
-
makers,
|
|
75
|
-
(_, i) => ({ maker: sorted[i], handle: (_ as any)(options) as any }) as any
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
return ctx as Context.Context<
|
|
79
|
-
GetEffectContext<T, typeof options["config"]>
|
|
80
|
-
>
|
|
81
|
-
}
|
|
82
|
-
)
|
|
83
|
-
}) as unknown as Effect<
|
|
84
|
-
(
|
|
85
|
-
options: { config: { [K in keyof T]?: T[K]["contextActivation"] }; headers: HttpHeaders.Headers }
|
|
86
|
-
) => Effect.Effect<
|
|
87
|
-
Context.Context<GetEffectContext<T, typeof options["config"]>>,
|
|
88
|
-
Effect.Error<ReturnType<Tag.Service<TI[keyof TI]>>>,
|
|
89
|
-
Effect.Context<ReturnType<Tag.Service<TI[keyof TI]>>>
|
|
90
|
-
>,
|
|
91
|
-
never,
|
|
92
|
-
Tag.Identifier<{ [K in keyof TI]: TI[K] }[keyof TI]>
|
|
93
|
-
>
|
|
94
|
-
})
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { type Rpc, type RpcMiddleware } from "@effect/rpc"
|
|
3
|
-
import { type SuccessValue
|
|
4
|
-
import { type Array, Context, Effect, type Layer, type NonEmptyReadonlyArray,
|
|
5
|
-
import { type
|
|
3
|
+
import { type SuccessValue } from "@effect/rpc/RpcMiddleware"
|
|
4
|
+
import { type Array, Context, Effect, type Layer, type NonEmptyReadonlyArray, Option, type Scope } from "effect-app"
|
|
5
|
+
import { type ContextRepr } from "effect-app/client"
|
|
6
6
|
import { type HttpHeaders } from "effect-app/http"
|
|
7
|
-
import { type Tag } from "effect/Context"
|
|
8
7
|
import { InfraLogger } from "../../../logger.js"
|
|
9
|
-
import { type
|
|
10
|
-
|
|
11
|
-
export type ContextRepr = NonEmptyReadonlyArray<Context.Tag<any, any>>
|
|
12
|
-
export namespace ContextRepr {
|
|
13
|
-
export type Identifier<A> = A extends ContextRepr ? Tag.Identifier<A[number]> : never
|
|
14
|
-
export type Service<A> = A extends ContextRepr ? Tag.Service<A[number]> : never
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface TagClassAny extends Context.Tag<any, any> {
|
|
18
|
-
readonly [TypeId]: TypeId
|
|
19
|
-
readonly optional: boolean
|
|
20
|
-
readonly provides?: Context.Tag<any, any> | ContextRepr | undefined
|
|
21
|
-
readonly requires?: Context.Tag<any, any> | ContextRepr | undefined
|
|
22
|
-
readonly failure: Schema.Schema.All
|
|
23
|
-
readonly requiredForClient: boolean
|
|
24
|
-
readonly wrap: boolean
|
|
25
|
-
}
|
|
8
|
+
import { type TagClassAny } from "./RpcMiddleware.js"
|
|
26
9
|
|
|
27
10
|
export interface GenericMiddlewareOptions<E> {
|
|
28
11
|
// Effect rpc middleware does not support changing payload or headers, but we do..
|
|
@@ -34,9 +17,6 @@ export interface GenericMiddlewareOptions<E> {
|
|
|
34
17
|
}
|
|
35
18
|
|
|
36
19
|
export type GenericMiddlewareMaker = TagClassAny & { Default: Layer.Layer.Any } // todo; and Layer..
|
|
37
|
-
export type DynamicMiddlewareMaker<RequestContext extends Record<string, RPCContextMap.Any>> =
|
|
38
|
-
& TagClassDynamicAny<RequestContext>
|
|
39
|
-
& { Default: Layer.Layer.Any } // todo; and Layer..
|
|
40
20
|
|
|
41
21
|
export namespace GenericMiddlewareMaker {
|
|
42
22
|
export type ApplyServices<A extends TagClassAny, R> = Exclude<R, Provided<A>> | Required<A>
|
|
@@ -56,8 +36,6 @@ export namespace GenericMiddlewareMaker {
|
|
|
56
36
|
: never
|
|
57
37
|
}
|
|
58
38
|
|
|
59
|
-
export const genericMiddleware = (i: GenericMiddlewareMaker) => i
|
|
60
|
-
|
|
61
39
|
export const genericMiddlewareMaker = <
|
|
62
40
|
T extends Array<GenericMiddlewareMaker>
|
|
63
41
|
>(...middlewares: T): {
|
|
@@ -70,11 +48,6 @@ export const genericMiddlewareMaker = <
|
|
|
70
48
|
dependencies: middlewares.map((_) => _.Default),
|
|
71
49
|
effect: Effect.gen(function*() {
|
|
72
50
|
const context = yield* Effect.context()
|
|
73
|
-
// const middlewares: readonly (RpcMiddlewareWrap<any, any> | RpcMiddleware.RpcMiddleware<any, any>)[] =
|
|
74
|
-
// (yield* Effect.all(
|
|
75
|
-
// middlewares
|
|
76
|
-
// )) as any
|
|
77
|
-
|
|
78
51
|
return <E>(
|
|
79
52
|
options: GenericMiddlewareOptions<E>
|
|
80
53
|
) => {
|
|
@@ -83,13 +56,13 @@ export const genericMiddlewareMaker = <
|
|
|
83
56
|
for (const tag of middlewares) {
|
|
84
57
|
if (tag.wrap) {
|
|
85
58
|
const middleware = Context.unsafeGet(context, tag)
|
|
86
|
-
handler = InfraLogger.logDebug("Applying middleware " + tag.key).pipe(
|
|
87
|
-
Effect.zipRight(middleware({ ...options, next: handler
|
|
59
|
+
handler = InfraLogger.logDebug("Applying middleware wrap " + tag.key).pipe(
|
|
60
|
+
Effect.zipRight(middleware({ ...options, next: handler }))
|
|
88
61
|
) as any
|
|
89
62
|
} else if (tag.optional) {
|
|
90
63
|
const middleware = Context.unsafeGet(context, tag) as RpcMiddleware.RpcMiddleware<any, any>
|
|
91
64
|
const previous = handler
|
|
92
|
-
handler = InfraLogger.logDebug("Applying middleware " + tag.key).pipe(
|
|
65
|
+
handler = InfraLogger.logDebug("Applying middleware optional " + tag.key).pipe(
|
|
93
66
|
Effect.zipRight(Effect.matchEffect(middleware(options), {
|
|
94
67
|
onFailure: () => previous,
|
|
95
68
|
onSuccess: tag.provides !== undefined
|
|
@@ -100,6 +73,22 @@ export const genericMiddlewareMaker = <
|
|
|
100
73
|
: (_) => previous
|
|
101
74
|
}))
|
|
102
75
|
)
|
|
76
|
+
} else if (tag.dynamic) {
|
|
77
|
+
const middleware = Context.unsafeGet(context, tag) as RpcMiddleware.RpcMiddleware<any, any>
|
|
78
|
+
const previous = handler
|
|
79
|
+
handler = InfraLogger.logDebug("Applying middleware dynamic " + tag.key, tag.dynamic).pipe(
|
|
80
|
+
Effect.zipRight(
|
|
81
|
+
middleware(options).pipe(
|
|
82
|
+
Effect.flatMap((value) =>
|
|
83
|
+
Option.isSome(value)
|
|
84
|
+
? Context.isContext(value.value)
|
|
85
|
+
? Effect.provide(previous, value.value)
|
|
86
|
+
: Effect.provideService(previous, tag.dynamic!.settings.service!, /* TODO */ value.value)
|
|
87
|
+
: previous
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
)
|
|
103
92
|
} else {
|
|
104
93
|
const middleware = Context.unsafeGet(context, tag) as RpcMiddleware.RpcMiddleware<any, any>
|
|
105
94
|
const previous = handler
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rpc } from "@effect/rpc"
|
|
2
|
+
import { Context, Effect, Layer, type NonEmptyArray, type NonEmptyReadonlyArray } from "effect-app"
|
|
2
3
|
import { type RPCContextMap } from "effect-app/client"
|
|
3
|
-
import { type
|
|
4
|
+
import { type LayerUtils } from "../../layerUtils.js"
|
|
5
|
+
import { type GenericMiddlewareMaker, genericMiddlewareMaker } from "./generic-middleware.js"
|
|
6
|
+
import { makeRpcEffect, type MiddlewareMakerId, type RPCHandlerFactory } from "./RouterMiddleware.js"
|
|
7
|
+
import { type AnyDynamic, type RpcDynamic, type TagClassAny } from "./RpcMiddleware.js"
|
|
4
8
|
|
|
5
|
-
// TODO:
|
|
6
|
-
//
|
|
7
|
-
export const contextMap =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// TODO: don't expect service when it's wrap/never
|
|
10
|
+
// perhaps RequestContextMap should be an object, instead of an interface, so that we don't need to provide anything here
|
|
11
|
+
export const contextMap =
|
|
12
|
+
<RequestContextMap extends Record<string, RPCContextMap.Any>>() =>
|
|
13
|
+
<K extends keyof RequestContextMap>(a: K, service: RequestContextMap[K]["service"]) => ({
|
|
14
|
+
key: a,
|
|
15
|
+
settings: { service } as any as RequestContextMap[typeof a]
|
|
16
|
+
})
|
|
11
17
|
|
|
12
18
|
export interface MiddlewareM<
|
|
13
19
|
RequestContext extends Record<string, RPCContextMap.Any>,
|
|
@@ -28,6 +34,13 @@ export interface MiddlewareM<
|
|
|
28
34
|
>
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
type GetDependsOnKeys<MW extends GenericMiddlewareMaker> = MW extends { dependsOn: NonEmptyReadonlyArray<TagClassAny> }
|
|
38
|
+
? {
|
|
39
|
+
[K in keyof MW["dependsOn"]]: MW["dependsOn"][K] extends AnyDynamic ? MW["dependsOn"][K]["dynamic"]["key"]
|
|
40
|
+
: never
|
|
41
|
+
}[keyof MW["dependsOn"]]
|
|
42
|
+
: never
|
|
43
|
+
|
|
31
44
|
export interface MiddlewareDynamic<
|
|
32
45
|
RequestContext extends Record<string, RPCContextMap.Any>,
|
|
33
46
|
Provided extends keyof RequestContext,
|
|
@@ -35,14 +48,17 @@ export interface MiddlewareDynamic<
|
|
|
35
48
|
DynamicMiddlewareProviders,
|
|
36
49
|
out MiddlewareR
|
|
37
50
|
> {
|
|
38
|
-
|
|
39
|
-
// either have to block it, or implement the support properly.
|
|
40
|
-
middleware<MW extends NonEmptyArray<DynamicMiddlewareMaker<RequestContext>> | NonEmptyArray<GenericMiddlewareMaker>>(
|
|
51
|
+
middleware<MW extends NonEmptyArray<GenericMiddlewareMaker>>(
|
|
41
52
|
...mw: MW
|
|
42
|
-
):
|
|
53
|
+
): MW extends NonEmptyArray<{ dynamic: RpcDynamic<any, RequestContext[keyof RequestContext]> }>
|
|
54
|
+
? DynamicMiddlewareMakerrsss<
|
|
43
55
|
RequestContext,
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
// when one dynamic middleware depends on another, substract the key, to enforce the dependency to be provided after.
|
|
57
|
+
Exclude<
|
|
58
|
+
Provided | MW[number]["dynamic"]["key"],
|
|
59
|
+
{ [K in keyof MW]: GetDependsOnKeys<MW[K]> }[number]
|
|
60
|
+
>,
|
|
61
|
+
[...Middlewares, ...MW],
|
|
46
62
|
& DynamicMiddlewareProviders
|
|
47
63
|
& {
|
|
48
64
|
[U in MW[number] as U["dynamic"]["key"]]: U
|
|
@@ -58,9 +74,6 @@ export interface MiddlewareDynamic<
|
|
|
58
74
|
>
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
type GetDynamicMiddleware<T, RequestContext extends Record<string, RPCContextMap.Any>> = T extends
|
|
62
|
-
RequestContextMapProvider<RequestContext> ? T : never
|
|
63
|
-
|
|
64
77
|
type DynamicMiddlewareMakerrsss<
|
|
65
78
|
RequestContext extends Record<string, RPCContextMap.Any>,
|
|
66
79
|
Provided extends keyof RequestContext = never,
|
|
@@ -71,16 +84,9 @@ type DynamicMiddlewareMakerrsss<
|
|
|
71
84
|
& ReturnType<
|
|
72
85
|
typeof makeMiddlewareBasic<
|
|
73
86
|
RequestContext,
|
|
74
|
-
GetDynamicMiddleware<DynamicMiddlewareProviders, RequestContext>,
|
|
75
87
|
Middlewares
|
|
76
88
|
>
|
|
77
89
|
>
|
|
78
|
-
// & {
|
|
79
|
-
// MiddlewareR: MiddlewareR
|
|
80
|
-
// Provided: Provided
|
|
81
|
-
// Middlewares: Middlewares
|
|
82
|
-
// DynamicMiddlewareProviders: Simplify<DynamicMiddlewareProviders>
|
|
83
|
-
// }
|
|
84
90
|
& MiddlewareM<
|
|
85
91
|
RequestContext,
|
|
86
92
|
Provided,
|
|
@@ -103,35 +109,91 @@ type DynamicMiddlewareMakerrsss<
|
|
|
103
109
|
MiddlewareR
|
|
104
110
|
>
|
|
105
111
|
|
|
106
|
-
export const
|
|
112
|
+
export const makeMiddleware: <
|
|
107
113
|
RequestContextMap extends Record<string, RPCContextMap.Any>
|
|
108
114
|
>() => DynamicMiddlewareMakerrsss<RequestContextMap> = () => {
|
|
109
|
-
|
|
110
|
-
let capturedMiddlewares: (DynamicMiddlewareMaker<any> | GenericMiddlewareMaker)[] = []
|
|
115
|
+
let allMiddleware: GenericMiddlewareMaker[] = []
|
|
111
116
|
const it = {
|
|
112
117
|
middleware: (...middlewares: any[]) => {
|
|
113
118
|
for (const mw of middlewares) {
|
|
114
|
-
|
|
115
|
-
if (mw.dynamic) {
|
|
116
|
-
console.log("Adding dynamic middleware", mw.key, mw.dynamic.key)
|
|
117
|
-
} else {
|
|
118
|
-
console.log("Adding generic middleware", mw.key)
|
|
119
|
-
}
|
|
119
|
+
allMiddleware = [mw, ...allMiddleware]
|
|
120
120
|
}
|
|
121
|
-
const [genericMiddlewares, dyn] = Array.partitionMap(
|
|
122
|
-
capturedMiddlewares,
|
|
123
|
-
(mw) =>
|
|
124
|
-
"dynamic" in mw && mw.dynamic
|
|
125
|
-
? Either.right(mw as DynamicMiddlewareMaker<any>)
|
|
126
|
-
: Either.left(mw as GenericMiddlewareMaker)
|
|
127
|
-
)
|
|
128
|
-
const dynamicMiddlewares = dyn.reduce(
|
|
129
|
-
(prev, cur) => ({ ...prev, [cur.dynamic.key]: cur }),
|
|
130
|
-
{} as Record<string, any>
|
|
131
|
-
)
|
|
132
121
|
// TODO: support dynamic and generic intertwined. treat them as one
|
|
133
|
-
return Object.assign(
|
|
122
|
+
return Object.assign(makeMiddlewareBasic<any, any>(...allMiddleware), it)
|
|
134
123
|
}
|
|
135
124
|
}
|
|
136
125
|
return it as any
|
|
137
126
|
}
|
|
127
|
+
|
|
128
|
+
export const makeMiddlewareBasic =
|
|
129
|
+
// by setting RequestContextMap beforehand, execute contextual typing does not fuck up itself to anys
|
|
130
|
+
<
|
|
131
|
+
RequestContextMap extends Record<string, RPCContextMap.Any>,
|
|
132
|
+
// RequestContextProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
|
|
133
|
+
GenericMiddlewareProviders extends ReadonlyArray<GenericMiddlewareMaker>
|
|
134
|
+
>(
|
|
135
|
+
...make: GenericMiddlewareProviders
|
|
136
|
+
) => {
|
|
137
|
+
const MiddlewareMaker = Context.GenericTag<
|
|
138
|
+
MiddlewareMakerId,
|
|
139
|
+
{
|
|
140
|
+
effect: RPCHandlerFactory<
|
|
141
|
+
RequestContextMap,
|
|
142
|
+
GenericMiddlewareMaker.Provided<GenericMiddlewareProviders[number]>
|
|
143
|
+
>
|
|
144
|
+
_tag: "MiddlewareMaker"
|
|
145
|
+
}
|
|
146
|
+
>(
|
|
147
|
+
"MiddlewareMaker"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const middlewares = genericMiddlewareMaker(...make)
|
|
151
|
+
|
|
152
|
+
const l = Layer.scoped(
|
|
153
|
+
MiddlewareMaker,
|
|
154
|
+
middlewares
|
|
155
|
+
.effect
|
|
156
|
+
.pipe(
|
|
157
|
+
Effect.map((generic) => ({
|
|
158
|
+
_tag: "MiddlewareMaker" as const,
|
|
159
|
+
effect: makeRpcEffect<
|
|
160
|
+
RequestContextMap,
|
|
161
|
+
GenericMiddlewareMaker.Provided<GenericMiddlewareProviders[number]>
|
|
162
|
+
>()(
|
|
163
|
+
(schema, next, moduleName) => {
|
|
164
|
+
return (payload, headers) =>
|
|
165
|
+
Effect
|
|
166
|
+
.gen(function*() {
|
|
167
|
+
const basic = {
|
|
168
|
+
config: schema.config ?? {},
|
|
169
|
+
payload,
|
|
170
|
+
headers,
|
|
171
|
+
clientId: 0, // TODO: get the clientId from the request context
|
|
172
|
+
rpc: {
|
|
173
|
+
...Rpc.fromTaggedRequest(schema as any),
|
|
174
|
+
key: `${moduleName}.${payload._tag}`,
|
|
175
|
+
_tag: `${moduleName}.${payload._tag}`
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return yield* generic({
|
|
179
|
+
...basic,
|
|
180
|
+
next: next(payload, headers) as any
|
|
181
|
+
})
|
|
182
|
+
}) as any // why?
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
}))
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const middlewareLayer = l
|
|
190
|
+
.pipe(
|
|
191
|
+
Layer.provide(middlewares.dependencies as any)
|
|
192
|
+
) as Layer.Layer<
|
|
193
|
+
MiddlewareMakerId,
|
|
194
|
+
LayerUtils.GetLayersError<typeof middlewares.dependencies>, // what could go wrong when building the dynamic middleware provider
|
|
195
|
+
LayerUtils.GetLayersContext<typeof middlewares.dependencies>
|
|
196
|
+
>
|
|
197
|
+
|
|
198
|
+
return Object.assign(MiddlewareMaker, { Default: middlewareLayer })
|
|
199
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Cause, Context, Duration, Effect, Layer, ParseResult, Request } from "e
|
|
|
3
3
|
import { pretty } from "effect-app/utils"
|
|
4
4
|
import { logError, reportError } from "../../../errorReporter.js"
|
|
5
5
|
import { InfraLogger } from "../../../logger.js"
|
|
6
|
-
import { Tag } from "
|
|
6
|
+
import { Tag } from "./RpcMiddleware.js"
|
|
7
7
|
|
|
8
8
|
const logRequestError = logError("Request")
|
|
9
9
|
const reportRequestError = reportError("Request")
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// codegen:start {preset: barrel, include: ./middleware/*.ts, nodir: false }
|
|
2
2
|
export * from "./middleware/ContextProvider.js"
|
|
3
3
|
export * from "./middleware/dynamic-middleware.js"
|
|
4
|
-
export * from "./middleware/DynamicMiddleware.js"
|
|
5
4
|
export * from "./middleware/generic-middleware.js"
|
|
6
5
|
export * from "./middleware/middleware-api.js"
|
|
7
6
|
export * from "./middleware/middleware.js"
|
|
7
|
+
export * from "./middleware/RouterMiddleware.js"
|
|
8
|
+
export * from "./middleware/RpcMiddleware.js"
|
|
8
9
|
// codegen:end
|
|
9
10
|
|
|
10
11
|
export * as Middleware from "./middleware.js"
|