@effect-app/infra 0.133.0-next.8 → 0.133.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 +494 -0
- package/_cjs/RequestContext.cjs +8 -9
- package/_cjs/RequestContext.cjs.map +1 -1
- package/_cjs/_ext/Array.cjs +4 -3
- package/_cjs/_ext/Array.cjs.map +1 -1
- package/_cjs/_global.ext.cjs +0 -25
- package/_cjs/_global.ext.cjs.map +1 -1
- package/_cjs/api/codec.cjs +1 -1
- package/_cjs/api/codec.cjs.map +1 -1
- package/_cjs/api/http.cjs +42 -0
- package/_cjs/api/http.cjs.map +1 -0
- package/_cjs/api/routing/base.cjs +169 -0
- package/_cjs/api/routing/base.cjs.map +1 -0
- package/_cjs/api/routing/defaultErrorHandler.cjs +50 -0
- package/_cjs/api/routing/defaultErrorHandler.cjs.map +1 -0
- package/_cjs/api/routing/makeRequestHandler.cjs +134 -0
- package/_cjs/api/routing/makeRequestHandler.cjs.map +1 -0
- package/_cjs/api/routing/match.cjs +45 -0
- package/_cjs/api/routing/match.cjs.map +1 -0
- package/_cjs/api/routing.cjs +50 -0
- package/_cjs/api/routing.cjs.map +1 -0
- package/_cjs/api/setupRequest.cjs +31 -30
- package/_cjs/api/setupRequest.cjs.map +1 -1
- package/_cjs/errorReporter.cjs +4 -2
- package/_cjs/errorReporter.cjs.map +1 -1
- package/_cjs/errors.cjs +18 -66
- package/_cjs/errors.cjs.map +1 -1
- package/_cjs/services/Operations/live.cjs +11 -12
- package/_cjs/services/Operations/live.cjs.map +1 -1
- package/_cjs/services/QueueMaker/memQueue.cjs +9 -10
- package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs +4 -4
- package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
- package/_cjs/services/Repository.cjs +3 -3
- package/_cjs/services/Repository.cjs.map +1 -1
- package/_cjs/services/RepositoryBase.cjs +14 -6
- package/_cjs/services/RepositoryBase.cjs.map +1 -1
- package/_cjs/services/RequestContextContainer.cjs +3 -3
- package/_cjs/services/RequestContextContainer.cjs.map +1 -1
- package/_cjs/services/Store/Redis.cjs.map +1 -1
- package/_cjs/test.arbs.cjs +24 -1
- package/_cjs/test.arbs.cjs.map +1 -1
- package/_src/RequestContext.ts +9 -12
- package/_src/_ext/Array.ts +4 -4
- package/_src/_global.ext.ts +0 -23
- package/_src/api/codec.ts +1 -1
- package/_src/api/http.ts +26 -0
- package/_src/api/routing/base.ts +476 -0
- package/_src/api/routing/defaultErrorHandler.ts +83 -0
- package/_src/api/routing/makeRequestHandler.ts +278 -0
- package/_src/api/routing/match.ts +119 -0
- package/_src/api/routing.ts +11 -0
- package/_src/api/setupRequest.ts +39 -34
- package/_src/errorReporter.ts +4 -2
- package/_src/errors.ts +1 -58
- package/_src/services/QueueMaker/memQueue.ts +3 -3
- package/_src/services/QueueMaker/sbqueue.ts +3 -3
- package/_src/services/Repository.ts +40 -43
- package/_src/services/RepositoryBase.ts +26 -20
- package/_src/services/Store/Redis.ts +1 -1
- package/_src/services/Store/service.ts +4 -4
- package/_src/test.arbs.ts +17 -14
- package/dist/RequestContext.d.ts +72 -24
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +10 -11
- package/dist/_ext/Array.d.ts +2 -2
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/Array.js +5 -4
- package/dist/_global.ext.d.ts +0 -22
- package/dist/_global.ext.d.ts.map +1 -1
- package/dist/_global.ext.js +1 -18
- package/dist/api/codec.d.ts +1 -1
- package/dist/api/codec.d.ts.map +1 -1
- package/dist/api/codec.js +2 -2
- package/dist/api/http.d.ts +16 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +12 -0
- package/dist/api/routing/base.d.ts +117 -0
- package/dist/api/routing/base.d.ts.map +1 -0
- package/dist/api/routing/base.js +137 -0
- package/dist/api/routing/defaultErrorHandler.d.ts +16 -0
- package/dist/api/routing/defaultErrorHandler.d.ts.map +1 -0
- package/dist/api/routing/defaultErrorHandler.js +42 -0
- package/dist/api/routing/makeRequestHandler.d.ts +20 -0
- package/dist/api/routing/makeRequestHandler.d.ts.map +1 -0
- package/dist/api/routing/makeRequestHandler.js +121 -0
- package/dist/api/routing/match.d.ts +12 -0
- package/dist/api/routing/match.d.ts.map +1 -0
- package/dist/api/routing/match.js +31 -0
- package/dist/api/routing.d.ts +5 -0
- package/dist/api/routing.d.ts.map +1 -0
- package/dist/api/routing.js +8 -0
- package/dist/api/setupRequest.d.ts +4 -4
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +31 -37
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +5 -3
- package/dist/errors.d.ts +1 -81
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -46
- package/dist/services/Operations/live.js +16 -17
- package/dist/services/QueueMaker/memQueue.d.ts +3 -3
- package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/memQueue.js +9 -10
- package/dist/services/QueueMaker/sbqueue.d.ts +3 -3
- package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/sbqueue.js +4 -4
- package/dist/services/QueueMaker/service.d.ts +1 -1
- package/dist/services/Repository.d.ts +37 -38
- package/dist/services/Repository.d.ts.map +1 -1
- package/dist/services/Repository.js +4 -4
- package/dist/services/RepositoryBase.d.ts +15 -15
- package/dist/services/RepositoryBase.d.ts.map +1 -1
- package/dist/services/RepositoryBase.js +12 -7
- package/dist/services/RequestContextContainer.d.ts +1 -1
- package/dist/services/RequestContextContainer.js +3 -3
- package/dist/services/Store/Redis.js +1 -1
- package/dist/services/Store/service.d.ts +4 -4
- package/dist/services/Store/service.d.ts.map +1 -1
- package/dist/services/simpledb/redisdb.d.ts +5 -1
- package/dist/services/simpledb/redisdb.d.ts.map +1 -1
- package/dist/services/simpledb/shared.d.ts +5 -1
- package/dist/services/simpledb/shared.d.ts.map +1 -1
- package/dist/test.arbs.d.ts +12 -1
- package/dist/test.arbs.d.ts.map +1 -1
- package/dist/test.arbs.js +18 -2
- package/package.json +73 -11
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
4
|
+
import * as S from "@effect-app/schema"
|
|
5
|
+
|
|
6
|
+
import type { EnforceNonEmptyRecord } from "@effect-app/core/utils"
|
|
7
|
+
import { ValidationError } from "@effect-app/infra/errors"
|
|
8
|
+
import type { REST, StructFields } from "@effect-app/schema"
|
|
9
|
+
import type { Simplify } from "effect/Types"
|
|
10
|
+
import type express from "express"
|
|
11
|
+
import type { HttpRequestError, HttpRoute } from "../http.js"
|
|
12
|
+
|
|
13
|
+
export type Flatten<T extends object> = object extends T ? object : {
|
|
14
|
+
[K in keyof T]-?: (
|
|
15
|
+
x: NonNullable<T[K]> extends infer V ? V extends object ? V extends readonly any[] ? Pick<T, K>
|
|
16
|
+
: FlattenLVL1<V> extends infer FV ? ({
|
|
17
|
+
[P in keyof FV as `${Extract<K, string | number>}.${Extract<P, string | number>}`]: FV[P]
|
|
18
|
+
})
|
|
19
|
+
: never
|
|
20
|
+
: Pick<T, K>
|
|
21
|
+
: never
|
|
22
|
+
) => void
|
|
23
|
+
} extends Record<keyof T, (y: infer O) => void> ? O extends unknown /* infer U */ ? { [K in keyof O]: O[K] } : never
|
|
24
|
+
: never
|
|
25
|
+
|
|
26
|
+
type FlattenLVL1<T extends object> = object extends T ? object : {
|
|
27
|
+
[K in keyof T]-?: (
|
|
28
|
+
x: NonNullable<T[K]> extends infer V ? V extends object ? V extends readonly any[] ? Pick<T, K>
|
|
29
|
+
/*: Flatten<V> extends infer FV ? ({
|
|
30
|
+
[P in keyof FV as `${Extract<K, string | number>}.${Extract<P, string | number>}`]: FV[P]
|
|
31
|
+
})
|
|
32
|
+
: never
|
|
33
|
+
*/
|
|
34
|
+
: Pick<T, K>
|
|
35
|
+
: never
|
|
36
|
+
: never
|
|
37
|
+
) => void
|
|
38
|
+
} extends Record<keyof T, (y: infer O) => void> ? O extends unknown /* infer U */ ? { [K in keyof O]: O[K] } : never
|
|
39
|
+
: never
|
|
40
|
+
|
|
41
|
+
export type RouteMatch<
|
|
42
|
+
R,
|
|
43
|
+
M,
|
|
44
|
+
PR = never
|
|
45
|
+
> // RErr = never,
|
|
46
|
+
= // PathA,
|
|
47
|
+
// CookieA,
|
|
48
|
+
// QueryA,
|
|
49
|
+
// BodyA,
|
|
50
|
+
// HeaderA,
|
|
51
|
+
// ReqA extends PathA & QueryA & BodyA,
|
|
52
|
+
// ResA,
|
|
53
|
+
// PR = never
|
|
54
|
+
Effect<
|
|
55
|
+
never,
|
|
56
|
+
never,
|
|
57
|
+
HttpRoute<Exclude<Exclude<R, EnforceNonEmptyRecord<M>>, PR>, HttpRequestError> // RouteDescriptor<R, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, ResA, SupportedErrors, Methods>
|
|
58
|
+
>
|
|
59
|
+
|
|
60
|
+
export interface ReqHandler<
|
|
61
|
+
Req,
|
|
62
|
+
R,
|
|
63
|
+
E,
|
|
64
|
+
Res,
|
|
65
|
+
ReqSchema extends S.Schema<any, any>,
|
|
66
|
+
ResSchema extends S.Schema<any, any>,
|
|
67
|
+
CTX = any,
|
|
68
|
+
Context = any
|
|
69
|
+
> {
|
|
70
|
+
h: (r: Req, ctx: CTX) => Effect<R, E, Res>
|
|
71
|
+
Request: ReqSchema
|
|
72
|
+
Response: ResSchema
|
|
73
|
+
ResponseOpenApi: any
|
|
74
|
+
CTX: CTX
|
|
75
|
+
Context: Context
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ReqFromSchema<ReqSchema extends S.Schema<any, any>> = S.Schema.To<ReqSchema>
|
|
79
|
+
|
|
80
|
+
export type Extr<T> = T extends { Model: S.Schema<any, any> } ? T["Model"]
|
|
81
|
+
: T extends S.Schema<any, any> ? T
|
|
82
|
+
: never
|
|
83
|
+
|
|
84
|
+
export type ResFromSchema<ResSchema> = S.Schema.To<Extr<ResSchema>>
|
|
85
|
+
|
|
86
|
+
export type _R<T extends Effect<any, any, any>> = [T] extends [
|
|
87
|
+
Effect<infer R, any, any>
|
|
88
|
+
] ? R
|
|
89
|
+
: never
|
|
90
|
+
|
|
91
|
+
export type _E<T extends Effect<any, any, any>> = [T] extends [
|
|
92
|
+
Effect<any, infer E, any>
|
|
93
|
+
] ? E
|
|
94
|
+
: never
|
|
95
|
+
|
|
96
|
+
export type Request2<
|
|
97
|
+
Path extends string,
|
|
98
|
+
Method extends S.REST.Methods.Rest,
|
|
99
|
+
ReqA extends StructFields
|
|
100
|
+
> = S.REST.ReqRes<{}, S.ToStruct<ReqA>> & {
|
|
101
|
+
method: Method
|
|
102
|
+
path: Path
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type Encode<A, E> = (a: A) => E
|
|
106
|
+
|
|
107
|
+
// function getErrorMessage(current: ContextEntry) {
|
|
108
|
+
// switch (current.type.name) {
|
|
109
|
+
// case "NonEmptyString":
|
|
110
|
+
// return "Must not be empty"
|
|
111
|
+
// }
|
|
112
|
+
// if (current.type.name?.startsWith("NonEmptyReadonlyArray<")) {
|
|
113
|
+
// return "Must not be empty"
|
|
114
|
+
// }
|
|
115
|
+
// return `Invalid value specified`
|
|
116
|
+
// }
|
|
117
|
+
export function decodeErrors(x: unknown) {
|
|
118
|
+
return [x]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// const ValidationApplicative = Effect.getValidationApplicative(
|
|
122
|
+
// makeAssociative<ReadonlyArray<{ type: string; errors: ReturnType<typeof decodeErrors> }>>(
|
|
123
|
+
// (l, r) => l.concat(r)
|
|
124
|
+
// )
|
|
125
|
+
// )
|
|
126
|
+
|
|
127
|
+
// const structValidation = DSL.structF(ValidationApplicative)
|
|
128
|
+
export function parseRequestParams<
|
|
129
|
+
PathA extends StructFields,
|
|
130
|
+
CookieA extends StructFields,
|
|
131
|
+
QueryA extends StructFields,
|
|
132
|
+
BodyA extends StructFields,
|
|
133
|
+
HeaderA extends StructFields
|
|
134
|
+
>(
|
|
135
|
+
parsers: RequestParsers<PathA, CookieA, QueryA, BodyA, HeaderA>
|
|
136
|
+
) {
|
|
137
|
+
return (
|
|
138
|
+
{ body, cookies, headers, params, query }: {
|
|
139
|
+
body: unknown
|
|
140
|
+
cookies: unknown
|
|
141
|
+
headers: unknown
|
|
142
|
+
params: unknown
|
|
143
|
+
query: unknown
|
|
144
|
+
}
|
|
145
|
+
) =>
|
|
146
|
+
Effect
|
|
147
|
+
.all({
|
|
148
|
+
body: parsers
|
|
149
|
+
.parseBody(body)
|
|
150
|
+
.exit
|
|
151
|
+
.flatMap((_) =>
|
|
152
|
+
_.isFailure() && !_.cause.isFailure()
|
|
153
|
+
? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError, never>)
|
|
154
|
+
: Effect(
|
|
155
|
+
_.isSuccess()
|
|
156
|
+
? { _tag: "Success" as const, value: _.value }
|
|
157
|
+
: { _tag: "Failure", errors: _.cause.failures }
|
|
158
|
+
)
|
|
159
|
+
),
|
|
160
|
+
cookie: parsers
|
|
161
|
+
.parseCookie(cookies)
|
|
162
|
+
.exit
|
|
163
|
+
.flatMap((_) =>
|
|
164
|
+
_.isFailure() && !_.cause.isFailure()
|
|
165
|
+
? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError, never>)
|
|
166
|
+
: Effect(
|
|
167
|
+
_.isSuccess()
|
|
168
|
+
? { _tag: "Success" as const, value: _.value }
|
|
169
|
+
: { _tag: "Failure", errors: _.cause.failures }
|
|
170
|
+
)
|
|
171
|
+
),
|
|
172
|
+
headers: parsers
|
|
173
|
+
.parseHeaders(headers)
|
|
174
|
+
.exit
|
|
175
|
+
.flatMap((_) =>
|
|
176
|
+
_.isFailure() && !_.cause.isFailure()
|
|
177
|
+
? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError, never>)
|
|
178
|
+
: Effect(
|
|
179
|
+
_.isSuccess()
|
|
180
|
+
? { _tag: "Success" as const, value: _.value }
|
|
181
|
+
: { _tag: "Failure", errors: _.cause.failures }
|
|
182
|
+
)
|
|
183
|
+
),
|
|
184
|
+
query: parsers
|
|
185
|
+
.parseQuery(query)
|
|
186
|
+
.exit
|
|
187
|
+
.flatMap((_) =>
|
|
188
|
+
_.isFailure() && !_.cause.isFailure()
|
|
189
|
+
? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError, never>)
|
|
190
|
+
: Effect(
|
|
191
|
+
_.isSuccess()
|
|
192
|
+
? { _tag: "Success" as const, value: _.value }
|
|
193
|
+
: { _tag: "Failure", errors: _.cause.failures }
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
path: parsers
|
|
197
|
+
.parsePath(params)
|
|
198
|
+
.exit
|
|
199
|
+
.flatMap((_) =>
|
|
200
|
+
_.isFailure() && !_.cause.isFailure()
|
|
201
|
+
? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError, never>)
|
|
202
|
+
: Effect(
|
|
203
|
+
_.isSuccess()
|
|
204
|
+
? { _tag: "Success" as const, value: _.value }
|
|
205
|
+
: { _tag: "Failure", errors: _.cause.failures }
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
.flatMap(({ body, cookie, headers, path, query }) => {
|
|
210
|
+
const errors: unknown[] = []
|
|
211
|
+
if (body._tag === "Failure") {
|
|
212
|
+
errors.push(makeError("body")(body.errors))
|
|
213
|
+
}
|
|
214
|
+
if (cookie._tag === "Failure") {
|
|
215
|
+
errors.push(makeError("cookie")(cookie.errors))
|
|
216
|
+
}
|
|
217
|
+
if (headers._tag === "Failure") {
|
|
218
|
+
errors.push(makeError("headers")(headers.errors))
|
|
219
|
+
}
|
|
220
|
+
if (path._tag === "Failure") {
|
|
221
|
+
errors.push(makeError("path")(path.errors))
|
|
222
|
+
}
|
|
223
|
+
if (query._tag === "Failure") {
|
|
224
|
+
errors.push(makeError("query")(query.errors))
|
|
225
|
+
}
|
|
226
|
+
if (errors.length) {
|
|
227
|
+
return new ValidationError({ errors })
|
|
228
|
+
}
|
|
229
|
+
return Effect({
|
|
230
|
+
body: body.value!,
|
|
231
|
+
cookie: cookie.value!,
|
|
232
|
+
headers: headers.value!,
|
|
233
|
+
path: path.value!,
|
|
234
|
+
query: query.value!
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
+
// function mapErrors_<E, NE, NER extends Record<string, Effect<any, E, any>>>(
|
|
241
|
+
// t: NER, // TODO: enforce non empty
|
|
242
|
+
// mapErrors: (k: keyof NER) => (err: E) => NE
|
|
243
|
+
// ): {
|
|
244
|
+
// [K in keyof NER]: Effect<_R<NER[K]>, NE, Effect.Success<NER[K]>>
|
|
245
|
+
// } {
|
|
246
|
+
// return typedKeysOf(t).reduce(
|
|
247
|
+
// (prev, cur) => {
|
|
248
|
+
// prev[cur] = t[cur].mapError(mapErrors(cur))
|
|
249
|
+
// return prev
|
|
250
|
+
// },
|
|
251
|
+
// {} as {
|
|
252
|
+
// [K in keyof NER]: Effect<_R<NER[K]>, NE, Effect.Success<NER[K]>>
|
|
253
|
+
// }
|
|
254
|
+
// )
|
|
255
|
+
// }
|
|
256
|
+
|
|
257
|
+
function makeError(type: string) {
|
|
258
|
+
return (e: unknown) => [{ type, errors: decodeErrors(e) }]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function respondSuccess<ReqA, A, E>(
|
|
262
|
+
encodeResponse: (req: ReqA) => Encode<A, E>
|
|
263
|
+
) {
|
|
264
|
+
return (req: ReqA, res: express.Response) =>
|
|
265
|
+
flow(encodeResponse(req), Effect.succeed, (_) =>
|
|
266
|
+
_.flatMap((r) =>
|
|
267
|
+
Effect.sync(() => {
|
|
268
|
+
r === undefined
|
|
269
|
+
? res.status(204).send()
|
|
270
|
+
: res.status(200).send(JSON.stringify(r))
|
|
271
|
+
})
|
|
272
|
+
))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface RequestHandler2<
|
|
276
|
+
R,
|
|
277
|
+
Path extends string,
|
|
278
|
+
Method extends S.REST.Methods.Rest,
|
|
279
|
+
ReqA extends StructFields,
|
|
280
|
+
ResA extends StructFields,
|
|
281
|
+
ResE
|
|
282
|
+
> {
|
|
283
|
+
h: (i: ReqA) => Effect<R, ResE, ResA>
|
|
284
|
+
Request: Request2<Path, Method, ReqA>
|
|
285
|
+
Response: S.REST.ReqRes<any, any>
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function makeRequestParsers<
|
|
289
|
+
R,
|
|
290
|
+
M,
|
|
291
|
+
PathA extends StructFields,
|
|
292
|
+
CookieA extends StructFields,
|
|
293
|
+
QueryA extends StructFields,
|
|
294
|
+
BodyA extends StructFields,
|
|
295
|
+
HeaderA extends StructFields,
|
|
296
|
+
ReqA extends PathA & QueryA & BodyA,
|
|
297
|
+
ResA extends StructFields,
|
|
298
|
+
Errors,
|
|
299
|
+
PPath extends `/${string}`,
|
|
300
|
+
CTX,
|
|
301
|
+
Context,
|
|
302
|
+
Config
|
|
303
|
+
>(
|
|
304
|
+
Request: RequestHandler<
|
|
305
|
+
R,
|
|
306
|
+
M,
|
|
307
|
+
PathA,
|
|
308
|
+
CookieA,
|
|
309
|
+
QueryA,
|
|
310
|
+
BodyA,
|
|
311
|
+
HeaderA,
|
|
312
|
+
ReqA,
|
|
313
|
+
ResA,
|
|
314
|
+
Errors,
|
|
315
|
+
PPath,
|
|
316
|
+
CTX,
|
|
317
|
+
Context,
|
|
318
|
+
Config
|
|
319
|
+
>["Request"]
|
|
320
|
+
): RequestParsers<PathA, CookieA, QueryA, BodyA, HeaderA> {
|
|
321
|
+
const ph = Effect(
|
|
322
|
+
Option
|
|
323
|
+
.fromNullable(Request.Headers)
|
|
324
|
+
.map((s) => s as unknown as Schema<any, any>)
|
|
325
|
+
.map(S.parse)
|
|
326
|
+
)
|
|
327
|
+
const parseHeaders = (u: unknown) => ph.flatMapOpt((d) => d(u))
|
|
328
|
+
|
|
329
|
+
const pq = Effect(
|
|
330
|
+
Option
|
|
331
|
+
.fromNullable(Request.Query)
|
|
332
|
+
.map((s) => s as unknown as Schema<any, any>)
|
|
333
|
+
.map(S.parse)
|
|
334
|
+
)
|
|
335
|
+
const parseQuery = (u: unknown) => pq.flatMapOpt((d) => d(u))
|
|
336
|
+
|
|
337
|
+
const pb = Effect(
|
|
338
|
+
Option
|
|
339
|
+
.fromNullable(Request.Body)
|
|
340
|
+
.map((s) => s as unknown as Schema<any, any>)
|
|
341
|
+
.map(S.parse)
|
|
342
|
+
)
|
|
343
|
+
const parseBody = (u: unknown) => pb.flatMapOpt((d) => d(u))
|
|
344
|
+
|
|
345
|
+
const pp = Effect(
|
|
346
|
+
Option
|
|
347
|
+
.fromNullable(Request.Path)
|
|
348
|
+
.map((s) => s as unknown as Schema<any, any>)
|
|
349
|
+
.map(S.parse)
|
|
350
|
+
)
|
|
351
|
+
const parsePath = (u: unknown) => pp.flatMapOpt((d) => d(u))
|
|
352
|
+
|
|
353
|
+
const pc = Effect(
|
|
354
|
+
Option
|
|
355
|
+
.fromNullable(Request.Cookie)
|
|
356
|
+
.map((s) => s as unknown as Schema<any, any>)
|
|
357
|
+
.map(S.parse)
|
|
358
|
+
)
|
|
359
|
+
const parseCookie = (u: unknown) => pc.flatMapOpt((d) => d(u))
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
parseBody,
|
|
363
|
+
parseCookie,
|
|
364
|
+
parseHeaders,
|
|
365
|
+
parsePath,
|
|
366
|
+
parseQuery
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
type Decode<A> = (u: unknown) => Effect<never, unknown, A>
|
|
371
|
+
|
|
372
|
+
export interface RequestParsers<
|
|
373
|
+
PathA extends StructFields,
|
|
374
|
+
CookieA extends StructFields,
|
|
375
|
+
QueryA extends StructFields,
|
|
376
|
+
BodyA extends StructFields,
|
|
377
|
+
HeaderA extends StructFields
|
|
378
|
+
> {
|
|
379
|
+
parseHeaders: Decode<Option<Simplify<S.ToStruct<HeaderA>>>>
|
|
380
|
+
parseQuery: Decode<Option<Simplify<S.ToStruct<QueryA>>>>
|
|
381
|
+
parseBody: Decode<Option<Simplify<S.ToStruct<BodyA>>>>
|
|
382
|
+
parsePath: Decode<Option<Simplify<S.ToStruct<PathA>>>>
|
|
383
|
+
parseCookie: Decode<Option<Simplify<S.ToStruct<CookieA>>>>
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export type EffectDeps<A> = {
|
|
387
|
+
[K in keyof A as A[K] extends Effect<any, any, any> ? K : never]: A[K] extends Effect<any, any, any> ? A[K] : never
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export type Request<
|
|
391
|
+
M,
|
|
392
|
+
PathA extends StructFields,
|
|
393
|
+
CookieA extends StructFields,
|
|
394
|
+
QueryA extends StructFields,
|
|
395
|
+
BodyA extends StructFields,
|
|
396
|
+
HeaderA extends StructFields,
|
|
397
|
+
ReqA extends PathA & QueryA & BodyA,
|
|
398
|
+
PPath extends `/${string}`
|
|
399
|
+
> = REST.ReqRes<any, any> & {
|
|
400
|
+
method: REST.Methods.Rest
|
|
401
|
+
path: PPath
|
|
402
|
+
Cookie?: CookieA
|
|
403
|
+
Path?: PathA
|
|
404
|
+
Body?: BodyA
|
|
405
|
+
Query?: QueryA
|
|
406
|
+
Headers?: HeaderA
|
|
407
|
+
Tag: Tag<M, M>
|
|
408
|
+
ReqA?: ReqA
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export interface RequestHandlerBase<
|
|
412
|
+
R,
|
|
413
|
+
M,
|
|
414
|
+
PathA extends StructFields,
|
|
415
|
+
CookieA extends StructFields,
|
|
416
|
+
QueryA extends StructFields,
|
|
417
|
+
BodyA extends StructFields,
|
|
418
|
+
HeaderA extends StructFields,
|
|
419
|
+
ReqA extends PathA & QueryA & BodyA,
|
|
420
|
+
ResA extends StructFields,
|
|
421
|
+
ResE,
|
|
422
|
+
PPath extends `/${string}`,
|
|
423
|
+
Config
|
|
424
|
+
> {
|
|
425
|
+
adaptResponse?: any
|
|
426
|
+
h: (i: PathA & QueryA & BodyA & {}) => Effect<R, ResE, ResA>
|
|
427
|
+
Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath>
|
|
428
|
+
Response: REST.ReqRes<any, any>
|
|
429
|
+
ResponseOpenApi?: any
|
|
430
|
+
config: Config
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export interface RequestHandler<
|
|
434
|
+
R,
|
|
435
|
+
M,
|
|
436
|
+
PathA extends StructFields,
|
|
437
|
+
CookieA extends StructFields,
|
|
438
|
+
QueryA extends StructFields,
|
|
439
|
+
BodyA extends StructFields,
|
|
440
|
+
HeaderA extends StructFields,
|
|
441
|
+
ReqA extends PathA & QueryA & BodyA,
|
|
442
|
+
ResA extends StructFields,
|
|
443
|
+
ResE,
|
|
444
|
+
PPath extends `/${string}`,
|
|
445
|
+
CTX,
|
|
446
|
+
Context,
|
|
447
|
+
Config
|
|
448
|
+
> {
|
|
449
|
+
adaptResponse?: any
|
|
450
|
+
h: (i: PathA & QueryA & BodyA & {}, ctx: any /* TODO */) => Effect<R, ResE, ResA>
|
|
451
|
+
Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath> & Config
|
|
452
|
+
Response: REST.ReqRes<any, any>
|
|
453
|
+
ResponseOpenApi?: any
|
|
454
|
+
CTX: CTX
|
|
455
|
+
Context: Context
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface RequestHandlerOrig<
|
|
459
|
+
R,
|
|
460
|
+
M,
|
|
461
|
+
PathA extends StructFields,
|
|
462
|
+
CookieA extends StructFields,
|
|
463
|
+
QueryA extends StructFields,
|
|
464
|
+
BodyA extends StructFields,
|
|
465
|
+
HeaderA extends StructFields,
|
|
466
|
+
ReqA extends PathA & QueryA & BodyA,
|
|
467
|
+
ResA extends StructFields,
|
|
468
|
+
ResE,
|
|
469
|
+
PPath extends `/${string}`
|
|
470
|
+
> {
|
|
471
|
+
adaptResponse?: any
|
|
472
|
+
h: (i: PathA & QueryA & BodyA & {}) => Effect<R, ResE, ResA>
|
|
473
|
+
Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath>
|
|
474
|
+
Response: REST.ReqRes<any, any>
|
|
475
|
+
ResponseOpenApi?: any
|
|
476
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { logError } from "@effect-app/infra/errorReporter"
|
|
2
|
+
import type { SupportedErrors, ValidationError } from "@effect-app/prelude/client/errors"
|
|
3
|
+
import { HttpBody, HttpHeaders, type HttpServerRequest, type HttpServerResponse } from "../http.js"
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
InsufficientScopeError,
|
|
7
|
+
InvalidRequestError,
|
|
8
|
+
InvalidTokenError,
|
|
9
|
+
UnauthorizedError as JWTUnauthorizedError
|
|
10
|
+
} from "express-oauth2-jwt-bearer"
|
|
11
|
+
|
|
12
|
+
export class JWTError extends Data.TaggedClass("JWTError")<{
|
|
13
|
+
error:
|
|
14
|
+
| InsufficientScopeError
|
|
15
|
+
| InvalidRequestError
|
|
16
|
+
| InvalidTokenError
|
|
17
|
+
| JWTUnauthorizedError
|
|
18
|
+
}> {}
|
|
19
|
+
|
|
20
|
+
const logRequestError = logError("Request")
|
|
21
|
+
|
|
22
|
+
export function defaultBasicErrorHandler<R>(
|
|
23
|
+
_req: HttpServerRequest,
|
|
24
|
+
res: HttpServerResponse,
|
|
25
|
+
r2: Effect<R, ValidationError, HttpServerResponse>
|
|
26
|
+
) {
|
|
27
|
+
const sendError = (code: number) => (body: unknown) => Effect(res.setBody(HttpBody.unsafeJson(body)).setStatus(code))
|
|
28
|
+
return r2
|
|
29
|
+
.tapErrorCause((cause) => cause.isFailure() ? logRequestError(cause) : Effect.unit)
|
|
30
|
+
.catchTag("ValidationError", (err) => sendError(400)(err.errors))
|
|
31
|
+
// final catch all; expecting never so that unhandled known errors will show up
|
|
32
|
+
.catchAll((err: never) =>
|
|
33
|
+
Effect
|
|
34
|
+
.logError(
|
|
35
|
+
"Program error, compiler probably silenced, got an unsupported Error in Error Channel of Effect" + err
|
|
36
|
+
)
|
|
37
|
+
.map(() => err as unknown)
|
|
38
|
+
.flatMap(Effect.die)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const optimisticConcurrencySchedule = Schedule.once
|
|
43
|
+
&& Schedule.recurWhile<SupportedErrors | JWTError>((a) => a._tag === "OptimisticConcurrencyException")
|
|
44
|
+
|
|
45
|
+
export function defaultErrorHandler<R>(
|
|
46
|
+
req: HttpServerRequest,
|
|
47
|
+
res: HttpServerResponse,
|
|
48
|
+
r2: Effect<R, SupportedErrors | JWTError, HttpServerResponse>
|
|
49
|
+
) {
|
|
50
|
+
const r3 = req.method === "PATCH"
|
|
51
|
+
? r2.retry(optimisticConcurrencySchedule)
|
|
52
|
+
: r2
|
|
53
|
+
const sendError = (code: number) => (body: unknown) => Effect(res.setStatus(code).setBody(HttpBody.unsafeJson(body)))
|
|
54
|
+
return r3
|
|
55
|
+
.tapErrorCause((cause) => cause.isFailure() ? logRequestError(cause) : Effect.unit)
|
|
56
|
+
.catchTags({
|
|
57
|
+
"JWTError": (err) =>
|
|
58
|
+
Effect.succeed(
|
|
59
|
+
HttpServerResponse.unsafeJson({ message: err.error.message }, {
|
|
60
|
+
status: err
|
|
61
|
+
.error
|
|
62
|
+
.status,
|
|
63
|
+
headers: HttpHeaders.fromInput(err.error.headers)
|
|
64
|
+
})
|
|
65
|
+
),
|
|
66
|
+
"ValidationError": (err) => sendError(400)(err.errors),
|
|
67
|
+
"NotFoundError": sendError(404),
|
|
68
|
+
"NotLoggedInError": sendError(401),
|
|
69
|
+
"UnauthorizedError": sendError(403),
|
|
70
|
+
"InvalidStateError": sendError(422),
|
|
71
|
+
// 412 or 409.. https://stackoverflow.com/questions/19122088/which-http-status-code-to-use-to-reject-a-put-due-to-optimistic-locking-failure
|
|
72
|
+
"OptimisticConcurrencyException": sendError(412)
|
|
73
|
+
})
|
|
74
|
+
// final catch all; expecting never so that unhandled known errors will show up
|
|
75
|
+
.catchAll((err: never) =>
|
|
76
|
+
Effect
|
|
77
|
+
.logError(
|
|
78
|
+
"Program error, compiler probably silenced, got an unsupported Error in Error Channel of Effect" + err
|
|
79
|
+
)
|
|
80
|
+
.map(() => err as unknown)
|
|
81
|
+
.flatMap(Effect.die)
|
|
82
|
+
)
|
|
83
|
+
}
|