@onlineapps/conn-infra-mq 1.1.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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/package.json +96 -0
- package/src/BaseClient.js +219 -0
- package/src/ConnectorMQClient.js +446 -0
- package/src/config/configSchema.js +70 -0
- package/src/config/defaultConfig.js +48 -0
- package/src/index.js +65 -0
- package/src/layers/ForkJoinHandler.js +312 -0
- package/src/layers/QueueManager.js +263 -0
- package/src/layers/RPCHandler.js +324 -0
- package/src/layers/RetryHandler.js +370 -0
- package/src/layers/WorkflowRouter.js +136 -0
- package/src/transports/rabbitmqClient.js +216 -0
- package/src/transports/transportFactory.js +33 -0
- package/src/utils/errorHandler.js +120 -0
- package/src/utils/logger.js +38 -0
- package/src/utils/serializer.js +44 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WorkflowRouter - Handles workflow routing between services
|
|
5
|
+
* Manages workflow.init, service routing, and workflow.completed patterns
|
|
6
|
+
*/
|
|
7
|
+
class WorkflowRouter {
|
|
8
|
+
constructor(mqClient, config = {}) {
|
|
9
|
+
this.client = mqClient;
|
|
10
|
+
this.config = {
|
|
11
|
+
workflowInitQueue: config.workflowInitQueue || 'workflow.init',
|
|
12
|
+
workflowCompletedQueue: config.workflowCompletedQueue || 'workflow.completed',
|
|
13
|
+
serviceName: config.serviceName || 'unknown',
|
|
14
|
+
...config
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Publish workflow to init queue (entry point for all workflows)
|
|
20
|
+
* @param {Object} workflow - Workflow message with cookbook
|
|
21
|
+
* @param {Object} options - Additional publish options
|
|
22
|
+
*/
|
|
23
|
+
async publishWorkflowInit(workflow, options = {}) {
|
|
24
|
+
return this.client.publish(workflow, {
|
|
25
|
+
queue: this.config.workflowInitQueue,
|
|
26
|
+
...options
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Publish to specific service workflow queue
|
|
32
|
+
* @param {string} serviceName - Target service name
|
|
33
|
+
* @param {Object} message - Workflow message
|
|
34
|
+
* @param {Object} options - Additional publish options
|
|
35
|
+
*/
|
|
36
|
+
async publishToServiceWorkflow(serviceName, message, options = {}) {
|
|
37
|
+
return this.client.publish(message, {
|
|
38
|
+
queue: `${serviceName}.workflow`,
|
|
39
|
+
...options
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Publish directly to service queue (non-workflow)
|
|
45
|
+
* @param {string} serviceName - Target service name
|
|
46
|
+
* @param {Object} message - Message to send
|
|
47
|
+
* @param {Object} options - Additional publish options
|
|
48
|
+
*/
|
|
49
|
+
async publishToService(serviceName, message, options = {}) {
|
|
50
|
+
return this.client.publish(message, {
|
|
51
|
+
queue: `${serviceName}.queue`,
|
|
52
|
+
...options
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Publish completed workflow result
|
|
58
|
+
* @param {Object} result - Workflow completion result
|
|
59
|
+
* @param {Object} options - Additional publish options
|
|
60
|
+
*/
|
|
61
|
+
async publishWorkflowCompleted(result, options = {}) {
|
|
62
|
+
return this.client.publish(result, {
|
|
63
|
+
queue: this.config.workflowCompletedQueue,
|
|
64
|
+
...options
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Route to next service based on workflow step
|
|
70
|
+
* @param {Object} workflow - Current workflow state
|
|
71
|
+
* @param {string} nextService - Next service to route to
|
|
72
|
+
* @param {Object} stepResult - Result from current step
|
|
73
|
+
*/
|
|
74
|
+
async routeToNextService(workflow, nextService, stepResult) {
|
|
75
|
+
const updatedWorkflow = {
|
|
76
|
+
...workflow,
|
|
77
|
+
current_step: (workflow.current_step || 0) + 1,
|
|
78
|
+
results: [...(workflow.results || []), stepResult],
|
|
79
|
+
last_service: this.config.serviceName,
|
|
80
|
+
routed_at: new Date().toISOString()
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (nextService) {
|
|
84
|
+
return this.publishToServiceWorkflow(nextService, updatedWorkflow);
|
|
85
|
+
} else {
|
|
86
|
+
// No next service, workflow is complete
|
|
87
|
+
updatedWorkflow.status = 'completed';
|
|
88
|
+
return this.publishWorkflowCompleted(updatedWorkflow);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Consume from workflow init queue (competing consumers pattern)
|
|
94
|
+
* @param {Function} handler - Message handler function
|
|
95
|
+
* @param {Object} options - Consume options
|
|
96
|
+
*/
|
|
97
|
+
async consumeWorkflowInit(handler, options = {}) {
|
|
98
|
+
return this.client.consume(handler, {
|
|
99
|
+
queue: this.config.workflowInitQueue,
|
|
100
|
+
...options
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Consume from service workflow queue
|
|
106
|
+
* @param {string} serviceName - Service name (defaults to configured)
|
|
107
|
+
* @param {Function} handler - Message handler function
|
|
108
|
+
* @param {Object} options - Consume options
|
|
109
|
+
*/
|
|
110
|
+
async consumeServiceWorkflow(serviceName, handler, options = {}) {
|
|
111
|
+
if (typeof serviceName === 'function') {
|
|
112
|
+
// If serviceName is actually the handler
|
|
113
|
+
handler = serviceName;
|
|
114
|
+
serviceName = this.config.serviceName;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return this.client.consume(handler, {
|
|
118
|
+
queue: `${serviceName}.workflow`,
|
|
119
|
+
...options
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Consume completed workflows
|
|
125
|
+
* @param {Function} handler - Message handler function
|
|
126
|
+
* @param {Object} options - Consume options
|
|
127
|
+
*/
|
|
128
|
+
async consumeWorkflowCompleted(handler, options = {}) {
|
|
129
|
+
return this.client.consume(handler, {
|
|
130
|
+
queue: this.config.workflowCompletedQueue,
|
|
131
|
+
...options
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = WorkflowRouter;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RabbitMQClient: transport implementation for RabbitMQ using amqplib.
|
|
5
|
+
* Implements connect, disconnect, publish, consume, ack, nack, and error propagation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const amqp = require('amqplib');
|
|
9
|
+
const EventEmitter = require('events');
|
|
10
|
+
|
|
11
|
+
class RabbitMQClient extends EventEmitter {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} config
|
|
14
|
+
* @param {string} config.host - AMQP URI or hostname (e.g., 'amqp://localhost:5672')
|
|
15
|
+
* @param {string} [config.queue] - Default queue name (optional; can be overridden per call)
|
|
16
|
+
* @param {string} [config.exchange] - Default exchange (default: '')
|
|
17
|
+
* @param {boolean} [config.durable] - Declare queues/exchanges as durable (default: true)
|
|
18
|
+
* @param {number} [config.prefetch] - Default prefetch count for consumers (default: 1)
|
|
19
|
+
* @param {boolean} [config.noAck] - Default auto-acknowledge setting (default: false)
|
|
20
|
+
* @param {Object} [config.retryPolicy] - { retries, initialDelayMs, maxDelayMs, factor } (not implemented here)
|
|
21
|
+
*/
|
|
22
|
+
constructor(config) {
|
|
23
|
+
super();
|
|
24
|
+
this._config = Object.assign(
|
|
25
|
+
{
|
|
26
|
+
exchange: '',
|
|
27
|
+
durable: true,
|
|
28
|
+
prefetch: 1,
|
|
29
|
+
noAck: false,
|
|
30
|
+
},
|
|
31
|
+
config
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
this._connection = null;
|
|
35
|
+
this._channel = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Connects to RabbitMQ server and creates a confirm channel.
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
* @throws {Error} If connection or channel creation fails.
|
|
42
|
+
*/
|
|
43
|
+
async connect() {
|
|
44
|
+
try {
|
|
45
|
+
this._connection = await amqp.connect(this._config.host);
|
|
46
|
+
this._connection.on('error', (err) => this.emit('error', err));
|
|
47
|
+
this._connection.on('close', () => {
|
|
48
|
+
// Emit a connection close error to notify listeners
|
|
49
|
+
this.emit('error', new Error('RabbitMQ connection closed unexpectedly'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Use ConfirmChannel to enable publisher confirms
|
|
53
|
+
this._channel = await this._connection.createConfirmChannel();
|
|
54
|
+
this._channel.on('error', (err) => this.emit('error', err));
|
|
55
|
+
this._channel.on('close', () => {
|
|
56
|
+
// Emit a channel close error
|
|
57
|
+
this.emit('error', new Error('RabbitMQ channel closed unexpectedly'));
|
|
58
|
+
});
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// Cleanup partially created resources
|
|
61
|
+
if (this._connection) {
|
|
62
|
+
try {
|
|
63
|
+
await this._connection.close();
|
|
64
|
+
} catch (_) {
|
|
65
|
+
/* ignore */
|
|
66
|
+
}
|
|
67
|
+
this._connection = null;
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Disconnects: closes channel and connection.
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
async disconnect() {
|
|
78
|
+
try {
|
|
79
|
+
if (this._channel) {
|
|
80
|
+
await this._channel.close();
|
|
81
|
+
this._channel = null;
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.emit('error', err);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
if (this._connection) {
|
|
88
|
+
await this._connection.close();
|
|
89
|
+
this._connection = null;
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
this.emit('error', err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Publishes a message buffer to the specified queue (or default queue) or exchange.
|
|
98
|
+
* @param {string} queue - Target queue name.
|
|
99
|
+
* @param {Buffer} buffer - Message payload as Buffer.
|
|
100
|
+
* @param {Object} [options] - Overrides: routingKey, persistent, headers.
|
|
101
|
+
* @returns {Promise<void>}
|
|
102
|
+
* @throws {Error} If publish fails or channel is not available.
|
|
103
|
+
*/
|
|
104
|
+
async publish(queue, buffer, options = {}) {
|
|
105
|
+
if (!this._channel) {
|
|
106
|
+
throw new Error('Cannot publish: channel is not initialized');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const exchange = this._config.exchange || '';
|
|
110
|
+
const routingKey = options.routingKey || queue;
|
|
111
|
+
const persistent = options.persistent !== undefined ? options.persistent : this._config.durable;
|
|
112
|
+
const headers = options.headers || {};
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Ensure queue exists if publishing directly to queue and using default exchange
|
|
116
|
+
if (!exchange) {
|
|
117
|
+
await this._channel.assertQueue(queue, { durable: this._config.durable });
|
|
118
|
+
this._channel.sendToQueue(queue, buffer, { persistent, headers, routingKey });
|
|
119
|
+
} else {
|
|
120
|
+
// If exchange is specified, assert exchange and publish to it
|
|
121
|
+
await this._channel.assertExchange(exchange, 'direct', { durable: this._config.durable });
|
|
122
|
+
this._channel.publish(exchange, routingKey, buffer, { persistent, headers });
|
|
123
|
+
}
|
|
124
|
+
// Wait for confirmation
|
|
125
|
+
await this._channel.waitForConfirms();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.emit('error', err);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Starts consuming messages from the specified queue.
|
|
134
|
+
* @param {string} queue - Queue name to consume from.
|
|
135
|
+
* @param {function(Object): Promise<void>} onMessage - Async handler receiving raw msg.
|
|
136
|
+
* @param {Object} [options] - Overrides: prefetch, noAck.
|
|
137
|
+
* @returns {Promise<void>}
|
|
138
|
+
* @throws {Error} If consume setup fails or channel is not available.
|
|
139
|
+
*/
|
|
140
|
+
async consume(queue, onMessage, options = {}) {
|
|
141
|
+
if (!this._channel) {
|
|
142
|
+
throw new Error('Cannot consume: channel is not initialized');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const durable = this._config.durable;
|
|
146
|
+
const prefetch = options.prefetch !== undefined ? options.prefetch : this._config.prefetch;
|
|
147
|
+
const noAck = options.noAck !== undefined ? options.noAck : this._config.noAck;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Ensure queue exists
|
|
151
|
+
await this._channel.assertQueue(queue, { durable });
|
|
152
|
+
// Set prefetch if provided
|
|
153
|
+
if (typeof prefetch === 'number') {
|
|
154
|
+
this._channel.prefetch(prefetch);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await this._channel.consume(
|
|
158
|
+
queue,
|
|
159
|
+
async (msg) => {
|
|
160
|
+
if (msg === null) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
await onMessage(msg);
|
|
165
|
+
if (!noAck) {
|
|
166
|
+
this._channel.ack(msg);
|
|
167
|
+
}
|
|
168
|
+
} catch (handlerErr) {
|
|
169
|
+
// Negative acknowledge and requeue by default
|
|
170
|
+
this._channel.nack(msg, false, true);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{ noAck }
|
|
174
|
+
);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
this.emit('error', err);
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Acknowledges a message.
|
|
183
|
+
* @param {Object} msg - RabbitMQ message object.
|
|
184
|
+
*/
|
|
185
|
+
async ack(msg) {
|
|
186
|
+
if (!this._channel) {
|
|
187
|
+
throw new Error('Cannot ack: channel is not initialized');
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
this._channel.ack(msg);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
this.emit('error', err);
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Negative-acknowledges a message.
|
|
199
|
+
* @param {Object} msg - RabbitMQ message object.
|
|
200
|
+
* @param {Object} [options] - { requeue: boolean }.
|
|
201
|
+
*/
|
|
202
|
+
async nack(msg, options = {}) {
|
|
203
|
+
if (!this._channel) {
|
|
204
|
+
throw new Error('Cannot nack: channel is not initialized');
|
|
205
|
+
}
|
|
206
|
+
const requeue = options.requeue !== undefined ? options.requeue : true;
|
|
207
|
+
try {
|
|
208
|
+
this._channel.nack(msg, false, requeue);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
this.emit('error', err);
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = RabbitMQClient;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* transportFactory: selects and instantiates the appropriate transport
|
|
5
|
+
* based on configuration. Currently supports RabbitMQ;
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const RabbitMQClient = require('./rabbitmqClient');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Factory method: returns a transport instance based on config.type.
|
|
12
|
+
* @param {Object} config
|
|
13
|
+
* @param {'rabbitmq'} config.type - Transport type ('rabbitmq' for now)
|
|
14
|
+
* @returns {Object} Instance of transport (RabbitMQClient, etc.)
|
|
15
|
+
* @throws {Error} If the configured type is unsupported or missing
|
|
16
|
+
*/
|
|
17
|
+
function create(config) {
|
|
18
|
+
if (!config || !config.type) {
|
|
19
|
+
throw new Error('Transport type is required in configuration');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
switch (config.type.toLowerCase()) {
|
|
23
|
+
case 'rabbitmq':
|
|
24
|
+
return new RabbitMQClient(config);
|
|
25
|
+
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported transport type: ${config.type}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
create,
|
|
33
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* errorHandler.js
|
|
5
|
+
*
|
|
6
|
+
* Defines all custom error types used by AgentMQClient and RabbitMQClient.
|
|
7
|
+
* Each extends the built-in Error class and carries additional contextual fields.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ValidationError
|
|
12
|
+
* Thrown when user-supplied configuration does not match schema.
|
|
13
|
+
* - message: human-readable description
|
|
14
|
+
* - details: array of { path, message } describing each invalid field
|
|
15
|
+
*/
|
|
16
|
+
class ValidationError extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} message
|
|
19
|
+
* @param {Array<{path: string, message: string}>} details
|
|
20
|
+
*/
|
|
21
|
+
constructor(message, details) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'ValidationError';
|
|
24
|
+
this.details = Array.isArray(details) ? details : [];
|
|
25
|
+
Error.captureStackTrace(this, ValidationError);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ConnectionError
|
|
31
|
+
* Thrown when client cannot connect to broker.
|
|
32
|
+
* - message: description of what went wrong
|
|
33
|
+
* - cause: original error object
|
|
34
|
+
*/
|
|
35
|
+
class ConnectionError extends Error {
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} message
|
|
38
|
+
* @param {Error} cause
|
|
39
|
+
*/
|
|
40
|
+
constructor(message, cause) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'ConnectionError';
|
|
43
|
+
this.cause = cause;
|
|
44
|
+
Error.captureStackTrace(this, ConnectionError);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* PublishError
|
|
50
|
+
* Thrown when publishing a message fails.
|
|
51
|
+
* - message: description
|
|
52
|
+
* - queue: target queue name
|
|
53
|
+
* - cause: original error
|
|
54
|
+
*/
|
|
55
|
+
class PublishError extends Error {
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} message
|
|
58
|
+
* @param {string} queue
|
|
59
|
+
* @param {Error} cause
|
|
60
|
+
*/
|
|
61
|
+
constructor(message, queue, cause) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = 'PublishError';
|
|
64
|
+
this.queue = queue;
|
|
65
|
+
this.cause = cause;
|
|
66
|
+
Error.captureStackTrace(this, PublishError);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* ConsumeError
|
|
72
|
+
* Thrown when setting up a consumer fails.
|
|
73
|
+
* - message: description
|
|
74
|
+
* - queue: queue name associated with the consumer
|
|
75
|
+
* - cause: original error
|
|
76
|
+
*/
|
|
77
|
+
class ConsumeError extends Error {
|
|
78
|
+
/**
|
|
79
|
+
* @param {string} message
|
|
80
|
+
* @param {string} queue
|
|
81
|
+
* @param {Error} cause
|
|
82
|
+
*/
|
|
83
|
+
constructor(message, queue, cause) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.name = 'ConsumeError';
|
|
86
|
+
this.queue = queue;
|
|
87
|
+
this.cause = cause;
|
|
88
|
+
Error.captureStackTrace(this, ConsumeError);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* SerializationError
|
|
94
|
+
* Thrown when JSON serialization or deserialization fails.
|
|
95
|
+
* - message: description
|
|
96
|
+
* - payload: original object or buffer that caused the error
|
|
97
|
+
* - cause: native exception (e.g., TypeError for circular references)
|
|
98
|
+
*/
|
|
99
|
+
class SerializationError extends Error {
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} message
|
|
102
|
+
* @param {*} payload
|
|
103
|
+
* @param {Error} cause
|
|
104
|
+
*/
|
|
105
|
+
constructor(message, payload, cause) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.name = 'SerializationError';
|
|
108
|
+
this.payload = payload;
|
|
109
|
+
this.cause = cause;
|
|
110
|
+
Error.captureStackTrace(this, SerializationError);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
ValidationError,
|
|
116
|
+
ConnectionError,
|
|
117
|
+
PublishError,
|
|
118
|
+
ConsumeError,
|
|
119
|
+
SerializationError,
|
|
120
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* logger.js
|
|
5
|
+
*
|
|
6
|
+
* Provides a simple abstraction over console or a custom logger.
|
|
7
|
+
* If the user passes a custom logger object with methods { info, warn, error, debug },
|
|
8
|
+
* these are used; otherwise console.* se použije jako fallback.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function createLogger(customLogger) {
|
|
12
|
+
const methods = ['info', 'warn', 'error', 'debug'];
|
|
13
|
+
if (
|
|
14
|
+
customLogger &&
|
|
15
|
+
typeof customLogger === 'object' &&
|
|
16
|
+
methods.every((fn) => typeof customLogger[fn] === 'function')
|
|
17
|
+
) {
|
|
18
|
+
// Wrap custom logger to ensure consistent signature
|
|
19
|
+
return {
|
|
20
|
+
info: (...args) => customLogger.info(...args),
|
|
21
|
+
warn: (...args) => customLogger.warn(...args),
|
|
22
|
+
error: (...args) => customLogger.error(...args),
|
|
23
|
+
debug: (...args) => customLogger.debug(...args),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Fallback to console
|
|
28
|
+
return {
|
|
29
|
+
info: (...args) => console.log('[INFO]', ...args),
|
|
30
|
+
warn: (...args) => console.warn('[WARN]', ...args),
|
|
31
|
+
error: (...args) => console.error('[ERROR]', ...args),
|
|
32
|
+
debug: (...args) => console.debug('[DEBUG]', ...args),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
createLogger,
|
|
38
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* serializer.js
|
|
5
|
+
*
|
|
6
|
+
* Contains helper functions for serializing and deserializing payloads.
|
|
7
|
+
* On failure, throws a SerializationError (defined in errorHandler.js).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { SerializationError } = require('./errorHandler');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Serializes a JavaScript object to JSON string.
|
|
14
|
+
* @param {Object} obj
|
|
15
|
+
* @returns {string}
|
|
16
|
+
* @throws {SerializationError} If JSON.stringify fails (e.g., circular reference).
|
|
17
|
+
*/
|
|
18
|
+
function serialize(obj) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(obj);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new SerializationError('Failed to serialize object', obj, err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Deserializes a Buffer or string into a JavaScript object via JSON.parse.
|
|
28
|
+
* @param {Buffer|string} buffer
|
|
29
|
+
* @returns {Object}
|
|
30
|
+
* @throws {SerializationError} If JSON.parse fails or buffer cannot be converted.
|
|
31
|
+
*/
|
|
32
|
+
function deserialize(buffer) {
|
|
33
|
+
try {
|
|
34
|
+
const str = Buffer.isBuffer(buffer) ? buffer.toString('utf8') : buffer;
|
|
35
|
+
return JSON.parse(str);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new SerializationError('Failed to deserialize payload', buffer, err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
serialize,
|
|
43
|
+
deserialize,
|
|
44
|
+
};
|