@effect-app/infra 1.30.2 → 1.32.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 (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/_cjs/api/routing/schema/jwt.cjs.map +1 -1
  3. package/_cjs/api/routing.cjs +48 -5
  4. package/_cjs/api/routing.cjs.map +1 -1
  5. package/_cjs/services/QueueMaker/SQLQueue.cjs +7 -1
  6. package/_cjs/services/QueueMaker/SQLQueue.cjs.map +1 -1
  7. package/_cjs/services/QueueMaker/errors.cjs.map +1 -1
  8. package/_cjs/services/QueueMaker/memQueue.cjs +7 -1
  9. package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
  10. package/_cjs/services/QueueMaker/sbqueue.cjs +7 -1
  11. package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
  12. package/dist/api/routing/schema/jwt.d.ts +1 -1
  13. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  14. package/dist/api/routing/schema/jwt.js +1 -1
  15. package/dist/api/routing.d.ts +11 -28
  16. package/dist/api/routing.d.ts.map +1 -1
  17. package/dist/api/routing.js +72 -8
  18. package/dist/services/QueueMaker/SQLQueue.d.ts +2 -2
  19. package/dist/services/QueueMaker/SQLQueue.d.ts.map +1 -1
  20. package/dist/services/QueueMaker/SQLQueue.js +8 -2
  21. package/dist/services/QueueMaker/errors.d.ts +1 -1
  22. package/dist/services/QueueMaker/errors.d.ts.map +1 -1
  23. package/dist/services/QueueMaker/errors.js +1 -1
  24. package/dist/services/QueueMaker/memQueue.d.ts +1 -1
  25. package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
  26. package/dist/services/QueueMaker/memQueue.js +8 -2
  27. package/dist/services/QueueMaker/sbqueue.d.ts +1 -1
  28. package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
  29. package/dist/services/QueueMaker/sbqueue.js +8 -2
  30. package/dist/services/Store/service.d.ts +1 -1
  31. package/package.json +13 -14
  32. package/src/api/routing/schema/jwt.ts +1 -1
  33. package/src/api/routing.ts +92 -24
  34. package/src/services/QueueMaker/SQLQueue.ts +8 -1
  35. package/src/services/QueueMaker/errors.ts +1 -1
  36. package/src/services/QueueMaker/memQueue.ts +8 -1
  37. package/src/services/QueueMaker/sbqueue.ts +10 -1
  38. package/src/services/Store/service.ts +1 -1
  39. package/tsconfig.src.json +0 -8
@@ -6,19 +6,23 @@ TODO: Effect.retry(r2, optimisticConcurrencySchedule) / was for PATCH only
6
6
  TODO: uninteruptible commands! was for All except GET.
7
7
  */
8
8
  import { allLower, type EffectUnunified, type LowerServices } from "@effect-app/core/Effect"
9
- import { typedKeysOf } from "@effect-app/core/utils"
9
+ import { pretty, typedKeysOf } from "@effect-app/core/utils"
10
10
  import type { Compute } from "@effect-app/core/utils"
11
11
  import type * as HttpApp from "@effect/platform/HttpApp"
12
- import type { Rpc } from "@effect/rpc"
13
- import { RpcRouter } from "@effect/rpc"
12
+ import { Rpc, RpcRouter } from "@effect/rpc"
14
13
  import { Serializable } from "@effect/schema"
15
- import { Chunk, Context, Effect, FiberRef, Predicate, S, Stream } from "effect-app"
14
+ import { Cause, Chunk, Context, Effect, FiberRef, Predicate, S, Stream } from "effect-app"
16
15
  import type { GetEffectContext, RPCContextMap } from "effect-app/client/req"
17
16
  import type { HttpServerError } from "effect-app/http"
18
17
  import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect-app/http"
18
+ import { logError, reportError } from "../errorReporter.js"
19
+ import { InfraLogger } from "../logger.js"
19
20
  import type { Middleware } from "./routing/DynamicMiddleware.js"
20
21
  import { makeRpc } from "./routing/DynamicMiddleware.js"
21
22
 
23
+ const logRequestError = logError("Request")
24
+ const reportRequestError = reportError("Request")
25
+
22
26
  export type _R<T extends Effect<any, any, any>> = [T] extends [
23
27
  Effect<any, any, infer R>
24
28
  ] ? R
@@ -53,8 +57,24 @@ export const toHttpApp = <R extends RpcRouter.RpcRouter<any, any>>(self: R, opti
53
57
  Stream.provideContext(context),
54
58
  Stream.runCollect,
55
59
  Effect.map((_) => Chunk.toReadonlyArray(_)),
56
- Effect.andThen((_) => HttpServerResponse.json(_)),
57
- Effect.orDie
60
+ Effect.andThen((_) => {
61
+ let status = 200
62
+ for (const r of _.flat()) {
63
+ if (typeof r === "number") continue
64
+ const results = Array.isArray(r) ? r : [r]
65
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Die")) {
66
+ status = 500
67
+ break
68
+ }
69
+ if (results.some((_: S.ExitEncoded<any, any, any>) => _._tag === "Failure" && _.cause._tag === "Fail")) {
70
+ status = 422 // 418
71
+ break
72
+ }
73
+ }
74
+ return HttpServerResponse.json(_, { status })
75
+ }),
76
+ Effect.orDie,
77
+ Effect.tapDefect(reportError("RPCHttpApp"))
58
78
  )
