@effect-app/infra 4.0.0-beta.121 → 4.0.0-beta.123
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 +17 -0
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +8 -10
- package/dist/Model/Repository/ext.d.ts +15 -3
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +25 -2
- package/dist/Model/Repository/internal/internal.d.ts +1 -1
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +9 -8
- package/dist/Model/Repository/makeRepo.d.ts +3 -3
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/service.d.ts +21 -21
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.d.ts +2 -2
- package/dist/Operations.d.ts +2 -2
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +54 -57
- package/dist/OperationsRepo.d.ts +2 -2
- package/dist/QueueMaker/SQLQueue.d.ts +2 -3
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +104 -115
- package/dist/QueueMaker/memQueue.d.ts +2 -2
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +51 -62
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +34 -50
- package/dist/Store/ContextMapContainer.d.ts +1 -1
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +304 -306
- package/dist/Store/Disk.d.ts +1 -1
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +2 -2
- package/dist/Store/Memory.d.ts +1 -1
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +2 -2
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +147 -149
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +6 -6
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +7 -9
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +1 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +2 -2
- package/dist/errorReporter.d.ts +3 -3
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +16 -23
- package/package.json +14 -14
- package/src/CUPS.ts +7 -9
- package/src/Model/Repository/ext.ts +71 -6
- package/src/Model/Repository/internal/internal.ts +13 -25
- package/src/Model/Repository/makeRepo.ts +4 -4
- package/src/Model/Repository/service.ts +22 -21
- package/src/Operations.ts +76 -111
- package/src/QueueMaker/SQLQueue.ts +119 -150
- package/src/QueueMaker/memQueue.ts +81 -102
- package/src/QueueMaker/sbqueue.ts +51 -81
- package/src/Store/Cosmos.ts +481 -484
- package/src/Store/Disk.ts +52 -53
- package/src/Store/Memory.ts +49 -50
- package/src/Store/SQL/Pg.ts +247 -250
- package/src/Store/SQL.ts +420 -426
- package/src/Store/utils.ts +23 -22
- package/src/adapters/ServiceBus.ts +106 -110
- package/src/api/internal/auth.ts +8 -6
- package/src/api/routing/middleware/middleware.ts +10 -11
- package/src/errorReporter.ts +58 -72
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/repository-ext.test.ts +58 -0
|
@@ -13,7 +13,7 @@ export const QueueId = S.Finite.pipe(S.brand("QueueId"))
|
|
|
13
13
|
export type QueueId = typeof QueueId.Type
|
|
14
14
|
|
|
15
15
|
// TODO: let the model track and Auto Generate versionColumn on every update instead
|
|
16
|
-
export
|
|
16
|
+
export const makeSQLQueue = Effect.fnUntraced(function*<
|
|
17
17
|
Evt extends { id: S.StringId; _tag: string },
|
|
18
18
|
DrainEvt extends { id: S.StringId; _tag: string },
|
|
19
19
|
EvtE,
|
|
@@ -24,167 +24,136 @@ export function makeSQLQueue<
|
|
|
24
24
|
schema: S.Codec<Evt, EvtE>,
|
|
25
25
|
drainSchema: S.Codec<DrainEvt, DrainEvtE>
|
|
26
26
|
) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const sql = yield* SqlClient.SqlClient
|
|
27
|
+
const base = {
|
|
28
|
+
id: SQLModel.Generated(QueueId),
|
|
29
|
+
meta: SQLModel.JsonFromString(QueueMeta),
|
|
30
|
+
name: S.NonEmptyString255,
|
|
31
|
+
createdAt: SQLModel.DateTimeInsert,
|
|
32
|
+
updatedAt: SQLModel.DateTimeUpdate,
|
|
33
|
+
// TODO: at+owner
|
|
34
|
+
processingAt: SQLModel.FieldOption(S.Date),
|
|
35
|
+
finishedAt: SQLModel.FieldOption(S.Date),
|
|
36
|
+
etag: S.String // TODO: use a SQLModel thing that auto updates it?
|
|
37
|
+
// TODO: record locking.. / optimistic locking
|
|
38
|
+
// rowVersion: SQLModel.DateTimeFromNumberWithNow
|
|
39
|
+
}
|
|
40
|
+
class Queue extends SQLModel.Class<Queue>("Queue")({
|
|
41
|
+
body: SQLModel.JsonFromString(schema),
|
|
42
|
+
...base
|
|
43
|
+
}) {}
|
|
44
|
+
class Drain extends SQLModel.Class<Drain>("Drain")({
|
|
45
|
+
body: SQLModel.JsonFromString(drainSchema),
|
|
46
|
+
...base
|
|
47
|
+
}) {}
|
|
48
|
+
const sql = yield* SqlClient.SqlClient
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
const queueRepo = yield* SQLModel.makeRepository(Queue, {
|
|
51
|
+
tableName: "queue",
|
|
52
|
+
spanPrefix: "QueueRepo",
|
|
53
|
+
idColumn: "id",
|
|
54
|
+
versionColumn: "etag"
|
|
55
|
+
})
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const drainRepo = yield* SQLModel.makeRepository(Drain, {
|
|
58
|
+
tableName: "queue",
|
|
59
|
+
spanPrefix: "DrainRepo",
|
|
60
|
+
idColumn: "id",
|
|
61
|
+
versionColumn: "etag"
|
|
62
|
+
})
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
const decodeDrain = S.decodeEffect(Drain)
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Effect
|
|
71
|
-
.andThen((limit) =>
|
|
72
|
-
sql<typeof Drain.Encoded>`SELECT *
|
|
66
|
+
const drain = Effect.gen(function*() {
|
|
67
|
+
const limit = subMinutes(new Date(), 15)
|
|
68
|
+
return yield* sql<typeof Drain.Encoded>`SELECT *
|
|
73
69
|
FROM queue
|
|
74
70
|
WHERE name = ${queueDrainName} AND finishedAt IS NULL AND (processingAt IS NULL OR processingAt < ${limit.getTime()})
|
|
75
71
|
LIMIT 1`
|
|
76
|
-
|
|
77
|
-
)
|
|
72
|
+
})
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
if (first) return first
|
|
101
|
-
yield* Effect.sleep(250)
|
|
74
|
+
const q = {
|
|
75
|
+
offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
|
|
76
|
+
yield* queueRepo.insertVoid(Queue.insert.make({
|
|
77
|
+
body,
|
|
78
|
+
meta,
|
|
79
|
+
name: queueName,
|
|
80
|
+
processingAt: Option.none(),
|
|
81
|
+
finishedAt: Option.none(),
|
|
82
|
+
etag: crypto.randomUUID()
|
|
83
|
+
}))
|
|
84
|
+
}),
|
|
85
|
+
take: Effect.gen(function*() {
|
|
86
|
+
while (true) {
|
|
87
|
+
const [first] = yield* drain.pipe(Effect.withTracerEnabled(false)) // disable sql tracer otherwise we spam it..
|
|
88
|
+
if (first) {
|
|
89
|
+
const dec = yield* decodeDrain(first)
|
|
90
|
+
const { createdAt, updatedAt, ...rest } = dec
|
|
91
|
+
return yield* drainRepo.update(
|
|
92
|
+
Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
|
|
93
|
+
)
|
|
102
94
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
if (first) return first
|
|
96
|
+
yield* Effect.sleep(250)
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
finish: Effect.fn(function*({ createdAt, updatedAt, ...q }: Drain) {
|
|
100
|
+
return yield* drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: crypto.randomUUID()
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
const queue = {
|
|
104
|
+
publish: Effect.fn("queue.publish: " + queueName, { kind: "producer" })(function*(
|
|
105
|
+
...messages: NonEmptyReadonlyArray<Evt>
|
|
106
|
+
) {
|
|
107
|
+
yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
|
|
108
|
+
const requestContext = yield* getRequestContext
|
|
109
|
+
yield* Effect.forEach(messages, (m) => q.offer(m, requestContext), { discard: true })
|
|
110
|
+
}),
|
|
111
|
+
drain: <DrainE, DrainR>(
|
|
112
|
+
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
113
|
+
sessionId?: string
|
|
114
|
+
) => {
|
|
115
|
+
const silenceAndReportError = reportNonInterruptedFailure({ name: "MemQueue.drain." + queueDrainName })
|
|
116
|
+
const processMessage = Effect.fnUntraced(function*({ body, meta }: Drain) {
|
|
117
|
+
let effect = InfraLogger
|
|
118
|
+
.logDebug(`[${queueDrainName}] Processing incoming message`)
|
|
111
119
|
.pipe(
|
|
112
|
-
Effect.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
|
|
121
|
+
Effect.andThen(handleEvent(body)),
|
|
122
|
+
silenceAndReportError,
|
|
123
|
+
(_) =>
|
|
124
|
+
setupRequestContextWithCustomSpan(
|
|
125
|
+
_,
|
|
126
|
+
meta,
|
|
127
|
+
`queue.drain: ${queueDrainName}.${body._tag}`,
|
|
128
|
+
{
|
|
129
|
+
captureStackTrace: false,
|
|
130
|
+
kind: "consumer",
|
|
131
|
+
attributes: {
|
|
132
|
+
"queue.name": queueDrainName,
|
|
133
|
+
"queue.sessionId": sessionId,
|
|
134
|
+
"queue.input": body
|
|
119
135
|
}
|
|
120
|
-
)
|
|
121
|
-
),
|
|
122
|
-
Effect.withSpan("queue.publish: " + queueName, {
|
|
123
|
-
kind: "producer",
|
|
124
|
-
attributes: { "message_tags": messages.map((_) => _._tag) }
|
|
125
|
-
}, { captureStackTrace: false })
|
|
126
|
-
),
|
|
127
|
-
drain: <DrainE, DrainR>(
|
|
128
|
-
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
129
|
-
sessionId?: string
|
|
130
|
-
) => {
|
|
131
|
-
const silenceAndReportError = reportNonInterruptedFailure({ name: "MemQueue.drain." + queueDrainName })
|
|
132
|
-
const processMessage = (msg: Drain) =>
|
|
133
|
-
Effect
|
|
134
|
-
.succeed(msg)
|
|
135
|
-
.pipe(Effect
|
|
136
|
-
.flatMap(({ body, meta }) => {
|
|
137
|
-
let effect = InfraLogger
|
|
138
|
-
.logDebug(`[${queueDrainName}] Processing incoming message`)
|
|
139
|
-
.pipe(
|
|
140
|
-
Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
|
|
141
|
-
Effect.andThen(handleEvent(body)),
|
|
142
|
-
silenceAndReportError,
|
|
143
|
-
(_) =>
|
|
144
|
-
setupRequestContextWithCustomSpan(
|
|
145
|
-
_,
|
|
146
|
-
meta,
|
|
147
|
-
`queue.drain: ${queueDrainName}.${body._tag}`,
|
|
148
|
-
{
|
|
149
|
-
captureStackTrace: false,
|
|
150
|
-
kind: "consumer",
|
|
151
|
-
attributes: {
|
|
152
|
-
"queue.name": queueDrainName,
|
|
153
|
-
"queue.sessionId": sessionId,
|
|
154
|
-
"queue.input": body
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
)
|
|
159
|
-
if (meta.span) {
|
|
160
|
-
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
161
136
|
}
|
|
162
|
-
return effect
|
|
163
|
-
}))
|
|
164
|
-
|
|
165
|
-
return q
|
|
166
|
-
.take
|
|
167
|
-
.pipe(
|
|
168
|
-
Effect.flatMap((x) =>
|
|
169
|
-
processMessage(x).pipe(
|
|
170
|
-
Effect.uninterruptible,
|
|
171
|
-
Effect.forkChild,
|
|
172
|
-
Effect.flatMap(Fiber.join),
|
|
173
|
-
Effect.tap(q.finish(x))
|
|
174
137
|
)
|
|
175
|
-
),
|
|
176
|
-
silenceAndReportError,
|
|
177
|
-
Effect.withSpan(`queue.drain: ${queueDrainName}`, {
|
|
178
|
-
attributes: {
|
|
179
|
-
"queue.type": "sql",
|
|
180
|
-
"queue.name": queueDrainName,
|
|
181
|
-
"queue.sessionId": sessionId
|
|
182
|
-
}
|
|
183
|
-
}),
|
|
184
|
-
Effect.forever
|
|
185
138
|
)
|
|
186
|
-
|
|
139
|
+
if (meta.span) {
|
|
140
|
+
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
141
|
+
}
|
|
142
|
+
return yield* effect
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return Effect.fn(`queue.drain: ${queueDrainName}`, {
|
|
146
|
+
attributes: { "queue.type": "sql", "queue.name": queueDrainName, "queue.sessionId": sessionId }
|
|
147
|
+
})(function*() {
|
|
148
|
+
const x = yield* q.take
|
|
149
|
+
yield* processMessage(x).pipe(
|
|
150
|
+
Effect.uninterruptible,
|
|
151
|
+
Effect.forkChild,
|
|
152
|
+
Effect.flatMap(Fiber.join),
|
|
153
|
+
Effect.tap(q.finish(x))
|
|
154
|
+
)
|
|
155
|
+
}, (effect) => effect.pipe(silenceAndReportError, Effect.forever))()
|
|
187
156
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
157
|
+
}
|
|
158
|
+
return queue as QueueBase<Evt, DrainEvt>
|
|
159
|
+
})
|
|
@@ -8,7 +8,7 @@ import { InfraLogger } from "../logger.js"
|
|
|
8
8
|
import { reportNonInterruptedFailure, reportNonInterruptedFailureCause } from "./errors.js"
|
|
9
9
|
import { type QueueBase, QueueMeta } from "./service.js"
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export const makeMemQueue = Effect.fnUntraced(function*<
|
|
12
12
|
Evt extends { id: S.StringId; _tag: string },
|
|
13
13
|
DrainEvt extends { id: S.StringId; _tag: string },
|
|
14
14
|
EvtE,
|
|
@@ -19,112 +19,91 @@ export function makeMemQueue<
|
|
|
19
19
|
schema: S.Codec<Evt, EvtE>,
|
|
20
20
|
drainSchema: S.Codec<DrainEvt, DrainEvtE>
|
|
21
21
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
|
|
22
|
+
const mem = yield* MemQueue
|
|
23
|
+
const q = yield* mem.getOrCreateQueue(queueName)
|
|
24
|
+
const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
|
|
27
|
+
const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
|
|
28
|
+
const encodePublish = S.encodeEffect(wireSchemaJson)
|
|
29
|
+
const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
|
|
30
|
+
const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Effect.flatMap((_) => Q.offer(q, _))
|
|
47
|
-
), { discard: true })
|
|
48
|
-
),
|
|
49
|
-
Effect.withSpan("queue.publish: " + queueName, {
|
|
50
|
-
kind: "producer",
|
|
51
|
-
attributes: { "message_tags": messages.map((_) => _._tag) }
|
|
52
|
-
}, { captureStackTrace: false })
|
|
53
|
-
),
|
|
54
|
-
drain: <DrainE, DrainR>(
|
|
55
|
-
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
56
|
-
sessionId?: string
|
|
57
|
-
) => {
|
|
58
|
-
const silenceAndReportError = reportNonInterruptedFailure({ name: "MemQueue.drain." + queueDrainName })
|
|
59
|
-
const reportError = reportNonInterruptedFailureCause({ name: "MemQueue.drain." + queueDrainName })
|
|
60
|
-
const processMessage = (msg: string) =>
|
|
61
|
-
// we JSON parse, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
|
|
62
|
-
parseDrain(msg).pipe(
|
|
34
|
+
const queue = {
|
|
35
|
+
publish: Effect.fn("queue.publish: " + queueName, { kind: "producer" })(function*(
|
|
36
|
+
...messages: NonEmptyReadonlyArray<Evt>
|
|
37
|
+
) {
|
|
38
|
+
yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
|
|
39
|
+
const requestContext = yield* getRequestContext
|
|
40
|
+
// we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
|
|
41
|
+
yield* Effect.forEach(
|
|
42
|
+
messages,
|
|
43
|
+
(m) =>
|
|
44
|
+
encodePublish({ body: m, meta: requestContext }).pipe(
|
|
63
45
|
Effect.orDie,
|
|
64
|
-
Effect
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
attributes: {
|
|
81
|
-
"queue.name": queueDrainName,
|
|
82
|
-
"queue.sessionId": sessionId,
|
|
83
|
-
"queue.input": body
|
|
84
|
-
}
|
|
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)
|
|
46
|
+
Effect.flatMap((_) => Q.offer(q, _))
|
|
47
|
+
),
|
|
48
|
+
{ discard: true }
|
|
49
|
+
)
|
|
50
|
+
}),
|
|
51
|
+
drain: <DrainE, DrainR>(
|
|
52
|
+
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
53
|
+
sessionId?: string
|
|
54
|
+
) => {
|
|
55
|
+
const silenceAndReportError = reportNonInterruptedFailure({ name: "MemQueue.drain." + queueDrainName })
|
|
56
|
+
const reportError = reportNonInterruptedFailureCause({ name: "MemQueue.drain." + queueDrainName })
|
|
57
|
+
const processMessage = Effect.fnUntraced(function*(msg: string) {
|
|
58
|
+
// we JSON parse, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
|
|
59
|
+
const { body, meta } = yield* parseDrain(msg).pipe(Effect.orDie)
|
|
60
|
+
let effect = InfraLogger
|
|
61
|
+
.logDebug(`[${queueDrainName}] Processing incoming message`)
|
|
96
62
|
.pipe(
|
|
97
|
-
Effect
|
|
98
|
-
|
|
99
|
-
processMessage(x).pipe(
|
|
100
|
-
Effect.uninterruptible,
|
|
101
|
-
Effect.forkChild,
|
|
102
|
-
Effect.flatMap(Fiber.join),
|
|
103
|
-
// normally a failed item would be returned to the queue and retried up to X times.
|
|
104
|
-
Effect.flatMap((_) =>
|
|
105
|
-
_._tag === "Failure" && !Cause.hasInterruptsOnly(_.cause)
|
|
106
|
-
? Q.offer(qDrain, x).pipe(
|
|
107
|
-
// TODO: retry count tracking and max retries.
|
|
108
|
-
Effect.delay("5 seconds"),
|
|
109
|
-
Effect.tapCause(reportError),
|
|
110
|
-
Effect.forkDetach
|
|
111
|
-
)
|
|
112
|
-
: Effect.void
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
),
|
|
63
|
+
Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
|
|
64
|
+
Effect.andThen(handleEvent(body)),
|
|
116
65
|
silenceAndReportError,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
66
|
+
(_) =>
|
|
67
|
+
setupRequestContextWithCustomSpan(
|
|
68
|
+
_,
|
|
69
|
+
meta,
|
|
70
|
+
`queue.drain: ${queueDrainName}.${body._tag}`,
|
|
71
|
+
{
|
|
72
|
+
captureStackTrace: false,
|
|
73
|
+
kind: "consumer",
|
|
74
|
+
attributes: {
|
|
75
|
+
"queue.name": queueDrainName,
|
|
76
|
+
"queue.sessionId": sessionId,
|
|
77
|
+
"queue.input": body
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
if (meta.span) {
|
|
83
|
+
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
84
|
+
}
|
|
85
|
+
return yield* effect
|
|
86
|
+
})
|
|
87
|
+
return Effect.fn(`queue.drain: ${queueDrainName}`, {
|
|
88
|
+
attributes: { "queue.type": "mem", "queue.name": queueDrainName, "queue.sessionId": sessionId }
|
|
89
|
+
})(function*() {
|
|
90
|
+
const x = yield* Q.take(qDrain)
|
|
91
|
+
const exit = yield* processMessage(x).pipe(
|
|
92
|
+
Effect.uninterruptible,
|
|
93
|
+
Effect.forkChild,
|
|
94
|
+
Effect.flatMap(Fiber.join)
|
|
95
|
+
)
|
|
96
|
+
if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) {
|
|
97
|
+
// normally a failed item would be returned to the queue and retried up to X times.
|
|
98
|
+
yield* Q.offer(qDrain, x).pipe(
|
|
99
|
+
// TODO: retry count tracking and max retries.
|
|
100
|
+
Effect.delay("5 seconds"),
|
|
101
|
+
Effect.tapCause(reportError),
|
|
102
|
+
Effect.forkDetach
|
|
125
103
|
)
|
|
126
|
-
|
|
104
|
+
}
|
|
105
|
+
}, (effect) => effect.pipe(silenceAndReportError, Effect.forever))()
|
|
127
106
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
107
|
+
}
|
|
108
|
+
return queue as QueueBase<Evt, DrainEvt>
|
|
109
|
+
})
|
|
@@ -42,95 +42,65 @@ export function makeServiceBusQueue<
|
|
|
42
42
|
handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
|
|
43
43
|
sessionId?: string
|
|
44
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
)
|
|
90
|
-
}
|
|
45
|
+
const processMessage = Effect.fnUntraced(function*(messageBody: unknown) {
|
|
46
|
+
const { body, meta } = yield* parseDrain(messageBody).pipe(Effect.orDie)
|
|
47
|
+
let effect = InfraLogger
|
|
48
|
+
.logDebug(`[${receiver.name}] Processing incoming message`)
|
|
49
|
+
.pipe(
|
|
50
|
+
Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
|
|
51
|
+
Effect.andThen(handleEvent(body)),
|
|
52
|
+
Effect.orDie,
|
|
53
|
+
// we silenceAndReportError here, so that the error is reported, and moves into the Exit.
|
|
54
|
+
silenceAndReportError,
|
|
55
|
+
(_) =>
|
|
56
|
+
setupRequestContextWithCustomSpan(
|
|
57
|
+
_,
|
|
58
|
+
meta,
|
|
59
|
+
`queue.drain: ${receiver.name}${sessionId ? `#${sessionId}` : ""}.${body._tag}`,
|
|
60
|
+
{
|
|
61
|
+
captureStackTrace: false,
|
|
62
|
+
kind: "consumer",
|
|
63
|
+
attributes: {
|
|
64
|
+
"queue.name": receiver.name,
|
|
65
|
+
"queue.sessionId": sessionId,
|
|
66
|
+
"queue.input": body
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
if (meta.span) {
|
|
72
|
+
effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
|
|
73
|
+
}
|
|
74
|
+
// we reportError here, so that we report the error only, and keep flowing
|
|
75
|
+
const exit = yield* Effect.tapCause(effect, reportError)
|
|
76
|
+
return yield* exit
|
|
77
|
+
})
|
|
91
78
|
|
|
92
79
|
return receiver
|
|
93
80
|
.subscribe({
|
|
94
81
|
processMessage: (x) => processMessage(x.body).pipe(Effect.uninterruptible),
|
|
95
82
|
processError: (err) => reportQueueError(Cause.fail(err.error))
|
|
96
|
-
// Deferred.completeWith(
|
|
97
|
-
// deferred,
|
|
98
|
-
// reportFatalQueueError(Cause.fail(err.error))
|
|
99
|
-
// .pipe(Effect.andThen(Effect.fail(err.error)))
|
|
100
|
-
// )
|
|
101
83
|
}, sessionId)
|
|
102
|
-
|
|
103
|
-
.pipe(
|
|
104
|
-
Effect.andThen(Effect.never)
|
|
105
|
-
)
|
|
84
|
+
.pipe(Effect.andThen(Effect.never))
|
|
106
85
|
},
|
|
107
86
|
|
|
108
|
-
publish: (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}))
|
|
126
|
-
))
|
|
127
|
-
.pipe(Effect.flatMap((msgs) => sender.sendMessages(msgs)))
|
|
128
|
-
),
|
|
129
|
-
Effect.withSpan("queue.publish: " + sender.name, {
|
|
130
|
-
kind: "producer",
|
|
131
|
-
attributes: { "message_tags": messages.map((_) => _._tag) }
|
|
132
|
-
}, { captureStackTrace: false })
|
|
133
|
-
)
|
|
87
|
+
publish: Effect.fn("queue.publish: " + sender.name, {
|
|
88
|
+
kind: "producer"
|
|
89
|
+
})(function*(...messages: NonEmptyReadonlyArray<Evt>) {
|
|
90
|
+
yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
|
|
91
|
+
const requestContext = yield* getRequestContext
|
|
92
|
+
const msgs = yield* Effect.forEach(messages, (m) =>
|
|
93
|
+
encodePublish({ body: m, meta: requestContext }).pipe(
|
|
94
|
+
Effect.orDie,
|
|
95
|
+
Effect.map((body) => ({
|
|
96
|
+
body,
|
|
97
|
+
messageId: m.id, /* correllationid: requestId */
|
|
98
|
+
contentType: "application/json",
|
|
99
|
+
sessionId: "sessionId" in m ? m.sessionId as string : undefined as unknown as string // TODO: optional
|
|
100
|
+
}))
|
|
101
|
+
))
|
|
102
|
+
yield* sender.sendMessages(msgs)
|
|
103
|
+
})
|
|
134
104
|
}
|
|
135
105
|
return queue as QueueBase<Evt, DrainEvt>
|
|
136
106
|
})
|