@effect-app/infra 1.28.3 → 1.29.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/_cjs/api/{routing2 → routing}/DynamicMiddleware.cjs +1 -1
  3. package/_cjs/api/routing/DynamicMiddleware.cjs.map +1 -0
  4. package/_cjs/api/routing.cjs +135 -43
  5. package/_cjs/api/routing.cjs.map +1 -1
  6. package/dist/api/routing/DynamicMiddleware.d.ts.map +1 -0
  7. package/dist/api/routing/DynamicMiddleware.js +33 -0
  8. package/dist/api/routing.d.ts +100 -4
  9. package/dist/api/routing.d.ts.map +1 -1
  10. package/dist/api/routing.js +116 -9
  11. package/dist/services/Repository/ext.d.ts +4 -4
  12. package/package.json +9 -79
  13. package/src/api/{routing2 → routing}/DynamicMiddleware.ts +1 -1
  14. package/src/api/routing.ts +434 -8
  15. package/_cjs/api/routing/base.cjs +0 -135
  16. package/_cjs/api/routing/base.cjs.map +0 -1
  17. package/_cjs/api/routing/defaultErrorHandler.cjs +0 -62
  18. package/_cjs/api/routing/defaultErrorHandler.cjs.map +0 -1
  19. package/_cjs/api/routing/makeRequestHandler.cjs +0 -130
  20. package/_cjs/api/routing/makeRequestHandler.cjs.map +0 -1
  21. package/_cjs/api/routing/match.cjs +0 -40
  22. package/_cjs/api/routing/match.cjs.map +0 -1
  23. package/_cjs/api/routing/schema/routing.cjs +0 -136
  24. package/_cjs/api/routing/schema/routing.cjs.map +0 -1
  25. package/_cjs/api/routing2/DynamicMiddleware.cjs.map +0 -1
  26. package/_cjs/api/routing2.cjs +0 -142
  27. package/_cjs/api/routing2.cjs.map +0 -1
  28. package/_cjs/router.cjs +0 -170
  29. package/_cjs/router.cjs.map +0 -1
  30. package/dist/api/routing/base.d.ts +0 -97
  31. package/dist/api/routing/base.d.ts.map +0 -1
  32. package/dist/api/routing/base.js +0 -129
  33. package/dist/api/routing/defaultErrorHandler.d.ts +0 -19
  34. package/dist/api/routing/defaultErrorHandler.d.ts.map +0 -1
  35. package/dist/api/routing/defaultErrorHandler.js +0 -68
  36. package/dist/api/routing/makeRequestHandler.d.ts +0 -20
  37. package/dist/api/routing/makeRequestHandler.d.ts.map +0 -1
  38. package/dist/api/routing/makeRequestHandler.js +0 -151
  39. package/dist/api/routing/match.d.ts +0 -12
  40. package/dist/api/routing/match.d.ts.map +0 -1
  41. package/dist/api/routing/match.js +0 -27
  42. package/dist/api/routing/schema/routing.d.ts +0 -31
  43. package/dist/api/routing/schema/routing.d.ts.map +0 -1
  44. package/dist/api/routing/schema/routing.js +0 -123
  45. package/dist/api/routing2/DynamicMiddleware.d.ts.map +0 -1
  46. package/dist/api/routing2/DynamicMiddleware.js +0 -33
  47. package/dist/api/routing2.d.ts +0 -93
  48. package/dist/api/routing2.d.ts.map +0 -1
  49. package/dist/api/routing2.js +0 -117
  50. package/dist/router.d.ts +0 -91
  51. package/dist/router.d.ts.map +0 -1
  52. package/dist/router.js +0 -154
  53. package/src/api/routing/base.ts +0 -379
  54. package/src/api/routing/defaultErrorHandler.ts +0 -140
  55. package/src/api/routing/makeRequestHandler.ts +0 -343
  56. package/src/api/routing/match.ts +0 -128
  57. package/src/api/routing/schema/routing.ts +0 -237
  58. package/src/api/routing2.ts +0 -425
  59. package/src/api/writeDocs.ts.bak +0 -31
  60. package/src/router.ts +0 -619
  61. /package/dist/api/{routing2 → routing}/DynamicMiddleware.d.ts +0 -0
