@effect-app/infra 1.41.6 → 1.42.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.
@@ -0,0 +1,642 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ /*
5
+ TODO: Effect.retry(r2, optimisticConcurrencySchedule) / was for PATCH only
6
+ TODO: uninteruptible commands! was for All except GET.
7
+ */
8
+ import { pretty, typedKeysOf, typedValuesOf } from "@effect-app/core/utils"
9
+ import type * as HttpApp from "@effect/platform/HttpApp"
10
+ import { Rpc, RpcRouter } from "@effect/rpc"
11
+ import { Serializable } from "@effect/schema"
12
+ import type { NonEmptyArray } from "effect-app"
13
+ import { Cause, Chunk, Context, Effect, FiberRef, flow, Layer, Predicate, S, Scope, Stream, Tracer } from "effect-app"
14
+ import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
15
+ import type { HttpServerError } from "effect-app/http"
16
+ import { HttpMiddleware, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
17
+ import { logError, reportError } from "../errorReporter.js"
18
+ import { InfraLogger } from "../logger.js"
19
+ import type { Middleware } from "./routing/DynamicMiddleware.js"
20
+ import { makeRpc } from "./routing/DynamicMiddleware.js"
21
+
22
+ const logRequestError = logError("Request")
23
+ const reportRequestError = reportError("Request")
24
+
25
+ export type _R<T extends Effect<any, any, any>> = [T] extends [
26
+ Effect<any, any, infer R>
27
+ ] ? R
28
+ : never
29
+
30
+ export type _E<T extends Effect<any, any, any>> = [T] extends [
31
+ Effect<any, infer E, any>
32
+ ] ? E
33
+ : never
34
+
35
+ export type EffectDeps<A> = {
36
+ [K in keyof A as A[K] extends Effect<any, any, any> ? K : never]: A[K] extends Effect<any, any, any> ? A[K] : never
37
+ }
38
+ /**
39
+ * Plain jane JSON version
40
+ * @deprecated use HttpRpcRouterNoStream.toHttpApp once support options
41
+ */
42
+ export const toHttpApp = <R extends RpcRouter.RpcRouter<any, any>>(self: R, options?: {
43
+ readonly spanPrefix?: string
44
+ }): HttpApp.Default<
45
+ HttpServerError.RequestError,
46
+ RpcRouter.RpcRouter.Context<R>
47
+ > => {
48
+ const handler = RpcRouter.toHandler(self, options)
49
+ return Effect.withFiberRuntime((fiber) => {
50
+ const context = fiber.getFiberRef(FiberRef.currentContext)
51
+ const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
52
+ return Effect.flatMap(
53
+ request.json,
54
+ (_) =>
55
+ handler(_).pipe(
56
+ Stream.provideContext(context),
57
+ Stream.runCollect,
58
+ Effect.map((_) => Chunk.toReadonlyArray(_)),
59
+ Effect.andThen((_) => {
60
+ let status = 200
61
+ for (const r of _.flat()) {
62
+ if (typeof r === "number") continue
63
+ const results = Array.isArray(r) ? r : [r]
64
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Die")) {
65
+ status = 500
66
+ break
67
+ }
68
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Fail")) {
69
+ status = 422 // 418
70
+ break
71
+ }
72
+ }
73
+ return HttpServerResponse.json(_, { status })
74
+ }),
75
+ Effect.orDie,
76
+ Effect.tapDefect(reportError("RPCHttpApp"))
77
+ )
78
+ )
79
+ })
80
+ }
81
+
82
+ export interface Hint<Err extends string> {
83
+ Err: Err
84
+ }
85
+
86
+ type HandleVoid<Expected, Actual, Result> = [Expected] extends [void]
87
+ ? [Actual] extends [void] ? Result : Hint<"You're returning non void for a void Response, please fix">
88
+ : Result
89
+
90
+ type AnyRequestModule = S.Schema.Any & { success?: S.Schema.Any; failure?: S.Schema.Any }
91
+
92
+ type GetSuccess<T> = T extends { success: S.Schema.Any } ? T["success"] : typeof S.Void
93
+
94
+ type GetSuccessShape<Action extends { success?: S.Schema.Any }, RT extends "d" | "raw"> = RT extends "raw"
95
+ ? S.Schema.Encoded<GetSuccess<Action>>
96
+ : S.Schema.Type<GetSuccess<Action>>
97
+ type GetFailure<T extends { failure?: S.Schema.Any }> = T["failure"] extends never ? typeof S.Never : T["failure"]
98
+
99
+ type HandlerFull<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> = {
100
+ new(): {}
101
+ _tag: RT
102
+ stack: string
103
+ handler: (
104
+ req: S.Schema.Type<Action>
105
+ ) => Effect<
106
+ A,
107
+ E,
108
+ R
109
+ >
110
+ }
111
+
112
+ export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", R> extends
113
+ HandlerFull<
114
+ Action,
115
+ RT,
116
+ GetSuccessShape<Action, RT>,
117
+ S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
118
+ R
119
+ >
120
+ {
121
+ }
122
+
123
+ type AHandler<Action extends AnyRequestModule> = Handler<
124
+ Action,
125
+ any,
126
+ any
127
+ >
128
+
129
+ type Filter<T> = {
130
+ [K in keyof T as T[K] extends S.Schema.All & { success: S.Schema.Any; failure: S.Schema.Any } ? K : never]: T[K]
131
+ }
132
+
133
+ export interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCContextMap.Any>>
134
+ extends Middleware<Context, CTXMap>
135
+ {
136
+ // TODO
137
+ makeContext: Effect<
138
+ { [K in keyof CTXMap as CTXMap[K][1] extends never ? never : CTXMap[K][0]]: CTXMap[K][1] },
139
+ never,
140
+ never
141
+ >
142
+ }
143
+
144
+ export const RouterSymbol = Symbol()
145
+ export interface RouterShape<Rsc> {
146
+ [RouterSymbol]: Rsc
147
+ }
148
+
149
+ type RPCRouteR<T extends Rpc.Rpc<any, any>> = [T] extends [
150
+ Rpc.Rpc<any, infer R>
151
+ ] ? R
152
+ : never
153
+
154
+ type RPCRouteReq<T extends Rpc.Rpc<any, any>> = [T] extends [
155
+ Rpc.Rpc<infer Req, any>
156
+ ] ? Req
157
+ : never
158
+
159
+ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.Any>>(
160
+ middleware: ExtendedMiddleware<Context, CTXMap>,
161
+ devMode: boolean
162
+ ) => {
163
+ const rpc = makeRpc(middleware)
164
+ function matchFor<
165
+ const ModuleName extends string,
166
+ const Rsc extends Record<string, any>
167
+ >(
168
+ rsc: Rsc & { meta: { moduleName: ModuleName } }
169
+ ) {
170
+ const meta = rsc.meta
171
+ type Filtered = Filter<Rsc>
172
+ const filtered = typedKeysOf(rsc).reduce((acc, cur) => {
173
+ if (Predicate.isObject(rsc[cur]) && rsc[cur]["success"]) {
174
+ acc[cur as keyof Filtered] = rsc[cur]
175
+ }
176
+ return acc
177
+ }, {} as Filtered)
178
+
179
+ type MatchWithServicesNew2<RT extends "raw" | "d", Key extends keyof Rsc> = {
180
+ success: RT extends "raw" ? S.SchemaClass<S.Schema.Encoded<Rsc[Key]["success"]>> : Rsc[Key]["success"]
181
+ failure: Rsc[Key]["failure"]
182
+
183
+ // TODO: deal with HandleVoid and ability to extends from GetSuccessShape...
184
+ // aka we want to make sure that the return type is void if the success is void,
185
+ // and make sure A is the actual expected type
186
+
187
+ // note: the defaults of = never prevent the whole router to error
188
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
189
+ f: Effect<A, E, R2>
190
+ ): HandleVoid<
191
+ GetSuccessShape<Rsc[Key], RT>,
192
+ A,
193
+ Handler<
194
+ Rsc[Key],
195
+ RT,
196
+ Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
197
+ >
198
+ >
199
+
200
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
201
+ f: (req: S.Schema.Type<Rsc[Key]>) => Effect<A, E, R2>
202
+ ): HandleVoid<
203
+ GetSuccessShape<Rsc[Key], RT>,
204
+ A,
205
+ Handler<
206
+ Rsc[Key],
207
+ RT,
208
+ Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
209
+ >
210
+ >
211
+ }
212
+
213
+ const items = typedKeysOf(filtered).reduce(
214
+ (prev, cur) => {
215
+ ;(prev as any)[cur] = Object.assign((fnOrEffect: any) => {
216
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
217
+ return Effect.isEffect(fnOrEffect)
218
+ ? class {
219
+ static stack = stack
220
+ static _tag = "d"
221
+ static handler = () => fnOrEffect
222
+ }
223
+ : class {
224
+ static stack = stack
225
+ static _tag = "d"
226
+ static handler = fnOrEffect
227
+ }
228
+ }, {
229
+ success: rsc[cur].success,
230
+ failure: rsc[cur].failure
231
+ }) // "Raw" variations are for when you don't want to decode just to encode it again on the response
232
+ // e.g for direct projection from DB
233
+ // but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
234
+ ;(prev as any)[(cur as any) + "Raw"] = Object.assign((fnOrEffect: any) => {
235
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
236
+ return Effect.isEffect(fnOrEffect)
237
+ ? class {
238
+ static stack = stack
239
+ static _tag = "raw"
240
+ static handler = () => fnOrEffect
241
+ }
242
+ : class {
243
+ static stack = stack
244
+ static _tag = "raw"
245
+ static handler = (req: any, ctx: any) => fnOrEffect(req, { ...ctx, Response: rsc[cur].success })
246
+ }
247
+ }, {
248
+ success: S.encodedSchema(rsc[cur].success),
249
+ failure: rsc[cur].failure
250
+ })
251
+ return prev
252
+ },
253
+ {} as
254
+ & {
255
+ // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
256
+ /**
257
+ * Requires the Type shape
258
+ */
259
+ [Key in keyof Filtered]: MatchWithServicesNew2<"d", Key>
260
+ }
261
+ & {
262
+ // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
263
+ /**
264
+ * Requires the Encoded shape (e.g directly undecoded from DB, so that we don't do multiple Decode/Encode)
265
+ */
266
+ [Key in keyof Filtered as Key extends string ? `${Key}Raw` : never]: MatchWithServicesNew2<"raw", Key>
267
+ }
268
+ )
269
+
270
+ type Keys = keyof Filtered
271
+
272
+ const controllers = <
273
+ THandlers extends {
274
+ // import to keep them separate via | for type checking!!
275
+ [K in Keys]: AHandler<Rsc[K]>
276
+ },
277
+ TLayers extends NonEmptyArray<Layer.Layer.Any>
278
+ >(
279
+ controllers: THandlers,
280
+ layers?: TLayers
281
+ ) => {
282
+ const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
283
+ const handler = controllers[cur as keyof typeof controllers]
284
+ const req = rsc[cur]
285
+
286
+ acc[cur] = rpc.effect(
287
+ handler._tag === "raw"
288
+ ? class extends (req as any) {
289
+ static success = S.encodedSchema(req.success)
290
+ get [Serializable.symbol]() {
291
+ return this.constructor
292
+ }
293
+ get [Serializable.symbolResult]() {
294
+ return {
295
+ failure: req.failure,
296
+ success: S.encodedSchema(req.success)
297
+ }
298
+ }
299
+ } as any
300
+ : req,
301
+ (req) =>
302
+ Effect
303
+ .annotateCurrentSpan(
304
+ "requestInput",
305
+ Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
306
+ prev[key] = key === "password"
307
+ ? "<redacted>"
308
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
309
+ ? typeof value === "string" && value.length > 256
310
+ ? (value.substring(0, 253) + "...")
311
+ : value
312
+ : Array.isArray(value)
313
+ ? `Array[${value.length}]`
314
+ : value === null || value === undefined
315
+ ? `${value}`
316
+ : typeof value === "object" && value
317
+ ? `Object[${Object.keys(value).length}]`
318
+ : typeof value
319
+ return prev
320
+ }, {} as Record<string, string | number | boolean>)
321
+ )
322
+ .pipe(
323
+ // can't use andThen due to some being a function and effect
324
+ Effect.zipRight(handler.handler(req as any) as any),
325
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
326
+ Effect.tapDefect((cause) =>
327
+ Effect
328
+ .all([
329
+ reportRequestError(cause, {
330
+ action: `${meta.moduleName}.${req._tag}`
331
+ }),
332
+ Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
333
+ return InfraLogger
334
+ .logError("Finished request", cause)
335
+ .pipe(Effect.annotateLogs({
336
+ action: `${meta.moduleName}.${req._tag}`,
337
+ req: pretty(req),
338
+ headers: pretty(headers)
339
+ // resHeaders: pretty(
340
+ // Object
341
+ // .entries(headers)
342
+ // .reduce((prev, [key, value]) => {
343
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
344
+ // return prev
345
+ // }, {} as Record<string, any>)
346
+ // )
347
+ }))
348
+ }))
349
+ ])
350
+ ),
351
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
352
+ Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
353
+ captureStackTrace: () => handler.stack
354
+ })
355
+ ),
356
+ meta.moduleName
357
+ ) // TODO
358
+ return acc
359
+ }, {} as any) as {
360
+ [K in Keys]: Rpc.Rpc<
361
+ Rsc[K],
362
+ _R<ReturnType<THandlers[K]["handler"]>>
363
+ >
364
+ }
365
+
366
+ const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
367
+ RPCRouteReq<typeof mapped[keyof typeof mapped]>,
368
+ RPCRouteR<typeof mapped[keyof typeof mapped]>
369
+ >
370
+
371
+ type Router = RouterShape<Rsc>
372
+ const r: HttpRouter.HttpRouter.TagClass<
373
+ Router,
374
+ `${typeof meta.moduleName}Router`,
375
+ never,
376
+ Exclude<
377
+ RPCRouteR<
378
+ { [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
379
+ >,
380
+ { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
381
+ >
382
+ > = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
383
+
384
+ const layer = r.use((router) =>
385
+ Effect.gen(function*() {
386
+ const httpApp = toHttpApp(rpcRouter, {
387
+ spanPrefix: rsc
388
+ .meta
389
+ .moduleName + "."
390
+ })
391
+ const services = (yield* Effect.context<never>()).pipe(
392
+ Context.omit(Scope.Scope as never),
393
+ Context.omit(Tracer.ParentSpan as never)
394
+ )
395
+ yield* router
396
+ .all(
397
+ "/",
398
+ (httpApp
399
+ .pipe(HttpMiddleware.make(Effect.provide(services)))) as any,
400
+ // TODO: not queries.
401
+ { uninterruptible: true }
402
+ )
403
+ })
404
+ )
405
+
406
+ const routes = layer.pipe(
407
+ Layer.provideMerge(r.Live),
408
+ layers ? Layer.provide(layers) : (_) => _
409
+ ) as Layer.Layer<
410
+ Router,
411
+ { [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number],
412
+ { [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
413
+ >
414
+
415
+ // Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
416
+
417
+ return {
418
+ moduleName: meta.moduleName,
419
+ Router: r,
420
+ routes
421
+ }
422
+ }
423
+
424
+ const effect = <
425
+ E,
426
+ R,
427
+ THandlers extends {
428
+ // import to keep them separate via | for type checking!!
429
+ [K in Keys]: AHandler<Rsc[K]>
430
+ },
431
+ TLayers extends NonEmptyArray<Layer.Layer.Any>
432
+ >(
433
+ layers: TLayers,
434
+ make: (requests: typeof items) => Effect<THandlers, E, R>
435
+ ) => {
436
+ type Router = RouterShape<Rsc>
437
+ const r: HttpRouter.HttpRouter.TagClass<
438
+ Router,
439
+ `${typeof meta.moduleName}Router`,
440
+ never,
441
+ Exclude<
442
+ RPCRouteR<
443
+ { [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
444
+ >,
445
+ { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
446
+ >
447
+ > = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
448
+
449
+ const layer = r.use((router) =>
450
+ Effect.gen(function*() {
451
+ const controllers = yield* make(items)
452
+ // return make.pipe(Effect.map((c) => controllers(c, layers)))
453
+ const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
454
+ const handler = controllers[cur as keyof typeof controllers]
455
+ const req = rsc[cur]
456
+
457
+ acc[cur] = rpc.effect(
458
+ handler._tag === "raw"
459
+ ? class extends (req as any) {
460
+ static success = S.encodedSchema(req.success)
461
+ get [Serializable.symbol]() {
462
+ return this.constructor
463
+ }
464
+ get [Serializable.symbolResult]() {
465
+ return {
466
+ failure: req.failure,
467
+ success: S.encodedSchema(req.success)
468
+ }
469
+ }
470
+ } as any
471
+ : req,
472
+ (req) =>
473
+ Effect
474
+ .annotateCurrentSpan(
475
+ "requestInput",
476
+ Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
477
+ prev[key] = key === "password"
478
+ ? "<redacted>"
479
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
480
+ ? typeof value === "string" && value.length > 256
481
+ ? (value.substring(0, 253) + "...")
482
+ : value
483
+ : Array.isArray(value)
484
+ ? `Array[${value.length}]`
485
+ : value === null || value === undefined
486
+ ? `${value}`
487
+ : typeof value === "object" && value
488
+ ? `Object[${Object.keys(value).length}]`
489
+ : typeof value
490
+ return prev
491
+ }, {} as Record<string, string | number | boolean>)
492
+ )
493
+ .pipe(
494
+ // can't use andThen due to some being a function and effect
495
+ Effect.zipRight(handler.handler(req as any) as any),
496
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
497
+ Effect.tapDefect((cause) =>
498
+ Effect
499
+ .all([
500
+ reportRequestError(cause, {
501
+ action: `${meta.moduleName}.${req._tag}`
502
+ }),
503
+ Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
504
+ return InfraLogger
505
+ .logError("Finished request", cause)
506
+ .pipe(Effect.annotateLogs({
507
+ action: `${meta.moduleName}.${req._tag}`,
508
+ req: pretty(req),
509
+ headers: pretty(headers)
510
+ // resHeaders: pretty(
511
+ // Object
512
+ // .entries(headers)
513
+ // .reduce((prev, [key, value]) => {
514
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
515
+ // return prev
516
+ // }, {} as Record<string, any>)
517
+ // )
518
+ }))
519
+ }))
520
+ ])
521
+ ),
522
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
523
+ Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
524
+ captureStackTrace: () => handler.stack
525
+ })
526
+ ),
527
+ meta.moduleName
528
+ ) // TODO
529
+ return acc
530
+ }, {} as any) as {
531
+ [K in Keys]: Rpc.Rpc<
532
+ Rsc[K],
533
+ _R<ReturnType<THandlers[K]["handler"]>>
534
+ >
535
+ }
536
+
537
+ const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
538
+ RPCRouteReq<typeof mapped[keyof typeof mapped]>,
539
+ RPCRouteR<typeof mapped[keyof typeof mapped]>
540
+ >
541
+ const httpApp = toHttpApp(rpcRouter, {
542
+ spanPrefix: rsc
543
+ .meta
544
+ .moduleName + "."
545
+ })
546
+ const services = (yield* Effect.context<never>()).pipe(
547
+ Context.omit(Scope.Scope as never),
548
+ Context.omit(Tracer.ParentSpan as never)
549
+ )
550
+ yield* router
551
+ .all(
552
+ "/",
553
+ (httpApp
554
+ .pipe(HttpMiddleware.make(Effect.provide(services)))) as any,
555
+ // TODO: not queries.
556
+ { uninterruptible: true }
557
+ )
558
+ })
559
+ )
560
+
561
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
562
+ const routes = layer.pipe(
563
+ Layer.provideMerge(r.Live),
564
+ layers ? Layer.provide(layers) as any : (_) => _
565
+ ) as Layer.Layer<
566
+ Router,
567
+ { [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number] | E,
568
+ | { [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
569
+ | Exclude<R, { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]>
570
+ >
571
+
572
+ // Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
573
+
574
+ return {
575
+ moduleName: meta.moduleName,
576
+ Router: r,
577
+ routes
578
+ }
579
+ }
580
+
581
+ const r = {
582
+ effect,
583
+ controllers
584
+ }
585
+ return r
586
+ }
587
+
588
+ type HR<T> = T extends HttpRouter.HttpRouter<any, infer R> ? R : never
589
+ type HE<T> = T extends HttpRouter.HttpRouter<infer E, any> ? E : never
590
+
591
+ type RequestHandlersTest = {
592
+ [key: string]: {
593
+ Router: { router: Effect<HttpRouter.HttpRouter<any, any>, any, any> }
594
+ routes: Layer.Layer<any, any, any>
595
+ moduleName: string
596
+ }
597
+ }
598
+ function matchAll<T extends RequestHandlersTest, A, E, R>(
599
+ handlers: T,
600
+ requestLayer: Layer.Layer<A, E, R>
601
+ ) {
602
+ const routers = typedValuesOf(handlers)
603
+
604
+ const rootRouter = class extends HttpRouter.Tag("RootRouter")<
605
+ "RootRouter",
606
+ HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
607
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>
608
+ >() {}
609
+
610
+ const r = rootRouter
611
+ .use((router) =>
612
+ Effect.gen(function*() {
613
+ for (const route of routers) {
614
+ yield* router.mount(
615
+ ("/rpc/" + route.moduleName) as any,
616
+ yield* route
617
+ .Router
618
+ .router
619
+ .pipe(Effect.map(HttpRouter.use(flow(Effect.provide(requestLayer))))) as any
620
+ )
621
+ }
622
+ })
623
+ )
624
+ .pipe(Layer.provide(routers.map((r) => r.routes).flat() as unknown as NonEmptyArray<Layer.Layer.Any>))
625
+
626
+ return {
627
+ layer: r as Layer.Layer<
628
+ never,
629
+ Layer.Layer.Error<typeof handlers[keyof typeof handlers]["routes"]>,
630
+ Layer.Layer.Context<typeof handlers[keyof typeof handlers]["routes"]>
631
+ >,
632
+ Router: rootRouter as any as HttpRouter.HttpRouter.TagClass<
633
+ "RootRouter",
634
+ "RootRouter",
635
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
636
+ R | Exclude<HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>, A>
637
+ >
638
+ }
639
+ }
640
+
641
+ return { matchAll, matchFor }
642
+ }
@@ -1,37 +0,0 @@
1
- // packages/infra/vitest.config.ts
2
- import { defineConfig } from "file:///Users/patrickroza/pj/effect-app/libs/node_modules/.pnpm/vite@5.2.6_@types+node@20.11.30/node_modules/vite/dist/node/index.js";
3
-
4
- // vite.config.base.ts
5
- import path from "path";
6
- import fs from "fs";
7
- var __vite_injected_original_dirname = "/Users/patrickroza/pj/effect-app/libs";
8
- function makeConfig(dirName) {
9
- const prefix = path.resolve(__vite_injected_original_dirname, "packages");
10
- const packages = fs.readdirSync(prefix).map((f) => prefix + "/" + f).filter((f) => fs.lstatSync(f).isDirectory());
11
- const cfg = {
12
- // eslint-disable-next-line @typescript-eslint/no-var-requires
13
- //plugins: [autoImport],
14
- test: {
15
- include: ["./test/**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
16
- reporters: "verbose",
17
- globals: true
18
- },
19
- resolve: {
20
- alias: packages.reduce((acc, cur) => {
21
- acc[JSON.parse(fs.readFileSync(cur + "/package.json", "utf-8")).name] = path.resolve(cur, cur.endsWith("core") ? "dist" : "src");
22
- return acc;
23
- }, {})
24
- // "@effect-app/core/Prelude": path.join(__dirname, "packages/core/src/Prelude.code.ts")
25
- }
26
- };
27
- console.log(cfg);
28
- return cfg;
29
- }
30
-
31
- // packages/infra/vitest.config.ts
32
- var __vite_injected_original_dirname2 = "/Users/patrickroza/pj/effect-app/libs/packages/infra";
33
- var vitest_config_default = defineConfig(makeConfig(__vite_injected_original_dirname2));
34
- export {
35
- vitest_config_default as default
36
- };
37
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicGFja2FnZXMvaW5mcmEvdml0ZXN0LmNvbmZpZy50cyIsICJ2aXRlLmNvbmZpZy5iYXNlLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL3BhdHJpY2tyb3phL3BqL2VmZmVjdC1hcHAvbGlicy9wYWNrYWdlcy9pbmZyYVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL3BhdHJpY2tyb3phL3BqL2VmZmVjdC1hcHAvbGlicy9wYWNrYWdlcy9pbmZyYS92aXRlc3QuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9Vc2Vycy9wYXRyaWNrcm96YS9wai9lZmZlY3QtYXBwL2xpYnMvcGFja2FnZXMvaW5mcmEvdml0ZXN0LmNvbmZpZy50c1wiOy8vLyA8cmVmZXJlbmNlIHR5cGVzPVwidml0ZXN0XCIgLz5cbmltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gXCJ2aXRlXCJcbmltcG9ydCBtYWtlQ29uZmlnIGZyb20gXCIuLi8uLi92aXRlLmNvbmZpZy5iYXNlXCJcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKG1ha2VDb25maWcoX19kaXJuYW1lKSlcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiL1VzZXJzL3BhdHJpY2tyb3phL3BqL2VmZmVjdC1hcHAvbGlic1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL1VzZXJzL3BhdHJpY2tyb3phL3BqL2VmZmVjdC1hcHAvbGlicy92aXRlLmNvbmZpZy5iYXNlLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9Vc2Vycy9wYXRyaWNrcm96YS9wai9lZmZlY3QtYXBwL2xpYnMvdml0ZS5jb25maWcuYmFzZS50c1wiOy8vLyA8cmVmZXJlbmNlIHR5cGVzPVwidml0ZXN0XCIgLz5cbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCJcbmltcG9ydCBmcyBmcm9tIFwiZnNcIlxuaW1wb3J0IEF1dG9JbXBvcnQgZnJvbSBcInVucGx1Z2luLWF1dG8taW1wb3J0L3ZpdGVcIlxuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVzdC9jb25maWdcIlxuXG4vLyBjb25zdCBhdXRvSW1wb3J0ID0gQXV0b0ltcG9ydCh7XG4vLyAgIGR0czogXCIuL3Rlc3QvYXV0by1pbXBvcnRzLmQudHNcIixcbi8vICAgLy8gaW5jbHVkZTogW1xuLy8gICAvLyAgIC9cXC50ZXN0XFwuW3RqXXN4PyQvIC8vIC50cywgLnRzeCwgLmpzLCAuanN4XG4vLyAgIC8vIF0sXG4vLyAgIGltcG9ydHM6IFtcbi8vICAgICBcInZpdGVzdFwiXG4vLyAgIF1cbi8vIH0pXG5cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIG1ha2VDb25maWcoZGlyTmFtZT86IHN0cmluZykge1xuICBjb25zdCBwcmVmaXggPSBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCBcInBhY2thZ2VzXCIpXG4gIGNvbnN0IHBhY2thZ2VzID0gZnMucmVhZGRpclN5bmMocHJlZml4KS5tYXAoZiA9PiBwcmVmaXggKyBcIi9cIiArIGYpLmZpbHRlcihmID0+IGZzLmxzdGF0U3luYyhmKS5pc0RpcmVjdG9yeSgpIClcbiAgY29uc3QgY2ZnID0ge1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdmFyLXJlcXVpcmVzXG4gICAgLy9wbHVnaW5zOiBbYXV0b0ltcG9ydF0sXG4gICAgdGVzdDoge1xuICAgICAgaW5jbHVkZTogIFtcIi4vdGVzdC8qKi8qLnRlc3Que2pzLG1qcyxjanMsdHMsbXRzLGN0cyxqc3gsdHN4fVwiXSxcbiAgICAgIHJlcG9ydGVyczogXCJ2ZXJib3NlXCIsXG4gICAgICBnbG9iYWxzOiB0cnVlXG4gICAgfSxcbiAgICByZXNvbHZlOiB7XG4gICAgICBhbGlhczogcGFja2FnZXMucmVkdWNlKChhY2MsIGN1cikgPT4geyAvLyB3b3JrYXJvdW5kIGZvciAvUHJlbHVkZSBpc3N1ZVxuICAgICAgYWNjW0pTT04ucGFyc2UoZnMucmVhZEZpbGVTeW5jKGN1ciArIFwiL3BhY2thZ2UuanNvblwiLCBcInV0Zi04XCIpKS5uYW1lXSA9IHBhdGgucmVzb2x2ZShjdXIsIGN1ci5lbmRzV2l0aChcImNvcmVcIikgPyBcImRpc3RcIiA6IFwic3JjXCIpXG4gICAgICByZXR1cm4gYWNjXG4gICAgfSwgeyB9KSAvLyBcIkBlZmZlY3QtYXBwL2NvcmUvUHJlbHVkZVwiOiBwYXRoLmpvaW4oX19kaXJuYW1lLCBcInBhY2thZ2VzL2NvcmUvc3JjL1ByZWx1ZGUuY29kZS50c1wiKVxuICB9XG4gIH1cbiAgY29uc29sZS5sb2coY2ZnKVxuICByZXR1cm4gY2ZnXG59XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxvQkFBb0I7OztBQ0E3QixPQUFPLFVBQVU7QUFDakIsT0FBTyxRQUFRO0FBRmYsSUFBTSxtQ0FBbUM7QUFnQjFCLFNBQVIsV0FBNEIsU0FBa0I7QUFDbkQsUUFBTSxTQUFTLEtBQUssUUFBUSxrQ0FBVyxVQUFVO0FBQ2pELFFBQU0sV0FBVyxHQUFHLFlBQVksTUFBTSxFQUFFLElBQUksT0FBSyxTQUFTLE1BQU0sQ0FBQyxFQUFFLE9BQU8sT0FBSyxHQUFHLFVBQVUsQ0FBQyxFQUFFLFlBQVksQ0FBRTtBQUM3RyxRQUFNLE1BQU07QUFBQTtBQUFBO0FBQUEsSUFHVixNQUFNO0FBQUEsTUFDSixTQUFVLENBQUMsa0RBQWtEO0FBQUEsTUFDN0QsV0FBVztBQUFBLE1BQ1gsU0FBUztBQUFBLElBQ1g7QUFBQSxJQUNBLFNBQVM7QUFBQSxNQUNQLE9BQU8sU0FBUyxPQUFPLENBQUMsS0FBSyxRQUFRO0FBQ3JDLFlBQUksS0FBSyxNQUFNLEdBQUcsYUFBYSxNQUFNLGlCQUFpQixPQUFPLENBQUMsRUFBRSxJQUFJLElBQUksS0FBSyxRQUFRLEtBQUssSUFBSSxTQUFTLE1BQU0sSUFBSSxTQUFTLEtBQUs7QUFDL0gsZUFBTztBQUFBLE1BQ1QsR0FBRyxDQUFFLENBQUM7QUFBQTtBQUFBLElBQ1I7QUFBQSxFQUNBO0FBQ0EsVUFBUSxJQUFJLEdBQUc7QUFDZixTQUFPO0FBQ1Q7OztBRHBDQSxJQUFNQSxvQ0FBbUM7QUFJekMsSUFBTyx3QkFBUSxhQUFhLFdBQVdDLGlDQUFTLENBQUM7IiwKICAibmFtZXMiOiBbIl9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lIiwgIl9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lIl0KfQo=