@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.
- 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 +117 -112
- package/_cjs/api/routing.cjs.map +1 -1
- package/dist/api/routing/DynamicMiddleware.d.ts +9 -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 +48 -30
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +151 -122
- package/package.json +1 -21
- package/src/api/routing/DynamicMiddleware.ts +41 -26
- package/src/api/routing.ts +373 -350
- package/vitest.config.ts.timestamp-1711656440838-19c636fe320df.mjs +0 -0
- package/vitest.config.ts.timestamp-1711724061890-6ecedb0a07fdd.mjs +0 -0
- package/vitest.config.ts.timestamp-1711743489537-da8d9e5f66c9f.mjs +0 -0
- package/vitest.config.ts.timestamp-1711744615239-dcf257a844e01.mjs +37 -0
- 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 -758
- package/src/api/routing3.ts +0 -511
package/src/api/routing.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
| Handler<
|
|
125
|
+
export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", R> extends
|
|
126
|
+
HandlerFull<
|
|
148
127
|
Action,
|
|
149
|
-
|
|
150
|
-
|
|
128
|
+
RT,
|
|
129
|
+
GetSuccessShape<Action, RT>,
|
|
151
130
|
S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
|
|
152
|
-
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
178
|
-
|
|
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
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Effect
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
313
|
+
layers: TLayers,
|
|
314
|
+
make: (requests: typeof items) => Effect<THandlers, E, R>
|
|
299
315
|
) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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]:
|
|
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>(
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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 }
|