@dotpress/events 0.0.0-stage → 2.0.0-alpha.1

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.
@@ -0,0 +1,99 @@
1
+ import { ServiceBusClient } from '@azure/service-bus';
2
+ import { Transport, Worker, Source } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Connection to a namespace: a client, or a connection string. */
6
+ type ServiceBusConnection = {
7
+ /** A client created by you (Microsoft Entra ID credentials…). */
8
+ client: ServiceBusClient;
9
+ connectionString?: never;
10
+ } | {
11
+ /** Connection string of the namespace, or of the queue. */
12
+ connectionString: string;
13
+ client?: never;
14
+ };
15
+ /** Options of `createServiceBusTransport()`. */
16
+ type ServiceBusTransportOptions = ServiceBusConnection & {
17
+ /** Name of the queue. */
18
+ queue: string;
19
+ };
20
+ /** Options of `createServiceBusSource()`. */
21
+ type ServiceBusSourceOptions = ServiceBusConnection & {
22
+ /** Name of the queue. */
23
+ queue: string;
24
+ /** Messages handled at the same time. Defaults to 1. */
25
+ maxConcurrentCalls?: number | undefined;
26
+ /**
27
+ * How long the lock of a message in progress is renewed, in
28
+ * milliseconds. Defaults to 5 minutes (the default of the SDK).
29
+ */
30
+ maxAutoLockRenewalDurationInMs?: number | undefined;
31
+ /**
32
+ * On stop, how long to wait for the messages in progress, in
33
+ * milliseconds. Defaults to 30 seconds.
34
+ */
35
+ closeTimeout?: number | undefined;
36
+ };
37
+ /** Options of `createServiceBusFunctionHandler()`. */
38
+ interface ServiceBusFunctionHandlerOptions {
39
+ /**
40
+ * Queue where messages that can never be handled are sent at once,
41
+ * instead of failing until `maxDeliveryCount`.
42
+ */
43
+ deadLetter?: ServiceBusTransportOptions | undefined;
44
+ }
45
+ /** Service Bus transport: close it to close the client it created. */
46
+ interface ServiceBusTransport extends Transport {
47
+ /** Closes the sender, and the client if the transport created it. */
48
+ close(): Promise<void>;
49
+ }
50
+ /** Invocation context of Azure Functions (the fields used here). */
51
+ interface ServiceBusInvocationContext {
52
+ triggerMetadata?: Record<string, unknown> | undefined;
53
+ }
54
+ /**
55
+ * Creates a transport that sends messages to an Azure Service Bus queue
56
+ * (`@azure/service-bus`, peer dependency; Node.js). The message id is the
57
+ * id of the envelope: with duplicate detection on the queue, emitting again
58
+ * is ignored.
59
+ *
60
+ * ```ts
61
+ * const transport = createServiceBusTransport({
62
+ * connectionString: process.env.SERVICE_BUS_CONNECTION!,
63
+ * queue: 'events',
64
+ * })
65
+ * ```
66
+ */
67
+ declare const createServiceBusTransport: (options: ServiceBusTransportOptions) => ServiceBusTransport;
68
+ /**
69
+ * Creates a source that receives the messages of a queue, for
70
+ * `startWorker()` and `dotpress-events work` (Node.js). Handled messages
71
+ * are completed, failed ones abandoned (delivered again until the
72
+ * `maxDeliveryCount` of the queue), and messages that can never be handled
73
+ * moved to the dead-letter queue at once.
74
+ *
75
+ * ```ts
76
+ * export default {
77
+ * worker: createWorker({ listeners, maxAttempts: 10 }),
78
+ * source: createServiceBusSource({ connectionString, queue: 'events' }),
79
+ * }
80
+ * ```
81
+ */
82
+ declare const createServiceBusSource: (options: ServiceBusSourceOptions) => Source;
83
+ /**
84
+ * Creates the handler of an Azure Functions Service Bus trigger (one
85
+ * message per invocation). A failure throws, so that the message is
86
+ * delivered again until the `maxDeliveryCount` of the queue.
87
+ *
88
+ * ```ts
89
+ * app.serviceBusQueue('events', {
90
+ * connection: 'SERVICE_BUS_CONNECTION',
91
+ * queueName: 'events',
92
+ * handler: createServiceBusFunctionHandler(createWorker({ listeners, maxAttempts: 10 })),
93
+ * })
94
+ * ```
95
+ */
96
+ declare const createServiceBusFunctionHandler: (worker: Worker, options?: ServiceBusFunctionHandlerOptions) => ((message: unknown, context?: ServiceBusInvocationContext) => Promise<void>);
97
+
98
+ export { createServiceBusFunctionHandler, createServiceBusSource, createServiceBusTransport };
99
+ export type { ServiceBusConnection, ServiceBusFunctionHandlerOptions, ServiceBusInvocationContext, ServiceBusSourceOptions, ServiceBusTransport, ServiceBusTransportOptions };
@@ -0,0 +1,320 @@
1
+ import {
2
+ PartialSendError
3
+ } from "./chunk-NCQ4ZTNB.js";
4
+
5
+ // src/transports/azure-service-bus.ts
6
+ var MAX_DESCRIPTION = 1024;
7
+ var MAX_BATCH_BYTES = 1048576;
8
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9
+ var checkOptions = (label, options, allowed) => {
10
+ if (!isRecord(options)) {
11
+ throw new TypeError(`${label}: expected an object of options.`);
12
+ }
13
+ for (const key of Object.keys(options)) {
14
+ if (!allowed.includes(key)) {
15
+ throw new TypeError(`${label}: unknown option "${key}".`);
16
+ }
17
+ }
18
+ return options;
19
+ };
20
+ var describe = (error) => error instanceof Error ? error.message : String(error);
21
+ var connectionOf = (label, values) => {
22
+ const { client, connectionString, queue } = values;
23
+ if (client === void 0 === (connectionString === void 0)) {
24
+ throw new TypeError(`${label}: set client or connectionString.`);
25
+ }
26
+ if (client !== void 0 && typeof client?.createSender !== "function") {
27
+ throw new TypeError(`${label}: client must be a ServiceBusClient.`);
28
+ }
29
+ if (connectionString !== void 0 && (typeof connectionString !== "string" || connectionString === "")) {
30
+ throw new TypeError(
31
+ `${label}: connectionString must be a non-empty string.`
32
+ );
33
+ }
34
+ if (typeof queue !== "string" || queue === "") {
35
+ throw new TypeError(`${label}: queue must be a non-empty string.`);
36
+ }
37
+ let created;
38
+ return {
39
+ queue,
40
+ owned: client === void 0,
41
+ client: () => client !== void 0 ? Promise.resolve(client) : created ??= import("@azure/service-bus").then(
42
+ ({ ServiceBusClient: Client }) => new Client(connectionString),
43
+ (error) => {
44
+ created = void 0;
45
+ throw new Error(
46
+ "Install @azure/service-bus to use Azure Service Bus.",
47
+ { cause: error }
48
+ );
49
+ }
50
+ ),
51
+ /** Closes the client, if created here. */
52
+ close: async () => {
53
+ await (await created)?.close();
54
+ }
55
+ };
56
+ };
57
+ var senderOf = (label, values) => {
58
+ const connection = connectionOf(label, values);
59
+ let sender;
60
+ const senderOnce = () => sender ??= connection.client().then((client) => client.createSender(connection.queue)).catch((error) => {
61
+ sender = void 0;
62
+ throw error;
63
+ });
64
+ const send = async (messages) => {
65
+ const failed = [];
66
+ const attempt = async (ids2, sending) => {
67
+ try {
68
+ await sending();
69
+ } catch (error) {
70
+ for (const id of ids2) failed.push({ id, error });
71
+ }
72
+ };
73
+ const output = await senderOnce();
74
+ let batch = await output.createMessageBatch();
75
+ const maxMessageSize = batch.maxSizeInBytes;
76
+ const options = maxMessageSize > MAX_BATCH_BYTES ? { maxSizeInBytes: MAX_BATCH_BYTES } : void 0;
77
+ if (options) batch = await output.createMessageBatch(options);
78
+ let ids = [];
79
+ for (const { id, message } of messages) {
80
+ if (batch.tryAddMessage(message)) {
81
+ ids.push(id);
82
+ continue;
83
+ }
84
+ if (ids.length > 0) {
85
+ const full = batch;
86
+ await attempt(ids, () => output.sendMessages(full));
87
+ batch = await output.createMessageBatch(options);
88
+ ids = [];
89
+ if (batch.tryAddMessage(message)) {
90
+ ids.push(id);
91
+ continue;
92
+ }
93
+ }
94
+ if (maxMessageSize > batch.maxSizeInBytes) {
95
+ await attempt([id], () => output.sendMessages(message));
96
+ } else {
97
+ failed.push({
98
+ id,
99
+ error: new RangeError(
100
+ `Service Bus accepts messages of at most ${maxMessageSize} bytes on this queue (256 KB in the Standard tier).`
101
+ )
102
+ });
103
+ }
104
+ }
105
+ if (ids.length > 0) {
106
+ const last = batch;
107
+ await attempt(ids, () => output.sendMessages(last));
108
+ }
109
+ return failed;
110
+ };
111
+ return {
112
+ send,
113
+ close: async () => {
114
+ await (await sender)?.close();
115
+ await connection.close();
116
+ }
117
+ };
118
+ };
119
+ var messageOf = (envelope, delay) => ({
120
+ body: envelope,
121
+ contentType: "application/json",
122
+ messageId: envelope.id,
123
+ subject: envelope.event,
124
+ applicationProperties: { event: envelope.event, listener: envelope.listener },
125
+ ...delay ? { scheduledEnqueueTimeUtc: new Date(Date.now() + delay * 1e3) } : {}
126
+ });
127
+ var createServiceBusTransport = (options) => {
128
+ const label = "createServiceBusTransport()";
129
+ const values = checkOptions(label, options, [
130
+ "client",
131
+ "connectionString",
132
+ "queue"
133
+ ]);
134
+ const sender = senderOf(label, values);
135
+ return {
136
+ kind: "azure-service-bus",
137
+ send: async (envelopes, { delay }) => {
138
+ const failed = await sender.send(
139
+ envelopes.map((envelope) => ({
140
+ id: envelope.id,
141
+ message: messageOf(envelope, delay)
142
+ }))
143
+ );
144
+ if (failed.length > 0) throw new PartialSendError(failed);
145
+ },
146
+ close: sender.close
147
+ };
148
+ };
149
+ var deadLetterProperties = (result) => ({
150
+ deadLetterReason: result.reason,
151
+ deadLetterErrorDescription: describe(result.error).slice(0, MAX_DESCRIPTION)
152
+ });
153
+ var createServiceBusSource = (options) => {
154
+ const label = "createServiceBusSource()";
155
+ const values = checkOptions(label, options, [
156
+ "client",
157
+ "connectionString",
158
+ "queue",
159
+ "maxConcurrentCalls",
160
+ "maxAutoLockRenewalDurationInMs",
161
+ "closeTimeout"
162
+ ]);
163
+ const connection = connectionOf(label, values);
164
+ const {
165
+ maxConcurrentCalls = 1,
166
+ maxAutoLockRenewalDurationInMs,
167
+ closeTimeout = 3e4
168
+ } = values;
169
+ if (!Number.isInteger(maxConcurrentCalls) || maxConcurrentCalls < 1) {
170
+ throw new TypeError(
171
+ `${label}: maxConcurrentCalls must be a positive integer.`
172
+ );
173
+ }
174
+ if (maxAutoLockRenewalDurationInMs !== void 0 && (!Number.isInteger(maxAutoLockRenewalDurationInMs) || maxAutoLockRenewalDurationInMs < 0)) {
175
+ throw new TypeError(
176
+ `${label}: maxAutoLockRenewalDurationInMs must be a non-negative integer (milliseconds).`
177
+ );
178
+ }
179
+ if (!Number.isInteger(closeTimeout) || closeTimeout < 0) {
180
+ throw new TypeError(
181
+ `${label}: closeTimeout must be a non-negative integer (milliseconds).`
182
+ );
183
+ }
184
+ return {
185
+ kind: "azure-service-bus",
186
+ consume: async (worker, { signal, logger }) => {
187
+ const client = await connection.client();
188
+ const receiver = client.createReceiver(connection.queue, {
189
+ receiveMode: "peekLock",
190
+ ...maxAutoLockRenewalDurationInMs === void 0 ? {} : {
191
+ maxAutoLockRenewalDurationInMs
192
+ }
193
+ });
194
+ const inProgress = /* @__PURE__ */ new Set();
195
+ const settle = async (message) => {
196
+ const result = await worker.handle(message.body, {
197
+ // The SDK counts the deliveries before this one.
198
+ attempt: (message.deliveryCount ?? 0) + 1
199
+ });
200
+ if (result.status === "success") {
201
+ await receiver.completeMessage(message);
202
+ } else if (result.status === "dead") {
203
+ await receiver.deadLetterMessage(
204
+ message,
205
+ deadLetterProperties(result)
206
+ );
207
+ } else {
208
+ await receiver.abandonMessage(message);
209
+ }
210
+ };
211
+ const subscription = receiver.subscribe(
212
+ {
213
+ processMessage: async (message) => {
214
+ const handling = settle(message);
215
+ inProgress.add(handling);
216
+ try {
217
+ await handling;
218
+ } finally {
219
+ inProgress.delete(handling);
220
+ }
221
+ },
222
+ processError: async ({ error, errorSource }) => {
223
+ logger.error(
224
+ {
225
+ queue: connection.queue,
226
+ source: errorSource,
227
+ error: error.message
228
+ },
229
+ "Service Bus receiver error"
230
+ );
231
+ }
232
+ },
233
+ {
234
+ autoCompleteMessages: false,
235
+ maxConcurrentCalls
236
+ }
237
+ );
238
+ await new Promise((resolve) => {
239
+ if (signal.aborted) resolve();
240
+ signal.addEventListener("abort", () => resolve(), { once: true });
241
+ });
242
+ let timer;
243
+ const stopped = await Promise.race([
244
+ (async () => {
245
+ await subscription.close();
246
+ await Promise.allSettled([...inProgress]);
247
+ return true;
248
+ })(),
249
+ new Promise((resolve) => {
250
+ timer = setTimeout(() => resolve(false), closeTimeout);
251
+ })
252
+ ]);
253
+ clearTimeout(timer);
254
+ if (!stopped) {
255
+ logger.warn(
256
+ { queue: connection.queue, closeTimeout },
257
+ "Service Bus receiver not closed in time: stopped without waiting"
258
+ );
259
+ }
260
+ await receiver.close().catch(() => void 0);
261
+ await connection.close().catch(() => void 0);
262
+ }
263
+ };
264
+ };
265
+ var createServiceBusFunctionHandler = (worker, options = {}) => {
266
+ const label = "createServiceBusFunctionHandler()";
267
+ if (typeof worker?.handle !== "function") {
268
+ throw new TypeError(
269
+ `${label}: expected a worker created by createWorker().`
270
+ );
271
+ }
272
+ const { deadLetter } = checkOptions(label, options, ["deadLetter"]);
273
+ const deadSender = deadLetter === void 0 ? void 0 : senderOf(
274
+ `${label}: deadLetter`,
275
+ checkOptions(`${label}: deadLetter`, deadLetter, [
276
+ "client",
277
+ "connectionString",
278
+ "queue"
279
+ ])
280
+ );
281
+ return async (message, context) => {
282
+ if (Array.isArray(message)) {
283
+ throw new TypeError(
284
+ `${label}: expected one message per invocation (cardinality "one").`
285
+ );
286
+ }
287
+ const deliveryCount = context?.triggerMetadata?.deliveryCount;
288
+ const result = await worker.handle(message, {
289
+ attempt: typeof deliveryCount === "number" ? deliveryCount : 1
290
+ });
291
+ if (result.status === "success") return;
292
+ if (result.status === "dead" && deadSender) {
293
+ const { deadLetterReason, deadLetterErrorDescription } = deadLetterProperties(result);
294
+ const failed = await deadSender.send([
295
+ {
296
+ id: "",
297
+ message: {
298
+ body: message,
299
+ contentType: "application/json",
300
+ applicationProperties: {
301
+ reason: deadLetterReason,
302
+ error: deadLetterErrorDescription
303
+ }
304
+ }
305
+ }
306
+ ]);
307
+ if (failed.length === 0) return;
308
+ throw new Error("Cannot send the message to the dead-letter queue.", {
309
+ cause: failed[0]?.error
310
+ });
311
+ }
312
+ throw result.error instanceof Error ? result.error : new Error(describe(result.error));
313
+ };
314
+ };
315
+ export {
316
+ createServiceBusFunctionHandler,
317
+ createServiceBusSource,
318
+ createServiceBusTransport
319
+ };
320
+ //# sourceMappingURL=azure-service-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transports/azure-service-bus.ts"],"sourcesContent":["import type {\n ServiceBusClient,\n ServiceBusMessage,\n ServiceBusReceivedMessage,\n ServiceBusSender,\n} from '@azure/service-bus'\nimport type { Envelope } from '../envelope.js'\nimport { PartialSendError } from '../errors.js'\nimport type { Source, Transport } from '../transport.js'\nimport type { HandleResult, Worker } from '../worker.js'\n\n/** Connection to a namespace: a client, or a connection string. */\nexport type ServiceBusConnection =\n | {\n /** A client created by you (Microsoft Entra ID credentials…). */\n client: ServiceBusClient\n connectionString?: never\n }\n | {\n /** Connection string of the namespace, or of the queue. */\n connectionString: string\n client?: never\n }\n\n/** Options of `createServiceBusTransport()`. */\nexport type ServiceBusTransportOptions = ServiceBusConnection & {\n /** Name of the queue. */\n queue: string\n}\n\n/** Options of `createServiceBusSource()`. */\nexport type ServiceBusSourceOptions = ServiceBusConnection & {\n /** Name of the queue. */\n queue: string\n /** Messages handled at the same time. Defaults to 1. */\n maxConcurrentCalls?: number | undefined\n /**\n * How long the lock of a message in progress is renewed, in\n * milliseconds. Defaults to 5 minutes (the default of the SDK).\n */\n maxAutoLockRenewalDurationInMs?: number | undefined\n /**\n * On stop, how long to wait for the messages in progress, in\n * milliseconds. Defaults to 30 seconds.\n */\n closeTimeout?: number | undefined\n}\n\n/** Options of `createServiceBusFunctionHandler()`. */\nexport interface ServiceBusFunctionHandlerOptions {\n /**\n * Queue where messages that can never be handled are sent at once,\n * instead of failing until `maxDeliveryCount`.\n */\n deadLetter?: ServiceBusTransportOptions | undefined\n}\n\n/** Service Bus transport: close it to close the client it created. */\nexport interface ServiceBusTransport extends Transport {\n /** Closes the sender, and the client if the transport created it. */\n close(): Promise<void>\n}\n\n/** Invocation context of Azure Functions (the fields used here). */\nexport interface ServiceBusInvocationContext {\n triggerMetadata?: Record<string, unknown> | undefined\n}\n\n// Dead-letter descriptions are kept short: they are message properties.\nconst MAX_DESCRIPTION = 1024\n// Batches: 256 KB in the Standard tier, 1 MB in the Premium tier.\nconst MAX_BATCH_BYTES = 1_048_576\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst checkOptions = (\n label: string,\n options: unknown,\n allowed: readonly string[]\n): Record<string, unknown> => {\n if (!isRecord(options)) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.includes(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n return options\n}\n\nconst describe = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\n/** The client of the options, created on first use when not given. */\nconst connectionOf = (label: string, values: Record<string, unknown>) => {\n const { client, connectionString, queue } = values\n if ((client === undefined) === (connectionString === undefined)) {\n throw new TypeError(`${label}: set client or connectionString.`)\n }\n if (\n client !== undefined &&\n typeof (client as ServiceBusClient | null)?.createSender !== 'function'\n ) {\n throw new TypeError(`${label}: client must be a ServiceBusClient.`)\n }\n if (\n connectionString !== undefined &&\n (typeof connectionString !== 'string' || connectionString === '')\n ) {\n throw new TypeError(\n `${label}: connectionString must be a non-empty string.`\n )\n }\n if (typeof queue !== 'string' || queue === '') {\n throw new TypeError(`${label}: queue must be a non-empty string.`)\n }\n\n let created: Promise<ServiceBusClient> | undefined\n return {\n queue,\n owned: client === undefined,\n client: (): Promise<ServiceBusClient> =>\n client !== undefined\n ? Promise.resolve(client as ServiceBusClient)\n : (created ??= import('@azure/service-bus').then(\n ({ ServiceBusClient: Client }) =>\n new Client(connectionString as string),\n (error: unknown) => {\n created = undefined\n throw new Error(\n 'Install @azure/service-bus to use Azure Service Bus.',\n { cause: error }\n )\n }\n )),\n /** Closes the client, if created here. */\n close: async () => {\n await (await created)?.close()\n },\n }\n}\n\n/** Sends envelopes in batches: a sender of the queue, created once. */\nconst senderOf = (label: string, values: Record<string, unknown>) => {\n const connection = connectionOf(label, values)\n let sender: Promise<ServiceBusSender> | undefined\n const senderOnce = () =>\n (sender ??= connection\n .client()\n .then((client) => client.createSender(connection.queue))\n .catch((error: unknown) => {\n sender = undefined\n throw error\n }))\n\n const send = async (\n messages: { id: string; message: ServiceBusMessage }[]\n ) => {\n const failed: { id: string; error: unknown }[] = []\n const attempt = async (ids: string[], sending: () => Promise<void>) => {\n try {\n await sending()\n } catch (error) {\n for (const id of ids) failed.push({ id, error })\n }\n }\n const output = await senderOnce()\n let batch = await output.createMessageBatch()\n // Largest message of the queue: Premium queues accept more than a batch.\n const maxMessageSize = batch.maxSizeInBytes\n const options =\n maxMessageSize > MAX_BATCH_BYTES\n ? { maxSizeInBytes: MAX_BATCH_BYTES }\n : undefined\n if (options) batch = await output.createMessageBatch(options)\n let ids: string[] = []\n\n for (const { id, message } of messages) {\n if (batch.tryAddMessage(message)) {\n ids.push(id)\n continue\n }\n if (ids.length > 0) {\n const full = batch\n await attempt(ids, () => output.sendMessages(full))\n batch = await output.createMessageBatch(options)\n ids = []\n if (batch.tryAddMessage(message)) {\n ids.push(id)\n continue\n }\n }\n if (maxMessageSize > batch.maxSizeInBytes) {\n // Larger than a batch: sent alone.\n await attempt([id], () => output.sendMessages(message))\n } else {\n failed.push({\n id,\n error: new RangeError(\n `Service Bus accepts messages of at most ${maxMessageSize} bytes on this queue (256 KB in the Standard tier).`\n ),\n })\n }\n }\n if (ids.length > 0) {\n const last = batch\n await attempt(ids, () => output.sendMessages(last))\n }\n return failed\n }\n\n return {\n send,\n close: async () => {\n await (await sender)?.close()\n await connection.close()\n },\n }\n}\n\nconst messageOf = (\n envelope: Envelope,\n delay: number | undefined\n): ServiceBusMessage => ({\n body: envelope,\n contentType: 'application/json',\n messageId: envelope.id,\n subject: envelope.event,\n applicationProperties: { event: envelope.event, listener: envelope.listener },\n ...(delay\n ? { scheduledEnqueueTimeUtc: new Date(Date.now() + delay * 1000) }\n : {}),\n})\n\n/**\n * Creates a transport that sends messages to an Azure Service Bus queue\n * (`@azure/service-bus`, peer dependency; Node.js). The message id is the\n * id of the envelope: with duplicate detection on the queue, emitting again\n * is ignored.\n *\n * ```ts\n * const transport = createServiceBusTransport({\n * connectionString: process.env.SERVICE_BUS_CONNECTION!,\n * queue: 'events',\n * })\n * ```\n */\nexport const createServiceBusTransport = (\n options: ServiceBusTransportOptions\n): ServiceBusTransport => {\n const label = 'createServiceBusTransport()'\n const values = checkOptions(label, options, [\n 'client',\n 'connectionString',\n 'queue',\n ])\n const sender = senderOf(label, values)\n\n return {\n kind: 'azure-service-bus',\n send: async (envelopes, { delay }) => {\n const failed = await sender.send(\n envelopes.map((envelope) => ({\n id: envelope.id,\n message: messageOf(envelope, delay),\n }))\n )\n if (failed.length > 0) throw new PartialSendError(failed)\n },\n close: sender.close,\n }\n}\n\nconst deadLetterProperties = (result: HandleResult & { status: 'dead' }) => ({\n deadLetterReason: result.reason,\n deadLetterErrorDescription: describe(result.error).slice(0, MAX_DESCRIPTION),\n})\n\n/**\n * Creates a source that receives the messages of a queue, for\n * `startWorker()` and `dotpress-events work` (Node.js). Handled messages\n * are completed, failed ones abandoned (delivered again until the\n * `maxDeliveryCount` of the queue), and messages that can never be handled\n * moved to the dead-letter queue at once.\n *\n * ```ts\n * export default {\n * worker: createWorker({ listeners, maxAttempts: 10 }),\n * source: createServiceBusSource({ connectionString, queue: 'events' }),\n * }\n * ```\n */\nexport const createServiceBusSource = (\n options: ServiceBusSourceOptions\n): Source => {\n const label = 'createServiceBusSource()'\n const values = checkOptions(label, options, [\n 'client',\n 'connectionString',\n 'queue',\n 'maxConcurrentCalls',\n 'maxAutoLockRenewalDurationInMs',\n 'closeTimeout',\n ])\n const connection = connectionOf(label, values)\n const {\n maxConcurrentCalls = 1,\n maxAutoLockRenewalDurationInMs,\n closeTimeout = 30_000,\n } = values\n if (\n !Number.isInteger(maxConcurrentCalls) ||\n (maxConcurrentCalls as number) < 1\n ) {\n throw new TypeError(\n `${label}: maxConcurrentCalls must be a positive integer.`\n )\n }\n if (\n maxAutoLockRenewalDurationInMs !== undefined &&\n (!Number.isInteger(maxAutoLockRenewalDurationInMs) ||\n (maxAutoLockRenewalDurationInMs as number) < 0)\n ) {\n throw new TypeError(\n `${label}: maxAutoLockRenewalDurationInMs must be a non-negative integer (milliseconds).`\n )\n }\n if (!Number.isInteger(closeTimeout) || (closeTimeout as number) < 0) {\n throw new TypeError(\n `${label}: closeTimeout must be a non-negative integer (milliseconds).`\n )\n }\n\n return {\n kind: 'azure-service-bus',\n consume: async (worker, { signal, logger }) => {\n const client = await connection.client()\n const receiver = client.createReceiver(connection.queue, {\n receiveMode: 'peekLock',\n ...(maxAutoLockRenewalDurationInMs === undefined\n ? {}\n : {\n maxAutoLockRenewalDurationInMs:\n maxAutoLockRenewalDurationInMs as number,\n }),\n })\n const inProgress = new Set<Promise<void>>()\n\n const settle = async (message: ServiceBusReceivedMessage) => {\n const result = await worker.handle(message.body, {\n // The SDK counts the deliveries before this one.\n attempt: (message.deliveryCount ?? 0) + 1,\n })\n if (result.status === 'success') {\n await receiver.completeMessage(message)\n } else if (result.status === 'dead') {\n await receiver.deadLetterMessage(\n message,\n deadLetterProperties(result)\n )\n } else {\n await receiver.abandonMessage(message)\n }\n }\n\n const subscription = receiver.subscribe(\n {\n processMessage: async (message) => {\n const handling = settle(message)\n inProgress.add(handling)\n try {\n await handling\n } finally {\n inProgress.delete(handling)\n }\n },\n processError: async ({ error, errorSource }) => {\n logger.error(\n {\n queue: connection.queue,\n source: errorSource,\n error: error.message,\n },\n 'Service Bus receiver error'\n )\n },\n },\n {\n autoCompleteMessages: false,\n maxConcurrentCalls: maxConcurrentCalls as number,\n }\n )\n\n await new Promise<void>((resolve) => {\n if (signal.aborted) resolve()\n signal.addEventListener('abort', () => resolve(), { once: true })\n })\n // Stop receiving, then let the messages in progress settle.\n let timer: ReturnType<typeof setTimeout> | undefined\n const stopped = await Promise.race([\n (async () => {\n await subscription.close()\n await Promise.allSettled([...inProgress])\n return true\n })(),\n new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), closeTimeout as number)\n }),\n ])\n clearTimeout(timer)\n if (!stopped) {\n logger.warn(\n { queue: connection.queue, closeTimeout },\n 'Service Bus receiver not closed in time: stopped without waiting'\n )\n }\n await receiver.close().catch(() => undefined)\n await connection.close().catch(() => undefined)\n },\n }\n}\n\n/**\n * Creates the handler of an Azure Functions Service Bus trigger (one\n * message per invocation). A failure throws, so that the message is\n * delivered again until the `maxDeliveryCount` of the queue.\n *\n * ```ts\n * app.serviceBusQueue('events', {\n * connection: 'SERVICE_BUS_CONNECTION',\n * queueName: 'events',\n * handler: createServiceBusFunctionHandler(createWorker({ listeners, maxAttempts: 10 })),\n * })\n * ```\n */\nexport const createServiceBusFunctionHandler = (\n worker: Worker,\n options: ServiceBusFunctionHandlerOptions = {}\n): ((\n message: unknown,\n context?: ServiceBusInvocationContext\n) => Promise<void>) => {\n const label = 'createServiceBusFunctionHandler()'\n if (typeof worker?.handle !== 'function') {\n throw new TypeError(\n `${label}: expected a worker created by createWorker().`\n )\n }\n const { deadLetter } = checkOptions(label, options, ['deadLetter'])\n const deadSender =\n deadLetter === undefined\n ? undefined\n : senderOf(\n `${label}: deadLetter`,\n checkOptions(`${label}: deadLetter`, deadLetter, [\n 'client',\n 'connectionString',\n 'queue',\n ])\n )\n\n return async (message, context) => {\n if (Array.isArray(message)) {\n throw new TypeError(\n `${label}: expected one message per invocation (cardinality \"one\").`\n )\n }\n const deliveryCount = context?.triggerMetadata?.deliveryCount\n const result = await worker.handle(message, {\n attempt: typeof deliveryCount === 'number' ? deliveryCount : 1,\n })\n if (result.status === 'success') return\n if (result.status === 'dead' && deadSender) {\n const { deadLetterReason, deadLetterErrorDescription } =\n deadLetterProperties(result)\n const failed = await deadSender.send([\n {\n id: '',\n message: {\n body: message,\n contentType: 'application/json',\n applicationProperties: {\n reason: deadLetterReason,\n error: deadLetterErrorDescription,\n },\n },\n },\n ])\n if (failed.length === 0) return\n throw new Error('Cannot send the message to the dead-letter queue.', {\n cause: failed[0]?.error,\n })\n }\n throw result.error instanceof Error\n ? result.error\n : new Error(describe(result.error))\n }\n}\n"],"mappings":"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","names":["ids"]}
@@ -0,0 +1,92 @@
1
+ import { JobsOptions, ConnectionOptions, Job } from 'bullmq';
2
+ import { Transport, Source, Worker, Envelope } from './index.js';
3
+ import 'zod';
4
+
5
+ /** A BullMQ queue, or any object with the same methods. */
6
+ interface BullmqQueue {
7
+ addBulk(jobs: {
8
+ name: string;
9
+ data: unknown;
10
+ opts?: JobsOptions;
11
+ }[]): Promise<unknown>;
12
+ close(): Promise<void>;
13
+ }
14
+ /** Options of `createBullmqTransport()`. */
15
+ type BullmqTransportOptions = {
16
+ /**
17
+ * Options of the jobs, merged with the defaults: 3 attempts, exponential
18
+ * backoff from 1 second, completed jobs removed, failed jobs kept.
19
+ */
20
+ jobOptions?: Omit<JobsOptions, 'jobId' | 'delay'> | undefined;
21
+ } & ({
22
+ /** Name of the queue. */
23
+ name: string;
24
+ /** Redis connection: ioredis options or instance. */
25
+ connection: ConnectionOptions;
26
+ /** Prefix of the Redis keys (instead of the ioredis `keyPrefix`). */
27
+ prefix?: string | undefined;
28
+ queue?: never;
29
+ } | {
30
+ /** Existing queue (another backend, shared connection…). */
31
+ queue: BullmqQueue;
32
+ name?: never;
33
+ connection?: never;
34
+ prefix?: never;
35
+ });
36
+ /** BullMQ transport: close it to release its Redis connection. */
37
+ interface BullmqTransport extends Transport {
38
+ close(): Promise<void>;
39
+ }
40
+ /** Options of `createBullmqSource()`. */
41
+ interface BullmqSourceOptions {
42
+ /** Name of the queue. */
43
+ name: string;
44
+ /** Redis connection: ioredis options or instance. */
45
+ connection: ConnectionOptions;
46
+ /** Prefix of the Redis keys, as in the transport. */
47
+ prefix?: string | undefined;
48
+ /** Messages handled at the same time. Defaults to 1. */
49
+ concurrency?: number | undefined;
50
+ /**
51
+ * On stop, how long to wait for the jobs in progress, in milliseconds,
52
+ * before stopping anyway (their jobs become stalled and are retried by
53
+ * another worker). Defaults to 30 seconds. Also bounds the stop when
54
+ * Redis is unreachable.
55
+ */
56
+ closeTimeout?: number | undefined;
57
+ }
58
+ declare const DEFAULT_JOB_OPTIONS: JobsOptions;
59
+ /**
60
+ * Creates a transport that adds messages as jobs to a BullMQ queue
61
+ * (`bullmq`, peer dependency; Redis by default). The job name is the
62
+ * listener, its id the message id: emitting again adds no duplicate.
63
+ *
64
+ * ```ts
65
+ * const transport = createBullmqTransport({
66
+ * name: 'events',
67
+ * connection: { host: 'localhost', port: 6379 },
68
+ * })
69
+ * ```
70
+ */
71
+ declare const createBullmqTransport: (options: BullmqTransportOptions) => BullmqTransport;
72
+ /**
73
+ * Processor of BullMQ jobs: a failed listener throws (retried within the
74
+ * `attempts` of the job), a message that can never be handled throws an
75
+ * `UnrecoverableError` (failed at once).
76
+ */
77
+ declare const processorOf: (worker: Worker) => (job: Pick<Job<Envelope>, "data" | "attemptsMade">) => Promise<void>;
78
+ /**
79
+ * Creates a source that runs a BullMQ worker on the queue, for
80
+ * `startWorker()` and `dotpress-events work` (Node.js and Bun).
81
+ *
82
+ * ```ts
83
+ * export default {
84
+ * worker: createWorker({ listeners, maxAttempts: 3 }),
85
+ * source: createBullmqSource({ name: 'events', connection, concurrency: 5 }),
86
+ * }
87
+ * ```
88
+ */
89
+ declare const createBullmqSource: (options: BullmqSourceOptions) => Source;
90
+
91
+ export { DEFAULT_JOB_OPTIONS, createBullmqSource, createBullmqTransport, processorOf };
92
+ export type { BullmqQueue, BullmqSourceOptions, BullmqTransport, BullmqTransportOptions };