@effect-app/infra 0.133.0-next.9 → 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.
Files changed (127) hide show
  1. package/CHANGELOG.md +485 -0
  2. package/_cjs/RequestContext.cjs +8 -9
  3. package/_cjs/RequestContext.cjs.map +1 -1
  4. package/_cjs/_ext/Array.cjs +4 -3
  5. package/_cjs/_ext/Array.cjs.map +1 -1
  6. package/_cjs/_global.ext.cjs +0 -25
  7. package/_cjs/_global.ext.cjs.map +1 -1
  8. package/_cjs/api/codec.cjs +1 -1
  9. package/_cjs/api/codec.cjs.map +1 -1
  10. package/_cjs/api/http.cjs +42 -0
  11. package/_cjs/api/http.cjs.map +1 -0
  12. package/_cjs/api/routing/base.cjs +169 -0
  13. package/_cjs/api/routing/base.cjs.map +1 -0
  14. package/_cjs/api/routing/defaultErrorHandler.cjs +50 -0
  15. package/_cjs/api/routing/defaultErrorHandler.cjs.map +1 -0
  16. package/_cjs/api/routing/makeRequestHandler.cjs +134 -0
  17. package/_cjs/api/routing/makeRequestHandler.cjs.map +1 -0
  18. package/_cjs/api/routing/match.cjs +45 -0
  19. package/_cjs/api/routing/match.cjs.map +1 -0
  20. package/_cjs/api/routing.cjs +50 -0
  21. package/_cjs/api/routing.cjs.map +1 -0
  22. package/_cjs/api/setupRequest.cjs +31 -30
  23. package/_cjs/api/setupRequest.cjs.map +1 -1
  24. package/_cjs/errorReporter.cjs +4 -2
  25. package/_cjs/errorReporter.cjs.map +1 -1
  26. package/_cjs/errors.cjs +18 -66
  27. package/_cjs/errors.cjs.map +1 -1
  28. package/_cjs/services/Operations/live.cjs +11 -12
  29. package/_cjs/services/Operations/live.cjs.map +1 -1
  30. package/_cjs/services/QueueMaker/memQueue.cjs +9 -10
  31. package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
  32. package/_cjs/services/QueueMaker/sbqueue.cjs +4 -4
  33. package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
  34. package/_cjs/services/Repository.cjs +3 -3
  35. package/_cjs/services/Repository.cjs.map +1 -1
  36. package/_cjs/services/RepositoryBase.cjs +14 -6
  37. package/_cjs/services/RepositoryBase.cjs.map +1 -1
  38. package/_cjs/services/RequestContextContainer.cjs +3 -3
  39. package/_cjs/services/RequestContextContainer.cjs.map +1 -1
  40. package/_cjs/services/Store/Redis.cjs.map +1 -1
  41. package/_cjs/test.arbs.cjs +24 -1
  42. package/_cjs/test.arbs.cjs.map +1 -1
  43. package/_src/RequestContext.ts +9 -12
  44. package/_src/_ext/Array.ts +4 -4
  45. package/_src/_global.ext.ts +0 -23
  46. package/_src/api/codec.ts +1 -1
  47. package/_src/api/http.ts +26 -0
  48. package/_src/api/routing/base.ts +476 -0
  49. package/_src/api/routing/defaultErrorHandler.ts +83 -0
  50. package/_src/api/routing/makeRequestHandler.ts +278 -0
  51. package/_src/api/routing/match.ts +119 -0
  52. package/_src/api/routing.ts +11 -0
  53. package/_src/api/setupRequest.ts +39 -34
  54. package/_src/errorReporter.ts +4 -2
  55. package/_src/errors.ts +1 -58
  56. package/_src/services/QueueMaker/memQueue.ts +3 -3
  57. package/_src/services/QueueMaker/sbqueue.ts +3 -3
  58. package/_src/services/Repository.ts +40 -43
  59. package/_src/services/RepositoryBase.ts +26 -20
  60. package/_src/services/Store/Redis.ts +1 -1
  61. package/_src/services/Store/service.ts +4 -4
  62. package/_src/test.arbs.ts +17 -14
  63. package/dist/RequestContext.d.ts +72 -24
  64. package/dist/RequestContext.d.ts.map +1 -1
  65. package/dist/RequestContext.js +10 -11
  66. package/dist/_ext/Array.d.ts +2 -2
  67. package/dist/_ext/Array.d.ts.map +1 -1
  68. package/dist/_ext/Array.js +5 -4
  69. package/dist/_global.ext.d.ts +0 -22
  70. package/dist/_global.ext.d.ts.map +1 -1
  71. package/dist/_global.ext.js +1 -18
  72. package/dist/api/codec.d.ts +1 -1
  73. package/dist/api/codec.d.ts.map +1 -1
  74. package/dist/api/codec.js +2 -2
  75. package/dist/api/http.d.ts +16 -0
  76. package/dist/api/http.d.ts.map +1 -0
  77. package/dist/api/http.js +12 -0
  78. package/dist/api/routing/base.d.ts +117 -0
  79. package/dist/api/routing/base.d.ts.map +1 -0
  80. package/dist/api/routing/base.js +137 -0
  81. package/dist/api/routing/defaultErrorHandler.d.ts +16 -0
  82. package/dist/api/routing/defaultErrorHandler.d.ts.map +1 -0
  83. package/dist/api/routing/defaultErrorHandler.js +42 -0
  84. package/dist/api/routing/makeRequestHandler.d.ts +20 -0
  85. package/dist/api/routing/makeRequestHandler.d.ts.map +1 -0
  86. package/dist/api/routing/makeRequestHandler.js +121 -0
  87. package/dist/api/routing/match.d.ts +12 -0
  88. package/dist/api/routing/match.d.ts.map +1 -0
  89. package/dist/api/routing/match.js +31 -0
  90. package/dist/api/routing.d.ts +5 -0
  91. package/dist/api/routing.d.ts.map +1 -0
  92. package/dist/api/routing.js +8 -0
  93. package/dist/api/setupRequest.d.ts +4 -4
  94. package/dist/api/setupRequest.d.ts.map +1 -1
  95. package/dist/api/setupRequest.js +31 -37
  96. package/dist/errorReporter.d.ts.map +1 -1
  97. package/dist/errorReporter.js +5 -3
  98. package/dist/errors.d.ts +1 -81
  99. package/dist/errors.d.ts.map +1 -1
  100. package/dist/errors.js +4 -46
  101. package/dist/services/Operations/live.js +16 -17
  102. package/dist/services/QueueMaker/memQueue.d.ts +3 -3
  103. package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
  104. package/dist/services/QueueMaker/memQueue.js +9 -10
  105. package/dist/services/QueueMaker/sbqueue.d.ts +3 -3
  106. package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
  107. package/dist/services/QueueMaker/sbqueue.js +4 -4
  108. package/dist/services/QueueMaker/service.d.ts +1 -1
  109. package/dist/services/Repository.d.ts +37 -38
  110. package/dist/services/Repository.d.ts.map +1 -1
  111. package/dist/services/Repository.js +4 -4
  112. package/dist/services/RepositoryBase.d.ts +15 -15
  113. package/dist/services/RepositoryBase.d.ts.map +1 -1
  114. package/dist/services/RepositoryBase.js +12 -7
  115. package/dist/services/RequestContextContainer.d.ts +1 -1
  116. package/dist/services/RequestContextContainer.js +3 -3
  117. package/dist/services/Store/Redis.js +1 -1
  118. package/dist/services/Store/service.d.ts +4 -4
  119. package/dist/services/Store/service.d.ts.map +1 -1
  120. package/dist/services/simpledb/redisdb.d.ts +5 -1
  121. package/dist/services/simpledb/redisdb.d.ts.map +1 -1
  122. package/dist/services/simpledb/shared.d.ts +5 -1
  123. package/dist/services/simpledb/shared.d.ts.map +1 -1
  124. package/dist/test.arbs.d.ts +12 -1
  125. package/dist/test.arbs.d.ts.map +1 -1
  126. package/dist/test.arbs.js +18 -2
  127. 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
+ }