@arvo-tools/postgres 1.0.0 → 1.2.0

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,287 @@
1
+ import { type ArvoEvent } from 'arvo-core';
2
+ import type { IArvoEventHandler } from 'arvo-event-handler';
3
+ import { PgBoss, type Queue } from 'pg-boss';
4
+ import type { HandlerRegistrationOptions, ILogger } from './types';
5
+ type PromiseLike<T> = Promise<T> | T;
6
+ /**
7
+ * Queue-based event broker for ArvoEvent handlers with automatic routing,
8
+ * retry logic, and OpenTelemetry tracing support.
9
+ *
10
+ * PostgresEventBroker extends PgBoss to provide event-driven workflow management
11
+ * through persistent PostgreSQL-backed queues. It automatically routes events between
12
+ * registered handlers, ensures reliable delivery with configurable retry policies,
13
+ * and maintains distributed tracing context across the entire workflow.
14
+ *
15
+ * Key capabilities include handler registration with dedicated queues, automatic
16
+ * event routing based on the 'to' field, workflow completion handling, support for
17
+ * domained events, and comprehensive queue statistics for monitoring.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const broker = new PostgresEventBroker({ connectionString: 'postgres://...' });
22
+ * await broker.start();
23
+ *
24
+ * // Register handlers with retry configuration
25
+ * await broker.register(calculatorHandler, {
26
+ * recreateQueue: true,
27
+ * worker: {
28
+ * concurrency: 5,
29
+ * retryLimit: 3,
30
+ * retryBackoff: true
31
+ * }
32
+ * });
33
+ *
34
+ * // Set up workflow completion handler
35
+ * await broker.onWorkflowComplete({
36
+ * source: 'my.workflow',
37
+ * listener: async (event) => {
38
+ * this.logger.log('Workflow completed:', event.data);
39
+ * }
40
+ * });
41
+ *
42
+ * // Handle domained events (e.g., human approval requests)
43
+ * broker.onDomainedEvent(async (event) => {
44
+ * if (event.domain === 'human.interaction') {
45
+ * await handleHumanApproval(event);
46
+ * }
47
+ * });
48
+ *
49
+ * // Dispatch events using ArvoEventFactory
50
+ * const event = createArvoEventFactory(contract.version('1.0.0')).accepts({
51
+ * source: 'my.workflow',
52
+ * data: { numbers: [1, 2, 3] }
53
+ * });
54
+ * await broker.dispatch(event);
55
+ *
56
+ * // Monitor queue health
57
+ * const stats = await broker.getStats();
58
+ * ```
59
+ */
60
+ export declare class PostgresEventBroker extends PgBoss {
61
+ /**
62
+ * Internal registry of handler configurations keyed by handler source.
63
+ */
64
+ private handlers;
65
+ /**
66
+ * Internal list of all registered queue names.
67
+ */
68
+ private _queues;
69
+ /**
70
+ * List of all registered queue names in the broker.
71
+ */
72
+ get queues(): string[];
73
+ /**
74
+ * Logger instance used for all broker operational logging.
75
+ * Defaults to console but can be replaced via setLogger().
76
+ */
77
+ private logger;
78
+ /**
79
+ * Sets a custom logger for broker operations.
80
+ *
81
+ * Allows integration with existing logging infrastructure (Winston, Pino, etc.)
82
+ * by providing a logger that implements the ILogger interface.
83
+ *
84
+ * @param logger - Logger instance implementing ILogger interface
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import winston from 'winston';
89
+ *
90
+ * const logger = winston.createLogger({
91
+ * level: 'info',
92
+ * format: winston.format.json(),
93
+ * transports: [new winston.transports.Console()]
94
+ * });
95
+ *
96
+ * broker.setLogger(logger);
97
+ * ```
98
+ */
99
+ setLogger(logger: ILogger): void;
100
+ /**
101
+ * The configured event source for workflow completion.
102
+ */
103
+ private injectionEventSource;
104
+ /**
105
+ * Default callback invoked when an event has no registered destination handler.
106
+ */
107
+ private _onHandlerNotFound;
108
+ /**
109
+ * Callback invoked when a domained event is encountered during routing.
110
+ */
111
+ private _onDomainedEvent;
112
+ /**
113
+ * Registers a handler for workflow completion events.
114
+ *
115
+ * This sets up the terminal point where events return after flowing through
116
+ * the handler chain. The handler receives events whose 'to' field matches
117
+ * the configured source, typically indicating workflow completion.
118
+ *
119
+ * Sets the injection event source that must match the source of events
120
+ * dispatched via the dispatch() method.
121
+ *
122
+ * **Note:** The listener must handle its own errors. Exceptions are caught
123
+ * and logged but do not cause job failures. This is by design.
124
+ *
125
+ * @param param - Configuration object
126
+ * @param param.source - Event source identifier for completion events
127
+ * @param param.listener - Callback invoked when completion events are received
128
+ * @param param.options - Optional queue and worker configuration
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * await broker.onWorkflowComplete({
133
+ * source: 'test.test.test',
134
+ * listener: async (event) => {
135
+ * try {
136
+ * this.logger.log('Final result:', event.data);
137
+ * } catch (error) {
138
+ * logger.error('Completion handler failed', error);
139
+ * }
140
+ * },
141
+ * });
142
+ * ```
143
+ */
144
+ onWorkflowComplete(param: {
145
+ source: string;
146
+ listener: (event: ArvoEvent) => PromiseLike<void>;
147
+ options?: HandlerRegistrationOptions;
148
+ }): Promise<void>;
149
+ /**
150
+ * Sets a custom handler for events with no registered destination.
151
+ *
152
+ * When a handler emits an event whose 'to' field doesn't match any
153
+ * registered handler, this callback is invoked. Useful for logging
154
+ * routing errors or implementing fallback behavior.
155
+ *
156
+ * **Note:** The listener must handle its own errors. Exceptions are
157
+ * suppressed by design to prevent routing failures from cascading.
158
+ *
159
+ * @param listner - Callback invoked with unroutable events
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * broker.onHandlerNotFound(async (event) => {
164
+ * try {
165
+ * logger.error('No handler for', event.to);
166
+ * } catch (error) {
167
+ * this.logger.error('Failed to log missing handler', error);
168
+ * }
169
+ * });
170
+ * ```
171
+ */
172
+ onHandlerNotFound(listner: (event: ArvoEvent) => PromiseLike<void>): void;
173
+ /**
174
+ * Sets a custom handler for domained events.
175
+ *
176
+ * Domained events are intercepted during routing and passed to this handler
177
+ * instead of being sent to a queue. Useful for handling external system
178
+ * interactions like human approvals or notifications.
179
+ *
180
+ * **Note:** The listener must handle its own errors. Exceptions are
181
+ * suppressed by design to prevent domained event failures from breaking workflows.
182
+ *
183
+ * @param listner - Callback invoked when domained events are encountered
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * broker.onDomainedEvent(async (event) => {
188
+ * try {
189
+ * if (event.domain === 'notification') {
190
+ * await alerting.notify(event);
191
+ * }
192
+ * } catch (error) {
193
+ * logger.error('Domained event handler failed', error);
194
+ * }
195
+ * });
196
+ * ```
197
+ */
198
+ onDomainedEvent(listner: (event: ArvoEvent) => PromiseLike<void>): void;
199
+ /**
200
+ * Creates a queue and tracks it in the internal queue registry.
201
+ * Overrides the base class method to maintain queue tracking.
202
+ *
203
+ * @param name - Queue name
204
+ * @param options - Queue configuration options
205
+ */
206
+ createQueue(name: string, options?: Omit<Queue, 'name'>): Promise<void>;
207
+ /**
208
+ * Registers an event handler with the broker.
209
+ *
210
+ * Creates a dedicated queue for the handler and spawns worker instances
211
+ * to process incoming events. Events emitted by the handler are automatically
212
+ * routed to their destinations based on the 'to' field.
213
+ *
214
+ * @param handler - The ArvoEvent handler to register
215
+ * @param options - Configuration for queue behavior, worker concurrency, and retry policies
216
+ * @throws {Error} If a handler with the same source is already registered
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * await broker.register(calculatorHandler, {
221
+ * recreateQueue: true,
222
+ * queue: { deadLetter: 'dlq' },
223
+ * worker: {
224
+ * concurrency: 5,
225
+ * onError: async (job, error) => {
226
+ * if (error.message.includes('timeout')) return 'RETRY';
227
+ * return 'FAIL';
228
+ * }
229
+ * }
230
+ * });
231
+ * ```
232
+ */
233
+ register(handler: IArvoEventHandler, options?: HandlerRegistrationOptions): Promise<void>;
234
+ /**
235
+ * Routes an ArvoEvent to its destination queue.
236
+ *
237
+ * Internal method that filters out worker configuration options and includes
238
+ * only job-level options when sending the event. This method performs no
239
+ * validation and assumes the event and handler have already been verified.
240
+ *
241
+ * @param event - The ArvoEvent to route
242
+ * @returns Job ID if sent successfully, null if handler not found
243
+ */
244
+ private _emitArvoEvent;
245
+ /**
246
+ * Dispatches an ArvoEvent into the broker system with validation.
247
+ *
248
+ * This is the primary entry point for injecting events into the workflow.
249
+ * The event must originate from the source configured in onWorkflowComplete()
250
+ * and target a registered handler.
251
+ *
252
+ * @param event - The ArvoEvent to dispatch
253
+ * @returns Job ID assigned by the queue system
254
+ * @throws {Error} If workflow completion handler is not configured
255
+ * @throws {Error} If event source doesn't match configured workflow source
256
+ * @throws {Error} If target handler is not registered
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const event = createArvoEvent({
261
+ * source: 'my.workflow',
262
+ * to: 'calculator.handler',
263
+ * type: 'calculate.request',
264
+ * data: { operation: 'add', values: [1, 2] }
265
+ * });
266
+ *
267
+ * await broker.dispatch(event);
268
+ * ```
269
+ */
270
+ dispatch(event: ArvoEvent): Promise<string | null>;
271
+ /**
272
+ * Retrieves statistics for all registered queues in the broker.
273
+ *
274
+ * @returns Array of queue statistics including active, queued, and total job counts
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const stats = await broker.getStats();
279
+ * stats.forEach(stat => {
280
+ * this.logger.log(`Queue ${stat.name}: ${stat.activeCount} active, ${stat.queuedCount} queued`);
281
+ * });
282
+ * ```
283
+ */
284
+ getStats(): Promise<import("pg-boss").QueueResult[]>;
285
+ }
286
+ export {};
287
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/broker/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAoB,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGnE,KAAK,WAAW,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBAAa,mBAAoB,SAAQ,MAAM;IAC7C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAKT;IAEP;;OAEG;IACH,OAAO,CAAC,OAAO,CAAgB;IAE/B;;OAEG;IACH,IAAW,MAAM,IAAI,MAAM,EAAE,CAE5B;IAED;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAoB;IAElC;;;;;;;;;;;;;;;;;;;;OAoBG;IACI,SAAS,CAAC,MAAM,EAAE,OAAO;IAIhC;;OAEG;IACH,OAAO,CAAC,oBAAoB,CAAuB;IAEnD;;OAEG;IACH,OAAO,CAAC,kBAAkB,CAC4C;IAEtE;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAC4C;IAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,kBAAkB,CAAC,KAAK,EAAE;QAC9B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,EAAE,0BAA0B,CAAC;KACtC;IA+BD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,iBAAiB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,WAAW,CAAC,IAAI,CAAC;IAIlE;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,eAAe,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,WAAW,CAAC,IAAI,CAAC;IAIhE;;;;;;OAMG;IACY,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtF;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,QAAQ,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IA6D/F;;;;;;;;;OASG;YACW,cAAc;IAiB5B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,QAAQ,CAAC,KAAK,EAAE,SAAS;IAgC/B;;;;;;;;;;;;OAYG;IACG,QAAQ;CAGf"}