@effect-app/infra 2.1.1 → 2.2.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.
@@ -7,13 +7,26 @@ TODO: uninteruptible commands! was for All except GET.
7
7
  */
8
8
  import type * as HttpApp from "@effect/platform/HttpApp"
9
9
  import { Rpc, RpcRouter } from "@effect/rpc"
10
- import { Cause, Chunk, Context, Effect, FiberRef, Predicate, S, Schema, Stream } from "effect-app"
10
+ import type { NonEmptyArray } from "effect-app"
11
+ import {
12
+ Cause,
13
+ Chunk,
14
+ Context,
15
+ Effect,
16
+ FiberRef,
17
+ flow,
18
+ Layer,
19
+ Predicate,
20
+ S,
21
+ Schema,
22
+ Scope,
23
+ Stream,
24
+ Tracer
25
+ } from "effect-app"
11
26
  import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
12
- import { allLower, type EffectUnunified, type LowerServices } from "effect-app/Effect"
13
27
  import type { HttpServerError } from "effect-app/http"
14
- import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
15
- import { pretty, typedKeysOf } from "effect-app/utils"
16
- import type { Compute } from "effect-app/utils"
28
+ import { HttpMiddleware, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
29
+ import { pretty, typedKeysOf, typedValuesOf } from "effect-app/utils"
17
30
  import { logError, reportError } from "../errorReporter.js"
18
31
  import { InfraLogger } from "../logger.js"
19
32
  import type { Middleware } from "./routing/DynamicMiddleware.js"
@@ -79,39 +92,6 @@ export const toHttpApp = <R extends RpcRouter.RpcRouter<any, any>>(self: R, opti
79
92
  })
80
93
  }
81
94
 
82
- type GetRouteContext<CTXMap extends Record<string, RPCContextMap.Any>, T> =
83
- // & CTX
84
- // inverted
85
- & {
86
- [
87
- key in keyof CTXMap as CTXMap[key][3] extends true ? never
88
- : key extends keyof T ? T[key] extends true ? CTXMap[key][0] : never
89
- : never
90
- ]?: CTXMap[key][1]
91
- }
92
- & {
93
- [
94
- key in keyof CTXMap as CTXMap[key][3] extends true ? never
95
- : key extends keyof T ? T[key] extends false ? CTXMap[key][0] : never
96
- : CTXMap[key][0]
97
- ]: CTXMap[key][1]
98
- }
99
- // normal
100
- & {
101
- [
102
- key in keyof CTXMap as CTXMap[key][3] extends false ? never
103
- : key extends keyof T ? T[key] extends true ? CTXMap[key][0] : never
104
- : never
105
- ]: CTXMap[key][1]
106
- }
107
- & {
108
- [
109
- key in keyof CTXMap as CTXMap[key][3] extends false ? never
110
- : key extends keyof T ? T[key] extends false ? CTXMap[key][0] : never
111
- : CTXMap[key][0]
112
- ]?: CTXMap[key][1]
113
- }
114
-
115
95
  export interface Hint<Err extends string> {
116
96
  Err: Err
117
97
  }
@@ -129,7 +109,7 @@ type GetSuccessShape<Action extends { success?: S.Schema.Any }, RT extends "d" |
129
109
  : S.Schema.Type<GetSuccess<Action>>
130
110
  type GetFailure<T extends { failure?: S.Schema.Any }> = T["failure"] extends never ? typeof S.Never : T["failure"]
131
111
 
