@effect-app/infra 4.0.0-beta.120 → 4.0.0-beta.122

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 (74) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/CUPS.d.ts.map +1 -1
  3. package/dist/CUPS.js +8 -10
  4. package/dist/Model/Repository/ext.d.ts +17 -5
  5. package/dist/Model/Repository/ext.d.ts.map +1 -1
  6. package/dist/Model/Repository/ext.js +25 -2
  7. package/dist/Model/Repository/internal/internal.d.ts +1 -1
  8. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  9. package/dist/Model/Repository/internal/internal.js +9 -8
  10. package/dist/Model/Repository/makeRepo.d.ts +3 -3
  11. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  12. package/dist/Model/Repository/service.d.ts +21 -21
  13. package/dist/Model/Repository/service.d.ts.map +1 -1
  14. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  15. package/dist/Model/query/new-kid-interpreter.js +3 -3
  16. package/dist/Operations.d.ts +3 -3
  17. package/dist/Operations.d.ts.map +1 -1
  18. package/dist/Operations.js +54 -57
  19. package/dist/OperationsRepo.d.ts +2 -2
  20. package/dist/QueueMaker/SQLQueue.d.ts +2 -3
  21. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  22. package/dist/QueueMaker/SQLQueue.js +104 -115
  23. package/dist/QueueMaker/memQueue.d.ts +2 -2
  24. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  25. package/dist/QueueMaker/memQueue.js +51 -62
  26. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  27. package/dist/QueueMaker/sbqueue.js +34 -50
  28. package/dist/Store/Cosmos.d.ts.map +1 -1
  29. package/dist/Store/Cosmos.js +304 -306
  30. package/dist/Store/Disk.d.ts +1 -1
  31. package/dist/Store/Disk.d.ts.map +1 -1
  32. package/dist/Store/Disk.js +2 -2
  33. package/dist/Store/Memory.d.ts +1 -1
  34. package/dist/Store/Memory.d.ts.map +1 -1
  35. package/dist/Store/Memory.js +2 -2
  36. package/dist/Store/SQL/Pg.d.ts.map +1 -1
  37. package/dist/Store/SQL/Pg.js +147 -149
  38. package/dist/Store/SQL.d.ts.map +1 -1
  39. package/dist/Store/SQL.js +6 -6
  40. package/dist/Store/utils.d.ts.map +1 -1
  41. package/dist/Store/utils.js +3 -4
  42. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  43. package/dist/adapters/ServiceBus.js +7 -9
  44. package/dist/api/internal/auth.d.ts.map +1 -1
  45. package/dist/api/internal/auth.js +1 -1
  46. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  47. package/dist/api/routing/middleware/middleware.js +2 -2
  48. package/dist/errorReporter.d.ts +3 -3
  49. package/dist/errorReporter.d.ts.map +1 -1
  50. package/dist/errorReporter.js +16 -23
  51. package/package.json +14 -14
  52. package/src/CUPS.ts +7 -9
  53. package/src/Model/Repository/ext.ts +71 -6
  54. package/src/Model/Repository/internal/internal.ts +13 -25
  55. package/src/Model/Repository/makeRepo.ts +4 -4
  56. package/src/Model/Repository/service.ts +22 -21
  57. package/src/Model/query/new-kid-interpreter.ts +2 -2
  58. package/src/Operations.ts +76 -111
  59. package/src/QueueMaker/SQLQueue.ts +119 -150
  60. package/src/QueueMaker/memQueue.ts +81 -102
  61. package/src/QueueMaker/sbqueue.ts +51 -81
  62. package/src/Store/Cosmos.ts +481 -484
  63. package/src/Store/Disk.ts +52 -53
  64. package/src/Store/Memory.ts +49 -50
  65. package/src/Store/SQL/Pg.ts +247 -250
  66. package/src/Store/SQL.ts +420 -426
  67. package/src/Store/utils.ts +23 -22
  68. package/src/adapters/ServiceBus.ts +106 -110
  69. package/src/api/internal/auth.ts +8 -6
  70. package/src/api/routing/middleware/middleware.ts +10 -11
  71. package/src/errorReporter.ts +58 -72
  72. package/test/dist/repository-ext.test.d.ts.map +1 -0
  73. package/test/query.test.ts +27 -0
  74. 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 function makeSQLQueue<
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
- return Effect.gen(function*() {
28
- const base = {
29
- id: SQLModel.Generated(QueueId),
30
- meta: SQLModel.JsonFromString(QueueMeta),
31
- name: S.NonEmptyString255,
32
- createdAt: SQLModel.DateTimeInsert,
33
- updatedAt: SQLModel.DateTimeUpdate,
34
- // TODO: at+owner
35
- processingAt: SQLModel.FieldOption(S.Date),
36
- finishedAt: SQLModel.FieldOption(S.Date),
37
- etag: S.String // TODO: use a SQLModel thing that auto updates it?
38
- // TODO: record locking.. / optimistic locking
39
- // rowVersion: SQLModel.DateTimeFromNumberWithNow
40
- }
41
- class Queue extends SQLModel.Class<Queue>("Queue")({
42
- body: SQLModel.JsonFromString(schema),
43
- ...base
44
- }) {}
45
- class Drain extends SQLModel.Class<Drain>("Drain")({
46
- body: SQLModel.JsonFromString(drainSchema),
47
- ...base
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
- const queueRepo = yield* SQLModel.makeRepository(Queue, {
52
- tableName: "queue",
53
- spanPrefix: "QueueRepo",
54
- idColumn: "id",
55
- versionColumn: "etag"
56
- })
50
+ const queueRepo = yield* SQLModel.makeRepository(Queue, {
51
+ tableName: "queue",
52
+ spanPrefix: "QueueRepo",
53
+ idColumn: "id",
54
+ versionColumn: "etag"
55
+ })
57
56
 
58
- const drainRepo = yield* SQLModel.makeRepository(Drain, {
59
- tableName: "queue",
60
- spanPrefix: "DrainRepo",
61
- idColumn: "id",
62
- versionColumn: "etag"
63
- })
57
+ const drainRepo = yield* SQLModel.makeRepository(Drain, {
58
+ tableName: "queue",
59
+ spanPrefix: "DrainRepo",
60
+ idColumn: "id",
61
+ versionColumn: "etag"
62
+ })
64
63
 
65
- const decodeDrain = S.decodeEffect(Drain)
64
+ const decodeDrain = S.decodeEffect(Drain)
66
65
 
67
- const drain = Effect
68
- .sync(() => subMinutes(new Date(), 15))
69
- .pipe(
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
- const q = {
80
- offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
81
- yield* queueRepo.insertVoid(Queue.insert.make({
82
- body,
83
- meta,
84
- name: queueName,
85
- processingAt: Option.none(),
86
- finishedAt: Option.none(),
87
- etag: crypto.randomUUID()
88
- }))
89
- }),
90
- take: Effect.gen(function*() {
91
- while (true) {
92
- const [first] = yield* drain.pipe(Effect.withTracerEnabled(false)) // disable sql tracer otherwise we spam it..
93
- if (first) {
94
- const dec = yield* decodeDrain(first)
95
- const { createdAt, updatedAt, ...rest } = dec
96
- return yield* drainRepo.update(
97
- Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
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
- 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
- }
108
- const queue = {
109
- publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
110
- getRequestContext
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.flatMap((requestContext) =>
113
- Effect
114
- .forEach(
115
- messages,
116
- (m) => q.offer(m, requestContext),
117
- {
118
- discard: true
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
- return queue as QueueBase<Evt, DrainEvt>
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 function makeMemQueue<
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
- return Effect.gen(function*() {
23
- const mem = yield* MemQueue
24
- const q = yield* mem.getOrCreateQueue(queueName)
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
- const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
28
- const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
29
- const encodePublish = S.encodeEffect(wireSchemaJson)
30
- const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
31
- const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
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
- const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
32
+ const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
34
33
 
35
- const queue = {
36
- publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
37
- getRequestContext
38
- .pipe(
39
- Effect.flatMap((requestContext) =>
40
- Effect
41
- .forEach(messages, (m) =>
42
- // we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
43
- encodePublish({ body: m, meta: requestContext }).pipe(
44
- Effect.orDie,
45
- // .tap((msg) => info("Publishing Mem Message: " + utils.inspect(msg)))
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
- .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
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
- .flatMap((x) =>
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
- Effect.withSpan(`queue.drain: ${queueDrainName}`, {
118
- attributes: {
119
- "queue.type": "mem",
120
- "queue.name": queueDrainName,
121
- "queue.sessionId": sessionId
122
- }
123
- }),
124
- Effect.forever
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
- return queue as QueueBase<Evt, DrainEvt>
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
- 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
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
- // .pipe(Effect.andThen(Deferred.await(deferred).pipe(Effect.orDie))),
103
- .pipe(
104
- Effect.andThen(Effect.never)
105
- )
84
+ .pipe(Effect.andThen(Effect.never))
106
85
  },
107
86
 
108
- publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
109
- getRequestContext
110
- .pipe(
111
- Effect.flatMap((requestContext) =>
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)))
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
  })