@orion-js/echoes 4.4.0 → 4.4.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.
package/dist/index.cjs CHANGED
@@ -43,20 +43,25 @@ __export(index_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(index_exports);
45
45
 
46
+ // src/startService/index.ts
47
+ var import_http2 = require("@orion-js/http");
48
+
46
49
  // src/config.ts
47
50
  var config = {};
48
51
  var config_default = config;
49
52
 
50
- // src/requestsHandler/getEcho.ts
51
- function getEcho_default(method) {
52
- const echo2 = config_default.echoes[method];
53
- if (!echo2) {
54
- throw new Error(`Echo named ${method} not found in this service`);
55
- }
56
- if (echo2.type !== "request") {
57
- throw new Error(`Echo named ${method} is not of type request`);
53
+ // src/startService/KafkaManager.ts
54
+ var import_node_crypto = require("crypto");
55
+ var import_logger = require("@orion-js/logger");
56
+ var import_kafkajs = require("kafkajs");
57
+
58
+ // src/echo/deserialize.ts
59
+ function deserialize_default(serializedJavascript) {
60
+ try {
61
+ return eval("(" + serializedJavascript + ")");
62
+ } catch (error) {
63
+ throw new Error("Error deserializing echo message");
58
64
  }
59
- return echo2;
60
65
  }
61
66
 
62
67
  // src/publish/serialize.ts
@@ -68,107 +73,90 @@ function serialize_default(data) {
68
73
  return serialized;
69
74
  }
70
75
 
71
- // src/request/getSignature.ts
72
- var import_jssha = __toESM(require("jssha"), 1);
73
-
74
- // src/request/getPassword.ts
75
- var import_logger = require("@orion-js/logger");
76
- var import_env = require("@orion-js/env");
77
- function getEchoesPassword() {
78
- var _a, _b;
79
- const secret = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.key) || (0, import_env.internalGetEnv)("echoes_password", "ECHOES_PASSWORD");
80
- if (!secret) {
81
- import_logger.logger.warn(
82
- 'Warning: no secret key found for echoes requests. Init echoes or set the env var "echoes_password" or process.env.ECHOES_PASSWORD'
83
- );
84
- }
85
- return secret;
86
- }
87
-
88
- // src/request/getSignature.ts
89
- function getSignature_default(body) {
90
- const password = getEchoesPassword();
91
- const shaObj = new import_jssha.default("SHA-1", "TEXT");
92
- shaObj.setHMACKey(password, "TEXT");
93
- shaObj.update(body);
94
- return shaObj.getHMAC("HEX");
95
- }
96
-
97
- // src/requestsHandler/checkSignature.ts
98
- function checkSignature_default(body, signature) {
99
- const generatedSignature = getSignature_default(body);
100
- if (generatedSignature !== signature) {
101
- throw new Error("Echoes invalid signature");
102
- }
103
- }
104
-
105
- // src/requestsHandler/index.ts
106
- var import_http = require("@orion-js/http");
107
- var requestsHandler_default = (options) => (0, import_http.route)({
108
- method: "post",
109
- path: options.requests.handlerPath || "/echoes-services",
110
- bodyParser: "json",
111
- bodyParserOptions: {
112
- limit: "10mb"
113
- },
114
- async resolve(req) {
115
- try {
116
- const { body, signature } = req.body;
117
- checkSignature_default(body, signature);
118
- const { method, serializedParams } = body;
119
- const echo2 = getEcho_default(method);
120
- const result = await echo2.onRequest(serializedParams);
121
- return {
122
- body: {
123
- result: serialize_default(result)
124
- }
125
- };
126
- } catch (error) {
127
- if (!error.getInfo) {
128
- console.error("Error at echo requests handler:", error);
129
- }
130
- return {
131
- body: {
132
- error: error.message,
133
- errorInfo: error.getInfo ? error.getInfo() : null,
134
- isValidationError: !!error.isValidationError,
135
- isUserError: !!error.isUserError
136
- }
137
- };
138
- }
139
- }
140
- });
141
-
142
76
  // src/startService/KafkaManager.ts
143
- var import_kafkajs = require("kafkajs");
144
- var import_logger2 = require("@orion-js/logger");
145
77
  var HEARTBEAT_INTERVAL_SECONDS = 5;
146
78
  var CHECK_JOIN_CONSUMER_INTERVAL_SECONDS = 30;
147
79
  var DEFAULT_PARTITIONS_CONSUMED_CONCURRENTLY = 4;
148
80
  var DEFAULT_MEMBERS_TO_PARTITIONS_RATIO = 1;
149
81
  var KafkaManager = class {
82
+ name = "kafka";
150
83
  kafka;
151
84
  options;
152
85
  producer;
153
86
  consumer;
154
- topics;
155
- started;
87
+ topics = [];
88
+ subscriptions = /* @__PURE__ */ new Map();
89
+ onEvent;
90
+ consumerStarted = false;
91
+ producerConnected = false;
156
92
  interval;
157
93
  constructor(options) {
158
94
  this.kafka = new import_kafkajs.Kafka(options.client);
159
95
  this.options = options;
160
- this.producer = this.kafka.producer(options.producer);
161
- this.consumer = this.kafka.consumer(options.consumer);
162
- this.topics = Object.keys(options.echoes).filter((key) => options.echoes[key].type === "event");
96
+ }
97
+ async start(options) {
98
+ var _a;
99
+ this.onEvent = options.onEvent;
100
+ this.subscriptions = new Map(
101
+ options.subscriptions.map((subscription) => [subscription.topic, subscription])
102
+ );
103
+ this.topics = options.subscriptions.map((subscription) => subscription.topic);
104
+ if (options.publish || options.consume) {
105
+ this.producer = this.kafka.producer(this.options.producer);
106
+ await this.producer.connect();
107
+ this.producerConnected = true;
108
+ }
109
+ if (!options.consume || this.topics.length === 0) return;
110
+ if (!((_a = this.options.consumer) == null ? void 0 : _a.groupId)) {
111
+ throw new Error("Echoes Kafka consumers require consumer.groupId");
112
+ }
113
+ this.consumer = this.kafka.consumer(this.options.consumer);
114
+ this.consumerStarted = await this.conditionalStart();
115
+ if (this.consumerStarted) return;
116
+ import_logger.logger.info("Echoes: Delaying consumer group join, waiting for conditions to be met");
117
+ this.interval = setInterval(async () => {
118
+ this.consumerStarted = await this.conditionalStart();
119
+ if (this.consumerStarted) clearInterval(this.interval);
120
+ }, CHECK_JOIN_CONSUMER_INTERVAL_SECONDS * 1e3);
121
+ }
122
+ async publish(options) {
123
+ if (!this.producer || !this.producerConnected) {
124
+ throw new Error("Echoes Kafka producer is not connected");
125
+ }
126
+ return await this.producer.send({
127
+ acks: options.acks,
128
+ timeout: options.timeout,
129
+ topic: options.topic,
130
+ messages: [
131
+ {
132
+ value: serialize_default({ params: options.params }),
133
+ headers: {
134
+ "echoes-event-id": (0, import_node_crypto.randomUUID)()
135
+ }
136
+ }
137
+ ]
138
+ });
139
+ }
140
+ async close() {
141
+ var _a, _b;
142
+ import_logger.logger.warn("Echoes: Stopping Kafka transport");
143
+ if (this.interval) clearInterval(this.interval);
144
+ await Promise.all([
145
+ (_a = this.consumer) == null ? void 0 : _a.disconnect(),
146
+ this.producerConnected ? (_b = this.producer) == null ? void 0 : _b.disconnect() : void 0
147
+ ]);
148
+ this.consumerStarted = false;
149
+ this.producerConnected = false;
163
150
  }
164
151
  async checkJoinConsumerGroupConditions() {
165
152
  const admin = this.kafka.admin();
166
153
  try {
167
154
  await admin.connect();
168
- const groupDescriptions = await admin.describeGroups([this.options.consumer.groupId]);
155
+ const groupId = this.options.consumer.groupId;
156
+ const groupDescriptions = await admin.describeGroups([groupId]);
169
157
  const group = groupDescriptions.groups[0];
170
158
  if (group.state === "Empty") {
171
- import_logger2.logger.info(`Echoes: Consumer group ${this.options.consumer.groupId} is empty, joining`);
159
+ import_logger.logger.info(`Echoes: Consumer group ${groupId} is empty, joining`);
172
160
  return true;
173
161
  }
174
162
  const topicsMetadata = await admin.fetchTopicMetadata({ topics: this.topics });
@@ -176,23 +164,24 @@ var KafkaManager = class {
176
164
  (acc, topic) => acc + topic.partitions.length,
177
165
  0
178
166
  );
179
- import_logger2.logger.info(
180
- `Echoes: Consumer group ${this.options.consumer.groupId} has ${group.members.length} members and ${totalPartitions} partitions`
167
+ import_logger.logger.info(
168
+ `Echoes: Consumer group ${groupId} has ${group.members.length} members and ${totalPartitions} partitions`
181
169
  );
182
170
  const partitionsRatio = this.options.membersToPartitionsRatio || DEFAULT_MEMBERS_TO_PARTITIONS_RATIO;
183
171
  const partitionsThreshold = Math.ceil(totalPartitions * partitionsRatio);
184
172
  if (partitionsThreshold > group.members.length) {
185
- import_logger2.logger.info(
186
- `Echoes: Consumer group ${this.options.consumer.groupId} has room for more members ${group.members.length}/${partitionsThreshold}, joining`
173
+ import_logger.logger.info(
174
+ `Echoes: Consumer group ${groupId} has room for more members ${group.members.length}/${partitionsThreshold}, joining`
187
175
  );
188
176
  return true;
189
177
  }
178
+ return false;
190
179
  } catch (error) {
191
- import_logger2.logger.error("Echoes: Error checking consumer group conditions, join", { error });
180
+ import_logger.logger.error("Echoes: Error checking consumer group conditions, join", { error });
192
181
  return true;
193
182
  } finally {
194
183
  await admin.disconnect().catch((error) => {
195
- import_logger2.logger.error("Echoes: Error disconnecting admin client", { error });
184
+ import_logger.logger.error("Echoes: Error disconnecting admin client", { error });
196
185
  });
197
186
  }
198
187
  }
@@ -209,59 +198,68 @@ var KafkaManager = class {
209
198
  await this.joinConsumerGroup();
210
199
  return true;
211
200
  }
212
- }
213
- async start() {
214
- if (this.started) return;
215
- await this.producer.connect();
216
- this.started = await this.conditionalStart();
217
- if (this.started) return;
218
- import_logger2.logger.info("Echoes: Delaying consumer group join, waiting for conditions to be met");
219
- this.interval = setInterval(async () => {
220
- this.started = await this.conditionalStart();
221
- if (this.started) clearInterval(this.interval);
222
- }, CHECK_JOIN_CONSUMER_INTERVAL_SECONDS * 1e3);
223
- }
224
- async stop() {
225
- import_logger2.logger.warn("Echoes: Stopping echoes");
226
- if (this.interval) clearInterval(this.interval);
227
- if (this.consumer) await this.consumer.disconnect();
228
- if (this.producer) await this.producer.disconnect();
201
+ return false;
229
202
  }
230
203
  async handleMessage(params) {
231
- const echo2 = this.options.echoes[params.topic];
232
- if (!echo2 || echo2.type !== "event") {
233
- import_logger2.logger.warn(`Echoes: Received a message for an unknown topic: ${params.topic}, ignoring it`);
204
+ const subscription = this.subscriptions.get(params.topic);
205
+ if (!subscription) {
206
+ import_logger.logger.warn(`Echoes: Received a message for an unknown topic: ${params.topic}, ignoring it`);
234
207
  return;
235
208
  }
236
209
  let intervalsCount = 0;
237
210
  const heartbeatInterval = setInterval(async () => {
238
211
  await params.heartbeat().catch((error) => {
239
- import_logger2.logger.warn(`Echoes: Error sending heartbeat: ${error.message}`);
212
+ import_logger.logger.warn(`Echoes: Error sending heartbeat: ${error.message}`);
240
213
  });
241
214
  intervalsCount++;
242
215
  if (intervalsCount * HEARTBEAT_INTERVAL_SECONDS % 30 === 0) {
243
- import_logger2.logger.warn(
216
+ import_logger.logger.warn(
244
217
  `Echoes: Event is taking too long to process: ${params.topic} ${intervalsCount * HEARTBEAT_INTERVAL_SECONDS}s`
245
218
  );
246
219
  }
247
220
  }, HEARTBEAT_INTERVAL_SECONDS * 1e3);
248
221
  try {
249
- await echo2.onMessage(params).catch((error) => this.handleRetries(echo2, params, error));
222
+ const event = this.createReceivedEvent(params);
223
+ await this.onEvent(event);
250
224
  } catch (error) {
251
- import_logger2.logger.error("Echoes: error processing a message", { error, topic: params.topic });
252
- throw error;
225
+ try {
226
+ await this.handleRetries(subscription, params, error);
227
+ } catch (retryError) {
228
+ import_logger.logger.error("Echoes: error processing a message", {
229
+ error: retryError,
230
+ topic: params.topic
231
+ });
232
+ throw retryError;
233
+ }
253
234
  } finally {
254
235
  clearInterval(heartbeatInterval);
255
236
  }
256
237
  }
257
- async handleRetries(echo2, params, error) {
238
+ createReceivedEvent(params) {
239
+ var _a, _b, _c, _d;
240
+ const { message, topic, partition } = params;
241
+ const data = deserialize_default(message.value.toString());
242
+ const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
243
+ const timestamp = Number.parseInt(message.timestamp || "", 10);
244
+ return {
245
+ id: ((_d = (_c = message.headers) == null ? void 0 : _c["echoes-event-id"]) == null ? void 0 : _d.toString()) || `kafka:${topic}:${partition}:${message.offset}`,
246
+ topic,
247
+ data,
248
+ transport: "kafka",
249
+ headers: message.headers,
250
+ createdAt: Number.isFinite(timestamp) ? new Date(timestamp) : /* @__PURE__ */ new Date(),
251
+ attempt: retries + 1,
252
+ context: params
253
+ };
254
+ }
255
+ async handleRetries(subscription, params, error) {
258
256
  var _a, _b;
259
257
  const { message, topic } = params;
260
258
  const retries = Number.parseInt(((_b = (_a = message == null ? void 0 : message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
261
- if (echo2.attemptsBeforeDeadLetter === void 0 || echo2.attemptsBeforeDeadLetter === null) {
259
+ if (subscription.attemptsBeforeDeadLetter === void 0 || subscription.attemptsBeforeDeadLetter === null) {
262
260
  throw error;
263
261
  }
264
- const maxRetries = echo2.attemptsBeforeDeadLetter || 0;
262
+ const maxRetries = subscription.attemptsBeforeDeadLetter || 0;
265
263
  const exceededMaxRetries = retries >= maxRetries;
266
264
  const nextTopic = exceededMaxRetries ? `DLQ-${topic}` : topic;
267
265
  await this.producer.send({
@@ -270,6 +268,7 @@ var KafkaManager = class {
270
268
  {
271
269
  value: message.value.toString(),
272
270
  headers: {
271
+ ...message.headers,
273
272
  retries: String(retries + 1),
274
273
  error: error.message
275
274
  }
@@ -277,59 +276,322 @@ var KafkaManager = class {
277
276
  ]
278
277
  });
279
278
  if (exceededMaxRetries) {
280
- import_logger2.logger.error(
279
+ import_logger.logger.error(
281
280
  "Echoes: a message has reached the maximum number of retries, sending it to DLQ",
282
281
  { topic: nextTopic }
283
282
  );
284
283
  } else {
285
- import_logger2.logger.warn("Echoes: a retryable message failed", { error, topic: nextTopic });
284
+ import_logger.logger.warn("Echoes: a retryable message failed", { error, topic: nextTopic });
286
285
  }
287
286
  }
288
287
  };
289
288
  var KafkaManager_default = KafkaManager;
290
289
 
290
+ // src/events/EventBus.ts
291
+ var import_logger2 = require("@orion-js/logger");
292
+ var EventBus = class {
293
+ echoes;
294
+ transports;
295
+ consumeFrom;
296
+ publishTo;
297
+ startedTransports = [];
298
+ started = false;
299
+ closed = false;
300
+ constructor(options) {
301
+ this.echoes = options.echoes;
302
+ this.transports = options.transports;
303
+ this.consumeFrom = [...new Set(options.consumeFrom)];
304
+ this.publishTo = options.publishTo;
305
+ }
306
+ async start() {
307
+ if (this.started) return;
308
+ const selected = new Set(this.consumeFrom);
309
+ if (this.publishTo) selected.add(this.publishTo);
310
+ for (const name of selected) {
311
+ if (!this.transports[name]) {
312
+ throw new Error(`Echoes event transport "${name}" is selected but not configured`);
313
+ }
314
+ }
315
+ const subscriptions = Object.entries(this.echoes).filter(([, echo2]) => echo2.type === "event").map(([topic, echo2]) => ({
316
+ topic,
317
+ attemptsBeforeDeadLetter: echo2.attemptsBeforeDeadLetter
318
+ }));
319
+ try {
320
+ for (const name of selected) {
321
+ const transport = this.transports[name];
322
+ this.startedTransports.push(transport);
323
+ await transport.start({
324
+ consume: this.consumeFrom.includes(name),
325
+ publish: this.publishTo === name,
326
+ subscriptions,
327
+ onEvent: (event) => this.handleEvent(event)
328
+ });
329
+ }
330
+ this.started = true;
331
+ } catch (error) {
332
+ await this.close().catch(() => void 0);
333
+ throw error;
334
+ }
335
+ }
336
+ async publish(options) {
337
+ if (!this.started) {
338
+ throw new Error("You must initialize echoes configuration to use publish");
339
+ }
340
+ if (!this.publishTo) {
341
+ throw new Error("Echoes does not have a publish transport configured");
342
+ }
343
+ return await this.transports[this.publishTo].publish(options);
344
+ }
345
+ async close() {
346
+ if (this.closed) return;
347
+ this.closed = true;
348
+ await Promise.all(this.startedTransports.map((transport) => transport.close()));
349
+ }
350
+ async handleEvent(event) {
351
+ const echo2 = this.echoes[event.topic];
352
+ if (!echo2 || echo2.type !== "event") {
353
+ import_logger2.logger.warn(`Echoes: Received a message for an unknown topic: ${event.topic}, ignoring it`);
354
+ return;
355
+ }
356
+ if (event.transport === "kafka" && event.context) {
357
+ await echo2.onMessage(event.context);
358
+ return;
359
+ }
360
+ if (echo2.onEvent) {
361
+ await echo2.onEvent(event);
362
+ return;
363
+ }
364
+ await echo2.resolve(event.data.params, {
365
+ ...event.context || {},
366
+ transport: event.transport,
367
+ eventId: event.id,
368
+ topic: event.topic,
369
+ headers: event.headers,
370
+ createdAt: event.createdAt,
371
+ attempt: event.attempt,
372
+ data: event.data
373
+ });
374
+ }
375
+ };
376
+
377
+ // src/events/PulseManager.ts
378
+ var PulseManager = class {
379
+ name = "pulse";
380
+ options;
381
+ pulse;
382
+ subscriptions = [];
383
+ constructor(options) {
384
+ this.options = options;
385
+ }
386
+ async start(options) {
387
+ let pulseModule;
388
+ try {
389
+ pulseModule = await import("@orion-js/pulse");
390
+ } catch (error) {
391
+ const wrapped = new Error(
392
+ "Echoes Pulse transport requires @orion-js/pulse to be installed in the application"
393
+ );
394
+ wrapped.cause = error;
395
+ throw wrapped;
396
+ }
397
+ const { subscription, ...connectOptions } = this.options;
398
+ this.pulse = pulseModule.connect(connectOptions);
399
+ await this.pulse.awaitConnection();
400
+ if (!options.consume) return;
401
+ this.subscriptions = await Promise.all(
402
+ options.subscriptions.map(
403
+ (definition) => this.pulse.subscribe(
404
+ definition.topic,
405
+ (event) => options.onEvent(this.createReceivedEvent(event)),
406
+ this.getSubscribeOptions(subscription, definition.attemptsBeforeDeadLetter)
407
+ )
408
+ )
409
+ );
410
+ }
411
+ async publish(options) {
412
+ if (!this.pulse) {
413
+ throw new Error("Echoes Pulse client is not connected");
414
+ }
415
+ return await this.pulse.publish({
416
+ topic: options.topic,
417
+ data: { params: options.params }
418
+ });
419
+ }
420
+ async close() {
421
+ var _a;
422
+ await ((_a = this.pulse) == null ? void 0 : _a.close());
423
+ this.subscriptions = [];
424
+ }
425
+ getSubscribeOptions(defaults = {}, attemptsBeforeDeadLetter) {
426
+ return {
427
+ ...defaults,
428
+ ...attemptsBeforeDeadLetter === void 0 ? {} : { maxRetries: attemptsBeforeDeadLetter }
429
+ };
430
+ }
431
+ createReceivedEvent(event) {
432
+ return {
433
+ id: event.id,
434
+ topic: event.topic,
435
+ data: event.data,
436
+ transport: "pulse",
437
+ headers: event.headers,
438
+ createdAt: event.createdAt,
439
+ attempt: event.attempt,
440
+ context: event
441
+ };
442
+ }
443
+ };
444
+
445
+ // src/events/createEventBus.ts
446
+ function resolveEventsConfig(options) {
447
+ var _a, _b, _c, _d;
448
+ const legacyKafka = options.client ? {
449
+ client: options.client,
450
+ producer: options.producer,
451
+ consumer: options.consumer,
452
+ readTopicsFromBeginning: options.readTopicsFromBeginning,
453
+ partitionsConsumedConcurrently: options.partitionsConsumedConcurrently,
454
+ membersToPartitionsRatio: options.membersToPartitionsRatio
455
+ } : void 0;
456
+ const kafka = ((_a = options.events) == null ? void 0 : _a.kafka) || legacyKafka;
457
+ const pulse = (_b = options.events) == null ? void 0 : _b.pulse;
458
+ if (!kafka && !pulse && !options.events) return void 0;
459
+ const defaultTransport = kafka ? "kafka" : pulse ? "pulse" : void 0;
460
+ return {
461
+ kafka,
462
+ pulse,
463
+ consumeFrom: ((_c = options.events) == null ? void 0 : _c.consumeFrom) || (defaultTransport ? [defaultTransport] : []),
464
+ publishTo: ((_d = options.events) == null ? void 0 : _d.publishTo) || defaultTransport
465
+ };
466
+ }
467
+ function createEventBus(options) {
468
+ const resolved = resolveEventsConfig(options);
469
+ if (!resolved) return void 0;
470
+ const transports = {};
471
+ if (resolved.kafka) transports.kafka = new KafkaManager_default(resolved.kafka);
472
+ if (resolved.pulse) transports.pulse = new PulseManager(resolved.pulse);
473
+ return new EventBus({
474
+ echoes: options.echoes,
475
+ transports,
476
+ consumeFrom: resolved.consumeFrom,
477
+ publishTo: resolved.publishTo
478
+ });
479
+ }
480
+
481
+ // src/requestsHandler/getEcho.ts
482
+ function getEcho_default(method) {
483
+ const echo2 = config_default.echoes[method];
484
+ if (!echo2) {
485
+ throw new Error(`Echo named ${method} not found in this service`);
486
+ }
487
+ if (echo2.type !== "request") {
488
+ throw new Error(`Echo named ${method} is not of type request`);
489
+ }
490
+ return echo2;
491
+ }
492
+
493
+ // src/request/getSignature.ts
494
+ var import_jssha = __toESM(require("jssha"), 1);
495
+
496
+ // src/request/getPassword.ts
497
+ var import_logger3 = require("@orion-js/logger");
498
+ var import_env = require("@orion-js/env");
499
+ function getEchoesPassword() {
500
+ var _a, _b;
501
+ const secret = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.key) || (0, import_env.internalGetEnv)("echoes_password", "ECHOES_PASSWORD");
502
+ if (!secret) {
503
+ import_logger3.logger.warn(
504
+ 'Warning: no secret key found for echoes requests. Init echoes or set the env var "echoes_password" or process.env.ECHOES_PASSWORD'
505
+ );
506
+ }
507
+ return secret;
508
+ }
509
+
510
+ // src/request/getSignature.ts
511
+ function getSignature_default(body) {
512
+ const password = getEchoesPassword();
513
+ const shaObj = new import_jssha.default("SHA-1", "TEXT");
514
+ shaObj.setHMACKey(password, "TEXT");
515
+ shaObj.update(body);
516
+ return shaObj.getHMAC("HEX");
517
+ }
518
+
519
+ // src/requestsHandler/checkSignature.ts
520
+ function checkSignature_default(body, signature) {
521
+ const generatedSignature = getSignature_default(body);
522
+ if (generatedSignature !== signature) {
523
+ throw new Error("Echoes invalid signature");
524
+ }
525
+ }
526
+
527
+ // src/requestsHandler/index.ts
528
+ var import_http = require("@orion-js/http");
529
+ var requestsHandler_default = (options) => (0, import_http.route)({
530
+ method: "post",
531
+ path: options.requests.handlerPath || "/echoes-services",
532
+ bodyParser: "json",
533
+ bodyParserOptions: {
534
+ limit: "10mb"
535
+ },
536
+ async resolve(req) {
537
+ try {
538
+ const { body, signature } = req.body;
539
+ checkSignature_default(body, signature);
540
+ const { method, serializedParams } = body;
541
+ const echo2 = getEcho_default(method);
542
+ const result = await echo2.onRequest(serializedParams);
543
+ return {
544
+ body: {
545
+ result: serialize_default(result)
546
+ }
547
+ };
548
+ } catch (error) {
549
+ if (!error.getInfo) {
550
+ console.error("Error at echo requests handler:", error);
551
+ }
552
+ return {
553
+ body: {
554
+ error: error.message,
555
+ errorInfo: error.getInfo ? error.getInfo() : null,
556
+ isValidationError: !!error.isValidationError,
557
+ isUserError: !!error.isUserError
558
+ }
559
+ };
560
+ }
561
+ }
562
+ });
563
+
291
564
  // src/startService/index.ts
292
- var import_http2 = require("@orion-js/http");
293
- var kafkaManager = null;
565
+ var eventBus = null;
294
566
  async function startService(options) {
295
567
  config_default.echoes = options.echoes;
296
568
  if (options.requests) {
297
569
  config_default.requests = options.requests;
298
570
  (0, import_http2.registerRoute)(requestsHandler_default(options));
299
571
  }
300
- if (options.client) {
301
- kafkaManager = new KafkaManager_default(options);
302
- await kafkaManager.start();
303
- config_default.producer = kafkaManager.producer;
304
- config_default.consumer = kafkaManager.consumer;
572
+ const nextEventBus = createEventBus(options);
573
+ if (nextEventBus) {
574
+ await nextEventBus.start();
575
+ eventBus = nextEventBus;
576
+ config_default.eventBus = eventBus;
305
577
  }
306
578
  }
307
579
  async function stopService() {
308
- if (kafkaManager) {
580
+ if (eventBus) {
309
581
  console.info("Stoping echoes...");
310
- await kafkaManager.stop();
582
+ await eventBus.close();
583
+ eventBus = null;
584
+ config_default.eventBus = void 0;
311
585
  console.info("Echoes stopped");
312
586
  }
313
587
  }
314
588
 
315
589
  // src/publish/index.ts
316
590
  async function publish(options) {
317
- if (!config_default.producer) {
318
- throw new Error("You must initialize echoes configruation to use publish");
591
+ if (!config_default.eventBus) {
592
+ throw new Error("You must initialize echoes configuration to use publish");
319
593
  }
320
- const payload = {
321
- params: options.params
322
- };
323
- return await config_default.producer.send({
324
- acks: options.acks,
325
- timeout: options.timeout,
326
- topic: options.topic,
327
- messages: [
328
- {
329
- value: serialize_default(payload)
330
- }
331
- ]
332
- });
594
+ return await config_default.eventBus.publish(options);
333
595
  }
334
596
 
335
597
  // src/request/getURL.ts
@@ -343,15 +605,6 @@ function getURL_default(serviceName) {
343
605
  return url;
344
606
  }
345
607
 
346
- // src/echo/deserialize.ts
347
- function deserialize_default(serializedJavascript) {
348
- try {
349
- return eval("(" + serializedJavascript + ")");
350
- } catch (error) {
351
- throw new Error("Error deserializing echo message");
352
- }
353
- }
354
-
355
608
  // src/request/makeRequest.ts
356
609
  var import_axios = __toESM(require("axios"), 1);
357
610
  var import_helpers2 = require("@orion-js/helpers");
@@ -424,7 +677,7 @@ async function request(options) {
424
677
  }
425
678
 
426
679
  // src/service/index.ts
427
- var import_logger3 = require("@orion-js/logger");
680
+ var import_logger4 = require("@orion-js/logger");
428
681
 
429
682
  // src/echo/index.ts
430
683
  var import_schema2 = require("@orion-js/schema");
@@ -437,20 +690,44 @@ var echo = function createNewEcho(options) {
437
690
  }
438
691
  return result;
439
692
  };
693
+ const onEvent = async (event) => {
694
+ const context = {
695
+ ...event.context || {},
696
+ transport: event.transport,
697
+ eventId: event.id,
698
+ topic: event.topic,
699
+ headers: event.headers,
700
+ createdAt: event.createdAt,
701
+ attempt: event.attempt,
702
+ data: event.data
703
+ };
704
+ await resolve(event.data.params, context);
705
+ };
440
706
  return {
441
707
  type: options.type,
442
708
  params: options.params,
443
709
  returns: options.returns,
444
710
  attemptsBeforeDeadLetter: options.type === "event" ? options.attemptsBeforeDeadLetter : void 0,
445
711
  resolve,
712
+ onEvent,
446
713
  onMessage: async (messageData) => {
714
+ var _a, _b, _c, _d;
447
715
  const { message } = messageData;
448
716
  const data = deserialize_default(message.value.toString());
449
- const context = {
450
- ...messageData,
451
- data
452
- };
453
- await resolve(data.params, context);
717
+ const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
718
+ const timestamp = Number(message.timestamp);
719
+ const createdAt = Number.isFinite(timestamp) ? new Date(timestamp) : /* @__PURE__ */ new Date();
720
+ const eventId = ((_d = (_c = message.headers) == null ? void 0 : _c["echoes-event-id"]) == null ? void 0 : _d.toString()) || `kafka:${messageData.topic}:${messageData.partition}:${message.offset}`;
721
+ await onEvent({
722
+ id: eventId,
723
+ topic: messageData.topic,
724
+ data,
725
+ transport: "kafka",
726
+ headers: message.headers,
727
+ createdAt,
728
+ attempt: retries + 1,
729
+ context: messageData
730
+ });
454
731
  },
455
732
  onRequest: async (serializedParams) => {
456
733
  const context = {};
@@ -491,7 +768,7 @@ function EchoEvent(options = {}) {
491
768
  return createEchoEvent({
492
769
  ...options,
493
770
  resolve: async (params, contextData) => {
494
- return await (0, import_logger3.runWithOrionAsyncContext)(
771
+ return await (0, import_logger4.runWithOrionAsyncContext)(
495
772
  {
496
773
  controllerType: "echo",
497
774
  echoName: propertyKey,
@@ -520,7 +797,7 @@ function EchoRequest(options = {}) {
520
797
  return createEchoRequest({
521
798
  ...options,
522
799
  resolve: async (params, contextData) => {
523
- return await (0, import_logger3.runWithOrionAsyncContext)(
800
+ return await (0, import_logger4.runWithOrionAsyncContext)(
524
801
  {
525
802
  controllerType: "echo",
526
803
  echoName: propertyKey,