@effect-app/infra 2.9.1 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/infra",
3
- "version": "2.9.1",
3
+ "version": "2.9.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -659,6 +659,16 @@
659
659
  "default": "./_cjs/api/routing/schema/jwt.cjs"
660
660
  }
661
661
  },
662
+ "./api/routing4": {
663
+ "import": {
664
+ "types": "./dist/api/routing4.d.ts",
665
+ "default": "./dist/api/routing4.js"
666
+ },
667
+ "require": {
668
+ "types": "./dist/api/routing4.d.ts",
669
+ "default": "./_cjs/api/routing4.cjs"
670
+ }
671
+ },
662
672
  "./api/setupRequest": {
663
673
  "import": {
664
674
  "types": "./dist/api/setupRequest.d.ts",
@@ -3,7 +3,6 @@ import { reportNonInterruptedFailure } from "@effect-app/infra/QueueMaker/errors
3
3
  import type { QueueBase } from "@effect-app/infra/QueueMaker/service"
4
4
  import { QueueMeta } from "@effect-app/infra/QueueMaker/service"
5
5
  import { SqlClient } from "@effect/sql"
6
- import { randomUUID } from "crypto"
7
6
  import { subMinutes } from "date-fns"
8
7
  import { Effect, Fiber, Option, S, Tracer } from "effect-app"
9
8
  import type { NonEmptyString255 } from "effect-app/Schema"
@@ -88,7 +87,7 @@ export function makeSQLQueue<
88
87
  name: queueName,
89
88
  processingAt: Option.none(),
90
89
  finishedAt: Option.none(),
91
- etag: randomUUID()
90
+ etag: crypto.randomUUID()
92
91
  })
93
92
  )
94
93
  }),
@@ -99,7 +98,7 @@ export function makeSQLQueue<
99
98
  const dec = yield* decodeDrain(first)
100
99
  const { createdAt, updatedAt, ...rest } = dec
101
100
  return yield* drainRepo.update(
102
- Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: randomUUID()
101
+ Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
103
102
  )
104
103
  }
105
104
  if (first) return first
@@ -107,7 +106,7 @@ export function makeSQLQueue<
107
106
  }
108
107
  }),
109
108
  finish: ({ createdAt, updatedAt, ...q }: Drain) =>
110
- drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: randomUUID()
109
+ drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: crypto.randomUUID()
111
110
  }
