Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/confirm-channel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@effect-messaging/amqp": minor
---

Add `confirm` option to `AMQPChannelOptions`. When set to `true`, the channel
is opened in publisher-confirm mode (`createConfirmChannel`) and every
`publish` call resolves only after the broker has acknowledged the message,
giving real backpressure and durability guarantees instead of relying on the
local socket buffer. Defaults to `false` so existing callers are unaffected.
Comment on lines +8 to +9
210 changes: 105 additions & 105 deletions packages/amqp/src/AMQPChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,128 +112,128 @@ export interface AMQPChannelOptions {
retryConnectionSchedule?: Schedule.Schedule<unknown, AMQPError.AMQPConnectionError>
retryConsumptionSchedule?: Schedule.Schedule<unknown, AMQPError.AMQPChannelError>
waitChannelTimeout?: Duration.DurationInput
/**
* When `true`, the channel is opened in publisher-confirm mode
* (`connection.createConfirmChannel`). Every `publish` call returns only
* after the broker has acknowledged the message, providing real
* backpressure and durability guarantees. Defaults to `false`.
*
* @since 0.7.0
*/
confirm?: boolean
}

/**
* @category constructors
* @since 0.1.0
*/
export const make = (options: AMQPChannelOptions = {}): Effect.Effect<
export const make = (
options: AMQPChannelOptions = {}
): Effect.Effect<
AMQPChannel,
AMQPError.AMQPChannelError | AMQPError.AMQPConnectionError,
Scope.Scope | AMQPConnection.AMQPConnection
> =>
Effect.gen(
function*() {
const internalChannel = yield* internal.InternalAMQPChannel
const provideInternal = Effect.provideService(internal.InternalAMQPChannel, internalChannel)
Effect.gen(function*() {
const internalChannel = yield* internal.InternalAMQPChannel
const provideInternal = Effect.provideService(internal.InternalAMQPChannel, internalChannel)

const channel = yield* Effect.acquireRelease(
Effect.gen(function*() {
yield* internal.initiateChannel
const connection = yield* AMQPConnection.AMQPConnection
const channel = yield* Effect.acquireRelease(
Effect.gen(function*() {
yield* internal.initiateChannel
const connection = yield* AMQPConnection.AMQPConnection

return {
[TypeId]: TypeId as TypeId,
connection,
consume: (queueName: string, options?: { readonly prefetch?: number }) =>
internal.consume(queueName, options).pipe(provideInternal),
ack: (...params: Parameters<Channel["ack"]>) =>
internal.wrapChannelMethod("ack", async (channel) => channel.ack(...params)).pipe(provideInternal),
ackAll: (...params: Parameters<Channel["ackAll"]>) =>
internal.wrapChannelMethod("ackAll", async (channel) => channel.ackAll(...params)).pipe(provideInternal),
nack: (...params: Parameters<Channel["nack"]>) =>
internal.wrapChannelMethod("nack", async (channel) => channel.nack(...params)).pipe(provideInternal),
nackAll: (...params: Parameters<Channel["nackAll"]>) =>
internal.wrapChannelMethod("nackAll", async (channel) => channel.nackAll(...params)).pipe(
provideInternal
),
reject: (...params: Parameters<Channel["reject"]>) =>
internal.wrapChannelMethod("reject", async (channel) => channel.reject(...params)).pipe(provideInternal),
publish: (...params: Parameters<Channel["publish"]>) => internal.publish(...params).pipe(provideInternal),
sendToQueue: (...params: Parameters<Channel["sendToQueue"]>) =>
internal.wrapChannelMethod("sendToQueue", async (channel) => channel.sendToQueue(...params)).pipe(
provideInternal
),
assertQueue: (...params: Parameters<Channel["assertQueue"]>) =>
internal.wrapChannelMethod("assertQueue", async (channel) => channel.assertQueue(...params)).pipe(
provideInternal
),
checkQueue: (...params: Parameters<Channel["checkQueue"]>) =>
internal.wrapChannelMethod("checkQueue", async (channel) => channel.checkQueue(...params)).pipe(
provideInternal
),
deleteQueue: (...params: Parameters<Channel["deleteQueue"]>) =>
internal.wrapChannelMethod("deleteQueue", async (channel) => channel.deleteQueue(...params)).pipe(
provideInternal
),
purgeQueue: (...params: Parameters<Channel["purgeQueue"]>) =>
internal.wrapChannelMethod("purgeQueue", async (channel) => channel.purgeQueue(...params)).pipe(
provideInternal
),
bindQueue: (...params: Parameters<Channel["bindQueue"]>) =>
internal.wrapChannelMethod("bindQueue", async (channel) => channel.bindQueue(...params)).pipe(
provideInternal
),
unbindQueue: (...params: Parameters<Channel["unbindQueue"]>) =>
internal.wrapChannelMethod("unbindQueue", async (channel) => channel.unbindQueue(...params)).pipe(
provideInternal
),
assertExchange: (...params: Parameters<Channel["assertExchange"]>) =>
internal.wrapChannelMethod(
"assertExchange",
async (channel) => channel.assertExchange(...params)
).pipe(provideInternal),
checkExchange: (...params: Parameters<Channel["checkExchange"]>) =>
internal.wrapChannelMethod(
"checkExchange",
async (channel) => channel.checkExchange(...params)
).pipe(provideInternal),
deleteExchange: (...params: Parameters<Channel["deleteExchange"]>) =>
internal.wrapChannelMethod(
"deleteExchange",
async (channel) => channel.deleteExchange(...params)
).pipe(provideInternal),
bindExchange: (...params: Parameters<Channel["bindExchange"]>) =>
internal.wrapChannelMethod(
"bindExchange",
async (channel) => channel.bindExchange(...params)
).pipe(provideInternal),
unbindExchange: (...params: Parameters<Channel["unbindExchange"]>) =>
internal.wrapChannelMethod(
"unbindExchange",
async (channel) => channel.unbindExchange(...params)
).pipe(provideInternal),
cancel: (...params: Parameters<Channel["cancel"]>) =>
internal.wrapChannelMethod("cancel", async (channel) => channel.cancel(...params)).pipe(provideInternal),
get: (...params: Parameters<Channel["get"]>) =>
internal.wrapChannelMethod("get", async (channel) => channel.get(...params)).pipe(provideInternal),
prefetch: (...params: Parameters<Channel["prefetch"]>) =>
internal.wrapChannelMethod("prefetch", async (channel) => channel.prefetch(...params)).pipe(
provideInternal
),
recover: (...params: Parameters<Channel["recover"]>) =>
internal.wrapChannelMethod("recover", async (channel) => channel.recover(...params)).pipe(
provideInternal
),
close: (opts: internal.CloseChannelOptions = {}) => internal.closeChannel(opts).pipe(provideInternal)
}
}),
(channel) => channel.close()
)
yield* Effect.forkScoped(internal.keepChannelAlive)
yield* Effect.forkScoped(internal.monitorChannelErrors)
return channel
}
).pipe(
Effect.provideServiceEffect(internal.InternalAMQPChannel, internal.InternalAMQPChannel.new(options))
)
return {
[TypeId]: TypeId as TypeId,
connection,
consume: (queueName: string, options?: { readonly prefetch?: number }) =>
internal.consume(queueName, options).pipe(provideInternal),
ack: (...params: Parameters<Channel["ack"]>) =>
internal.wrapChannelMethod("ack", async (channel) => channel.ack(...params)).pipe(provideInternal),
ackAll: (...params: Parameters<Channel["ackAll"]>) =>
internal.wrapChannelMethod("ackAll", async (channel) => channel.ackAll(...params)).pipe(provideInternal),
nack: (...params: Parameters<Channel["nack"]>) =>
internal.wrapChannelMethod("nack", async (channel) => channel.nack(...params)).pipe(provideInternal),
nackAll: (...params: Parameters<Channel["nackAll"]>) =>
internal.wrapChannelMethod("nackAll", async (channel) => channel.nackAll(...params)).pipe(provideInternal),
reject: (...params: Parameters<Channel["reject"]>) =>
internal.wrapChannelMethod("reject", async (channel) => channel.reject(...params)).pipe(provideInternal),
publish: (...params: Parameters<Channel["publish"]>) => internal.publish(...params).pipe(provideInternal),
sendToQueue: (...params: Parameters<Channel["sendToQueue"]>) =>
internal
.wrapChannelMethod("sendToQueue", async (channel) => channel.sendToQueue(...params))
.pipe(provideInternal),
Comment on lines +162 to +165

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He has a point no? Shouldn't we mirror the publish branching in wrapChannelMethod/sendToQueue?

assertQueue: (...params: Parameters<Channel["assertQueue"]>) =>
internal
.wrapChannelMethod("assertQueue", async (channel) => channel.assertQueue(...params))
.pipe(provideInternal),
checkQueue: (...params: Parameters<Channel["checkQueue"]>) =>
internal
.wrapChannelMethod("checkQueue", async (channel) => channel.checkQueue(...params))
.pipe(provideInternal),
deleteQueue: (...params: Parameters<Channel["deleteQueue"]>) =>
internal
.wrapChannelMethod("deleteQueue", async (channel) => channel.deleteQueue(...params))
.pipe(provideInternal),
purgeQueue: (...params: Parameters<Channel["purgeQueue"]>) =>
internal
.wrapChannelMethod("purgeQueue", async (channel) => channel.purgeQueue(...params))
.pipe(provideInternal),
bindQueue: (...params: Parameters<Channel["bindQueue"]>) =>
internal
.wrapChannelMethod("bindQueue", async (channel) => channel.bindQueue(...params))
.pipe(provideInternal),
unbindQueue: (...params: Parameters<Channel["unbindQueue"]>) =>
internal
.wrapChannelMethod("unbindQueue", async (channel) => channel.unbindQueue(...params))
.pipe(provideInternal),
assertExchange: (...params: Parameters<Channel["assertExchange"]>) =>
internal
.wrapChannelMethod("assertExchange", async (channel) => channel.assertExchange(...params))
.pipe(provideInternal),
checkExchange: (...params: Parameters<Channel["checkExchange"]>) =>
internal
.wrapChannelMethod("checkExchange", async (channel) => channel.checkExchange(...params))
.pipe(provideInternal),
deleteExchange: (...params: Parameters<Channel["deleteExchange"]>) =>
internal
.wrapChannelMethod("deleteExchange", async (channel) => channel.deleteExchange(...params))
.pipe(provideInternal),
bindExchange: (...params: Parameters<Channel["bindExchange"]>) =>
internal
.wrapChannelMethod("bindExchange", async (channel) => channel.bindExchange(...params))
.pipe(provideInternal),
unbindExchange: (...params: Parameters<Channel["unbindExchange"]>) =>
internal
.wrapChannelMethod("unbindExchange", async (channel) => channel.unbindExchange(...params))
.pipe(provideInternal),
cancel: (...params: Parameters<Channel["cancel"]>) =>
internal.wrapChannelMethod("cancel", async (channel) => channel.cancel(...params)).pipe(provideInternal),
get: (...params: Parameters<Channel["get"]>) =>
internal.wrapChannelMethod("get", async (channel) => channel.get(...params)).pipe(provideInternal),
prefetch: (...params: Parameters<Channel["prefetch"]>) =>
internal
.wrapChannelMethod("prefetch", async (channel) => channel.prefetch(...params))
.pipe(provideInternal),
recover: (...params: Parameters<Channel["recover"]>) =>
internal.wrapChannelMethod("recover", async (channel) => channel.recover(...params)).pipe(provideInternal),
close: (opts: internal.CloseChannelOptions = {}) => internal.closeChannel(opts).pipe(provideInternal)
}
}),
(channel) => channel.close()
)
yield* Effect.forkScoped(internal.keepChannelAlive)
yield* Effect.forkScoped(internal.monitorChannelErrors)
return channel
}).pipe(Effect.provideServiceEffect(internal.InternalAMQPChannel, internal.InternalAMQPChannel.new(options)))

/**
* @since 0.1.0
* @category Layers
*/
export const layer = (options: AMQPChannelOptions = {}): Layer.Layer<
export const layer = (
options: AMQPChannelOptions = {}
): Layer.Layer<
AMQPChannel,
AMQPError.AMQPChannelError | AMQPError.AMQPConnectionError,
AMQPConnection.AMQPConnection
Expand Down
Loading
Loading