59
79
  )
60
80
  })
@@ -110,13 +130,12 @@ type GetSuccessShape<Action extends { success?: S.Schema.Any }, RT extends "d" |
110
130
  : S.Schema.Type<GetSuccess<Action>>
111
131
  type GetFailure<T extends { failure?: S.Schema.Any }> = T["failure"] extends never ? typeof S.Never : T["failure"]
112
132
 
113
- export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R, Context> {
133
+ export interface Handler<Action extends AnyRequestModule, RT extends "raw" | "d", A, E, R> {
114
134
  new(): {}
115
135
  _tag: RT
116
136
  stack: string
117
137
  handler: (
118
- req: S.Schema.Type<Action>,
119
- ctx: Context
138
+ req: S.Schema.Type<Action>
120
139
  ) => Effect<
121
140
  A,
122
141
  E,
@@ -131,16 +150,14 @@ type AHandler<Action extends AnyRequestModule> =
131
150
  "raw",
132
151
  S.Schema.Encoded<GetSuccess<Action>>,
133
152
  S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
134
- any,
135
- { Response: any }
153
+ any
136
154
  >
137
155
  | Handler<
138
156
  Action,
139
157
  "d",
140
158
  S.Schema.Type<GetSuccess<Action>>,
141
159
  S.Schema.Type<GetFailure<Action>> | S.ParseResult.ParseError,
142
- any,
143
- { Response: any }
160
+ any
144
161
  >
145
162
 
146
163
  type Filter<T> = {
@@ -159,7 +176,8 @@ interface ExtendedMiddleware<Context, CTXMap extends Record<string, RPCContextMa
159
176
  }
160
177
 
161
178
  export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.Any>>(
162
- middleware: ExtendedMiddleware<Context, CTXMap>
179
+ middleware: ExtendedMiddleware<Context, CTXMap>,
180
+ devMode: boolean
163
181
  ) => {
164
182
  const rpc = makeRpc(middleware)
165
183
  function matchFor<Rsc extends Record<string, any> & { meta: { moduleName: string } }>(
@@ -215,8 +233,7 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
215
233
  RT,
216
234
  A,
217
235
  E,
218
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
219
- { Response: Rsc[Key]["success"] } //
236
+ Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
220
237
  >
221
238
  >
222
239
 
@@ -233,8 +250,7 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
233
250
  RT,
234
251
  A,
235
252
  E,
236
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
237
- { Response: Rsc[Key]["success"] } //
253
+ Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
238
254
  >
239
255
  >
240
256
 
@@ -265,8 +281,7 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
265
281
  RT,
266
282
  A,
267
283
  E,
268
- Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>,
269
- { Response: Rsc[Key]["success"] } //
284
+ Exclude<R2, GetEffectContext<CTXMap, Rsc[Key]["config"]>>
270
285
  >
271
286
  >
272
287
  }
@@ -301,9 +316,60 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
301
316
  } as any
302
317
  : req,
303
318
  (req) =>
304
- Effect.withSpan("Request." + meta.moduleName + "." + req._tag, { captureStackTrace: () => handler.stack })(
305
- (handler.handler as any)(req)
306
- ),
319
+ Effect
320
+ .annotateCurrentSpan(
321
+ "requestInput",
322
+ Object.entries(req).reduce((prev, [key, value]: [string, unknown]) => {
323
+ prev[key] = key === "password"
324
+ ? "<redacted>"
325
+ : typeof value === "string" || typeof value === "number" || typeof value === "boolean"
326
+ ? typeof value === "string" && value.length > 256
327
+ ? (value.substring(0, 253) + "...")
328
+ : value
329
+ : Array.isArray(value)
330
+ ? `Array[${value.length}]`
331
+ : value === null || value === undefined
332
+ ? `${value}`
333
+ : typeof value === "object" && value
334
+ ? `Object[${Object.keys(value).length}]`
335
+ : typeof value
336
+ return prev
337
+ }, {} as Record<string, string | number | boolean>)
338
+ )
339
+ .pipe(
340
+ // can't use andThen due to some being a function and effect
341
+ Effect.zipRight(handler.handler(req as any) as any),
342
+ Effect.tapErrorCause((cause) => Cause.isFailure(cause) ? logRequestError(cause) : Effect.void),
343
+ Effect.tapDefect((cause) =>
344
+ Effect
345
+ .all([
346
+ reportRequestError(cause, {
347
+ action: `${meta.moduleName}.${req._tag}`
348
+ }),
349
+ Rpc.currentHeaders.pipe(Effect.andThen((headers) => {
350
+ return InfraLogger
351
+ .logError("Finished request", cause)
352
+ .pipe(Effect.annotateLogs({
353
+ action: `${meta.moduleName}.${req._tag}`,
354
+ req: pretty(req),
355
+ headers: pretty(headers)
356
+ // resHeaders: pretty(
357
+ // Object
358
+ // .entries(headers)
359
+ // .reduce((prev, [key, value]) => {
360
+ // prev[key] = value && typeof value === "string" ? snipString(value) : value
361
+ // return prev
362
+ // }, {} as Record<string, any>)
363
+ // )
364
+ }))
365
+ }))
366
+ ])
367
+ ),
368
+ devMode ? (_) => _ : Effect.catchAllDefect(() => Effect.die("Internal Server Error")),
369
+ Effect.withSpan("Request." + meta.moduleName + "." + req._tag, {
370
+ captureStackTrace: () => handler.stack
371
+ })
372
+ ),
307
373
  meta.moduleName
308
374
  ) // TODO
309
375
  return acc
@@ -366,7 +432,9 @@ export const makeRouter = <Context, CTXMap extends Record<string, RPCContextMap.
366
432
  static _tag = "d"
367
433
  static handler = matchWithServices(cur)(svcOrFnOrEffect, fnOrNone)
368
434
  }
