@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.
- package/CHANGELOG.md +24 -0
- package/_cjs/api/routing/schema/jwt.cjs.map +1 -1
- package/_cjs/api/routing.cjs +48 -5
- package/_cjs/api/routing.cjs.map +1 -1
- package/_cjs/services/QueueMaker/SQLQueue.cjs +7 -1
- package/_cjs/services/QueueMaker/SQLQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/errors.cjs.map +1 -1
- package/_cjs/services/QueueMaker/memQueue.cjs +7 -1
- package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs +7 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/schema/jwt.js +1 -1
- package/dist/api/routing.d.ts +11 -28
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +72 -8
- package/dist/services/QueueMaker/SQLQueue.d.ts +2 -2
- package/dist/services/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/SQLQueue.js +8 -2
- package/dist/services/QueueMaker/errors.d.ts +1 -1
- package/dist/services/QueueMaker/errors.d.ts.map +1 -1
- package/dist/services/QueueMaker/errors.js +1 -1
- package/dist/services/QueueMaker/memQueue.d.ts +1 -1
- package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/memQueue.js +8 -2
- package/dist/services/QueueMaker/sbqueue.d.ts +1 -1
- package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/sbqueue.js +8 -2
- package/dist/services/Store/service.d.ts +1 -1
- package/package.json +13 -14
- package/src/api/routing/schema/jwt.ts +1 -1
- package/src/api/routing.ts +92 -24
- package/src/services/QueueMaker/SQLQueue.ts +8 -1
- package/src/services/QueueMaker/errors.ts +1 -1
- package/src/services/QueueMaker/memQueue.ts +8 -1
- package/src/services/QueueMaker/sbqueue.ts +10 -1
- package/src/services/Store/service.ts +1 -1
- package/tsconfig.src.json +0 -8
package/src/api/routing.ts
CHANGED
|
@@ -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
|
|
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((_) =>
|
|
57
|
-
|
|
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
|
|
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
|
|
305
|
-
|
|
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>
|
|
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(
|
|
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
|
|
162
|
+
export type PersistenceModelType<Encoded extends object> = Encoded & {
|
|
163
163
|
_etag?: string | undefined
|
|
164
164
|
}
|
|
165
165
|
|