112
111
  return {
113
112
  publish: (...messages) =>
@@ -1,4 +1,4 @@
1
- import { createHash } from "crypto"
1
+ import crypto from "crypto"
2
2
  import { Effect, Option } from "effect-app"
3
3
  import { OptimisticConcurrencyException } from "../errors.js"
4
4
  import type { PersistenceModelType, SupportedValues2 } from "./service.js"
@@ -9,7 +9,7 @@ export const makeETag = <E extends PersistenceModelType<{}>>(
9
9
  ({
10
10
  ...e,
11
11
  // we have to hash the JSON, as hashing the object might contain elements that won't be serialized anyway
12
- _etag: createHash("sha256").update(JSON.stringify(e)).digest("hex")
12
+ _etag: crypto.createHash("sha256").update(JSON.stringify(e)).digest("hex")
13
13
  }) as any
14
14
 
15
15
  export const makeUpdateETag =
@@ -12,7 +12,7 @@ import * as VariantSchema from "@effect/experimental/VariantSchema"
12
12
  import { SqlClient } from "@effect/sql/SqlClient"
13
13
  import * as SqlResolver from "@effect/sql/SqlResolver"
14
14
  import * as SqlSchema from "@effect/sql/SqlSchema"
15
- import { randomUUID } from "crypto" // TODO
15
+ import crypto from "crypto" // TODO
16
16
  import type { Brand } from "effect/Brand"
17
17
  import * as DateTime from "effect/DateTime"
18
18
  import type { DurationInput } from "effect/Duration"
@@ -711,7 +711,7 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID(
711
711
  sql.onDialectOrElse({
712
712
  mysql: () =>
713
713
  sql`update ${sql(options.tableName)} set ${
714
- sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
714
+ sql.update({ ...request, [versionColumn]: crypto.randomUUID() }, [idColumn])
715
715
  } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]};
716
716
  select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idColumn]};`
717
717
  .unprepared
@@ -720,7 +720,7 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idCol
720
720
  ),
721
721
  orElse: () =>
722
722
  sql`update ${sql(options.tableName)} set ${
723
- sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
723
+ sql.update({ ...request, [versionColumn]: crypto.randomUUID() }, [idColumn])
724
724
  } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${
725
725
  request[versionColumn]
726
726
  } returning *`
@@ -758,7 +758,7 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idCol
758
758
  execute: versionColumn
759
759
  ? (request) =>
760
760
  sql`update ${sql(options.tableName)} set ${
761
- sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
761
+ sql.update({ ...request, [versionColumn]: crypto.randomUUID() }, [idColumn])
762
762
  } where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]}`
763
763
  : (request) =>
764
764
  sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${
@@ -0,0 +1,510 @@
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 type * as HttpApp from "@effect/platform/HttpApp"
9
+ import { Rpc, RpcRouter } from "@effect/rpc"
10
+ import type { NonEmptyArray } from "effect-app"
11
+ import { Cause, Chunk, Context, Effect, FiberRef, flow, Layer, Predicate, S, Schema, Stream } from "effect-app"
12
+ import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
13
+ import type { HttpServerError } from "effect-app/http"
14
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
15
+ import { pretty, typedKeysOf, typedValuesOf } from "effect-app/utils"
16
+ import { logError, reportError } from "../errorReporter.js"
17
+ import { InfraLogger } from "../logger.js"
18
+ import type { Middleware } from "./routing/DynamicMiddleware.js"
19
+ import { makeRpc } from "./routing/DynamicMiddleware.js"
20
+
21
+ const logRequestError = logError("Request")
22
+ const reportRequestError = reportError("Request")
23
+
24
+ export type _R<T extends Effect<any, any, any>> = [T] extends [
25
+ Effect<any, any, infer R>
26
+ ] ? R
27
+ : never
28
+
29
+ export type _E<T extends Effect<any, any, any>> = [T] extends [
30
+ Effect<any, infer E, any>
31
+ ] ? E
32
+ : never
33
+
34
+ export type EffectDeps<A> = {
35
+ [K in keyof A as A[K] extends Effect<any, any, any> ? K : never]: A[K] extends Effect<any, any, any> ? A[K] : never
36
+ }
37
+ /**
38
+ * Plain jane JSON version
39
+ * @deprecated use HttpRpcRouterNoStream.toHttpApp once support options
40
+ */
41
+ export const toHttpApp = <R extends RpcRouter.RpcRouter<any, any>>(self: R, options?: {
42
+ readonly spanPrefix?: string
43
+ }): HttpApp.Default<
44
+ HttpServerError.RequestError,
45
+ RpcRouter.RpcRouter.Context<R>
46
+ > => {
47
+ const handler = RpcRouter.toHandler(self, options)
48
+ return Effect.withFiberRuntime((fiber) => {
49
+ const context = fiber.getFiberRef(FiberRef.currentContext)
50
+ const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
51
+ return Effect.flatMap(
52
+ request.json,
53
+ (_) =>
54
+ handler(_).pipe(
55
+ Stream.provideContext(context),
56
+ Stream.runCollect,
57
+ Effect.map((_) => Chunk.toReadonlyArray(_)),
58
+ Effect.andThen((_) => {
59
+ let status = 200
60
+ for (const r of _.flat()) {
61
+ if (typeof r === "number") continue
62
+ const results = Array.isArray(r) ? r : [r]
63
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Die")) {
64
+ status = 500
65
+ break
66
+ }
67
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Fail")) {
68
+ status = 422 // 418
69
+ break
70
+ }
71
+ }
72
+ return HttpServerResponse.json(_, { status })
73
+ }),
74
+ Effect.orDie,
75
+ Effect.tapDefect(reportError("RPCHttpApp"))
76
+ )
77
+ )
78
+ })
79
+ }
80
+
81
+ export interface Hint<Err extends string> {
82
+ Err: Err
83
+ }
84
+
85
+ type HandleVoid<Expected, Actual, Result> = [Expected] extends [void]
86
+ ? [Actual] extends [void] ? Result : Hint<"You're returning non void for a void Response, please fix">
87
+ : Result
88
+
89
+ type AnyRequestModule = S.Schema.Any & { success?: S.Schema.Any; failure?: S.Schema.Any }
90
+
91
+ type GetSuccess<T> = T extends { success: S.Schema.Any } ? T["success"] : typeof S.Void
92
+
93
+ type GetSuccessShape<Action extends { success?: S.Schema.Any }, RT extends "d" | "raw"> = RT extends "raw"
94
+ ? S.Schema.Encoded<GetSuccess<Action>>
95
+ : S.Schema.Type<GetSuccess<Action>>
96
+ type GetFailure<T extends { failure?: S.Schema.Any }> = T["failure"] extends never ? typeof S.Never : T["failure"]
97
+
98
+ type HandlerFull<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> = {
99
+ new(): {}
100
+ _tag: RT
101
+ stack: string
102
+ handler: (
103
+ req: S.Schema.Type<Action>
104
+ ) => Effect<
105
+ A,
106
+ E,
107
+ R
108
+ >
109
+ }
110
+
111
+ export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", R> extends
112
+ HandlerFull<
113
+ Action,
114
+ RT,
115
+ GetSuccessShape<Action, RT>,
116
+ S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
117
+ R
118
+ >
119
+ {
120
+ }
121
+
122
+ type AHandler<Action extends AnyRequestModule> = Handler<
123
+ Action,
124
+ any,
125
+ any
126
+ >
127
+
128
+ type Filter<T> = {
129
+ [K in keyof T as T[K] extends S.Schema.All & { success: S.Schema.Any; failure: S.Schema.Any } ? K : never]: T[K]
130
+ }
131
+
132
+ export const RouterSymbol = Symbol()
133
+ export interface RouterShape<Rsc> {
134
+ [RouterSymbol]: Rsc
135
+ }
136
+
137
+ type RPCRouteR<T extends Rpc.Rpc<any, any>> = [T] extends [
138
+ Rpc.Rpc<any, infer R>
139
+ ] ? R
140
+ : never
141
+
142
+ type RPCRouteReq<T extends Rpc.Rpc<any, any>> = [T] extends [
143
+ Rpc.Rpc<infer Req, any>
144
+ ] ? Req
145
+ : never
146
+
147
+ type Match<
148
+ Rsc extends Record<string, any>,
149
+ CTXMap extends Record<string, any>,
150
+ RT extends "raw" | "d",
151
+ Key extends keyof Rsc,
152
+ Context
153
+ > = {
154
+ // TODO: deal with HandleVoid and ability to extends from GetSuccessShape...
155
+ // aka we want to make sure that the return type is void if the success is void,
156
+ // and make sure A is the actual expected type
157
+
158
+ // note: the defaults of = never prevent the whole router to error
159
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
160
+ f: Effect<A, E, R2>
161
+ ): HandleVoid<
162
+ GetSuccessShape<Rsc[Key], RT>,
163
+ A,
164
+ Handler<
165
+ Rsc[Key],
166
+ RT,
167
+ Exclude<
168
+ Context | Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
169
+ HttpRouter.HttpRouter.Provided
170
+ >
171
+ >
172
+ >
173
+
174
+ <A extends GetSuccessShape<Rsc[Key], RT>, R2 = never, E = never>(
175
+ f: (req: S.Schema.Type<Rsc[Key]>) => Effect<A, E, R2>
176
+ ): HandleVoid<
177
+ GetSuccessShape<Rsc[Key], RT>,
178
+ A,
179
+ Handler<
180
+ Rsc[Key],
181
+ RT,
182
+ Exclude<
183
+ Context | Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
184
+ HttpRouter.HttpRouter.Provided
185
+ >
186
+ >
187
+ >
188
+ }
189
+
190
+ export type RouteMatcher<
191
+ CTXMap extends Record<string, any>,
192
+ Rsc extends Record<string, any>,
193
+ Context
194
+ > = {
195
+ // use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
196
+ /**
197
+ * Requires the Type shape
198
+ */
199
+ [Key in keyof Filter<Rsc>]: Match<Rsc, CTXMap, "d", Key, Context> & {
200
+ success: Rsc[Key]["success"]
201
+ successRaw: S.SchemaClass<S.Schema.Encoded<Rsc[Key]["success"]>>
202
+ failure: Rsc[Key]["failure"]
203
+ /**
204
+ * Requires the Encoded shape (e.g directly undecoded from DB, so that we don't do multiple Decode/Encode)
205
+ */
206
+ raw: Match<Rsc, CTXMap, "raw", Key, Context>
207
+ }
208
+ }
209
+ // export interface RouteMatcher<
210
+ // Filtered extends Record<string, any>,
211
+ // CTXMap extends Record<string, any>,
212
+ // Rsc extends Filtered
213
+ // > extends RouteMatcherInt<Filtered, CTXMap, Rsc> {}
214
+
215
+ export const makeMiddleware = <
216
+ Context,
217
+ CTXMap extends Record<string, RPCContextMap.Any>,
218
+ RMW,
219
+ Layers extends Array<Layer.Layer.Any>
220
+ >(content: Middleware<Context, CTXMap, RMW, Layers>): Middleware<Context, CTXMap, RMW, Layers> => content
221
+
222
+ export const makeRouter = <
223
+ Context,
224
+ CTXMap extends Record<string, RPCContextMap.Any>,
225
+ RMW,
226
+ Layers extends Array<Layer.Layer.Any>
227
+ >(
228
+ middleware: Middleware<Context, CTXMap, RMW, Layers>,
229
+ devMode: boolean
230
+ ) => {
231
+ function matchFor<
232
+ const ModuleName extends string,
233
+ const Rsc extends Record<string, any>
234
+ >(
235
+ rsc: Rsc & { meta: { moduleName: ModuleName } }
236
+ ) {
237
+ const meta = rsc.meta
238
+ type Filtered = Filter<Rsc>
239
+ const filtered = typedKeysOf(rsc).reduce((acc, cur) => {
240
+ if (Predicate.isObject(rsc[cur]) && rsc[cur]["success"]) {
241
+ acc[cur as keyof Filtered] = rsc[cur]
242
+ }
243
+ return acc
244
+ }, {} as Filtered)
245
+
246
+ const items = typedKeysOf(filtered).reduce(
247
+ (prev, cur) => {
248
+ ;(prev as any)[cur] = Object.assign((fnOrEffect: any) => {
249
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
250
+ return Effect.isEffect(fnOrEffect)
251
+ ? class {
252
+ static stack = stack
253
+ static _tag = "d"
254
+ static handler = () => fnOrEffect
255
+ }
256
+ : class {
257
+ static stack = stack
258
+ static _tag = "d"
259
+ static handler = fnOrEffect
260
+ }
261
+ }, {
262
+ success: rsc[cur].success,
263
+ successRaw: S.encodedSchema(rsc[cur].success),
264
+ failure: rsc[cur].failure,
265
+ raw: // "Raw" variations are for when you don't want to decode just to encode it again on the response
266
+ // e.g for direct projection from DB
267
+ // but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
268
+ (fnOrEffect: any) => {
269
+ const stack = new Error().stack?.split("\n").slice(2).join("\n")
270
+ return Effect.isEffect(fnOrEffect)
271
+ ? class {
272
+ static stack = stack
273
+ static _tag = "raw"
274
+ static handler = () => fnOrEffect
275
+ }
276
+ : class {
277
+ static stack = stack
278
+ static _tag = "raw"
279
+ static handler = (req: any, ctx: any) => fnOrEffect(req, { ...ctx, Response: rsc[cur].success })
280
+ }
281
+ }
282
+ })
283
+ return prev
284
+ },
285
+ {} as RouteMatcher<CTXMap, Rsc, Context>
286
+ )
287
+
288
+ type Keys = keyof Filtered
289
+
290
+ const effect = <
291
+ E,
292
+ R,
293
+ THandlers extends {
294
+ // import to keep them separate via | for type checking!!
295
+ [K in Keys]: AHandler<Rsc[K]>
296
+ },
297
+ TLayers extends NonEmptyArray<Layer.Layer.Any>
298
+ >(
299
+ layers: TLayers,
300
+ make: (requests: typeof items) => Effect<THandlers, E, R>
301
+ ) => {
302
+ type ProvidedLayers =
303
+ | { [k in keyof Layers]: Layer.Layer.Success<Layers[k]> }[number]
304
+ | { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
305
+ type Router = RouterShape<Rsc>
306
+ const r: HttpRouter.HttpRouter.TagClass<
307
+ Router,
308
+ `${typeof meta.moduleName}Router`,
309
+ never,
310
+ Exclude<
311
+ | Context
312
+ | RPCRouteR<
313
+ { [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
314
+ >,
315
+ HttpRouter.HttpRouter.Provided
316
+ >
317
+ > = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
318
+
319
+ const layer = r.use((router) =>
320
+ Effect.gen(function*() {
321
+ const controllers = yield* make(items)
322
+ const rpc = yield* makeRpc(middleware)
323
+
324
+ // return make.pipe(Effect.map((c) => controllers(c, layers)))
325
+ const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
326
+ const handler = controllers[cur as keyof typeof controllers]
327
+ const req = rsc[cur]
328
+
329
+ acc[cur] = rpc.effect(
330
+ handler._tag === "raw"
331
+ ? class extends (req as any) {
332
+ static success = S.encodedSchema(req.success)
333
+ get [Schema.symbolSerializable]() {
334
+ return this.constructor
335
+ }
336
+ get [Schema.symbolWithResult]() {
337
+ return {
338
+ failure: req.failure,
339
+ success: S.encodedSchema(req.success)
340
+ }
341
+ }
342
+ } as any
343
+ : req,
344
+ (req) =>
345
+ Effect
346
+ .annotateCurrentSpan(
347
+ "requestInput",
348
+ Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
349
+ prev[key] = key === "password"
350
+ ? "<redacted>"
351
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
352
+ ? typeof value === "string" && value.length > 256
353
+ ? (value.substring(0, 253) + "...")
354
+ : value
355
+ : Array.isArray(value)
356
+ ? `Array[${value.length}]`
357
+ : value === null || value === undefined
358
+ ? `${value}`
359
+ : typeof value === "object" && value
360
+ ? `Object[${Object.keys(value).length}]`
361
+ : typeof value
362
+ return prev
363
+ }, {} as Record<string, string | number | boolean>)
364
+ )
365
+ .pipe(
366
+ // can't use andThen due to some being a function and effect
367
+ Effect.zipRight(handler.handler(req as any) as any),
368
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
369
+ Effect.tapDefect((cause) =>
370
+ Effect
371
+ .all([
372
+ reportRequestError(cause, {
373
+ action: `${meta.moduleName}.${req._tag}`
374
+ }),
375
+ Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
376
+ return InfraLogger
377
+ .logError("Finished request", cause)
378
+ .pipe(Effect.annotateLogs({
379
+ action: `${meta.moduleName}.${req._tag}`,
380
+ req: pretty(req),
381
+ headers: pretty(headers)
382
+ // resHeaders: pretty(
383
+ // Object
384
+ // .entries(headers)
385
+ // .reduce((prev, [key, value]) => {
386
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
387
+ // return prev
388
+ // }, {} as Record<string, any>)
389
+ // )
390
+ }))
391
+ }))
392
+ ])
393
+ ),
394
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
395
+ Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
396
+ captureStackTrace: () => handler.stack
397
+ })
398
+ ),
399
+ meta.moduleName
400
+ ) // TODO
401
+ return acc
402
+ }, {} as any) as {
403
+ [K in Keys]: Rpc.Rpc<
404
+ Rsc[K],
405
+ Context | _R<ReturnType<THandlers[K]["handler"]>>
406
+ >
407
+ }
408
+
409
+ const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
410
+ RPCRouteReq<typeof mapped[keyof typeof mapped]>,
411
+ RPCRouteR<typeof mapped[keyof typeof mapped]>
412
+ >
413
+ const httpApp = toHttpApp(rpcRouter, {
414
+ spanPrefix: rsc
415
+ .meta
416
+ .moduleName + "."
417
+ })
418
+ yield* router
419
+ .all(
420
+ "/",
421
+ httpApp as any,
422
+ // TODO: not queries.
423
+ { uninterruptible: true }
424
+ )
425
+ })
426
+ )
427
+
428
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
429
+ const routes = layer.pipe(
430
+ Layer.provideMerge(r.Live),
431
+ layers ? Layer.provide(layers) as any : (_) => _,
432
+ // TODO: only provide to the middleware?
433
+ middleware.dependencies ? Layer.provide(middleware.dependencies as any) : (_) => _
434
+ ) as Layer.Layer<
435
+ Router,
436
+ { [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number] | E,
437
+ | { [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
438
+ | Exclude<
439
+ RMW | R,
440
+ ProvidedLayers
441
+ >
442
+ >
443
+
444
+ // Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
445
+
446
+ return {
447
+ moduleName: meta.moduleName,
448
+ Router: r,
449
+ routes
450
+ }
451
+ }
452
+
453
+ return effect
454
+ }
455
+
456
+ type HR<T> = T extends HttpRouter.HttpRouter<any, infer R> ? R : never
457
+ type HE<T> = T extends HttpRouter.HttpRouter<infer E, any> ? E : never
458
+
459
+ type RequestHandlersTest = {
460
+ [key: string]: {
461
+ Router: { router: Effect<HttpRouter.HttpRouter<any, any>, any, any> }
462
+ routes: Layer.Layer<any, any, any>
463
+ moduleName: string
464
+ }
465
+ }
466
+ function matchAll<T extends RequestHandlersTest, A, E, R>(
467
+ handlers: T,
468
+ requestLayer: Layer.Layer<A, E, R>
469
+ ) {
470
+ const routers = typedValuesOf(handlers)
471
+
472
+ const rootRouter = class extends HttpRouter.Tag("RootRouter")<
473
+ "RootRouter",
474
+ HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
475
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>
476
+ >() {}
477
+
478
+ const r = rootRouter
479
+ .use((router) =>
480
+ Effect.gen(function*() {
481
+ for (const route of routers) {
482
+ yield* router.mount(
483
+ ("/rpc/" + route.moduleName) as any,
484
+ yield* route
485
+ .Router
486
+ .router
487
+ .pipe(Effect.map(HttpRouter.use(flow(Effect.provide(requestLayer))))) as any
488
+ )
489
+ }
490
+ })
491
+ )
492
+ .pipe(Layer.provide(routers.map((r) => r.routes).flat() as unknown as NonEmptyArray<Layer.Layer.Any>))
493
+
494
+ return {
495
+ layer: r as Layer.Layer<
496
+ never,
497
+ Layer.Layer.Error<typeof handlers[keyof typeof handlers]["routes"]>,
498
+ Layer.Layer.Context<typeof handlers[keyof typeof handlers]["routes"]>
499
+ >,
500
+ Router: rootRouter as any as HttpRouter.HttpRouter.TagClass<
501
+ "RootRouter",
502
+ "RootRouter",
503
+ HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
504
+ R | Exclude<HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>, A>
505
+ >
506
+ }
507
+ }
508
+
509
+ return { matchAll, matchFor }
510
+ }