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