132
- export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> {
112
+ type HandlerFull<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> = {
133
113
  new(): {}
134
114
  _tag: RT
135
115
  stack: string
@@ -142,29 +122,29 @@ export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d"
142
122
  >
143
123
  }
144
124
 
145
- // Separate "raw" vs "d" to verify A (Encoded for "raw" vs Type for "d")
146
- type AHandler<Action extends AnyRequestModule> =
147
- | Handler<
148
- Action,
149
- "raw",
150
- S.Schema.Encoded<GetSuccess<Action>>,
151
- S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
152
- any
153
- >
154
- | Handler<
125
+ export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", R> extends
126
+ HandlerFull<
155
127
  Action,
156
- "d",
157
- S.Schema.Type<GetSuccess<Action>>,
128
+ RT,
129
+ GetSuccessShape<Action, RT>,
158
130
  S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
159
- any
131
+ R
160
132
  >
133
+ {
134
+ }
135
+
136
+ type AHandler<Action extends AnyRequestModule> = Handler<
137
+ Action,
138
+ any,
139
+ any
140
+ >
161
141
 
162
142
  type Filter<T> = {
163
143
  [K in keyof T as T[K] extends S.Schema.All & { success: S.Schema.Any; failure: S.Schema.Any } ? K : never]: T[K]
164
144
  }
165
145
 
166
- export interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCContextMap.Any>>
167
- extends Middleware<Context, CTXMap>
146
+ export interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCContextMap.Any>, R>
147
+ extends Middleware<Context, CTXMap, R>
168
148
  {
169
149
  // TODO
170
150
  makeContext: Effect<
@@ -174,11 +154,93 @@ export interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCCo
174
154
  >
175
155
  }
176
156
 
177
- export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.Any>>(
178
- middleware: ExtendedMiddleware<Context, CTXMap>,
157
+ export const RouterSymbol = Symbol()
158
+ export interface RouterShape<Rsc> {
159
+ [RouterSymbol]: Rsc
160
+ }
161
+
162
+ type RPCRouteR<T extends Rpc.Rpc<any, any>> = [T] extends [
163
+ Rpc.Rpc<any, infer R>
164
+ ] ? R
165
+ : never
166
+
167
+ type RPCRouteReq<T extends Rpc.Rpc<any, any>> = [T] extends [
168
+ Rpc.Rpc<infer Req, any>
169
+ ] ? Req
170
+ : never
171
+
172
+ type Match<
173
+ Rsc extends Record<string, any>,
174
+ CTXMap extends Record<string, any>,
175
+ RT extends "raw" | "d",
176
+ Key extends keyof Rsc,
177
+ Context
178
+ > = {
179
+ // TODO: deal with HandleVoid and ability to extends from GetSuccessShape...
180
+ // aka we want to make sure that the return type is void if the success is void,
181
+ // and make sure A is the actual expected type
182
+
183
+ // note: the defaults of = never prevent the whole router to error
184
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
185
+ f: Effect<A, E, R2>
186
+ ): HandleVoid<
187
+ GetSuccessShape<Rsc[Key], RT>,
188
+ A,
189
+ Handler<
190
+ Rsc[Key],
191
+ RT,
192
+ Exclude<
193
+ Context | Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
194
+ HttpRouter.HttpRouter.Provided
195
+ >
196
+ >
197
+ >
198
+
199
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
200
+ f: (req: S.Schema.Type<Rsc[Key]>) => Effect<A, E, R2>
201
+ ): HandleVoid<
202
+ GetSuccessShape<Rsc[Key], RT>,
203
+ A,
204
+ Handler<
205
+ Rsc[Key],
206
+ RT,
207
+ Exclude<
208
+ Context | Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
209
+ HttpRouter.HttpRouter.Provided
210
+ >
211
+ >
212
+ >
213
+ }
214
+
215
+ export type RouteMatcher<
216
+ CTXMap extends Record<string, any>,
217
+ Rsc extends Record<string, any>,
218
+ Context
219
+ > = {
220
+ // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
221
+ /**
222
+ * Requires the Type shape
223
+ */
224
+ [Key in keyof Filter<Rsc>]: Match<Rsc, CTXMap, "d", Key, Context> & {
225
+ success: Rsc[Key]["success"]
226
+ successRaw: S.SchemaClass<S.Schema.Encoded<Rsc[Key]["success"]>>
227
+ failure: Rsc[Key]["failure"]
228
+ /**
229
+ * Requires the Encoded shape (e.g directly undecoded from DB, so that we don't do multiple Decode/Encode)
230
+ */
231
+ raw: Match<Rsc, CTXMap, "raw", Key, Context>
232
+ }
233
+ }
234
+ // export interface RouteMatcher<
235
+ // Filtered extends Record<string, any>,
236
+ // CTXMap extends Record<string, any>,
237
+ // Rsc extends Filtered
238
+ // > extends RouteMatcherInt<Filtered, CTXMap, Rsc> {}
239
+
240
+ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.Any>, RMW>(
241
+ middleware: ExtendedMiddleware<Context, CTXMap, RMW>,
179
242
  devMode: boolean
180
243
  ) => {
181
- const rpc = makeRpc(middleware)
182
244
  function matchFor<
183
245
  const ModuleName extends string,
184
246
  const Rsc extends Record<string, any>
@@ -186,7 +248,6 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
186
248
  rsc: Rsc & { meta: { moduleName: ModuleName } }
187
249
  ) {
188
250
  const meta = rsc.meta
189
-
190
251
  type Filtered = Filter<Rsc>
191
252
  const filtered = typedKeysOf(rsc).reduce((acc, cur) => {
192
253
  if (Predicate.isObject(rsc[cur]) && rsc[cur]["success"]) {
@@ -195,311 +256,266 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
195
256
  return acc
196
257
  }, {} as Filtered)
197
258
 
198
- const matchWithServices = <Key extends keyof Filtered>(action: Key) => {
199
- return <
200
- SVC extends Record<
201
- string,
202
- Effect<any, any, any>
203
- >,
204
- R2,
205
- E,
206
- A
207
- >(
208
- services: SVC,
209
- f: (
210
- req: S.Schema.Type<Rsc[Key]>,
211
- ctx: Compute<
212
- LowerServices<EffectDeps<SVC>> & GetRouteContext<CTXMap, Rsc[Key]["config"]> & {
213
- Response: Rsc[Key]["success"]
214
- },
215
- "flat"
216
- >
217
- ) => Effect<A, E, R2>
218
- ) =>
219
- (req: any) =>
220
- Effect.andThen(
221
- Effect.all({ svc: allLower(services), ctx: middleware.makeContext }),
222
- ({ ctx, svc }) => f(req, { ...svc, ...ctx, Response: rsc[action].success } as any)
223
- )
224
- }
225
-
226
- type MatchWithServicesNew<RT extends "raw" | "d", Key extends keyof Rsc> = {
227
- <R2, E, A>(
228
- f: Effect<A, E, R2>
229
- ): HandleVoid<
230
- GetSuccessShape<Rsc[Key], RT>,
231
- A,
232
- Handler<
233
- Rsc[Key],
234
- RT,
235
- A,
236
- E,
237
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
238
- >
239
- >
240
-
241
- <R2, E, A>(
242
- f: (
243
- req: S.Schema.Type<Rsc[Key]>,
244
- ctx: GetRouteContext<CTXMap, Rsc[Key]["config"]> & { Response: Rsc[Key]["success"] }
245
- ) => Effect<A, E, R2>
246
- ): HandleVoid<
247
- GetSuccessShape<Rsc[Key], RT>,
248
- A,
249
- Handler<
250
- Rsc[Key],
251
- RT,
252
- A,
253
- E,
254
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
255
- >
256
- >
257
-
258
- <
259
- SVC extends Record<
260
- string,
261
- EffectUnunified<any, any, any>
262
- >,
263
- R2,
264
- E,
265
- A
266
- >(
267
- services: SVC,
268
- f: (
269
- req: S.Schema.Type<Rsc[Key]>,
270
- ctx: Compute<
271
- LowerServices<EffectDeps<SVC>> & GetRouteContext<CTXMap, Rsc[Key]["config"]> & {
272
- Response: Rsc[Key]["success"]
273
- },
274
- "flat"
275
- >
276
- ) => Effect<A, E, R2>
277
- ): HandleVoid<
278
- GetSuccessShape<Rsc[Key], RT>,
279
- A,
280
- Handler<
281
- Rsc[Key],
282
- RT,
283
- A,
284
- E,
285
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
286
- >
287
- >
288
- }
259
+ const items = typedKeysOf(filtered).reduce(
260
+ (prev, cur) => {
261
+ ;(prev as any)[cur] = Object.assign((fnOrEffect: any) => {
262
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
263
+ return Effect.isEffect(fnOrEffect)
264
+ ? class {
265
+ static stack = stack
266
+ static _tag = "d"
267
+ static handler = () => fnOrEffect
268
+ }
269
+ : class {
270
+ static stack = stack
271
+ static _tag = "d"
272
+ static handler = fnOrEffect
273
+ }
274
+ }, {
275
+ success: rsc[cur].success,
276
+ successRaw: S.encodedSchema(rsc[cur].success),
277
+ failure: rsc[cur].failure,
278
+ raw: // "Raw" variations are for when you don't want to decode just to encode it again on the response
279
+ // e.g for direct projection from DB
280
+ // but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
281
+ (fnOrEffect: any) => {
282
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
283
+ return Effect.isEffect(fnOrEffect)
284
+ ? class {
285
+ static stack = stack
286
+ static _tag = "raw"
287
+ static handler = () => fnOrEffect
288
+ }
289
+ : class {
290
+ static stack = stack
291
+ static _tag = "raw"
292
+ static handler = (req: any, ctx: any) => fnOrEffect(req, { ...ctx, Response: rsc[cur].success })
293
+ }
294
+ }
295
+ })
296
+ return prev
297
+ },
298
+ {} as RouteMatcher<CTXMap, Rsc, Context>
299
+ )
289
300
 
290
301
  type Keys = keyof Filtered
291
302
 
292
- const controllers = <
303
+ const effect = <
304
+ E,
305
+ R,
293
306
  THandlers extends {
294
307
  // import to keep them separate via | for type checking!!
295
308
  [K in Keys]: AHandler<Rsc[K]>
296
- }
309
+ },
310
+ TLayers extends NonEmptyArray<Layer.Layer.Any>
297
311
  >(
298
- controllers: THandlers
312
+ layers: TLayers,
313
+ make: (requests: typeof items) => Effect<THandlers, E, R>
299
314
  ) => {
300
- const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
301
- const handler = controllers[cur as keyof typeof controllers]
302
- const req = rsc[cur]
303
-
304
- acc[cur] = rpc.effect(
305
- handler._tag === "raw"
306
- ? class extends (req as any) {
307
- static success = S.encodedSchema(req.success)
308
- get [Schema.symbolSerializable]() {
309
- return this.constructor
310
- }
311
- get [Schema.symbolWithResult]() {
312
- return {
313
- failure: req.failure,
314
- success: S.encodedSchema(req.success)
315
- }
316
- }
317
- } as any
318
- : req,
319
- (req) =>
320
- Effect
321
- .annotateCurrentSpan(
322
- "requestInput",
323
- Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
324
- prev[key] = key === "password"
325
- ? "<redacted>"
326
- : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
327
- ? typeof value === "string" && value.length > 256
328
- ? (value.substring(0, 253) + "...")
329
- : value
330
- : Array.isArray(value)
331
- ? `Array[${value.length}]`
332
- : value === null || value === undefined
333
- ? `${value}`
334
- : typeof value === "object" && value
335
- ? `Object[${Object.keys(value).length}]`
336
- : typeof value
337
- return prev
338
- }, {} as Record<string, string | number | boolean>)
339
- )
340
- .pipe(
341
- // can't use andThen due to some being a function and effect
342
- Effect.zipRight(handler.handler(req as any) as any),
343
- Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
344
- Effect.tapDefect((cause) =>
345
- Effect
346
- .all([
347
- reportRequestError(cause, {
348
- action: `${meta.moduleName}.${req._tag}`
349
- }),
350
- Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
351
- return InfraLogger
352
- .logError("Finished request", cause)
353
- .pipe(Effect.annotateLogs({
354
- action: `${meta.moduleName}.${req._tag}`,
355
- req: pretty(req),
356
- headers: pretty(headers)
357
- // resHeaders: pretty(
358
- // Object
359
- // .entries(headers)
360
- // .reduce((prev, [key, value]) => {
361
- // prev[key] = value && typeof value === "string" ? snipString(value) : value
362
- // return prev
363
- // }, {} as Record<string, any>)
364
- // )
365
- }))
366
- }))
367
- ])
368
- ),
369
- devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
370
- Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
371
- captureStackTrace: () => handler.stack
372
- })
373
- ),
374
- meta.moduleName
375
- ) // TODO
376
- return acc
377
- }, {} as any) as {
378
- [K in Keys]: Rpc.Rpc<
379
- Rsc[K],
380
- _R<ReturnType<THandlers[K]["handler"]>>
315
+ type Router = RouterShape<Rsc>
316
+ const r: HttpRouter.HttpRouter.TagClass<
317
+ Router,
318
+ `${typeof meta.moduleName}Router`,
319
+ never,
320
+ Exclude<
321
+ | Context
322
+ | RPCRouteR<
323
+ { [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
324
+ >,
325
+ | { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
326
+ | HttpRouter.HttpRouter.Provided
381
327
  >
382
- }
383
-
384
- type RPCRouteR<T extends Rpc.Rpc<any, any>> = [T] extends [
385
- Rpc.Rpc<any, infer R>
386
- ] ? R
387
- : never
328
+ > = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
329
+
330
+ const layer = r.use((router) =>
331
+ Effect.gen(function*() {
332
+ const controllers = yield* make(items)
333
+ const rpc = yield* makeRpc(middleware)
334
+
335
+ // return make.pipe(Effect.map((c) => controllers(c, layers)))
336
+ const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
337
+ const handler = controllers[cur as keyof typeof controllers]
338
+ const req = rsc[cur]
339
+
340
+ acc[cur] = rpc.effect(
341
+ handler._tag === "raw"
342
+ ? class extends (req as any) {
343
+ static success = S.encodedSchema(req.success)
344
+ get [Schema.symbolSerializable]() {
345
+ return this.constructor
346
+ }
347
+ get [Schema.symbolWithResult]() {
348
+ return {
349
+ failure: req.failure,
350
+ success: S.encodedSchema(req.success)
351
+ }
352
+ }
353
+ } as any
354
+ : req,
355
+ (req) =>
356
+ Effect
357
+ .annotateCurrentSpan(
358
+ "requestInput",
359
+ Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
360
+ prev[key] = key === "password"
361
+ ? "<redacted>"
362
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
363
+ ? typeof value === "string" && value.length > 256
364
+ ? (value.substring(0, 253) + "...")
365
+ : value
366
+ : Array.isArray(value)
367
+ ? `Array[${value.length}]`
368
+ : value === null || value === undefined
369
+ ? `${value}`
370
+ : typeof value === "object" && value
371
+ ? `Object[${Object.keys(value).length}]`
372
+ : typeof value
373
+ return prev
374
+ }, {} as Record<string, string | number | boolean>)
375
+ )
376
+ .pipe(
377
+ // can't use andThen due to some being a function and effect
378
+ Effect.zipRight(handler.handler(req as any) as any),
379
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
380
+ Effect.tapDefect((cause) =>
381
+ Effect
382
+ .all([
383
+ reportRequestError(cause, {
384
+ action: `${meta.moduleName}.${req._tag}`
385
+ }),
386
+ Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
387
+ return InfraLogger
388
+ .logError("Finished request", cause)
389
+ .pipe(Effect.annotateLogs({
390
+ action: `${meta.moduleName}.${req._tag}`,
391
+ req: pretty(req),
392
+ headers: pretty(headers)
393
+ // resHeaders: pretty(
394
+ // Object
395
+ // .entries(headers)
396
+ // .reduce((prev, [key, value]) => {
397
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
398
+ // return prev
399
+ // }, {} as Record<string, any>)
400
+ // )
401
+ }))
402
+ }))
403
+ ])
404
+ ),
405
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
406
+ Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
407
+ captureStackTrace: () => handler.stack
408
+ })
409
+ ),
410
+ meta.moduleName
411
+ ) // TODO
412
+ return acc
413
+ }, {} as any) as {
414
+ [K in Keys]: Rpc.Rpc<
415
+ Rsc[K],
416
+ Context | _R<ReturnType<THandlers[K]["handler"]>>
417
+ >
418
+ }
388
419
 