@@ -1,379 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- /* eslint-disable @typescript-eslint/ban-types */
4
- import { ValidationError } from "@effect-app/infra/errors"
5
- import type { Struct } from "@effect/schema/Schema"
6
- import * as S from "@effect/schema/Schema"
7
- import type { Context } from "effect-app"
8
- import { Cause, Effect, Exit, Option } from "effect-app"
9
- import type { REST } from "effect-app/schema"
10
- import type { Simplify } from "effect/Types"
11
-
12
- export interface ReqHandler<
13
- Req,
14
- R,
15
- E,
16
- Res,
17
- ReqSchema extends S.Schema.Any,
18
- ResSchema extends S.Schema.Any,
19
- CTX = any,
20
- Context = any
21
- > {
22
- h: (r: Req, ctx: CTX) => Effect<Res, E, R>
23
- Request: ReqSchema
24
- Response: ResSchema
25
- ResponseOpenApi: any
26
- name: string
27
- CTX: CTX
28
- Context: Context
29
- rt: "raw" | "d"
30
- }
31
-
32
- export type ReqFromSchema<ReqSchema extends S.Schema<any, any, any>> = S.Schema.Type<ReqSchema>
33
-
34
- export type Extr<T> = T extends { Model: S.Schema<any, any, any> } ? T["Model"]
35
- : T extends S.Schema<any, any, any> ? T
36
- : never
37
-
38
- export type ResFromSchema<ResSchema> = S.Schema.Type<Extr<ResSchema>>
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 Encode<A, E> = (a: A) => E
51
-
52
- // function getErrorMessage(current: ContextEntry) {
53
- // switch (current.type.name) {
54
- // case "NonEmptyString":
55
- // return "Must not be empty"
56
- // }
57
- // if (current.type.name?.startsWith("NonEmptyReadonlyArray<")) {
58
- // return "Must not be empty"
59
- // }
60
- // return `Invalid value specified`
61
- // }
62
- export function decodeErrors(x: unknown) {
63
- return [x]
64
- }
65
-
66
- // const ValidationApplicative = Effect.getValidationApplicative(
67
- // makeAssociative<ReadonlyArray<{ type: string; errors: ReturnType<typeof decodeErrors> }>>(
68
- // (l, r) => l.concat(r)
69
- // )
70
- // )
71
-
72
- // const structValidation = DSL.structF(ValidationApplicative)
73
- export function parseRequestParams<
74
- PathA extends Struct.Fields,
75
- CookieA extends Struct.Fields,
76
- QueryA extends Struct.Fields,
77
- BodyA extends Struct.Fields,
78
- HeaderA extends Struct.Fields
79
- >(
80
- parsers: RequestParsers<PathA, CookieA, QueryA, BodyA, HeaderA>
81
- ) {
82
- const handleParse = <A, E, R>(effect: Effect<A, E, R>) =>
83
- effect.pipe(
84
- Effect.exit,
85
- Effect
86
- .flatMap((_) =>
87
- Exit.isFailure(_) && !Cause.isFailure(_.cause)
88
- ? (Effect.failCauseSync(() => _.cause) as Effect<never, ValidationError>)
89
- : Effect.sync(() =>
90
- Exit.isSuccess(_)
91
- ? { _tag: "Success" as const, value: _.value }
92
- : { _tag: "Failure", errors: Cause.failures(_.cause) }
93
- )
94
- )
95
- )
96
- return (
97
- { body, cookies, headers, params, query }: {
98
- body: unknown
99
- cookies: unknown
100
- headers: unknown
101
- params: unknown
102
- query: unknown
103
- }
104
- ) =>
105
- Effect
106
- .all({
107
- body: parsers
108
- .parseBody(body)
109
- .pipe(handleParse),
110
- cookie: parsers
111
- .parseCookie(cookies)
112
- .pipe(handleParse),
113
- headers: parsers
114
- .parseHeaders(headers)
115
- .pipe(handleParse),
116
- query: parsers
117
- .parseQuery(query)
118
- .pipe(handleParse),
119
- path: parsers
120
- .parsePath(params)
121
- .pipe(handleParse)
122
- })
123
- .pipe(Effect
124
- .flatMap(({ body, cookie, headers, path, query }) => {
125
- const errors: unknown[] = []
126
- if (body._tag === "Failure") {
127
- errors.push(makeError("body")(body.errors))
128
- }
129
- if (cookie._tag === "Failure") {
130
- errors.push(makeError("cookie")(cookie.errors))
131
- }
132
- if (headers._tag === "Failure") {
133
- errors.push(makeError("headers")(headers.errors))
134
- }
135
- if (path._tag === "Failure") {
136
- errors.push(makeError("path")(path.errors))
137
- }
138
- if (query._tag === "Failure") {
139
- errors.push(makeError("query")(query.errors))
140
- }
141
- if (errors.length) {
142
- return new ValidationError({ errors })
143
- }
144
- return Effect.sync(() => ({
145
- body: body.value!,
146
- cookie: cookie.value!,
147
- headers: headers.value!,
148
- path: path.value!,
149
- query: query.value!
150
- }))
151
- }))
152
- }
153
-
154
- // // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
- // function mapErrors_<E, NE, NER extends Record<string, Effect<any, E, any>>>(
156
- // t: NER, // TODO: enforce non empty
157
- // mapErrors: (k: keyof NER) => (err: E) => NE
158
- // ): {
159
- // [K in keyof NER]: Effect<_R<NER[K]>, NE, Effect.Success<NER[K]>>
160
- // } {
161
- // return typedKeysOf(t).reduce(
162
- // (prev, cur) => {
163
- // prev[cur] = t[cur].mapError(mapErrors(cur))
164
- // return prev
165
- // },
166
- // {} as {
167
- // [K in keyof NER]: Effect<_R<NER[K]>, NE, Effect.Success<NER[K]>>
168
- // }
169
- // )
170
- // }
171
-
172
- function makeError(type: string) {
173
- return (e: unknown) => [{ type, errors: decodeErrors(e) }]
174
- }
175
-
176
- export function makeRequestParsers<
177
- R,
178
- M,
179
- PathA extends Struct.Fields,
180
- CookieA extends Struct.Fields,
181
- QueryA extends Struct.Fields,
182
- BodyA extends Struct.Fields,
183
- HeaderA extends Struct.Fields,
184
- ReqA extends PathA & QueryA & BodyA,
185
- ResA extends Struct.Fields,
186
- Errors,
187
- PPath extends `/${string}`,
188
- CTX,
189
- Context,
190
- Config
191
- >(
192
- Request: RequestHandler<
193
- R,
194
- M,
195
- PathA,
196
- CookieA,
197
- QueryA,
198
- BodyA,
199
- HeaderA,
200
- ReqA,
201
- ResA,
202
- Errors,
203
- PPath,
204
- CTX,
205
- Context,
206
- Config
207
- >["Request"]
208
- ): RequestParsers<PathA, CookieA, QueryA, BodyA, HeaderA> {
209
- const ph = Effect.sync(() =>
210
- Option
211
- .fromNullable(Request.Headers)
212
- .pipe(
213
- Option.map((s) => s as unknown as S.Schema<any>),
214
- Option.map(S.decodeUnknown)
215
- )
216
- )
217
- const parseHeaders = (u: unknown) => Effect.flatMapOption(ph, (d) => d(u))
218
-
219
- const pq = Effect.sync(() =>
220
- Option
221
- .fromNullable(Request.Query)
222
- .pipe(
223
- Option.map((s) => s as unknown as S.Schema<any>),
224
- Option.map(S.decodeUnknown)
225
- )
226
- )
227
- const parseQuery = (u: unknown) => Effect.flatMapOption(pq, (d) => d(u))
228
-
229
- const pb = Effect.sync(() =>
230
- Option
231
- .fromNullable(Request.Body)
232
- .pipe(
233
- Option.map((s) => s as unknown as S.Schema<any>),
234
- Option.map(S.decodeUnknown)
235
- )
236
- )
237
- const parseBody = (u: unknown) => Effect.flatMapOption(pb, (d) => d(u))
238
-
239
- const pp = Effect.sync(() =>
240
- Option
241
- .fromNullable(Request.Path)
242
- .pipe(
243
- Option.map((s) => s as unknown as S.Schema<any>),
244
- Option.map(S.decodeUnknown)
245
- )
246
- )
247
- const parsePath = (u: unknown) => Effect.flatMapOption(pp, (d) => d(u))
248
-
249
- const pc = Effect.sync(() =>
250
- Option
251
- .fromNullable(Request.Cookie)
252
- .pipe(
253
- Option.map((s) => s as unknown as S.Schema<any>),
254
- Option.map(S.decodeUnknown)
255
- )
256
- )
257
- const parseCookie = (u: unknown) => Effect.flatMapOption(pc, (d) => d(u))
258
-
259
- return {
260
- parseBody,
261
- parseCookie,
262
- parseHeaders,
263
- parsePath,
264
- parseQuery
265
- }
266
- }
267
-
268
- type Decode<A> = (u: unknown) => Effect<A, unknown>
269
-
270
- export interface RequestParsers<
271
- PathA extends Struct.Fields,
272
- CookieA extends Struct.Fields,
273
- QueryA extends Struct.Fields,
274
- BodyA extends Struct.Fields,
275
- HeaderA extends Struct.Fields
276
- > {
277
- parseHeaders: Decode<Option<Simplify<S.Struct.Type<HeaderA>>>>
278
- parseQuery: Decode<Option<Simplify<S.Struct.Type<QueryA>>>>
279
- parseBody: Decode<Option<Simplify<S.Struct.Type<BodyA>>>>
280
- parsePath: Decode<Option<Simplify<S.Struct.Type<PathA>>>>
281
- parseCookie: Decode<Option<Simplify<S.Struct.Type<CookieA>>>>
282
- }
283
-
284
- export type EffectDeps<A> = {
285
- [K in keyof A as A[K] extends Effect<any, any, any> ? K : never]: A[K] extends Effect<any, any, any> ? A[K] : never
286
- }
287
-
288
- export type Request<
289
- M,
290
- PathA extends Struct.Fields,
291
- CookieA extends Struct.Fields,
292
- QueryA extends Struct.Fields,
293
- BodyA extends Struct.Fields,
294
- HeaderA extends Struct.Fields,
295
- ReqA extends PathA & QueryA & BodyA,
296
- PPath extends `/${string}`
297
- > = REST.ReqRes<any, any, any> & {
298
- method: REST.Methods.Rest
299
- path: PPath
300
- Cookie?: CookieA
301
- Path?: PathA
302
- Body?: BodyA
303
- Query?: QueryA
304
- Headers?: HeaderA
305
- Tag: Context.Tag<M, M>
306
- ReqA?: ReqA
307
- }
308
-
309
- export interface RequestHandlerBase<
310
- R,
311
- M,
312
- PathA extends Struct.Fields,
313
- CookieA extends Struct.Fields,
314
- QueryA extends Struct.Fields,
315
- BodyA extends Struct.Fields,
316
- HeaderA extends Struct.Fields,
317
- ReqA extends PathA & QueryA & BodyA,
318
- ResA extends Struct.Fields,
319
- ResE,
320
- PPath extends `/${string}`,
321
- Config
322
- > {
323
- adaptResponse?: any
324
- h: (i: PathA & QueryA & BodyA & {}) => Effect<ResA, ResE, R>
325
- Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath>
326
- Response: REST.ReqRes<any, any, any>
327
- ResponseOpenApi?: any
328
- config: Config
329
- name: string
330
- rt: "raw" | "d"
331
- }
332
-
333
- export interface RequestHandler<
334
- R,
335
- M,
336
- PathA extends Struct.Fields,
337
- CookieA extends Struct.Fields,
338
- QueryA extends Struct.Fields,
339
- BodyA extends Struct.Fields,
340
- HeaderA extends Struct.Fields,
341
- ReqA extends PathA & QueryA & BodyA,
342
- ResA extends Struct.Fields,
343
- ResE,
344
- PPath extends `/${string}`,
345
- CTX,
346
- Context,
347
- Config
348
- > {
349
- adaptResponse?: any
350
- h: (i: PathA & QueryA & BodyA & {}, ctx: any /* TODO */) => Effect<ResA, ResE, R>
351
- Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath> & Config
352
- Response: REST.ReqRes<any, any, any>
353
- ResponseOpenApi?: any
354
- name: string
355
- CTX: CTX
356
- rt: "raw" | "d"
357
- Context: Context
358
- }
359
-
360
- export interface RequestHandlerOrig<
361
- R,
362
- M,
363
- PathA extends Struct.Fields,
364
- CookieA extends Struct.Fields,
365
- QueryA extends Struct.Fields,
366
- BodyA extends Struct.Fields,
367
- HeaderA extends Struct.Fields,
368
- ReqA extends PathA & QueryA & BodyA,
369
- ResA extends Struct.Fields,
370
- ResE,
371
- PPath extends `/${string}`
372
- > {
373
- adaptResponse?: any
374
- h: (i: PathA & QueryA & BodyA & {}) => Effect<ResA, ResE, R>
375
- Request: Request<M, PathA, CookieA, QueryA, BodyA, HeaderA, ReqA, PPath>
376
- Response: REST.ReqRes<any, any, any>
377
- name: string
378
- ResponseOpenApi?: any
379
- }
@@ -1,140 +0,0 @@
1
- import { logError } from "@effect-app/infra/errorReporter"
2
- import type { Schema } from "@effect-app/schema"
3
- import { setBody, setStatus } from "@effect/platform/HttpServerResponse"
4
- import { Cause, Data, Effect, S, Schedule } from "effect-app"
5
- import type { SupportedErrors } from "effect-app/client/errors"
6
- import {
7
- InvalidStateError,
8
- NotFoundError,
9
- NotLoggedInError,
10
- OptimisticConcurrencyException,
11
- ServiceUnavailableError,
12
- UnauthorizedError,
13
- ValidationError
14
- } from "effect-app/client/errors"
15
- import { HttpBody, HttpHeaders, type HttpServerRequest, HttpServerResponse } from "effect-app/http"
16
- import type {
17
- InsufficientScopeError,
18
- InvalidRequestError,
19
- InvalidTokenError,
20
- UnauthorizedError as JWTUnauthorizedError
21
- } from "express-oauth2-jwt-bearer"
22
- import { InfraLogger } from "../../logger.js"
23
-
24
- export class JWTError extends Data.TaggedClass("JWTError")<{
25
- error:
26
- | InsufficientScopeError
27
- | InvalidRequestError
28
- | InvalidTokenError
29
- | JWTUnauthorizedError
30
- }> {}
31
-
32
- const logRequestError = logError("Request")
33
-
34
- export function defaultBasicErrorHandler<R>(
35
- _req: HttpServerRequest.HttpServerRequest,
36
- res: HttpServerResponse.HttpServerResponse,
37
- r2: Effect<HttpServerResponse.HttpServerResponse, ValidationError, R>
38
- ) {
39
- const sendError = (code: number) => (body: unknown) =>
40
- Effect.sync(() => setBody(res, HttpBody.unsafeJson(body)).pipe(setStatus(code)))
41
- return r2.pipe(
42
- Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
43
- Effect.catchTag("ValidationError", (err) => sendError(400)(err.errors)),
44
- Effect
45
- // final catch all; expecting never so that unhandled known errors will show up
46
- .catchAll((err: never) =>
47
- InfraLogger
48
- .logError(
49
- "Program error, compiler probably silenced, got an unsupported Error in Error Channel of Effect" + err
50
- )
51
- .pipe(
52
- Effect.map(() => err as unknown),
53
- Effect.flatMap(Effect.die)
54
- )
55
- )
56
- )
57
- }
58
-
59
- const optimisticConcurrencySchedule = Schedule.once
60
- && Schedule.recurWhile<{ _tag: string }>((a) => a._tag === "OptimisticConcurrencyException")
61
-
62
- export function defaultErrorHandler<R, A extends { _tag: string } = never>(
63
- req: HttpServerRequest.HttpServerRequest,
64
- res: HttpServerResponse.HttpServerResponse,
65
- r2: Effect<HttpServerResponse.HttpServerResponse, SupportedErrors | JWTError, R>,
66
- customErrorSchema?: Schema<A, unknown>
67
- ) {
68
- const r3 = req.method === "PATCH"
69
- ? Effect.retry(r2, optimisticConcurrencySchedule)
70
- : r2
71
- const sendError = <R, From, To>(code: number, schema: Schema<To, From, R>) => (body: To) =>
72
- S
73
- .encode(schema)(body)
74
- .pipe(
75
- Effect.orDie,
76
- Effect.andThen((body) => res.pipe(setStatus(code), setBody(HttpBody.unsafeJson(body))))
77
- )
78
- return r3
79
- .pipe(
80
- Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
81
- Effect.tapErrorCause((cause) =>
82
- Effect.annotateCurrentSpan({
83
- "exception.escaped": true,
84
- "exception.message": "Request Error",
85
- "exception.stacktrace": Cause.pretty(cause),
86
- "exception.type": Cause.squashWith(
87
- cause,
88
- (_) => _._tag
89
- // Predicate.hasProperty(_, "_tag")
90
- // ? _._tag
91
- // : Predicate.hasProperty(_, "name")
92
- // ? _.name
93
- // // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
94
- // : `${_}`
95
- ),
96
- "error.type": cause._tag
97
- })
98
- ),
99
- Effect
100
- .catchTags({
101
- "JWTError": (err) =>
102
- Effect.succeed(
103
- HttpServerResponse.unsafeJson({ message: err.error.message }, {
104
- status: err
105
- .error
106
- .status,
107
- headers: HttpHeaders.fromInput(err.error.headers)
108
- })
109
- ),
110
- "ValidationError": sendError(400, ValidationError),
111
- "NotFoundError": sendError(404, NotFoundError),
112
- "NotLoggedInError": sendError(401, NotLoggedInError),
113
- "UnauthorizedError": sendError(403, UnauthorizedError),
114
- "InvalidStateError": sendError(422, InvalidStateError),
115
- "ServiceUnavailableError": sendError(503, ServiceUnavailableError),
116
- // 412 or 409.. https://stackoverflow.com/questions/19122088/which-http-status-code-to-use-to-reject-a-put-due-to-optimistic-locking-failure
117
- "OptimisticConcurrencyException": sendError(412, OptimisticConcurrencyException)
118
- }),
119
- customErrorSchema
120
- ? Effect.catchAll((x) =>
121
- S.is(customErrorSchema)(x)
122
- // TODO: customize error code
123
- ? sendError(422, customErrorSchema)(x)
124
- : Effect.fail(x)
125
- )
126
- : (x) => x,
127
- Effect
128
- // final catch all; expecting never so that unhandled known errors will show up
129
- .catchAll((err: never) =>
130
- InfraLogger
131
- .logError(
132
- "Program error, compiler probably silenced, got an unsupported Error in Error Channel of Effect" + err
133
- )
134
- .pipe(
135
- Effect.map(() => err as unknown),
136
- Effect.flatMap(Effect.die)
137
- )
138
- )
139
- )
140
- }