@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.
- package/CHANGELOG.md +13 -0
- package/_cjs/api/routing/DynamicMiddleware.cjs +6 -7
- package/_cjs/api/routing/DynamicMiddleware.cjs.map +1 -1
- package/_cjs/api/routing.cjs +115 -112
- package/_cjs/api/routing.cjs.map +1 -1
- package/dist/api/routing/DynamicMiddleware.d.ts +8 -7
- package/dist/api/routing/DynamicMiddleware.d.ts.map +1 -1
- package/dist/api/routing/DynamicMiddleware.js +7 -8
- package/dist/api/routing.d.ts +49 -27
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +150 -122
- package/dist/services/CUPS.d.ts +1 -1
- package/package.json +1 -21
- package/src/api/routing/DynamicMiddleware.ts +30 -26
- package/src/api/routing.ts +360 -344
- package/tsconfig.src.json +1 -1
- package/_cjs/api/routing2.cjs +0 -270
- package/_cjs/api/routing2.cjs.map +0 -1
- package/_cjs/api/routing3.cjs +0 -187
- package/_cjs/api/routing3.cjs.map +0 -1
- package/dist/api/routing2.d.ts +0 -112
- package/dist/api/routing2.d.ts.map +0 -1
- package/dist/api/routing2.js +0 -302
- package/dist/api/routing3.d.ts +0 -108
- package/dist/api/routing3.d.ts.map +0 -1
- package/dist/api/routing3.js +0 -206
- package/src/api/routing2.ts +0 -757
- package/src/api/routing3.ts +0 -510
package/src/api/routing2.ts
DELETED
|
@@ -1,757 +0,0 @@
|
|
|
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 {
|
|
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"
|
|
26
|
-
import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
|
|
27
|
-
import { allLower, type EffectUnunified, type LowerServices } from "effect-app/Effect"
|
|
28
|
-
import type { HttpServerError } from "effect-app/http"
|
|
29
|
-
import { HttpMiddleware, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
|
|
30
|
-
import { pretty, typedKeysOf, typedValuesOf } from "effect-app/utils"
|
|
31
|
-
import type { Compute } from "effect-app/utils"
|
|
32
|
-
import { logError, reportError } from "../errorReporter.js"
|
|
33
|
-
import { InfraLogger } from "../logger.js"
|
|
34
|
-
import type { Middleware } from "./routing/DynamicMiddleware.js"
|
|
35
|
-
import { makeRpc } from "./routing/DynamicMiddleware.js"
|
|
36
|
-
|
|
37
|
-
const logRequestError = logError("Request")
|
|
38
|
-
const reportRequestError = reportError("Request")
|
|
39
|
-
|
|
40
|
-
export type _R<T extends Effect<any, any, any>> = [T] extends [
|
|
41
|
-
Effect<any, any, infer R>
|
|
42
|
-
] ? R
|
|
43
|
-
: never
|
|
44
|
-
|
|
45
|
-
export type _E<T extends Effect<any, any, any>> = [T] extends [
|
|
46
|
-
Effect<any, infer E, any>
|
|
47
|
-
] ? E
|
|
48
|
-
: never
|
|
49
|
-
|
|
50
|
-
export type EffectDeps<A> = {
|
|
51
|
-
[K in keyof A as A[K] extends Effect<any, any, any> ? K : never]: A[K] extends Effect<any, any, any> ? A[K] : never
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Plain jane JSON version
|
|
55
|
-
* @deprecated use HttpRpcRouterNoStream.toHttpApp once support options
|
|
56
|
-
*/
|
|
57
|
-
export const toHttpApp = <R extends RpcRouter.RpcRouter<any, any>>(self: R, options?: {
|
|
58
|
-
readonly spanPrefix?: string
|
|
59
|
-
}): HttpApp.Default<
|
|
60
|
-
HttpServerError.RequestError,
|
|
61
|
-
RpcRouter.RpcRouter.Context<R>
|
|
62
|
-
> => {
|
|
63
|
-
const handler = RpcRouter.toHandler(self, options)
|
|
64
|
-
return Effect.withFiberRuntime((fiber) => {
|
|
65
|
-
const context = fiber.getFiberRef(FiberRef.currentContext)
|
|
66
|
-
const request = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
|
|
67
|
-
return Effect.flatMap(
|
|
68
|
-
request.json,
|
|
69
|
-
(_) =>
|
|
70
|
-
handler(_).pipe(
|
|
71
|
-
Stream.provideContext(context),
|
|
72
|
-
Stream.runCollect,
|
|
73
|
-
Effect.map((_) => Chunk.toReadonlyArray(_)),
|
|
74
|
-
Effect.andThen((_) => {
|
|
75
|
-
let status = 200
|
|
76
|
-
for (const r of _.flat()) {
|
|
77
|
-
if (typeof r === "number") continue
|
|
78
|
-
const results = Array.isArray(r) ? r : [r]
|
|
79
|
-
if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Die")) {
|
|
80
|
-
status = 500
|
|
81
|
-
break
|
|
82
|
-
}
|
|
83
|
-
if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Fail")) {
|
|
84
|
-
status = 422 // 418
|
|
85
|
-
break
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return HttpServerResponse.json(_, { status })
|
|
89
|
-
}),
|
|
90
|
-
Effect.orDie,
|
|
91
|
-
Effect.tapDefect(reportError("RPCHttpApp"))
|
|
92
|
-
)
|
|
93
|
-
)
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type GetRouteContext<CTXMap extends Record<string, RPCContextMap.Any>, T> =
|
|
98
|
-
// & CTX
|
|
99
|
-
// inverted
|
|
100
|
-
& {
|
|
101
|
-
[
|
|
102
|
-
key in keyof CTXMap as CTXMap[key][3] extends true ? 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 true ? never
|
|
110
|
-
: key extends keyof T ? T[key] extends false ? CTXMap[key][0] : never
|
|
111
|
-
: CTXMap[key][0]
|
|
112
|
-
]: CTXMap[key][1]
|
|
113
|
-
}
|
|
114
|
-
// normal
|
|
115
|
-
& {
|
|
116
|
-
[
|
|
117
|
-
key in keyof CTXMap as CTXMap[key][3] extends false ? never
|
|
118
|
-
: key extends keyof T ? T[key] extends true ? CTXMap[key][0] : never
|
|
119
|
-
: never
|
|
120
|
-
]: CTXMap[key][1]
|
|
121
|
-
}
|
|
122
|
-
& {
|
|
123
|
-
[
|
|
124
|
-
key in keyof CTXMap as CTXMap[key][3] extends false ? never
|
|
125
|
-
: key extends keyof T ? T[key] extends false ? CTXMap[key][0] : never
|
|
126
|
-
: CTXMap[key][0]
|
|
127
|
-
]?: CTXMap[key][1]
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface Hint<Err extends string> {
|
|
131
|
-
Err: Err
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
type HandleVoid<Expected, Actual, Result> = [Expected] extends [void]
|
|
135
|
-
? [Actual] extends [void] ? Result : Hint<"You're returning non void for a void Response, please fix">
|
|
136
|
-
: Result
|
|
137
|
-
|
|
138
|
-
type AnyRequestModule = S.Schema.Any & { success?: S.Schema.Any; failure?: S.Schema.Any }
|
|
139
|
-
|
|
140
|
-
type GetSuccess<T> = T extends { success: S.Schema.Any } ? T["success"] : typeof S.Void
|
|
141
|
-
|
|
142
|
-
type GetSuccessShape<Action extends { success?: S.Schema.Any }, RT extends "d" | "raw"> = RT extends "raw"
|
|
143
|
-
? S.Schema.Encoded<GetSuccess<Action>>
|
|
144
|
-
: S.Schema.Type<GetSuccess<Action>>
|
|
145
|
-
type GetFailure<T extends { failure?: S.Schema.Any }> = T["failure"] extends never ? typeof S.Never : T["failure"]
|
|
146
|
-
|
|
147
|
-
export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> {
|
|
148
|
-
new(): {}
|
|
149
|
-
_tag: RT
|
|
150
|
-
stack: string
|
|
151
|
-
handler: (
|
|
152
|
-
req: S.Schema.Type<Action>
|
|
153
|
-
) => Effect<
|
|
154
|
-
A,
|
|
155
|
-
E,
|
|
156
|
-
R
|
|
157
|
-
>
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Separate "raw" vs "d" to verify A (Encoded for "raw" vs Type for "d")
|
|
161
|
-
type AHandler<Action extends AnyRequestModule> =
|
|
162
|
-
| Handler<
|
|
163
|
-
Action,
|
|
164
|
-
"raw",
|
|
165
|
-
S.Schema.Encoded<GetSuccess<Action>>,
|
|
166
|
-
S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
|
|
167
|
-
any
|
|
168
|
-
>
|
|
169
|
-
| Handler<
|
|
170
|
-
Action,
|
|
171
|
-
"d",
|
|
172
|
-
S.Schema.Type<GetSuccess<Action>>,
|
|
173
|
-
S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
|
|
174
|
-
any
|
|
175
|
-
>
|
|
176
|
-
|
|
177
|
-
type Filter<T> = {
|
|
178
|
-
[K in keyof T as T[K] extends S.Schema.All & { success: S.Schema.Any; failure: S.Schema.Any } ? K : never]: T[K]
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCContextMap.Any>>
|
|
182
|
-
extends Middleware<Context, CTXMap>
|
|
183
|
-
{
|
|
184
|
-
// TODO
|
|
185
|
-
makeContext: Effect<
|
|
186
|
-
{ [K in keyof CTXMap as CTXMap[K][1] extends never ? never : CTXMap[K][0]]: CTXMap[K][1] },
|
|
187
|
-
never,
|
|
188
|
-
never
|
|
189
|
-
>
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export const RouterSymbol = Symbol()
|
|
193
|
-
export interface RouterShape<Rsc> {
|
|
194
|
-
[RouterSymbol]: Rsc
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
type RPCRouteR<T extends Rpc.Rpc<any, any>> = [T] extends [
|
|
198
|
-
Rpc.Rpc<any, infer R>
|
|
199
|
-
] ? R
|
|
200
|
-
: never
|
|
201
|
-
|
|
202
|
-
type RPCRouteReq<T extends Rpc.Rpc<any, any>> = [T] extends [
|
|
203
|
-
Rpc.Rpc<infer Req, any>
|
|
204
|
-
] ? Req
|
|
205
|
-
: never
|
|
206
|
-
|
|
207
|
-
export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.Any>>(
|
|
208
|
-
middleware: ExtendedMiddleware<Context, CTXMap>,
|
|
209
|
-
devMode: boolean
|
|
210
|
-
) => {
|
|
211
|
-
const rpc = makeRpc(middleware)
|
|
212
|
-
function matchFor<
|
|
213
|
-
const ModuleName extends string,
|
|
214
|
-
const Rsc extends Record<string, any>
|
|
215
|
-
>(
|
|
216
|
-
rsc: Rsc & { meta: { moduleName: ModuleName } }
|
|
217
|
-
) {
|
|
218
|
-
const meta = rsc.meta
|
|
219
|
-
type Filtered = Filter<Rsc>
|
|
220
|
-
const filtered = typedKeysOf(rsc).reduce((acc, cur) => {
|
|
221
|
-
if (Predicate.isObject(rsc[cur]) && rsc[cur]["success"]) {
|
|
222
|
-
acc[cur as keyof Filtered] = rsc[cur]
|
|
223
|
-
}
|
|
224
|
-
return acc
|
|
225
|
-
}, {} as Filtered)
|
|
226
|
-
|
|
227
|
-
const matchWithServices = <Key extends keyof Filtered>(action: Key) => {
|
|
228
|
-
return <
|
|
229
|
-
SVC extends Record<
|
|
230
|
-
string,
|
|
231
|
-
Effect<any, any, any>
|
|
232
|
-
>,
|
|
233
|
-
R2,
|
|
234
|
-
E,
|
|
235
|
-
A
|
|
236
|
-
>(
|
|
237
|
-
services: SVC,
|
|
238
|
-
f: (
|
|
239
|
-
req: S.Schema.Type<Rsc[Key]>,
|
|
240
|
-
ctx: Compute<
|
|
241
|
-
LowerServices<EffectDeps<SVC>> & GetRouteContext<CTXMap, Rsc[Key]["config"]> & {
|
|
242
|
-
Response: Rsc[Key]["success"]
|
|
243
|
-
},
|
|
244
|
-
"flat"
|
|
245
|
-
>
|
|
246
|
-
) => Effect<A, E, R2>
|
|
247
|
-
) =>
|
|
248
|
-
(req: any) =>
|
|
249
|
-
Effect.andThen(
|
|
250
|
-
Effect.all({ svc: allLower(services), ctx: middleware.makeContext }),
|
|
251
|
-
({ ctx, svc }) => f(req, { ...svc, ...ctx, Response: rsc[action].success } as any)
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
type MatchWithServicesNew<RT extends "raw" | "d", Key extends keyof Rsc> = {
|
|
256
|
-
<R2, E, A>(
|
|
257
|
-
f: Effect<A, E, R2>
|
|
258
|
-
): HandleVoid<
|
|
259
|
-
GetSuccessShape<Rsc[Key], RT>,
|
|
260
|
-
A,
|
|
261
|
-
Handler<
|
|
262
|
-
Rsc[Key],
|
|
263
|
-
RT,
|
|
264
|
-
A,
|
|
265
|
-
E,
|
|
266
|
-
Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
|
|
267
|
-
>
|
|
268
|
-
>
|
|
269
|
-
|
|
270
|
-
<R2, E, A>(
|
|
271
|
-
f: (
|
|
272
|
-
req: S.Schema.Type<Rsc[Key]>,
|
|
273
|
-
ctx: GetRouteContext<CTXMap, Rsc[Key]["config"]> & { Response: Rsc[Key]["success"] }
|
|
274
|
-
) => Effect<A, E, R2>
|
|
275
|
-
): HandleVoid<
|
|
276
|
-
GetSuccessShape<Rsc[Key], RT>,
|
|
277
|
-
A,
|
|
278
|
-
Handler<
|
|
279
|
-
Rsc[Key],
|
|
280
|
-
RT,
|
|
281
|
-
A,
|
|
282
|
-
E,
|
|
283
|
-
Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
|
|
284
|
-
>
|
|
285
|
-
>
|
|
286
|
-
|
|
287
|
-
<
|
|
288
|
-
SVC extends Record<
|
|
289
|
-
string,
|
|
290
|
-
EffectUnunified<any, any, any>
|
|
291
|
-
>,
|
|
292
|
-
R2,
|
|
293
|
-
E,
|
|
294
|
-
A
|
|
295
|
-
>(
|
|
296
|
-
services: SVC,
|
|
297
|
-
f: (
|
|
298
|
-
req: S.Schema.Type<Rsc[Key]>,
|
|
299
|
-
ctx: Compute<
|
|
300
|
-
LowerServices<EffectDeps<SVC>> & GetRouteContext<CTXMap, Rsc[Key]["config"]> & {
|
|
301
|
-
Response: Rsc[Key]["success"]
|
|
302
|
-
},
|
|
303
|
-
"flat"
|
|
304
|
-
>
|
|
305
|
-
) => Effect<A, E, R2>
|
|
306
|
-
): HandleVoid<
|
|
307
|
-
GetSuccessShape<Rsc[Key], RT>,
|
|
308
|
-
A,
|
|
309
|
-
Handler<
|
|
310
|
-
Rsc[Key],
|
|
311
|
-
RT,
|
|
312
|
-
A,
|
|
313
|
-
E,
|
|
314
|
-
Exclude<R2 | Effect.Context<SVC[keyof SVC]>, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
|
|
315
|
-
>
|
|
316
|
-
>
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
type Keys = keyof Filtered
|
|
320
|
-
|
|
321
|
-
const controllers = <
|
|
322
|
-
THandlers extends {
|
|
323
|
-
// import to keep them separate via | for type checking!!
|
|
324
|
-
[K in Keys]: AHandler<Rsc[K]>
|
|
325
|
-
},
|
|
326
|
-
TLayers extends NonEmptyArray<Layer.Layer.Any>
|
|
327
|
-
>(
|
|
328
|
-
controllers: THandlers,
|
|
329
|
-
layers?: TLayers
|
|
330
|
-
) => {
|
|
331
|
-
const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
|
|
332
|
-
const handler = controllers[cur as keyof typeof controllers]
|
|
333
|
-
const req = rsc[cur]
|
|
334
|
-
|
|
335
|
-
acc[cur] = rpc.effect(
|
|
336
|
-
handler._tag === "raw"
|
|
337
|
-
? class extends (req as any) {
|
|
338
|
-
static success = S.encodedSchema(req.success)
|
|
339
|
-
get [Schema.symbolSerializable]() {
|
|
340
|
-
return this.constructor
|
|
341
|
-
}
|
|
342
|
-
get [Schema.symbolWithResult]() {
|
|
343
|
-
return {
|
|
344
|
-
failure: req.failure,
|
|
345
|
-
success: S.encodedSchema(req.success)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
} as any
|
|
349
|
-
: req,
|
|
350
|
-
(req) =>
|
|
351
|
-
Effect
|
|
352
|
-
.annotateCurrentSpan(
|
|
353
|
-
"requestInput",
|
|
354
|
-
Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
|
|
355
|
-
prev[key] = key === "password"
|
|
356
|
-
? "<redacted>"
|
|
357
|
-
: typeof value === "string" || typeof value === "number" || typeof value === "boolean"
|
|
358
|
-
? typeof value === "string" && value.length > 256
|
|
359
|
-
? (value.substring(0, 253) + "...")
|
|
360
|
-
: value
|
|
361
|
-
: Array.isArray(value)
|
|
362
|
-
? `Array[${value.length}]`
|
|
363
|
-
: value === null || value === undefined
|
|
364
|
-
? `${value}`
|
|
365
|
-
: typeof value === "object" && value
|
|
366
|
-
? `Object[${Object.keys(value).length}]`
|
|
367
|
-
: typeof value
|
|
368
|
-
return prev
|
|
369
|
-
}, {} as Record<string, string | number | boolean>)
|
|
370
|
-
)
|
|
371
|
-
.pipe(
|
|
372
|
-
// can't use andThen due to some being a function and effect
|
|
373
|
-
Effect.zipRight(handler.handler(req as any) as any),
|
|
374
|
-
Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
|
|
375
|
-
Effect.tapDefect((cause) =>
|
|
376
|
-
Effect
|
|
377
|
-
.all([
|
|
378
|
-
reportRequestError(cause, {
|
|
379
|
-
action: `${meta.moduleName}.${req._tag}`
|
|
380
|
-
}),
|
|
381
|
-
Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
|
|
382
|
-
return InfraLogger
|
|
383
|
-
.logError("Finished request", cause)
|
|
384
|
-
.pipe(Effect.annotateLogs({
|
|
385
|
-
action: `${meta.moduleName}.${req._tag}`,
|
|
386
|
-
req: pretty(req),
|
|
387
|
-
headers: pretty(headers)
|
|
388
|
-
// resHeaders: pretty(
|
|
389
|
-
// Object
|
|
390
|
-
// .entries(headers)
|
|
391
|
-
// .reduce((prev, [key, value]) => {
|
|
392
|
-
// prev[key] = value && typeof value === "string" ? snipString(value) : value
|
|
393
|
-
// return prev
|
|
394
|
-
// }, {} as Record<string, any>)
|
|
395
|
-
// )
|
|
396
|
-
}))
|
|
397
|
-
}))
|
|
398
|
-
])
|
|
399
|
-
),
|
|
400
|
-
devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
|
|
401
|
-
Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
|
|
402
|
-
captureStackTrace: () => handler.stack
|
|
403
|
-
})
|
|
404
|
-
),
|
|
405
|
-
meta.moduleName
|
|
406
|
-
) // TODO
|
|
407
|
-
return acc
|
|
408
|
-
}, {} as any) as {
|
|
409
|
-
[K in Keys]: Rpc.Rpc<
|
|
410
|
-
Rsc[K],
|
|
411
|
-
_R<ReturnType<THandlers[K]["handler"]>>
|
|
412
|
-
>
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
|
|
416
|
-
RPCRouteReq<typeof mapped[keyof typeof mapped]>,
|
|
417
|
-
RPCRouteR<typeof mapped[keyof typeof mapped]>
|
|
418
|
-
>
|
|
419
|
-
|
|
420
|
-
type Router = RouterShape<Rsc>
|
|
421
|
-
const r: HttpRouter.HttpRouter.TagClass<
|
|
422
|
-
Router,
|
|
423
|
-
`${typeof meta.moduleName}Router`,
|
|
424
|
-
never,
|
|
425
|
-
Exclude<
|
|
426
|
-
RPCRouteR<
|
|
427
|
-
{ [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
|
|
428
|
-
>,
|
|
429
|
-
{ [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
|
|
430
|
-
>
|
|
431
|
-
> = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
|
|
432
|
-
|
|
433
|
-
const layer = r.use((router) =>
|
|
434
|
-
Effect.gen(function*() {
|
|
435
|
-
const httpApp = toHttpApp(rpcRouter, {
|
|
436
|
-
spanPrefix: rsc
|
|
437
|
-
.meta
|
|
438
|
-
.moduleName + "."
|
|
439
|
-
})
|
|
440
|
-
const services = (yield* Effect.context<never>()).pipe(
|
|
441
|
-
Context.omit(Scope.Scope as never),
|
|
442
|
-
Context.omit(Tracer.ParentSpan as never)
|
|
443
|
-
)
|
|
444
|
-
yield* router
|
|
445
|
-
.all(
|
|
446
|
-
"/",
|
|
447
|
-
(httpApp
|
|
448
|
-
.pipe(HttpMiddleware.make(Effect.provide(services)))) as any,
|
|
449
|
-
// TODO: not queries.
|
|
450
|
-
{ uninterruptible: true }
|
|
451
|
-
)
|
|
452
|
-
})
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
const routes = layer.pipe(
|
|
456
|
-
Layer.provideMerge(r.Live),
|
|
457
|
-
layers ? Layer.provide(layers) : (_) => _
|
|
458
|
-
) as Layer.Layer<
|
|
459
|
-
Router,
|
|
460
|
-
{ [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number],
|
|
461
|
-
{ [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
|
|
462
|
-
>
|
|
463
|
-
|
|
464
|
-
// Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
|
|
465
|
-
|
|
466
|
-
return {
|
|
467
|
-
moduleName: meta.moduleName,
|
|
468
|
-
Router: r,
|
|
469
|
-
routes
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const effect = <
|
|
474
|
-
E,
|
|
475
|
-
R,
|
|
476
|
-
THandlers extends {
|
|
477
|
-
// import to keep them separate via | for type checking!!
|
|
478
|
-
[K in Keys]: AHandler<Rsc[K]>
|
|
479
|
-
},
|
|
480
|
-
TLayers extends NonEmptyArray<Layer.Layer.Any>
|
|
481
|
-
>(
|
|
482
|
-
layers: TLayers,
|
|
483
|
-
make: Effect<THandlers, E, R>
|
|
484
|
-
) => {
|
|
485
|
-
type Router = RouterShape<Rsc>
|
|
486
|
-
const r: HttpRouter.HttpRouter.TagClass<
|
|
487
|
-
Router,
|
|
488
|
-
`${typeof meta.moduleName}Router`,
|
|
489
|
-
never,
|
|
490
|
-
Exclude<
|
|
491
|
-
RPCRouteR<
|
|
492
|
-
{ [K in keyof Filter<Rsc>]: Rpc.Rpc<Rsc[K], _R<ReturnType<THandlers[K]["handler"]>>> }[keyof Filter<Rsc>]
|
|
493
|
-
>,
|
|
494
|
-
{ [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]
|
|
495
|
-
>
|
|
496
|
-
> = (class Router extends HttpRouter.Tag(`${meta.moduleName}Router`)<Router>() {}) as any
|
|
497
|
-
|
|
498
|
-
const layer = r.use((router) =>
|
|
499
|
-
Effect.gen(function*() {
|
|
500
|
-
const controllers = yield* make
|
|
501
|
-
// return make.pipe(Effect.map((c) => controllers(c, layers)))
|
|
502
|
-
const mapped = typedKeysOf(filtered).reduce((acc, cur) => {
|
|
503
|
-
const handler = controllers[cur as keyof typeof controllers]
|
|
504
|
-
const req = rsc[cur]
|
|
505
|
-
|
|
506
|
-
acc[cur] = rpc.effect(
|
|
507
|
-
handler._tag === "raw"
|
|
508
|
-
? class extends (req as any) {
|
|
509
|
-
static success = S.encodedSchema(req.success)
|
|
510
|
-
get [Schema.symbolSerializable]() {
|
|
511
|
-
return this.constructor
|
|
512
|
-
}
|
|
513
|
-
get [Schema.symbolWithResult]() {
|
|
514
|
-
return {
|
|
515
|
-
failure: req.failure,
|
|
516
|
-
success: S.encodedSchema(req.success)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
} as any
|
|
520
|
-
: req,
|
|
521
|
-
(req) =>
|
|
522
|
-
Effect
|
|
523
|
-
.annotateCurrentSpan(
|
|
524
|
-
"requestInput",
|
|
525
|
-
Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
|
|
526
|
-
prev[key] = key === "password"
|
|
527
|
-
? "<redacted>"
|
|
528
|
-
: typeof value === "string" || typeof value === "number" || typeof value === "boolean"
|
|
529
|
-
? typeof value === "string" && value.length > 256
|
|
530
|
-
? (value.substring(0, 253) + "...")
|
|
531
|
-
: value
|
|
532
|
-
: Array.isArray(value)
|
|
533
|
-
? `Array[${value.length}]`
|
|
534
|
-
: value === null || value === undefined
|
|
535
|
-
? `${value}`
|
|
536
|
-
: typeof value === "object" && value
|
|
537
|
-
? `Object[${Object.keys(value).length}]`
|
|
538
|
-
: typeof value
|
|
539
|
-
return prev
|
|
540
|
-
}, {} as Record<string, string | number | boolean>)
|
|
541
|
-
)
|
|
542
|
-
.pipe(
|
|
543
|
-
// can't use andThen due to some being a function and effect
|
|
544
|
-
Effect.zipRight(handler.handler(req as any) as any),
|
|
545
|
-
Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
|
|
546
|
-
Effect.tapDefect((cause) =>
|
|
547
|
-
Effect
|
|
548
|
-
.all([
|
|
549
|
-
reportRequestError(cause, {
|
|
550
|
-
action: `${meta.moduleName}.${req._tag}`
|
|
551
|
-
}),
|
|
552
|
-
Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
|
|
553
|
-
return InfraLogger
|
|
554
|
-
.logError("Finished request", cause)
|
|
555
|
-
.pipe(Effect.annotateLogs({
|
|
556
|
-
action: `${meta.moduleName}.${req._tag}`,
|
|
557
|
-
req: pretty(req),
|
|
558
|
-
headers: pretty(headers)
|
|
559
|
-
// resHeaders: pretty(
|
|
560
|
-
// Object
|
|
561
|
-
// .entries(headers)
|
|
562
|
-
// .reduce((prev, [key, value]) => {
|
|
563
|
-
// prev[key] = value && typeof value === "string" ? snipString(value) : value
|
|
564
|
-
// return prev
|
|
565
|
-
// }, {} as Record<string, any>)
|
|
566
|
-
// )
|
|
567
|
-
}))
|
|
568
|
-
}))
|
|
569
|
-
])
|
|
570
|
-
),
|
|
571
|
-
devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
|
|
572
|
-
Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
|
|
573
|
-
captureStackTrace: () => handler.stack
|
|
574
|
-
})
|
|
575
|
-
),
|
|
576
|
-
meta.moduleName
|
|
577
|
-
) // TODO
|
|
578
|
-
return acc
|
|
579
|
-
}, {} as any) as {
|
|
580
|
-
[K in Keys]: Rpc.Rpc<
|
|
581
|
-
Rsc[K],
|
|
582
|
-
_R<ReturnType<THandlers[K]["handler"]>>
|
|
583
|
-
>
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const rpcRouter = RpcRouter.make(...Object.values(mapped) as any) as RpcRouter.RpcRouter<
|
|
587
|
-
RPCRouteReq<typeof mapped[keyof typeof mapped]>,
|
|
588
|
-
RPCRouteR<typeof mapped[keyof typeof mapped]>
|
|
589
|
-
>
|
|
590
|
-
const httpApp = toHttpApp(rpcRouter, {
|
|
591
|
-
spanPrefix: rsc
|
|
592
|
-
.meta
|
|
593
|
-
.moduleName + "."
|
|
594
|
-
})
|
|
595
|
-
const services = (yield* Effect.context<never>()).pipe(
|
|
596
|
-
Context.omit(Scope.Scope as never),
|
|
597
|
-
Context.omit(Tracer.ParentSpan as never)
|
|
598
|
-
)
|
|
599
|
-
yield* router
|
|
600
|
-
.all(
|
|
601
|
-
"/",
|
|
602
|
-
(httpApp
|
|
603
|
-
.pipe(HttpMiddleware.make(Effect.provide(services)))) as any,
|
|
604
|
-
// TODO: not queries.
|
|
605
|
-
{ uninterruptible: true }
|
|
606
|
-
)
|
|
607
|
-
})
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
611
|
-
const routes = layer.pipe(
|
|
612
|
-
Layer.provideMerge(r.Live),
|
|
613
|
-
layers ? Layer.provide(layers) as any : (_) => _
|
|
614
|
-
) as Layer.Layer<
|
|
615
|
-
Router,
|
|
616
|
-
{ [k in keyof TLayers]: Layer.Layer.Error<TLayers[k]> }[number] | E,
|
|
617
|
-
| { [k in keyof TLayers]: Layer.Layer.Context<TLayers[k]> }[number]
|
|
618
|
-
| Exclude<R, { [k in keyof TLayers]: Layer.Layer.Success<TLayers[k]> }[number]>
|
|
619
|
-
>
|
|
620
|
-
|
|
621
|
-
// Effect.Effect<HttpRouter.HttpRouter<unknown, HttpRouter.HttpRouter.DefaultServices>, never, UserRouter>
|
|
622
|
-
|
|
623
|
-
return {
|
|
624
|
-
moduleName: meta.moduleName,
|
|
625
|
-
Router: r,
|
|
626
|
-
routes
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const r = {
|
|
631
|
-
effect,
|
|
632
|
-
controllers,
|
|
633
|
-
...typedKeysOf(filtered).reduce(
|
|
634
|
-
(prev, cur) => {
|
|
635
|
-
;(prev as any)[cur] = (svcOrFnOrEffect: any, fnOrNone: any) => {
|
|
636
|
-
const stack = new Error().stack?.split("\n").slice(2).join("\n")
|
|
637
|
-
return Effect.isEffect(svcOrFnOrEffect)
|
|
638
|
-
? class {
|
|
639
|
-
static stack = stack
|
|
640
|
-
static _tag = "d"
|
|
641
|
-
static handler = () => svcOrFnOrEffect
|
|
642
|
-
}
|
|
643
|
-
: typeof svcOrFnOrEffect === "function"
|
|
644
|
-
? class {
|
|
645
|
-
static stack = stack
|
|
646
|
-
static _tag = "d"
|
|
647
|
-
static handler = (req: any) =>
|
|
648
|
-
Effect.andThen(
|
|
649
|
-
Effect.all({ ctx: middleware.makeContext }),
|
|
650
|
-
({ ctx }) => svcOrFnOrEffect(req, { ...ctx, Response: rsc[cur].success })
|
|
651
|
-
)
|
|
652
|
-
}
|
|
653
|
-
: class {
|
|
654
|
-
static stack = stack
|
|
655
|
-
static _tag = "d"
|
|
656
|
-
static handler = matchWithServices(cur)(svcOrFnOrEffect, fnOrNone)
|
|
657
|
-
}
|
|
658
|
-
} // "Raw" variations are for when you don't want to decode just to encode it again on the response
|
|
659
|
-
// e.g for direct projection from DB
|
|
660
|
-
// but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
|
|
661
|
-
;(prev as any)[(cur as any) + "Raw"] = (svcOrFnOrEffect: any, fnOrNone: any) => {
|
|
662
|
-
const stack = new Error().stack?.split("\n").slice(2).join("\n")
|
|
663
|
-
return Effect.isEffect(svcOrFnOrEffect)
|
|
664
|
-
? class {
|
|
665
|
-
static stack = stack
|
|
666
|
-
static _tag = "raw"
|
|
667
|
-
static handler = () => svcOrFnOrEffect
|
|
668
|
-
}
|
|
669
|
-
: typeof svcOrFnOrEffect === "function"
|
|
670
|
-
? class {
|
|
671
|
-
static stack = stack
|
|
672
|
-
static _tag = "raw"
|
|
673
|
-
static handler = (req: any, ctx: any) => svcOrFnOrEffect(req, { ...ctx, Response: rsc[cur].success })
|
|
674
|
-
}
|
|
675
|
-
: class {
|
|
676
|
-
static stack = stack
|
|
677
|
-
static _tag = "raw"
|
|
678
|
-
static handler = matchWithServices(cur)(svcOrFnOrEffect, fnOrNone)
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return prev
|
|
682
|
-
},
|
|
683
|
-
{} as
|
|
684
|
-
& {
|
|
685
|
-
// use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
|
|
686
|
-
/**
|
|
687
|
-
* Requires the Type shape
|
|
688
|
-
*/
|
|
689
|
-
[Key in keyof Filtered]: MatchWithServicesNew<"d", Key>
|
|
690
|
-
}
|
|
691
|
-
& {
|
|
692
|
-
// use Rsc as Key over using Keys, so that the Go To on X.Action remain in tact in Controllers files
|
|
693
|
-
/**
|
|
694
|
-
* Requires the Encoded shape (e.g directly undecoded from DB, so that we don't do multiple Decode/Encode)
|
|
695
|
-
*/
|
|
696
|
-
[Key in keyof Filtered as Key extends string ? `${Key}Raw` : never]: MatchWithServicesNew<"raw", Key>
|
|
697
|
-
}
|
|
698
|
-
)
|
|
699
|
-
}
|
|
700
|
-
return r
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
type HR<T> = T extends HttpRouter.HttpRouter<any, infer R> ? R : never
|
|
704
|
-
type HE<T> = T extends HttpRouter.HttpRouter<infer E, any> ? E : never
|
|
705
|
-
|
|
706
|
-
type RequestHandlersTest = {
|
|
707
|
-
[key: string]: {
|
|
708
|
-
Router: { router: Effect<HttpRouter.HttpRouter<any, any>, any, any> }
|
|
709
|
-
routes: Layer.Layer<any, any, any>
|
|
710
|
-
moduleName: string
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
function matchAll<T extends RequestHandlersTest, A, E, R>(
|
|
714
|
-
handlers: T,
|
|
715
|
-
requestLayer: Layer.Layer<A, E, R>
|
|
716
|
-
) {
|
|
717
|
-
const routers = typedValuesOf(handlers)
|
|
718
|
-
|
|
719
|
-
const rootRouter = class extends HttpRouter.Tag("RootRouter")<
|
|
720
|
-
"RootRouter",
|
|
721
|
-
HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
|
|
722
|
-
HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>
|
|
723
|
-
>() {}
|
|
724
|
-
|
|
725
|
-
const r = rootRouter
|
|
726
|
-
.use((router) =>
|
|
727
|
-
Effect.gen(function*() {
|
|
728
|
-
for (const route of routers) {
|
|
729
|
-
yield* router.mount(
|
|
730
|
-
("/rpc/" + route.moduleName) as any,
|
|
731
|
-
yield* route
|
|
732
|
-
.Router
|
|
733
|
-
.router
|
|
734
|
-
.pipe(Effect.map(HttpRouter.use(flow(Effect.provide(requestLayer))))) as any
|
|
735
|
-
)
|
|
736
|
-
}
|
|
737
|
-
})
|
|
738
|
-
)
|
|
739
|
-
.pipe(Layer.provide(routers.map((r) => r.routes).flat() as unknown as NonEmptyArray<Layer.Layer.Any>))
|
|
740
|
-
|
|
741
|
-
return {
|
|
742
|
-
layer: r as Layer.Layer<
|
|
743
|
-
never,
|
|
744
|
-
Layer.Layer.Error<typeof handlers[keyof typeof handlers]["routes"]>,
|
|
745
|
-
Layer.Layer.Context<typeof handlers[keyof typeof handlers]["routes"]>
|
|
746
|
-
>,
|
|
747
|
-
Router: rootRouter as any as HttpRouter.HttpRouter.TagClass<
|
|
748
|
-
"RootRouter",
|
|
749
|
-
"RootRouter",
|
|
750
|
-
HE<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>,
|
|
751
|
-
R | Exclude<HR<Effect.Success<typeof handlers[keyof typeof handlers]["Router"]["router"]>>, A>
|
|
752
|
-
>
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return { matchAll, matchFor }
|
|
757
|
-
}
|