389
- type RPCRouteReq<T extends Rpc.Rpc<any, any>> = [T] extends [
390
- Rpc.Rpc<infer Req, any>
391
- ] ? Req
392
- : never
420
+ const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
421
+ RPCRouteReq<typeof mapped[keyof typeof mapped]>,
422
+ RPCRouteR<typeof mapped[keyof typeof mapped]>
423
+ >
424
+ const httpApp = toHttpApp(rpcRouter, {
425
+ spanPrefix: rsc
426
+ .meta
427
+ .moduleName + "."
428
+ })
429
+ const services = (yield* Effect.context<never>()).pipe(
430
+ Context.omit(Scope.Scope as never),
431
+ Context.omit(Tracer.ParentSpan as never)
432
+ )
433
+ yield* router
434
+ .all(
435
+ "/",
436
+ (httpApp
437
+ .pipe(HttpMiddleware.make(Effect.provide(services)))) as any,
438
+ // TODO: not queries.
439
+ { uninterruptible: true }
440
+ )
441
+ })
442
+ )
393
443
 
394
- const router = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
395
- RPCRouteReq<typeof mapped[keyof typeof mapped]>,
396
- RPCRouteR<typeof mapped[keyof typeof mapped]>
444
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
445
+ const routes = layer.pipe(
446
+ Layer.provideMerge(r.Live),
447
+ layers ? Layer.provide(layers) as any : (_) => _
448
+ ) as Layer.Layer<
449
+ Router,
450
+ { [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number] | E,
451
+ | { [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
452
+ | RMW
453
+ | Exclude<R, { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]>
397
454
  >
398
455
 
399
- return HttpRouter.empty.pipe(
400
- HttpRouter.all(
401
- ("/rpc/" + rsc.meta.moduleName) as any,
402
- toHttpApp(router, { spanPrefix: rsc.meta.moduleName + "." }),
403
- // TODO: not queries.
404
- { uninterruptible: true }
405
- )
406
- )
407
- }
456
+ // Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
408
457
 
409
- const r = {
410
- controllers,
411
- ...typedKeysOf(filtered).reduce(
412
- (prev, cur) => {
413
- ;(prev as any)[cur] = (svcOrFnOrEffect: any, fnOrNone: any) => {
414
- const stack = new Error().stack?.split("\n").slice(2).join("\n")
415
- return Effect.isEffect(svcOrFnOrEffect)
416
- ? class {
417
- static stack = stack
418
- static _tag = "d"
419
- static handler = () => svcOrFnOrEffect
420
- }
421
- : typeof svcOrFnOrEffect === "function"
422
- ? class {
423
- static stack = stack
424
- static _tag = "d"
425
- static handler = (req: any) =>
426
- Effect.andThen(
427
- Effect.all({ ctx: middleware.makeContext }),
428
- ({ ctx }) => svcOrFnOrEffect(req, { ...ctx, Response: rsc[cur].success })
429
- )
430
- }
431
- : class {
432
- static stack = stack
433
- static _tag = "d"
434
- static handler = matchWithServices(cur)(svcOrFnOrEffect, fnOrNone)
435
- }
436
- } // "Raw" variations are for when you don't want to decode just to encode it again on the response
437
- // e.g for direct projection from DB
438
- // but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
439
- ;(prev as any)[(cur as any) + "Raw"] = (svcOrFnOrEffect: any, fnOrNone: any) => {
440
- const stack = new Error().stack?.split("\n").slice(2).join("\n")
441
- return Effect.isEffect(svcOrFnOrEffect)
442
- ? class {
443
- static stack = stack
444
- static _tag = "raw"
445
- static handler = () => svcOrFnOrEffect
446
- }
447
- : typeof svcOrFnOrEffect === "function"
448
- ? class {
449
- static stack = stack
450
- static _tag = "raw"
451
- static handler = (req: any, ctx: any) => svcOrFnOrEffect(req, { ...ctx, Response: rsc[cur].success })
452
- }
453
- : class {
454
- static stack = stack
455
- static _tag = "raw"
456
- static handler = matchWithServices(cur)(svcOrFnOrEffect, fnOrNone)
457
- }
458
- }
459
- return prev
460
- },
461
- {} as
462
- & {
463
- // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
464
- /**
465
- * Requires the Type shape
466
- */
467
- [Key in keyof Filtered]: MatchWithServicesNew<"d", Key>
468
- }
469
- & {
470
- // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
471
- /**
472
- * Requires the Encoded shape (e.g directly undecoded from DB, so that we don't do multiple Decode/Encode)
473
- */
474
- [Key in keyof Filtered as Key extends string ? `${Key}Raw` : never]: MatchWithServicesNew<"raw", Key>
475
- }
476
- )
458
+ return {
459
+ moduleName: meta.moduleName,
460
+ Router: r,
461
+ routes
462
+ }
477
463
  }
478
- return r
464
+
465
+ return effect
479
466
  }
480
467
 
468
+ type HR<T> = T extends HttpRouter.HttpRouter<any, infer R> ? R : never
469
+ type HE<T> = T extends HttpRouter.HttpRouter<infer E, any> ? E : never
470
+
481
471
  type RequestHandlersTest = {
482
- [key: string]: HttpRouter.HttpRouter<any, any>
472
+ [key: string]: {
473
+ Router: { router: Effect<HttpRouter.HttpRouter<any, any>, any, any> }
474
+ routes: Layer.Layer<any, any, any>
475
+ moduleName: string
476
+ }
483
477
  }
484
- function matchAll<T extends RequestHandlersTest>(handlers: T) {
485
- const r = typedKeysOf(handlers).reduce((acc, cur) => {
486
- return HttpRouter.concat(acc, handlers[cur] as any)
487
- }, HttpRouter.empty)
488
-
489
- type _RRouter<T extends HttpRouter.HttpRouter<any, any>> = [T] extends [
490
- HttpRouter.HttpRouter<any, infer R>
491
- ] ? R
492
- : never
493
-
494
- type _ERouter<T extends HttpRouter.HttpRouter<any, any>> = [T] extends [
495
- HttpRouter.HttpRouter<infer E, any>
496
- ] ? E
497
- : never
498
-
499
- return r as HttpRouter.HttpRouter<
500
- _ERouter<typeof handlers[keyof typeof handlers]>,
501
- _RRouter<typeof handlers[keyof typeof handlers]>
502
- >
478
+ function matchAll<T extends RequestHandlersTest, A, E, R>(
479
+ handlers: T,
480
+ requestLayer: Layer.Layer<A, E, R>
481
+ ) {
482
+ const routers = typedValuesOf(handlers)
483
+
484
+ const rootRouter = class extends HttpRouter.Tag("RootRouter")<
485
+ "RootRouter",
486
+ HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
487
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>
488
+ >() {}
489
+
490
+ const r = rootRouter
491
+ .use((router) =>
492
+ Effect.gen(function*() {
493
+ for (const route of routers) {
494
+ yield* router.mount(
495
+ ("/rpc/" + route.moduleName) as any,
496
+ yield* route
497
+ .Router
498
+ .router
499
+ .pipe(Effect.map(HttpRouter.use(flow(Effect.provide(requestLayer))))) as any
500
+ )
501
+ }
502
+ })
503
+ )
504
+ .pipe(Layer.provide(routers.map((r) => r.routes).flat() as unknown as NonEmptyArray<Layer.Layer.Any>))
505
+
506
+ return {
507
+ layer: r as Layer.Layer<
508
+ never,
509
+ Layer.Layer.Error<typeof handlers[keyof typeof handlers]["routes"]>,
510
+ Layer.Layer.Context<typeof handlers[keyof typeof handlers]["routes"]>
511
+ >,
512
+ Router: rootRouter as any as HttpRouter.HttpRouter.TagClass<
513
+ "RootRouter",
514
+ "RootRouter",
515
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
516
+ R | Exclude<HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>, A>
517
+ >
518
+ }
503
519
  }
504
520
 
505
521
  return { matchAll, matchFor }