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