@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/layerUtils.d.ts +1 -5
  3. package/dist/api/layerUtils.d.ts.map +1 -1
  4. package/dist/api/layerUtils.js +3 -11
  5. package/dist/api/routing/middleware/RouterMiddleware.d.ts +33 -0
  6. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -0
  7. package/dist/api/routing/middleware/RouterMiddleware.js +5 -0
  8. package/dist/api/routing/middleware/RpcMiddleware.d.ts +199 -0
  9. package/dist/api/routing/middleware/RpcMiddleware.d.ts.map +1 -0
  10. package/dist/api/routing/middleware/RpcMiddleware.js +14 -0
  11. package/dist/api/routing/middleware/dynamic-middleware.d.ts +2 -12
  12. package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +1 -1
  13. package/dist/api/routing/middleware/dynamic-middleware.js +2 -18
  14. package/dist/api/routing/middleware/generic-middleware.d.ts +4 -23
  15. package/dist/api/routing/middleware/generic-middleware.d.ts.map +1 -1
  16. package/dist/api/routing/middleware/generic-middleware.js +13 -9
  17. package/dist/api/routing/middleware/middleware-api.d.ts +27 -7
  18. package/dist/api/routing/middleware/middleware-api.d.ts.map +1 -1
  19. package/dist/api/routing/middleware/middleware-api.js +47 -22
  20. package/dist/api/routing/middleware/middleware.d.ts +3 -3
  21. package/dist/api/routing/middleware/middleware.js +2 -2
  22. package/dist/api/routing/middleware.d.ts +2 -1
  23. package/dist/api/routing/middleware.d.ts.map +1 -1
  24. package/dist/api/routing/middleware.js +3 -2
  25. package/dist/api/routing/tsort.d.ts +2 -2
  26. package/dist/api/routing/tsort.d.ts.map +1 -1
  27. package/dist/api/routing/tsort.js +1 -1
  28. package/package.json +9 -5
  29. package/src/api/layerUtils.ts +4 -18
  30. package/src/api/routing/middleware/RouterMiddleware.ts +149 -0
  31. package/src/api/routing/middleware/RpcMiddleware.ts +287 -0
  32. package/src/api/routing/middleware/dynamic-middleware.ts +5 -52
  33. package/src/api/routing/middleware/generic-middleware.ts +23 -34
  34. package/src/api/routing/middleware/middleware-api.ts +107 -45
  35. package/src/api/routing/middleware/middleware.ts +1 -1
  36. package/src/api/routing/middleware.ts +2 -1
  37. package/src/api/routing/tsort.ts +2 -2
  38. package/test/controller.test.ts +22 -58
  39. package/test/dist/controller.test.d.ts.map +1 -1
  40. package/test/dist/fixtures.d.ts +4 -2
  41. package/test/dist/fixtures.d.ts.map +1 -1
  42. package/test/dist/fixtures.js +21 -9
  43. package/test/dist/requires.test.d.ts.map +1 -1
  44. package/test/fixtures.ts +29 -12
  45. package/test/requires.test.ts +98 -17
  46. package/dist/api/routing/middleware/DynamicMiddleware.d.ts +0 -229
  47. package/dist/api/routing/middleware/DynamicMiddleware.d.ts.map +0 -1
  48. package/dist/api/routing/middleware/DynamicMiddleware.js +0 -168
  49. 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 { Array, type Context, Effect, type S } from "effect-app"
3
- import { type GetEffectContext, type RPCContextMap } from "effect-app/client"
4
- import { type Tag } from "effect-app/Context"
5
- import { type HttpHeaders } from "effect-app/http"
6
- import { typedValuesOf } from "effect-app/utils"
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, type TypeId } from "@effect/rpc/RpcMiddleware"
4
- import { type Array, Context, Effect, type Layer, type NonEmptyReadonlyArray, type Schema, type Scope } from "effect-app"
5
- import { type RPCContextMap } from "effect-app/client"
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 TagClassDynamicAny } from "./DynamicMiddleware.js"
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 as any }))
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 { Array, Either, type NonEmptyArray } from "effect-app"
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 DynamicMiddlewareMaker, type GenericMiddlewareMaker, makeMiddleware, type makeMiddlewareBasic, type RequestContextMapProvider } from "../../routing.js"
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: ContextMap should be physical Tag (so typeof Tag), so that we can retrieve Identifier and Service separately.
6
- // in Service classes and TagId, the Id and Service are the same, but don't have to be in classic Tag or GenericTag.
7
- export const contextMap = <RequestContextMap>() => <K extends keyof RequestContextMap>(a: K) => ({
8
- key: a,
9
- settings: null as any as RequestContextMap[typeof a]
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
- // TODO: this still allows to mix both types of middleware but with bad typing result
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
- ): [MW] extends [NonEmptyArray<DynamicMiddlewareMaker<RequestContext>>] ? DynamicMiddlewareMakerrsss<
53
+ ): MW extends NonEmptyArray<{ dynamic: RpcDynamic<any, RequestContext[keyof RequestContext]> }>
54
+ ? DynamicMiddlewareMakerrsss<
43
55
  RequestContext,
44
- Provided | MW[number]["dynamic"]["key"],
45
- Middlewares,
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 makeNewMiddleware: <
112
+ export const makeMiddleware: <
107
113
  RequestContextMap extends Record<string, RPCContextMap.Any>
108
114
  >() => DynamicMiddlewareMakerrsss<RequestContextMap> = () => {
109
- const make = makeMiddleware<any>()
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
- capturedMiddlewares = [mw, ...capturedMiddlewares]
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(make({ genericMiddlewares, dynamicMiddlewares }), it)
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 "../middleware.js"
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"