369
- }
435
+ } // "Raw" variations are for when you don't want to decode just to encode it again on the response
436
+ // e.g for direct projection from DB
437
+ // but more importantly, to skip Effectful decoders, like to resolve relationships from the database or remote client.
370
438
  ;(prev as any)[(cur as any) + "Raw"] = (svcOrFnOrEffect: any, fnOrNone: any) => {
371
439
  const stack = new Error().stack?.split("\n").slice(2).join("\n")
372
440
  return Effect.isEffect(svcOrFnOrEffect)
@@ -191,7 +191,14 @@ export function makeSQLQueue<
191
191
  )
192
192
  ),
193
193
  silenceAndReportError,
194
- Effect.forever
194
+ Effect.forever,
195
+ Effect.withSpan(`queue.drain: ${queueDrainName}`, {
196
+ attributes: {
197
+ "queue.type": "sql",
198
+ "queue.name": queueDrainName,
199
+ "queue.sessionId": sessionId
200
+ }
201
+ })
195
202
  )
196
203
  })
197
204
  } satisfies QueueBase<Evt, DrainEvt>
@@ -5,7 +5,7 @@ import { MainFiberSet } from "effect-app/services/MainFiberSet"
5
5
 
6
6
  const reportQueueError_ = reportError("Queue")
7
7
 
8
- export const reportQueueError = <E>(cause: Cause<E>, extras?: Record<string, unknown> | undefined) =>
8
+ export const reportQueueError = <E>(cause: Cause<E>, extras?: Record<string, unknown>) =>
9
9
  reportQueueError_(cause, extras)
10
10
 
11
11
  /**
@@ -115,7 +115,14 @@ export function makeMemQueue<
115
115
  // TODO: normally a failed item would be returned to the queue and retried up to X times.
116
116
  // .flatMap(_ => _._tag === "Failure" && !isInterrupted ? qDrain.offer(x) : Effect.unit) // TODO: retry count tracking and max retries.
117
117
  silenceAndReportError,
118
- Effect.forever
118
+ Effect.forever,
119
+ Effect.withSpan(`queue.drain: ${queueDrainName}`, {
120
+ attributes: {
121
+ "queue.type": "mem",
122
+ "queue.name": queueDrainName,
123
+ "queue.sessionId": sessionId
124
+ }
125
+ })
119
126
  )
120
127
  })
121
128
  } satisfies QueueBase<Evt, DrainEvt>
@@ -126,7 +126,16 @@ export function makeServiceBusQueue<
126
126
  .pipe(Effect.provideService(ServiceBusReceiverFactory, receiver))
127
127
  })
128
128
  // .pipe(Effect.andThen(Deferred.await(deferred).pipe(Effect.orDie))),
129
- .pipe(Effect.andThen(Effect.never)),
129
+ .pipe(
130
+ Effect.andThen(Effect.never),
131
+ Effect.withSpan(`queue.drain: ${queueDrainName}`, {
132
+ attributes: {
133
+ "queue.type": "servicebus",
134
+ "queue.name": queueDrainName,
135
+ "queue.sessionId": sessionId
136
+ }
137
+ })
138
+ ),
130
139
 
131
140
  publish: (...messages) =>
132
141
  Effect
@@ -159,7 +159,7 @@ const makeMap = Effect.sync(() => makeContextMap())
159
159
  export class ContextMap extends Context.TagMakeId("effect-app/ContextMap", makeMap)<ContextMap>() {
160
160
  }
161
161
 
162
- export type PersistenceModelType<Encoded extends Object> = Encoded & {
162
+ export type PersistenceModelType<Encoded extends object> = Encoded & {
163
163
  _etag?: string | undefined
164
164
  }
165
165
 
package/tsconfig.src.json CHANGED
@@ -30,14 +30,6 @@
30
30
  "**/.*",
31
31
  "**/*.tmp"
32
32
  ],
33
- "ts-node": {
34
- "require": [
35
- "tsconfig-paths/register"
36
- ],
37
- "compilerOptions": {
38
- "module": "CommonJS"
39
- }
40
- },
41
33
  "references": [
42
34
  {
43
35
  "path": "../prelude"