@effect-app/infra 4.0.0-beta.1 → 4.0.0-beta.100
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 +720 -0
- package/dist/CUPS.d.ts +3 -3
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +3 -3
- package/dist/Emailer/Sendgrid.js +1 -1
- package/dist/Emailer/service.d.ts +3 -3
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/MainFiberSet.d.ts +2 -2
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/internal/internal.d.ts +3 -3
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +11 -7
- package/dist/Model/Repository/makeRepo.d.ts +2 -2
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +1 -1
- package/dist/Model/Repository/validation.d.ts +5 -4
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/query/dsl.d.ts +9 -9
- package/dist/Operations.d.ts +2 -2
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +3 -3
- package/dist/OperationsRepo.d.ts +2 -2
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +3 -5
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +9 -7
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +10 -9
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +11 -9
- package/dist/RequestContext.d.ts +24 -19
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +5 -5
- package/dist/RequestFiberSet.d.ts +2 -2
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +5 -5
- package/dist/Store/ContextMapContainer.d.ts +14 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +64 -3
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +136 -68
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +3 -4
- package/dist/Store/Memory.d.ts +2 -2
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +4 -4
- package/dist/Store/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +186 -0
- package/dist/Store/SQL/query.d.ts +37 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +362 -0
- package/dist/Store/SQL.d.ts +11 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +212 -0
- package/dist/Store/index.d.ts +1 -1
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +11 -1
- package/dist/Store/service.d.ts +8 -5
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +14 -6
- package/dist/adapters/SQL/Model.d.ts +2 -5
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +21 -13
- package/dist/adapters/ServiceBus.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +9 -9
- package/dist/adapters/cosmos-client.d.ts +2 -2
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +2 -2
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +2 -2
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +6 -6
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/internal/auth.d.ts +1 -1
- package/dist/api/internal/events.d.ts +2 -2
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +7 -5
- package/dist/api/layerUtils.d.ts +5 -5
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.d.ts +35 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +39 -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 +15 -21
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +5 -4
- package/dist/api/setupRequest.d.ts +6 -3
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +13 -8
- package/dist/errorReporter.d.ts +1 -1
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/fileUtil.js +3 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.js +1 -1
- package/examples/query.ts +30 -26
- package/package.json +32 -18
- package/src/CUPS.ts +2 -2
- package/src/Emailer/Sendgrid.ts +1 -1
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/internal/internal.ts +11 -8
- package/src/Model/Repository/makeRepo.ts +2 -2
- package/src/Operations.ts +2 -2
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +10 -10
- package/src/QueueMaker/memQueue.ts +41 -42
- package/src/QueueMaker/sbqueue.ts +65 -62
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +98 -2
- package/src/Store/Cosmos.ts +352 -253
- package/src/Store/Disk.ts +2 -3
- package/src/Store/Memory.ts +4 -6
- package/src/Store/SQL/Pg.ts +328 -0
- package/src/Store/SQL/query.ts +402 -0
- package/src/Store/SQL.ts +357 -0
- package/src/Store/index.ts +10 -0
- package/src/Store/service.ts +16 -7
- package/src/adapters/SQL/Model.ts +76 -71
- package/src/adapters/ServiceBus.ts +8 -8
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +11 -11
- package/src/api/internal/events.ts +7 -6
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +43 -0
- package/src/api/routing/schema/jwt.ts +2 -3
- package/src/api/routing.ts +18 -23
- package/src/api/setupRequest.ts +29 -9
- package/src/errorReporter.ts +1 -1
- package/src/fileUtil.ts +2 -1
- package/src/rateLimit.ts +2 -2
- package/test/contextProvider.test.ts +5 -5
- package/test/controller.test.ts +19 -14
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/date-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +19 -9
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +11 -9
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +10 -8
- package/test/query.test.ts +162 -16
- package/test/rawQuery.test.ts +19 -17
- package/test/requires.test.ts +6 -5
- package/test/rpc-multi-middleware.test.ts +73 -4
- package/test/sql-store.test.ts +776 -0
- package/test/validateSample.test.ts +1 -1
- package/tsconfig.json +0 -1
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { getRequestContext, setupRequestContextWithCustomSpan } from "@effect-app/infra/api/setupRequest"
|
|
2
2
|
import { reportNonInterruptedFailure } from "@effect-app/infra/QueueMaker/errors"
|
|
3
3
|
import { type QueueBase, QueueMeta } from "@effect-app/infra/QueueMaker/service"
|
|
4
|
-
import { SqlClient } from "effect/unstable/sql"
|
|
5
4
|
import { subMinutes } from "date-fns"
|
|
6
|
-
import { Effect, Fiber, Option, S, Tracer } from "effect-app"
|
|
7
|
-
import type
|
|
5
|
+
import { Effect, Fiber, type NonEmptyReadonlyArray, Option, S, Tracer } from "effect-app"
|
|
6
|
+
import { type NonEmptyString255 } from "effect-app/Schema"
|
|
8
7
|
import { pretty } from "effect-app/utils"
|
|
9
|
-
import
|
|
8
|
+
import { SqlClient } from "effect/unstable/sql"
|
|
10
9
|
import { SQLModel } from "../adapters/SQL.js"
|
|
11
10
|
import { InfraLogger } from "../logger.js"
|
|
12
11
|
|
|
13
|
-
export const QueueId = S.
|
|
12
|
+
export const QueueId = S.Finite.pipe(S.brand("QueueId"))
|
|
14
13
|
export type QueueId = typeof QueueId.Type
|
|
15
14
|
|
|
16
15
|
// TODO: let the model track and Auto Generate versionColumn on every update instead
|
|
@@ -79,14 +78,14 @@ export function makeSQLQueue<
|
|
|
79
78
|
|
|
80
79
|
const q = {
|
|
81
80
|
offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
|
|
82
|
-
yield* queueRepo.insertVoid({
|
|
81
|
+
yield* queueRepo.insertVoid(Queue.insert.make({
|
|
83
82
|
body,
|
|
84
83
|
meta,
|
|
85
84
|
name: queueName,
|
|
86
85
|
processingAt: Option.none(),
|
|
87
86
|
finishedAt: Option.none(),
|
|
88
87
|
etag: crypto.randomUUID()
|
|
89
|
-
})
|
|
88
|
+
}))
|
|
90
89
|
}),
|
|
91
90
|
take: Effect.gen(function*() {
|
|
92
91
|
while (true) {
|
|
@@ -95,15 +94,16 @@ export function makeSQLQueue<
|
|
|
95
94
|
const dec = yield* decodeDrain(first)
|
|
96
95
|
const { createdAt, updatedAt, ...rest } = dec
|
|
97
96
|
return yield* drainRepo.update(
|
|
98
|
-
{ ...rest, processingAt: Option.some(new Date()) } // auto in lib , etag: crypto.randomUUID()
|
|
97
|
+
Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
|
|
99
98
|
)
|
|
100
99
|
}
|
|
101
100
|
if (first) return first
|
|
102
101
|
yield* Effect.sleep(250)
|
|
103
102
|
}
|
|
104
103
|
}),
|
|
105
|
-
finish: ({ createdAt, updatedAt, ...q }: Drain)
|
|
106
|
-
drainRepo.updateVoid({ ...q, finishedAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
|
|
104
|
+
finish: Effect.fn(function*({ createdAt, updatedAt, ...q }: Drain) {
|
|
105
|
+
return yield* drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: crypto.randomUUID()
|
|
106
|
+
})
|
|
107
107
|
}
|
|
108
108
|
const queue = {
|
|
109
109
|
publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Cause, Tracer } from "effect"
|
|
2
|
-
import { Effect, Fiber, flow, S } from "effect-app"
|
|
3
|
-
import * as Q from "effect/Queue"
|
|
2
|
+
import { Effect, Fiber, flow, type NonEmptyReadonlyArray, S } from "effect-app"
|
|
4
3
|
import { pretty } from "effect-app/utils"
|
|
4
|
+
import * as Q from "effect/Queue"
|
|
5
5
|
import { MemQueue } from "../adapters/memQueue.js"
|
|
6
6
|
import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
|
|
7
7
|
import { InfraLogger } from "../logger.js"
|
|
8
8
|
import { reportNonInterruptedFailure, reportNonInterruptedFailureCause } from "./errors.js"
|
|
9
|
-
import type { NonEmptyReadonlyArray } from "effect-app"
|
|
10
9
|
import { type QueueBase, QueueMeta } from "./service.js"
|
|
11
10
|
|
|
12
11
|
export function makeMemQueue<
|
|
@@ -26,8 +25,12 @@ export function makeMemQueue<
|
|
|
26
25
|
const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
|
|
27
26
|
|
|
28
27
|
const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
|
|
28
|
+
const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
|
|
29
|
+
const encodePublish = S.encodeEffect(wireSchemaJson)
|
|
29
30
|
const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
|
|
30
|
-
const
|
|
31
|
+
const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
|
|
32
|
+
|
|
33
|
+
const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
|
|
31
34
|
|
|
32
35
|
const queue = {
|
|
33
36
|
publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
|
|
@@ -37,10 +40,8 @@ export function makeMemQueue<
|
|
|
37
40
|
Effect
|
|
38
41
|
.forEach(messages, (m) =>
|
|
39
42
|
// we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
|
|
40
|
-
|
|
43
|
+
encodePublish({ body: m, meta: requestContext }).pipe(
|
|
41
44
|
Effect.orDie,
|
|
42
|
-
Effect
|
|
43
|
-
.map(JSON.stringify),
|
|
44
45
|
// .tap((msg) => info("Publishing Mem Message: " + utils.inspect(msg)))
|
|
45
46
|
Effect.flatMap((_) => Q.offer(q, _))
|
|
46
47
|
), { discard: true })
|
|
@@ -58,42 +59,40 @@ export function makeMemQueue<
|
|
|
58
59
|
const reportError = reportNonInterruptedFailureCause({ name: "MemQueue.drain." + queueDrainName })
|
|
59
60
|
const processMessage = (msg: string) =>
|
|
60
61
|
// we JSON parse, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"queue.name": queueDrainName,
|
|
84
|
-
"queue.sessionId": sessionId,
|
|
85
|
-
"queue.input": body
|
|
86
|
-
}
|
|
62
|
+
parseDrain(msg).pipe(
|
|
63
|
+
Effect.orDie,
|
|
64
|
+
Effect
|
|
65
|
+
.flatMap(({ body, meta }) => {
|
|
66
|
+
let effect = InfraLogger
|
|
67
|
+
.logDebug(`[${queueDrainName}] Processing incoming message`)
|
|
68
|
+
.pipe(
|
|
69
|
+
Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
|
|
70
|
+
Effect.andThen(handleEvent(body)),
|
|
71
|
+
silenceAndReportError,
|
|
72
|
+
(_) =>
|
|
73
|
+
setupRequestContextWithCustomSpan(
|
|
74
|
+
_,
|
|
75
|
+
meta,
|
|
76
|
+
`queue.drain: ${queueDrainName}.${body._tag}`,
|
|
77
|
+
{
|
|
78
|
+
captureStackTrace: false,
|
|
79
|
+
kind: "consumer",
|
|
80
|
+
attributes: {
|
|
81
|
+
"queue.name": queueDrainName,
|
|
82
|
+
"queue.sessionId": sessionId,
|
|
83
|
+
"queue.input": body
|
|
87
84
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
if (meta.span) {
|
|
89
|
+
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
90
|
+
}
|
|
91
|
+
return effect
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
return Q
|
|
95
|
+
.take(qDrain)
|
|
97
96
|
.pipe(
|
|
98
97
|
Effect
|
|
99
98
|
.flatMap((x) =>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Tracer } from "effect"
|
|
2
|
-
import { Cause, Effect, flow, S } from "effect-app"
|
|
2
|
+
import { Cause, Effect, flow, type NonEmptyReadonlyArray, S } from "effect-app"
|
|
3
3
|
import type { StringId } from "effect-app/Schema"
|
|
4
4
|
import { pretty } from "effect-app/utils"
|
|
5
5
|
import { Receiver, Sender } from "../adapters/ServiceBus.js"
|
|
6
6
|
import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
|
|
7
7
|
import { InfraLogger } from "../logger.js"
|
|
8
8
|
import { reportNonInterruptedFailure, reportNonInterruptedFailureCause, reportQueueError } from "./errors.js"
|
|
9
|
-
import type { NonEmptyReadonlyArray } from "effect-app"
|
|
10
9
|
import { type QueueBase, QueueMeta } from "./service.js"
|
|
11
10
|
|
|
12
11
|
export function makeServiceBusQueue<
|
|
@@ -22,8 +21,11 @@ export function makeServiceBusQueue<
|
|
|
22
21
|
body: schema,
|
|
23
22
|
meta: QueueMeta
|
|
24
23
|
})
|
|
24
|
+
const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
|
|
25
|
+
const encodePublish = S.encodeEffect(wireSchemaJson)
|
|
25
26
|
const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
|
|
26
|
-
const
|
|
27
|
+
const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
|
|
28
|
+
const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
|
|
27
29
|
|
|
28
30
|
return Effect.gen(function*() {
|
|
29
31
|
const sender = yield* Sender
|
|
@@ -40,55 +42,51 @@ export function makeServiceBusQueue<
|
|
|
40
42
|
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
41
43
|
sessionId?: string
|
|
42
44
|
) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
attributes: {
|
|
74
|
-
"queue.name": receiver.name,
|
|
75
|
-
"queue.sessionId": sessionId,
|
|
76
|
-
"queue.input": body
|
|
77
|
-
}
|
|
45
|
+
function processMessage(messageBody: unknown) {
|
|
46
|
+
return parseDrain(messageBody).pipe(
|
|
47
|
+
Effect.orDie,
|
|
48
|
+
Effect
|
|
49
|
+
.flatMap(({ body, meta }) => {
|
|
50
|
+
let effect = InfraLogger
|
|
51
|
+
.logDebug(`[${receiver.name}] Processing incoming message`)
|
|
52
|
+
.pipe(
|
|
53
|
+
Effect.annotateLogs({
|
|
54
|
+
body: pretty(body),
|
|
55
|
+
meta: pretty(meta)
|
|
56
|
+
}),
|
|
57
|
+
Effect.andThen(handleEvent(body)),
|
|
58
|
+
Effect.orDie
|
|
59
|
+
)
|
|
60
|
+
// we silenceAndReportError here, so that the error is reported, and moves into the Exit.
|
|
61
|
+
.pipe(
|
|
62
|
+
silenceAndReportError,
|
|
63
|
+
(_) =>
|
|
64
|
+
setupRequestContextWithCustomSpan(
|
|
65
|
+
_,
|
|
66
|
+
meta,
|
|
67
|
+
`queue.drain: ${receiver.name}${sessionId ? `#${sessionId}` : ""}.${body._tag}`,
|
|
68
|
+
{
|
|
69
|
+
captureStackTrace: false,
|
|
70
|
+
kind: "consumer",
|
|
71
|
+
attributes: {
|
|
72
|
+
"queue.name": receiver.name,
|
|
73
|
+
"queue.sessionId": sessionId,
|
|
74
|
+
"queue.input": body
|
|
78
75
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
if (meta.span) {
|
|
80
|
+
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
81
|
+
}
|
|
82
|
+
return effect
|
|
83
|
+
}),
|
|
84
|
+
Effect
|
|
85
|
+
// we reportError here, so that we report the error only, and keep flowing
|
|
86
|
+
.tapCause(reportError),
|
|
87
|
+
// we still need to flatten the Exit.
|
|
88
|
+
Effect.flatMap((_) => _)
|
|
89
|
+
)
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
return receiver
|
|
@@ -111,17 +109,22 @@ export function makeServiceBusQueue<
|
|
|
111
109
|
getRequestContext
|
|
112
110
|
.pipe(
|
|
113
111
|
Effect.flatMap((requestContext) =>
|
|
114
|
-
Effect
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
Effect
|
|
113
|
+
.forEach(messages, (m) =>
|
|
114
|
+
encodePublish({
|
|
115
|
+
body: m,
|
|
116
|
+
meta: requestContext
|
|
117
|
+
})
|
|
118
|
+
.pipe(
|
|
119
|
+
Effect.orDie,
|
|
120
|
+
Effect.map((body) => ({
|
|
121
|
+
body,
|
|
122
|
+
messageId: m.id, /* correllationid: requestId */
|
|
123
|
+
contentType: "application/json",
|
|
124
|
+
sessionId: "sessionId" in m ? m.sessionId as string : undefined as unknown as string // TODO: optional
|
|
125
|
+
}))
|
|
126
|
+
))
|
|
127
|
+
.pipe(Effect.flatMap((msgs) => sender.sendMessages(msgs)))
|
|
125
128
|
),
|
|
126
129
|
Effect.withSpan("queue.publish: " + sender.name, {
|
|
127
130
|
kind: "producer",
|
package/src/RequestContext.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Context, S } from "effect-app"
|
|
2
2
|
import { UserProfileId } from "effect-app/ids"
|
|
3
3
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
4
4
|
|
|
5
|
-
export const Locale = S.
|
|
5
|
+
export const Locale = S.Literals(["en", "de"])
|
|
6
6
|
export type Locale = typeof Locale.Type
|
|
7
7
|
|
|
8
|
-
export class LocaleRef extends
|
|
8
|
+
export class LocaleRef extends Context.Reference("Locale", { defaultValue: (): Locale => "en" }) {}
|
|
9
9
|
|
|
10
10
|
export class RequestContext extends S.ExtendedClass<
|
|
11
11
|
RequestContext,
|
|
@@ -23,7 +23,7 @@ export class RequestContext extends S.ExtendedClass<
|
|
|
23
23
|
/** @deprecated */
|
|
24
24
|
userProfile: S.optional(S.Struct({ sub: UserProfileId })) //
|
|
25
25
|
}) {
|
|
26
|
-
// static Tag =
|
|
26
|
+
// static Tag = Context.Tag<RequestContext>()
|
|
27
27
|
|
|
28
28
|
static toMonitoring(this: void, self: RequestContext) {
|
|
29
29
|
return {
|
package/src/RequestFiberSet.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { Effect, Fiber, FiberSet, Layer,
|
|
2
|
+
import { Context, Effect, Fiber, FiberSet, Layer, Option, type Tracer } from "effect-app"
|
|
3
3
|
import { reportRequestError, reportUnknownRequestError } from "./api/reportError.js"
|
|
4
4
|
import { InfraLogger } from "./logger.js"
|
|
5
5
|
|
|
@@ -8,8 +8,8 @@ const getRootParentSpan = Effect.gen(function*() {
|
|
|
8
8
|
Effect.catchTag("NoSuchElementError", () => Effect.succeed(null))
|
|
9
9
|
)
|
|
10
10
|
if (!span) return span
|
|
11
|
-
while (span._tag === "Span" && span.parent
|
|
12
|
-
span = span.parent
|
|
11
|
+
while (span._tag === "Span" && Option.isSome(span.parent)) {
|
|
12
|
+
span = span.parent.value
|
|
13
13
|
}
|
|
14
14
|
return span
|
|
15
15
|
})
|
|
@@ -92,7 +92,7 @@ const make = Effect.gen(function*() {
|
|
|
92
92
|
* Whenever you fork a fiber for a Request, and you want to prevent dependent services to close prematurely on interruption,
|
|
93
93
|
* like the ServiceBus Sender, you should register these fibers in this FiberSet.
|
|
94
94
|
*/
|
|
95
|
-
export class RequestFiberSet extends
|
|
95
|
+
export class RequestFiberSet extends Context.Service<RequestFiberSet>()("RequestFiberSet", { make }) {
|
|
96
96
|
static readonly Live = Layer.effect(this, this.make)
|
|
97
97
|
static readonly register = <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
98
98
|
this.asEffect().pipe(Effect.andThen((_) => _.register(self)))
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { Data, Effect, Layer,
|
|
1
|
+
import { Context, Data, Effect, type Exit, Layer, RequestResolver } from "effect-app"
|
|
2
|
+
import type { NonEmptyArray } from "effect/Array"
|
|
3
|
+
import { dual } from "effect/Function"
|
|
4
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
5
|
+
import type * as Request from "effect/Request"
|
|
2
6
|
import { ContextMap } from "./service.js"
|
|
3
7
|
|
|
4
8
|
// TODO: we have to create a new contextmap on every request.
|
|
@@ -7,7 +11,7 @@ import { ContextMap } from "./service.js"
|
|
|
7
11
|
// we can call another start after startup. but it would be even better if we could Die on accessing rootmap
|
|
8
12
|
// we could also make the ContextMap optional, and when missing, issue a warning instead?
|
|
9
13
|
|
|
10
|
-
export class ContextMapContainer extends
|
|
14
|
+
export class ContextMapContainer extends Context.Reference("ContextMapContainer", {
|
|
11
15
|
defaultValue: (): ContextMap | "root" => "root"
|
|
12
16
|
}) {
|
|
13
17
|
static readonly layer = Layer.effect(this, ContextMap.make.pipe(Effect.map(ContextMap.of)))
|
|
@@ -18,3 +22,95 @@ export class ContextMapNotStartedError extends Data.TaggedError("ContextMapNotSt
|
|
|
18
22
|
export const getContextMap = ContextMapContainer.asEffect().pipe(
|
|
19
23
|
Effect.filterOrFail((_) => _ !== "root", () => new ContextMapNotStartedError())
|
|
20
24
|
)
|
|
25
|
+
|
|
26
|
+
export const withRequestResolverCache: {
|
|
27
|
+
<A extends Request.Request<any, any>>(options: {
|
|
28
|
+
readonly capacity: number
|
|
29
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
30
|
+
}): (self: RequestResolver.RequestResolver<A>) => RequestResolver.RequestResolver<A>
|
|
31
|
+
<A extends Request.Request<any, any>>(
|
|
32
|
+
self: RequestResolver.RequestResolver<A>,
|
|
33
|
+
options: {
|
|
34
|
+
readonly capacity: number
|
|
35
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
36
|
+
}
|
|
37
|
+
): RequestResolver.RequestResolver<A>
|
|
38
|
+
} = dual(2, <A extends Request.Request<any, any>>(
|
|
39
|
+
self: RequestResolver.RequestResolver<A>,
|
|
40
|
+
options: {
|
|
41
|
+
readonly capacity: number
|
|
42
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
43
|
+
}
|
|
44
|
+
): RequestResolver.RequestResolver<A> => {
|
|
45
|
+
const cacheKey = Symbol()
|
|
46
|
+
const strategy = options.strategy ?? "lru"
|
|
47
|
+
type CacheEntry = {
|
|
48
|
+
readonly entry: Request.Entry<A>
|
|
49
|
+
exit: Exit.Exit<Request.Success<A>, Request.Error<A>> | undefined
|
|
50
|
+
}
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
52
|
+
return RequestResolver.makeWith({
|
|
53
|
+
...(self as any),
|
|
54
|
+
runAll(
|
|
55
|
+
entries: NonEmptyArray<Request.Entry<A>>,
|
|
56
|
+
key: unknown
|
|
57
|
+
) {
|
|
58
|
+
return Effect.flatMap(
|
|
59
|
+
getContextMap.pipe(Effect.orDie),
|
|
60
|
+
(contextMap) => {
|
|
61
|
+
const cache = contextMap.getOrCreateStore<MutableHashMap.MutableHashMap<A, CacheEntry>>(
|
|
62
|
+
cacheKey,
|
|
63
|
+
() => MutableHashMap.empty()
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const uncached: Array<Request.Entry<A>> = []
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const ocached = MutableHashMap.get(cache, entry.request)
|
|
69
|
+
if (ocached._tag === "None") {
|
|
70
|
+
const cached: CacheEntry = { entry, exit: undefined }
|
|
71
|
+
MutableHashMap.set(cache, entry.request, cached)
|
|
72
|
+
const prevComplete = entry.completeUnsafe.bind(entry)
|
|
73
|
+
entry.completeUnsafe = (exit) => {
|
|
74
|
+
cached.exit = exit
|
|
75
|
+
prevComplete(exit)
|
|
76
|
+
}
|
|
77
|
+
uncached.push(entry)
|
|
78
|
+
} else {
|
|
79
|
+
const cached = ocached.value
|
|
80
|
+
if (cached.exit) {
|
|
81
|
+
if (strategy === "lru") {
|
|
82
|
+
MutableHashMap.remove(cache, cached.entry.request)
|
|
83
|
+
MutableHashMap.set(cache, cached.entry.request, cached)
|
|
84
|
+
}
|
|
85
|
+
entry.completeUnsafe(cached.exit as any)
|
|
86
|
+
} else {
|
|
87
|
+
cached.entry.uninterruptible = true
|
|
88
|
+
const prevComplete = cached.entry.completeUnsafe.bind(cached.entry)
|
|
89
|
+
cached.entry.completeUnsafe = (exit) => {
|
|
90
|
+
prevComplete(exit)
|
|
91
|
+
entry.completeUnsafe(exit)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (uncached.length === 0) return Effect.void
|
|
98
|
+
|
|
99
|
+
return Effect.onExit(
|
|
100
|
+
(self as any).runAll(uncached, key),
|
|
101
|
+
() => {
|
|
102
|
+
let toRemove = MutableHashMap.size(cache) - options.capacity
|
|
103
|
+
if (toRemove <= 0) return Effect.void
|
|
104
|
+
for (const k of MutableHashMap.keys(cache)) {
|
|
105
|
+
MutableHashMap.remove(cache, k)
|
|
106
|
+
toRemove--
|
|
107
|
+
if (toRemove <= 0) break
|
|
108
|
+
}
|
|
109
|
+
return Effect.void
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|