@effect-app/infra 4.0.0-beta.281 → 4.0.0-beta.283

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.
@@ -1,136 +0,0 @@
1
- import type { NonEmptyReadonlyArray } from "effect-app/Array"
2
- import * as Effect from "effect-app/Effect"
3
- import { QueueMeta } from "effect-app/QueueMaker"
4
- import * as S from "effect-app/Schema"
5
- import { getRequestContext, setupRequestContextWithCustomSpan } from "effect-app/setupRequest"
6
- import { pretty } from "effect-app/utils"
7
- import * as Cause from "effect/Cause"
8
- import * as Fiber from "effect/Fiber"
9
- import { flow } from "effect/Function"
10
- import * as Q from "effect/Queue"
11
- import * as Tracer from "effect/Tracer"
12
- import { InfraLogger } from "../logger.ts"
13
- import { MemQueue } from "../memQueue.ts"
14
- import { messagingSpanArgs } from "../otel.ts"
15
- import { reportNonInterruptedFailure, reportNonInterruptedFailureCause } from "./errors.ts"
16
-
17
- export const makeMemQueue = Effect.fnUntraced(function*<
18
- Evt extends { id: S.StringId; _tag: string },
19
- DrainEvt extends { id: S.StringId; _tag: string },
20
- EvtE,
21
- DrainEvtE
22
- >(
23
- queueName: string,
24
- queueDrainName: string,
25
- schema: S.Codec<Evt, EvtE>,
26
- drainSchema: S.Codec<DrainEvt, DrainEvtE>
27
- ) {
28
- const mem = yield* MemQueue
29
- const q = yield* mem.getOrCreateQueue(queueName)
30
- const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
31
-
32
- const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
33
- const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
34
- const encodePublish = S.encodeEffect(wireSchemaJson)
35
- const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
36
- const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
37
-
38
- const parseDrain = flow(S.decodeUnknownEffectConcurrently(drainWJson), Effect.orDie)
39
-
40
- const queue = {
41
- publish: Effect.fn(`publish ${queueName}`, {
42
- kind: "producer",
43
- attributes: {
44
- "messaging.system": "memory",
45
- "messaging.operation.name": "publish",
46
- "messaging.destination.name": queueName
47
- }
48
- })(function*(
49
- ...messages: NonEmptyReadonlyArray<Evt>
50
- ) {
51
- yield* Effect.annotateCurrentSpan({
52
- "messaging.batch.message_count": messages.length,
53
- "messaging.message.types": messages.map((_) => _._tag)
54
- })
55
- const requestContext = yield* getRequestContext
56
- // we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
57
- yield* Effect.forEach(
58
- messages,
59
- (m) =>
60
- encodePublish({ body: m, meta: requestContext }).pipe(
61
- Effect.orDie,
62
- Effect.flatMap((_) => Q.offer(q, _))
63
- ),
64
- { discard: true }
65
- )
66
- }),
67
- drain: <DrainE, DrainR>(
68
- handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
69
- sessionId?: string
70
- ) => {
71
- const silenceAndReportError = reportNonInterruptedFailure({ name: "MemQueue.drain." + queueDrainName })
72
- const reportError = reportNonInterruptedFailureCause({ name: "MemQueue.drain." + queueDrainName })
73
- const processMessage = Effect.fnUntraced(function*(msg: string) {
74
- // we JSON parse, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
75
- const { body, meta } = yield* parseDrain(msg).pipe(Effect.orDie)
76
- let effect = InfraLogger
77
- .logDebug(`[${queueDrainName}] Processing incoming message`)
78
- .pipe(
79
- Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
80
- Effect.andThen(handleEvent(body)),
81
- silenceAndReportError,
82
- (_) => {
83
- const args = messagingSpanArgs({
84
- operation: "process",
85
- system: "memory",
86
- destination: queueDrainName,
87
- messageId: body.id,
88
- conversationId: sessionId,
89
- extra: { "messaging.message.type": body._tag, "messaging.message.body": body }
90
- }, "consumer")
91
- return setupRequestContextWithCustomSpan(
92
- _,
93
- meta,
94
- args.name,
95
- {
96
- captureStackTrace: false,
97
- kind: args.kind,
98
- attributes: args.attributes
99
- }
100
- )
101
- }
102
- )
103
- if (meta.span) {
104
- effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
105
- }
106
- return yield* effect
107
- })
108
- return Effect.fn(`receive ${queueDrainName}`, {
109
- kind: "consumer",
110
- attributes: {
111
- "messaging.system": "memory",
112
- "messaging.operation.name": "receive",
113
- "messaging.destination.name": queueDrainName,
114
- ...(sessionId !== undefined && { "messaging.message.conversation_id": sessionId })
115
- }
116
- })(function*() {
117
- const x = yield* Q.take(qDrain)
118
- const exit = yield* processMessage(x).pipe(
119
- Effect.uninterruptible,
120
- Effect.forkChild,
121
- Effect.flatMap(Fiber.join)
122
- )
123
- if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) {
124
- // normally a failed item would be returned to the queue and retried up to X times.
125
- yield* Q.offer(qDrain, x).pipe(
126
- // TODO: retry count tracking and max retries.
127
- Effect.delay("5 seconds"),
128
- Effect.tapCause(reportError),
129
- Effect.forkDetach
130
- )
131
- }
132
- }, (effect) => effect.pipe(silenceAndReportError, Effect.forever))()
133
- }
134
- }
135
- return queue
136
- })
@@ -1,125 +0,0 @@
1
- import type { NonEmptyReadonlyArray } from "effect-app/Array"
2
- import * as Effect from "effect-app/Effect"
3
- import { QueueMeta } from "effect-app/QueueMaker"
4
- import * as S from "effect-app/Schema"
5
- import type { StringId } from "effect-app/Schema"
6
- import { getRequestContext, setupRequestContextWithCustomSpan } from "effect-app/setupRequest"
7
- import { pretty } from "effect-app/utils"
8
- import * as Cause from "effect/Cause"
9
- import { flow } from "effect/Function"
10
- import * as Tracer from "effect/Tracer"
11
- import { InfraLogger } from "../logger.ts"
12
- import { messagingSpanArgs } from "../otel.ts"
13
- import { Receiver, Sender } from "../ServiceBus.ts"
14
- import { reportNonInterruptedFailure, reportNonInterruptedFailureCause, reportQueueError } from "./errors.ts"
15
-
16
- export function makeServiceBusQueue<
17
- Evt extends { id: StringId; _tag: string },
18
- DrainEvt extends { id: StringId; _tag: string },
19
- EvtE,
20
- DrainEvtE
21
- >(
22
- schema: S.Codec<Evt, EvtE>,
23
- drainSchema: S.Codec<DrainEvt, DrainEvtE>
24
- ) {
25
- const wireSchema = S.Struct({
26
- body: schema,
27
- meta: QueueMeta
28
- })
29
- const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
30
- const encodePublish = S.encodeEffect(wireSchemaJson)
31
- const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
32
- const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
33
- const parseDrain = flow(S.decodeUnknownEffectConcurrently(drainWJson), Effect.orDie)
34
-
35
- return Effect.gen(function*() {
36
- const sender = yield* Sender
37
- const receiver = yield* Receiver
38
- const silenceAndReportError = reportNonInterruptedFailure({ name: receiver.name })
39
- const reportError = reportNonInterruptedFailureCause({ name: receiver.name })
40
-
41
- // TODO: or do async?
42
- // This will make sure that the host receives the error (MainFiberSet.join), who will then interrupt everything and commence a shutdown and restart of app
43
- // const deferred = yield* Deferred.make<never, ServiceBusError | Error>()
44
-
45
- const queue = {
46
- drain: <DrainE, DrainR>(
47
- handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
48
- sessionId?: string
49
- ) => {
50
- const processMessage = Effect.fnUntraced(function*(messageBody: unknown) {
51
- const { body, meta } = yield* parseDrain(messageBody).pipe(Effect.orDie)
52
- let effect = InfraLogger
53
- .logDebug(`[${receiver.name}] Processing incoming message`)
54
- .pipe(
55
- Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
56
- Effect.andThen(handleEvent(body)),
57
- Effect.orDie,
58
- // we silenceAndReportError here, so that the error is reported, and moves into the Exit.
59
- silenceAndReportError,
60
- (_) => {
61
- const args = messagingSpanArgs({
62
- operation: "process",
63
- system: "servicebus",
64
- destination: receiver.name,
65
- messageId: body.id,
66
- conversationId: sessionId,
67
- extra: { "messaging.message.type": body._tag, "messaging.message.body": body }
68
- }, "consumer")
69
- return setupRequestContextWithCustomSpan(
70
- _,
71
- meta,
72
- args.name,
73
- {
74
- captureStackTrace: false,
75
- kind: args.kind,
76
- attributes: args.attributes
77
- }
78
- )
79
- }
80
- )
81
- if (meta.span) {
82
- effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
83
- }
84
- // we reportError here, so that we report the error only, and keep flowing
85
- const exit = yield* Effect.tapCause(effect, reportError)
86
- return yield* exit
87
- })
88
-
89
- return receiver
90
- .subscribe({
91
- processMessage: (x) => processMessage(x.body).pipe(Effect.uninterruptible),
92
- processError: (err) => reportQueueError(Cause.fail(err.error))
93
- }, sessionId)
94
- .pipe(Effect.andThen(Effect.never))
95
- },
96
-
97
- publish: Effect.fn(`publish ${sender.name}`, {
98
- kind: "producer",
99
- attributes: {
100
- "messaging.system": "servicebus",
101
- "messaging.operation.name": "publish",
102
- "messaging.destination.name": sender.name
103
- }
104
- })(function*(...messages: NonEmptyReadonlyArray<Evt>) {
105
- yield* Effect.annotateCurrentSpan({
106
- "messaging.batch.message_count": messages.length,
107
- "messaging.message.types": messages.map((_) => _._tag)
108
- })
109
- const requestContext = yield* getRequestContext
110
- const msgs = yield* Effect.forEach(messages, (m) =>
111
- encodePublish({ body: m, meta: requestContext }).pipe(
112
- Effect.orDie,
113
- Effect.map((body) => ({
114
- body,
115
- messageId: m.id, /* correllationid: requestId */
116
- contentType: "application/json",
117
- sessionId: "sessionId" in m ? m.sessionId as string : undefined as unknown as string // TODO: optional
118
- }))
119
- ))
120
- yield* sender.sendMessages(msgs)
121
- })
122
- }
123
- return queue
124
- })
125
- }
@@ -1,110 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Context from "effect-app/Context"
3
- import * as Effect from "effect-app/Effect"
4
- import * as Layer from "effect-app/Layer"
5
- import * as Option from "effect-app/Option"
6
- import * as Fiber from "effect/Fiber"
7
- import * as FiberSet from "effect/FiberSet"
8
- import type * as Tracer from "effect/Tracer"
9
- import { InfraLogger } from "./logger.ts"
10
- import { reportRequestError, reportUnknownRequestError } from "./reportError.ts"
11
-
12
- const getRootParentSpan = Effect.gen(function*() {
13
- let span: Tracer.AnySpan | null = yield* Effect.currentSpan.pipe(
14
- Effect.catchTag("NoSuchElementError", () => Effect.succeed(null))
15
- )
16
- if (!span) return span
17
- while (span._tag === "Span" && Option.isSome(span.parent)) {
18
- span = span.parent.value
19
- }
20
- return span
21
- })
22
-
23
- export const setRootParentSpan = <A, E, R>(self: Effect.Effect<A, E, R>) =>
24
- getRootParentSpan.pipe(Effect.andThen((span) => span ? Effect.withParentSpan(self, span) : self))
25
-
26
- const make = Effect.gen(function*() {
27
- const set = yield* FiberSet.make<any, any>()
28
- const add = (...fibers: Fiber.Fiber<any, any>[]) =>
29
- Effect.sync(() => fibers.forEach((_) => FiberSet.addUnsafe(set, _)))
30
- const addAll = (fibers: readonly Fiber.Fiber<any, any>[]) =>
31
- Effect.sync(() => fibers.forEach((_) => FiberSet.addUnsafe(set, _)))
32
- const join = FiberSet.size(set).pipe(
33
- Effect.andThen((count) => InfraLogger.logInfo(`Joining ${count} current fibers on the RequestFiberSet`)),
34
- Effect.andThen(FiberSet.join(set))
35
- )
36
- const run = FiberSet.run(set)
37
- const register = <A, E, R>(self: Effect.Effect<A, E, R>) =>
38
- self.pipe(Effect.forkChild, Effect.tap(add), Effect.andThen(Fiber.join))
39
-
40
- // const waitUntilEmpty = Effect.gen(function*() {
41
- // const currentSize = yield* FiberSet.size(set)
42
- // if (currentSize === 0) {
43
- // return
44
- // }
45
- // yield* Effect.logInfo("Waiting RequestFiberSet to be empty: " + currentSize)
46
- // while ((yield* FiberSet.size(set)) > 0) yield* Effect.sleep("250 millis")
47
- // yield* Effect.logDebug("RequestFiberSet is empty")
48
- // })
49
- // TODO: loop and interrupt all fibers in the set continuously?
50
- const interrupt = Fiber.interruptAll(set)
51
-
52
- /**
53
- * Forks the effect into a new fiber attached to the RequestFiberSet scope. Because the
54
- * new fiber isn't attached to the parent, when the fiber executing the
55
- * returned effect terminates, the forked fiber will continue running.
56
- * The fiber will be interrupted when the RequestFiberSet scope is closed.
57
- *
58
- * Reports errors.
59
- */
60
- function forkDaemonReport<R, E, A>(self: Effect.Effect<A, E, R>) {
61
- return self.pipe(
62
- reportRequestError,
63
- Effect.uninterruptible,
64
- run
65
- )
66
- }
67
-
68
- /**
69
- * Forks the effect into a new fiber attached to the RequestFiberSet scope. Because the
70
- * new fiber isn't attached to the parent, when the fiber executing the
71
- * returned effect terminates, the forked fiber will continue running.
72
- * The fiber will be interrupted when the RequestFiberSet scope is closed.
73
- *
74
- * Reports unexpected errors.
75
- */
76
- function forkDaemonReportUnexpected<R, E, A>(self: Effect.Effect<A, E, R>) {
77
- return self
78
- .pipe(
79
- reportUnknownRequestError,
80
- Effect.uninterruptible,
81
- run
82
- )
83
- }
84
-
85
- return {
86
- interrupt,
87
- join,
88
- run,
89
- add,
90
- addAll,
91
- register,
92
- forkDaemonReport,
93
- forkDaemonReportUnexpected
94
- }
95
- })
96
-
97
- /**
98
- * Whenever you fork a fiber for a Request, and you want to prevent dependent services to close prematurely on interruption,
99
- * like the ServiceBus Sender, you should register these fibers in this FiberSet.
100
- */
101
- export class RequestFiberSet extends Context.Service<RequestFiberSet>()("RequestFiberSet", { make }) {
102
- static readonly Live = Layer.effect(this, this.make)
103
- static readonly register = <A, E, R>(self: Effect.Effect<A, E, R>) =>
104
- this.pipe(Effect.andThen((_) => _.register(self)))
105
- static readonly run = <A, E, R>(self: Effect.Effect<A, E, R>) => this.pipe(Effect.andThen((_) => _.run(self)))
106
- static readonly forkDaemonReport = <R, E, A>(self: Effect.Effect<A, E, R>) =>
107
- this.pipe(Effect.andThen((_) => _.forkDaemonReport(self)))
108
- static readonly forkDaemonReportUnexpected = <R, E, A>(self: Effect.Effect<A, E, R>) =>
109
- this.pipe(Effect.andThen((_) => _.forkDaemonReportUnexpected(self)))
110
- }
package/src/ServiceBus.ts DELETED
@@ -1,219 +0,0 @@
1
- /* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
2
- import { type OperationOptionsBase, type ProcessErrorArgs, ServiceBusClient, type ServiceBusMessage, type ServiceBusMessageBatch, type ServiceBusReceivedMessage, type ServiceBusReceiver } from "@azure/service-bus"
3
- import * as Context from "effect-app/Context"
4
- import * as Effect from "effect-app/Effect"
5
- import * as Layer from "effect-app/Layer"
6
- import * as Cause from "effect/Cause"
7
- import * as Exit from "effect/Exit"
8
- import * as FiberSet from "effect/FiberSet"
9
- import type * as Scope from "effect/Scope"
10
- import { InfraLogger } from "./logger.ts"
11
-
12
- const logged = (name: string) => <A, E, R>(self: Effect.Effect<A, E, R>) =>
13
- Effect.logInfo(name).pipe(
14
- Effect.andThen(self),
15
- Effect.tap(Effect.logInfo(name + " done")),
16
- Effect.withLogSpan(name)
17
- )
18
-
19
- function makeClient(url: string) {
20
- return Effect.acquireRelease(
21
- Effect.sync(() => new ServiceBusClient(url)).pipe(logged("ServiceBus.client.create")),
22
- (client) => Effect.promise(() => client.close()).pipe(logged("ServiceBus.client.close"))
23
- )
24
- }
25
-
26
- export class ServiceBusClientTag
27
- extends Context.Opaque<ServiceBusClientTag, ServiceBusClient>()("@services/Client", { make: makeClient })
28
- {
29
- static readonly layer = (url: string) => this.toLayer(this.make(url))
30
- }
31
-
32
- const makeSender_ = Effect.fnUntraced(function*(queueName: string) {
33
- const serviceBusClient = yield* ServiceBusClientTag
34
-
35
- return yield* Effect.acquireRelease(
36
- Effect.sync(() => serviceBusClient.createSender(queueName)).pipe(
37
- logged(`ServiceBus.sender.create ${queueName}`)
38
- ),
39
- (sender) => Effect.promise(() => sender.close()).pipe(logged(`ServiceBus.sender.close ${queueName}`))
40
- )
41
- })
42
-
43
- const makeSender = Effect.fnUntraced(function*(name: string) {
44
- const sender = yield* makeSender_(name)
45
- const sendMessages = Effect.fnUntraced(function*(
46
- messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
47
- options?: Omit<OperationOptionsBase, "abortSignal">
48
- ) {
49
- return yield* Effect.promise((abortSignal) => sender.sendMessages(messages, { ...options, abortSignal }))
50
- })
51
-
52
- return { name, sendMessages }
53
- })
54
-
55
- export class Sender extends Context.Opaque<Sender, {
56
- name: string
57
- sendMessages: (
58
- messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
59
- options?: Omit<OperationOptionsBase, "abortSignal">
60
- ) => Effect.Effect<void>
61
- }>()("Sender", { make: makeSender }) {
62
- static readonly layer = (name: string) => this.toLayer(this.make(name))
63
- }
64
-
65
- export const SenderTag = <Id>() => <Key extends string>(queueName: Key) => {
66
- const tag = Context.Service<Id, Sender>(`ServiceBus.Sender.${queueName}`)
67
-
68
- return Object.assign(tag, {
69
- layer: Layer.effect(
70
- tag,
71
- Sender.make(queueName).pipe(Effect.map(Sender.of))
72
- )
73
- })
74
- }
75
-
76
- const makeReceiver = Effect.fnUntraced(function*(name: string) {
77
- const serviceBusClient = yield* ServiceBusClientTag
78
-
79
- const makeReceiver = Effect.fnUntraced(
80
- function*(queueName: string, waitTillEmpty: Effect.Effect<void>, sessionId?: string) {
81
- const annotate = sessionId !== undefined
82
- ? Effect.annotateLogs({ "messaging.session.id": sessionId })
83
- : <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => self
84
- return yield* Effect.acquireRelease(
85
- (sessionId
86
- ? Effect.promise(() => serviceBusClient.acceptSession(queueName, sessionId))
87
- : Effect.sync(() => serviceBusClient.createReceiver(queueName)))
88
- .pipe(logged(`ServiceBus.receiver.create ${queueName}`), annotate),
89
- (r) =>
90
- waitTillEmpty.pipe(
91
- logged(`ServiceBus.receiver.waitTillEmpty ${queueName}`),
92
- Effect.andThen(
93
- Effect.promise(() => r.close()).pipe(
94
- logged(`ServiceBus.receiver.close ${queueName}`)
95
- )
96
- ),
97
- logged(`ServiceBus.receiver.release ${queueName}`),
98
- annotate
99
- )
100
- )
101
- }
102
- )
103
-
104
- const make = (waitTillEmpty: Effect.Effect<void>) => makeReceiver(name, waitTillEmpty)
105
-
106
- const makeSession = (sessionId: string, waitTillEmpty: Effect.Effect<void>) =>
107
- makeReceiver(name, waitTillEmpty, sessionId)
108
-
109
- return {
110
- name,
111
- make,
112
- makeSession,
113
- subscribe: Effect.fnUntraced(function*<RMsg, RErr>(hndlr: MessageHandlers<RMsg, RErr>, sessionId?: string) {
114
- const fs = yield* FiberSet.make()
115
- const fr = yield* FiberSet.runtime(fs)<RMsg | RErr>()
116
- const wait = Effect
117
- .gen(function*() {
118
- if ((yield* FiberSet.size(fs)) > 0) {
119
- yield* InfraLogger.logDebug("Waiting ServiceBusFiberSet to be empty: " + (yield* FiberSet.size(fs)))
120
- }
121
- while ((yield* FiberSet.size(fs)) > 0) yield* Effect.sleep("250 millis")
122
- })
123
- const r = yield* sessionId
124
- ? makeSession(
125
- sessionId,
126
- wait
127
- )
128
- : make(wait)
129
-
130
- const runEffect = <E>(effect: Effect.Effect<void, E, RMsg | RErr>) =>
131
- new Promise<void>((resolve, reject) =>
132
- fr(effect)
133
- .addObserver((exit) => {
134
- if (Exit.isSuccess(exit)) {
135
- resolve(exit.value)
136
- } else {
137
- // disable @typescript-eslint/prefer-promise-reject-errors
138
- reject(Cause.pretty(exit.cause))
139
- }
140
- })
141
- )
142
-
143
- const annotate = sessionId !== undefined
144
- ? Effect.annotateLogs({ "messaging.session.id": sessionId })
145
- : <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => self
146
- yield* Effect.acquireRelease(
147
- Effect
148
- .sync(() => {
149
- const s = r
150
- .subscribe({
151
- processError: (err) =>
152
- runEffect(
153
- hndlr
154
- .processError(err)
155
- .pipe(
156
- Effect.catchCause((cause) => Effect.logError("ServiceBus Error", cause)),
157
- annotate
158
- )
159
- ),
160
- processMessage: (msg) => runEffect(hndlr.processMessage(msg).pipe(annotate))
161
- // DO NOT CATCH ERRORS here as they should return to the queue!
162
- })
163
- return { close: Effect.promise(() => s.close()) }
164
- })
165
- .pipe(logged("ServiceBus.subscription.create"), annotate),
166
- (subscription) =>
167
- subscription.close.pipe(
168
- logged("ServiceBus.subscription.close"),
169
- annotate
170
- )
171
- ) as Effect.Effect<void, never, Scope.Scope> // wth is going on here
172
- })
173
- }
174
- })
175
-
176
- export class Receiver extends Context.Opaque<Receiver, {
177
- name: string
178
- make: (waitTillEmpty: Effect.Effect<void>) => Effect.Effect<ServiceBusReceiver, never, Scope.Scope>
179
- makeSession: (
180
- sessionId: string,
181
- waitTillEmpty: Effect.Effect<void>
182
- ) => Effect.Effect<ServiceBusReceiver, never, Scope.Scope>
183
- subscribe<RMsg, RErr>(
184
- hndlr: MessageHandlers<RMsg, RErr>,
185
- sessionId?: string
186
- ): Effect.Effect<void, never, Scope.Scope | RMsg | RErr>
187
- }>()("Receiver") {
188
- static readonly make = makeReceiver
189
- static readonly layer = (name: string) => this.toLayer(makeReceiver(name))
190
- }
191
-
192
- export const ReceiverTag = <Id>() => <Key extends string>(queueName: Key) => {
193
- const tag = Context.Service<Id, Receiver>(`ServiceBus.Receiver.${queueName}`)
194
-
195
- return Object.assign(tag, {
196
- layer: Layer.effect(
197
- tag,
198
- makeReceiver(queueName).pipe(Effect.map(Receiver.of))
199
- )
200
- })
201
- }
202
-
203
- export const SenderReceiver = (queue: string, queueDrain?: string) =>
204
- Layer.mergeAll(Sender.layer(queue), Receiver.layer(queueDrain ?? queue))
205
-
206
- export interface MessageHandlers<RMsg, RErr> {
207
- /**
208
- * Handler that processes messages from service bus.
209
- *
210
- * @param message - A message received from Service Bus.
211
- */
212
- processMessage(message: ServiceBusReceivedMessage): Effect.Effect<void, never, RMsg>
213
- /**
214
- * Handler that processes errors that occur during receiving.
215
- * @param args - The error and additional context to indicate where
216
- * the error originated.
217
- */
218
- processError(args: ProcessErrorArgs): Effect.Effect<void, never, RErr>
219
- }
package/src/memQueue.ts DELETED
@@ -1,22 +0,0 @@
1
- import * as Context from "effect-app/Context"
2
- import * as Effect from "effect-app/Effect"
3
- import * as Queue from "effect/Queue"
4
-
5
- const make = Effect
6
- .gen(function*() {
7
- const store = yield* Effect.sync(() => new Map<string, Queue.Queue<string>>())
8
-
9
- return {
10
- getOrCreateQueue: Effect.fnUntraced(function*(k: string) {
11
- const q = store.get(k)
12
- if (q) return q
13
- const newQ = yield* Queue.unbounded<string>()
14
- store.set(k, newQ)
15
- return newQ
16
- })
17
- }
18
- })
19
-
20
- export class MemQueue extends Context.Opaque<MemQueue>()("effect-app/MemQueue", { make }) {
21
- static readonly Live = this.toLayer(this.make)
22
- }