@orion-js/echoes 4.4.0 → 4.5.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/dist/index.cjs CHANGED
@@ -32,14 +32,26 @@ __export(index_exports, {
32
32
  EchoEvent: () => EchoEvent,
33
33
  EchoRequest: () => EchoRequest,
34
34
  Echoes: () => Echoes,
35
+ EchoesUserError: () => EchoesUserError,
36
+ EchoesValidationError: () => EchoesValidationError,
37
+ cleanEchoesSchema: () => cleanEchoesSchema,
38
+ configureEchoesRuntime: () => configureEchoesRuntime,
35
39
  createEchoEvent: () => createEchoEvent,
36
40
  createEchoRequest: () => createEchoRequest,
41
+ createEchoesUserError: () => createEchoesUserError,
42
+ createEchoesValidationError: () => createEchoesValidationError,
37
43
  echo: () => echo,
44
+ getEchoesContext: () => getEchoesContext,
45
+ getEchoesLogger: () => getEchoesLogger,
46
+ getEchoesRuntime: () => getEchoesRuntime,
38
47
  getServiceEchoes: () => getServiceEchoes,
48
+ parseEchoesSchema: () => parseEchoesSchema,
39
49
  publish: () => publish,
40
50
  request: () => request,
51
+ runWithEchoesContext: () => runWithEchoesContext,
41
52
  startService: () => startService,
42
- stopService: () => stopService
53
+ stopService: () => stopService,
54
+ typedEchoesSchema: () => typedEchoesSchema
43
55
  });
44
56
  module.exports = __toCommonJS(index_exports);
45
57
 
@@ -47,38 +59,175 @@ module.exports = __toCommonJS(index_exports);
47
59
  var config = {};
48
60
  var config_default = config;
49
61
 
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`);
62
+ // src/publish/index.ts
63
+ async function publish(options) {
64
+ if (!config_default.eventBus) {
65
+ throw new Error("You must initialize echoes configuration to use publish");
55
66
  }
56
- if (echo2.type !== "request") {
57
- throw new Error(`Echo named ${method} is not of type request`);
67
+ return await config_default.eventBus.publish(options);
68
+ }
69
+
70
+ // src/echo/deserialize.ts
71
+ function deserialize_default(serializedJavascript) {
72
+ try {
73
+ return eval("(" + serializedJavascript + ")");
74
+ } catch (error) {
75
+ throw new Error("Error deserializing echo message");
58
76
  }
59
- return echo2;
60
77
  }
61
78
 
62
79
  // src/publish/serialize.ts
63
80
  var import_serialize_javascript = __toESM(require("serialize-javascript"), 1);
64
- var import_helpers = require("@orion-js/helpers");
65
81
  function serialize_default(data) {
66
- const cloned = (0, import_helpers.clone)(data);
67
- const serialized = (0, import_serialize_javascript.default)(cloned, { ignoreFunction: true });
82
+ const serialized = (0, import_serialize_javascript.default)(data, { ignoreFunction: true });
68
83
  return serialized;
69
84
  }
70
85
 
86
+ // src/runtime.ts
87
+ var import_node_async_hooks = require("async_hooks");
88
+ var import_node_crypto = require("crypto");
89
+
90
+ // src/errors.ts
91
+ var EchoesUserError = class extends Error {
92
+ isEchoesError = true;
93
+ isOrionError = true;
94
+ isUserError = true;
95
+ code;
96
+ extra;
97
+ constructor(code, message, extra) {
98
+ if (!message) {
99
+ message = code;
100
+ code = "error";
101
+ }
102
+ super(message);
103
+ this.name = "EchoesUserError";
104
+ this.code = code;
105
+ this.extra = extra;
106
+ }
107
+ getInfo() {
108
+ return { error: this.code, message: this.message, extra: this.extra };
109
+ }
110
+ };
111
+ var EchoesValidationError = class extends Error {
112
+ isEchoesError = true;
113
+ isOrionError = true;
114
+ isValidationError = true;
115
+ code = "validationError";
116
+ validationErrors;
117
+ labels;
118
+ constructor(validationErrors, labels = {}) {
119
+ const printableErrors = Object.entries(validationErrors).map(([key, value]) => `${key}: ${value}`).join(", ");
120
+ super(`Validation Error: {${printableErrors}}`);
121
+ this.name = "EchoesValidationError";
122
+ this.validationErrors = validationErrors;
123
+ this.labels = Object.fromEntries(
124
+ Object.keys(validationErrors).filter((key) => labels[key]).map((key) => [key, labels[key]])
125
+ );
126
+ }
127
+ getInfo() {
128
+ return {
129
+ error: this.code,
130
+ message: "Validation Error",
131
+ validationErrors: this.validationErrors,
132
+ labels: this.labels
133
+ };
134
+ }
135
+ };
136
+
137
+ // src/runtime.ts
138
+ var defaultLogger = {
139
+ debug: (message, metadata) => metadata === void 0 ? console.debug(message) : console.debug(message, metadata),
140
+ info: (message, metadata) => metadata === void 0 ? console.info(message) : console.info(message, metadata),
141
+ warn: (message, metadata) => metadata === void 0 ? console.warn(message) : console.warn(message, metadata),
142
+ error: (message, metadata) => metadata === void 0 ? console.error(message) : console.error(message, metadata)
143
+ };
144
+ var runtime = {};
145
+ var contextStorage = new import_node_async_hooks.AsyncLocalStorage();
146
+ function configureEchoesRuntime(adapter) {
147
+ const previous = runtime;
148
+ runtime = { ...runtime, ...adapter };
149
+ return () => {
150
+ runtime = previous;
151
+ };
152
+ }
153
+ function getEchoesRuntime() {
154
+ return runtime;
155
+ }
156
+ function getEchoesLogger() {
157
+ return runtime.logger || defaultLogger;
158
+ }
159
+ function getEchoesContext() {
160
+ return contextStorage.getStore();
161
+ }
162
+ async function runWithEchoesContext(context, callback) {
163
+ const contextWithId = { contextId: context.contextId || (0, import_node_crypto.randomUUID)(), ...context };
164
+ return await contextStorage.run(contextWithId, async () => {
165
+ if (runtime.runWithContext) {
166
+ return await runtime.runWithContext(contextWithId, callback);
167
+ }
168
+ return await callback();
169
+ });
170
+ }
171
+ function isSimpleSchemaLike(schema) {
172
+ return !!schema && typeof schema === "object" && typeof schema.clean === "function" && typeof schema.validate === "function";
173
+ }
174
+ function cloneValue(value) {
175
+ if (value === null || typeof value !== "object") return value;
176
+ if (value instanceof Date) return new Date(value.getTime());
177
+ if (Buffer.isBuffer(value)) return Buffer.from(value);
178
+ if (Array.isArray(value)) return value.map(cloneValue);
179
+ const prototype = Object.getPrototypeOf(value);
180
+ if (prototype !== Object.prototype && prototype !== null) return value;
181
+ const result = {};
182
+ for (const [key, child] of Object.entries(value)) {
183
+ result[key] = cloneValue(child);
184
+ }
185
+ return result;
186
+ }
187
+ async function cleanSimpleSchema(schema, value) {
188
+ const cloned = cloneValue(value);
189
+ return await schema.clean(cloned, { mutate: false });
190
+ }
191
+ async function cleanEchoesSchema(schema, value) {
192
+ if (isSimpleSchemaLike(schema)) {
193
+ return await cleanSimpleSchema(schema, value);
194
+ }
195
+ if (runtime.schema) {
196
+ return await runtime.schema.clean(schema, value);
197
+ }
198
+ throw new Error(
199
+ "Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
200
+ );
201
+ }
202
+ async function parseEchoesSchema(schema, value) {
203
+ if (isSimpleSchemaLike(schema)) {
204
+ const cleaned = await cleanSimpleSchema(schema, value);
205
+ await schema.validate(cleaned);
206
+ return cleaned;
207
+ }
208
+ if (runtime.schema) {
209
+ return await runtime.schema.parse(schema, value);
210
+ }
211
+ throw new Error(
212
+ "Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
213
+ );
214
+ }
215
+ function createEchoesValidationError(info) {
216
+ return runtime.createValidationError ? runtime.createValidationError(info) : new EchoesValidationError(info.validationErrors || {}, info.labels);
217
+ }
218
+ function createEchoesUserError(info) {
219
+ return runtime.createUserError ? runtime.createUserError(info) : new EchoesUserError(info.error, info.message, info.extra);
220
+ }
221
+
71
222
  // src/request/getSignature.ts
72
- var import_jssha = __toESM(require("jssha"), 1);
223
+ var import_node_crypto2 = require("crypto");
73
224
 
74
225
  // src/request/getPassword.ts
75
- var import_logger = require("@orion-js/logger");
76
- var import_env = require("@orion-js/env");
77
226
  function getEchoesPassword() {
78
227
  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");
228
+ const secret = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.key) || process.env.echoes_password || process.env.ECHOES_PASSWORD;
80
229
  if (!secret) {
81
- import_logger.logger.warn(
230
+ getEchoesLogger().warn(
82
231
  'Warning: no secret key found for echoes requests. Init echoes or set the env var "echoes_password" or process.env.ECHOES_PASSWORD'
83
232
  );
84
233
  }
@@ -86,89 +235,244 @@ function getEchoesPassword() {
86
235
  }
87
236
 
88
237
  // src/request/getSignature.ts
89
- function getSignature_default(body) {
238
+ function getSignature_default(_body) {
90
239
  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");
240
+ return (0, import_node_crypto2.createHmac)("sha1", password || "").update("").digest("hex");
95
241
  }
96
242
 
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");
243
+ // src/request/getURL.ts
244
+ function getURL_default(serviceName) {
245
+ var _a, _b, _c;
246
+ if (serviceName.startsWith("http")) return serviceName;
247
+ const url = (_c = (_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.services) == null ? void 0 : _c[serviceName];
248
+ if (!url) {
249
+ throw new Error(`No URL found in echoes config for service ${serviceName}`);
102
250
  }
251
+ return url;
103
252
  }
104
253
 
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)
254
+ // src/request/makeRequest.ts
255
+ var import_node_http = __toESM(require("http"), 1);
256
+ var import_node_https = __toESM(require("https"), 1);
257
+ async function executeWithRetries(callback, retries = 0, delayMs = 200) {
258
+ try {
259
+ return await callback();
260
+ } catch (error) {
261
+ if (retries <= 0) throw error;
262
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
263
+ return await executeWithRetries(callback, retries - 1, delayMs);
264
+ }
265
+ }
266
+ async function postJSON(urlValue, data, timeout, redirectsRemaining = 5) {
267
+ const url = new URL(urlValue);
268
+ const body = JSON.stringify(data);
269
+ const transport = url.protocol === "https:" ? import_node_https.default : import_node_http.default;
270
+ return await new Promise((resolve, reject) => {
271
+ const request2 = transport.request(
272
+ url,
273
+ {
274
+ method: "POST",
275
+ headers: {
276
+ "User-Agent": "Orionjs-Echoes/1.1",
277
+ "Content-Type": "application/json",
278
+ "Content-Length": Buffer.byteLength(body)
124
279
  }
125
- };
126
- } catch (error) {
127
- if (!error.getInfo) {
128
- console.error("Error at echo requests handler:", error);
280
+ },
281
+ (response) => {
282
+ const chunks = [];
283
+ const statusCode = response.statusCode || 0;
284
+ if (statusCode >= 300 && statusCode < 400 && response.headers.location) {
285
+ response.resume();
286
+ if (redirectsRemaining <= 0) {
287
+ reject(new Error("Echoes request exceeded the redirect limit"));
288
+ return;
289
+ }
290
+ resolve(
291
+ postJSON(
292
+ new URL(response.headers.location, url).toString(),
293
+ data,
294
+ timeout,
295
+ redirectsRemaining - 1
296
+ )
297
+ );
298
+ return;
299
+ }
300
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
301
+ response.on("end", () => {
302
+ try {
303
+ const responseBody = Buffer.concat(chunks).toString("utf8");
304
+ if (statusCode < 200 || statusCode >= 300) {
305
+ reject(new Error(`Request failed with status code ${statusCode}`));
306
+ return;
307
+ }
308
+ resolve({
309
+ statusCode,
310
+ data: responseBody ? JSON.parse(responseBody) : {}
311
+ });
312
+ } catch (error) {
313
+ reject(error);
314
+ }
315
+ });
129
316
  }
130
- return {
131
- body: {
132
- error: error.message,
133
- errorInfo: error.getInfo ? error.getInfo() : null,
134
- isValidationError: !!error.isValidationError,
135
- isUserError: !!error.isUserError
317
+ );
318
+ request2.on("error", reject);
319
+ if (timeout) {
320
+ request2.setTimeout(timeout, () => {
321
+ request2.destroy(new Error(`Echoes request timed out after ${timeout}ms`));
322
+ });
323
+ }
324
+ request2.end(body);
325
+ });
326
+ }
327
+ var makeRequest = async (options) => {
328
+ return await executeWithRetries(
329
+ () => postJSON(options.url, options.data, options.timeout),
330
+ options.retries || 0
331
+ );
332
+ };
333
+
334
+ // src/request/index.ts
335
+ async function request(options) {
336
+ var _a, _b;
337
+ const { method, service, params } = options;
338
+ const serializedParams = serialize_default(params);
339
+ const date = /* @__PURE__ */ new Date();
340
+ const body = { method, service, serializedParams, date };
341
+ const signature = getSignature_default(body);
342
+ try {
343
+ const requestMaker = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.makeRequest) || makeRequest;
344
+ const requestOptions = {
345
+ url: getURL_default(service),
346
+ retries: options.retries,
347
+ timeout: options.timeout,
348
+ data: {
349
+ body,
350
+ signature
351
+ }
352
+ };
353
+ const result = await requestMaker(requestOptions);
354
+ if (result.statusCode !== 200) {
355
+ throw new Error(`Wrong status code ${result.statusCode}`);
356
+ }
357
+ const data = result.data;
358
+ if (data.error) {
359
+ const info = data.errorInfo;
360
+ if (info) {
361
+ if (data.isValidationError) {
362
+ throw createEchoesValidationError(info);
136
363
  }
137
- };
364
+ if (data.isUserError) {
365
+ throw createEchoesUserError(info);
366
+ }
367
+ }
368
+ throw new Error(`${data.error}`);
138
369
  }
370
+ const response = deserialize_default(data.result);
371
+ return response;
372
+ } catch (error) {
373
+ const caught = error;
374
+ if (caught.isOrionError || caught.isEchoesError) throw caught;
375
+ throw new Error(`Echoes request network error calling ${service}/${method}: ${caught.message}`);
139
376
  }
140
- });
377
+ }
141
378
 
142
379
  // src/startService/KafkaManager.ts
143
- var import_kafkajs = require("kafkajs");
144
- var import_logger2 = require("@orion-js/logger");
380
+ var import_node_crypto3 = require("crypto");
145
381
  var HEARTBEAT_INTERVAL_SECONDS = 5;
146
382
  var CHECK_JOIN_CONSUMER_INTERVAL_SECONDS = 30;
147
383
  var DEFAULT_PARTITIONS_CONSUMED_CONCURRENTLY = 4;
148
384
  var DEFAULT_MEMBERS_TO_PARTITIONS_RATIO = 1;
149
385
  var KafkaManager = class {
386
+ name = "kafka";
150
387
  kafka;
151
388
  options;
152
389
  producer;
153
390
  consumer;
154
- topics;
155
- started;
391
+ topics = [];
392
+ subscriptions = /* @__PURE__ */ new Map();
393
+ onEvent;
394
+ consumerStarted = false;
395
+ producerConnected = false;
156
396
  interval;
157
397
  constructor(options) {
158
- this.kafka = new import_kafkajs.Kafka(options.client);
159
398
  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");
399
+ }
400
+ async start(options) {
401
+ var _a;
402
+ let kafkaModule;
403
+ try {
404
+ kafkaModule = await import("kafkajs");
405
+ } catch (error) {
406
+ const wrapped = new Error(
407
+ "Echoes Kafka transport requires kafkajs to be installed in the application"
408
+ );
409
+ wrapped.cause = error;
410
+ throw wrapped;
411
+ }
412
+ this.kafka = new kafkaModule.Kafka(this.options.client);
413
+ this.onEvent = options.onEvent;
414
+ this.subscriptions = new Map(
415
+ options.subscriptions.map((subscription) => [subscription.topic, subscription])
416
+ );
417
+ this.topics = options.subscriptions.map((subscription) => subscription.topic);
418
+ if (options.publish || options.consume) {
419
+ this.producer = this.kafka.producer(this.options.producer);
420
+ await this.producer.connect();
421
+ this.producerConnected = true;
422
+ }
423
+ if (!options.consume || this.topics.length === 0) return;
424
+ if (!((_a = this.options.consumer) == null ? void 0 : _a.groupId)) {
425
+ throw new Error("Echoes Kafka consumers require consumer.groupId");
426
+ }
427
+ this.consumer = this.kafka.consumer(this.options.consumer);
428
+ this.consumerStarted = await this.conditionalStart();
429
+ if (this.consumerStarted) return;
430
+ getEchoesLogger().info("Echoes: Delaying consumer group join, waiting for conditions to be met");
431
+ this.interval = setInterval(async () => {
432
+ this.consumerStarted = await this.conditionalStart();
433
+ if (this.consumerStarted) clearInterval(this.interval);
434
+ }, CHECK_JOIN_CONSUMER_INTERVAL_SECONDS * 1e3);
435
+ }
436
+ async publish(options) {
437
+ if (!this.producer || !this.producerConnected) {
438
+ throw new Error("Echoes Kafka producer is not connected");
439
+ }
440
+ return await this.producer.send({
441
+ acks: options.acks,
442
+ timeout: options.timeout,
443
+ topic: options.topic,
444
+ messages: [
445
+ {
446
+ value: serialize_default({ params: options.params }),
447
+ headers: {
448
+ "echoes-event-id": (0, import_node_crypto3.randomUUID)()
449
+ }
450
+ }
451
+ ]
452
+ });
453
+ }
454
+ async close() {
455
+ var _a, _b;
456
+ const logger = getEchoesLogger();
457
+ logger.warn("Echoes: Stopping Kafka transport");
458
+ if (this.interval) clearInterval(this.interval);
459
+ await Promise.all([
460
+ (_a = this.consumer) == null ? void 0 : _a.disconnect(),
461
+ this.producerConnected ? (_b = this.producer) == null ? void 0 : _b.disconnect() : void 0
462
+ ]);
463
+ this.consumerStarted = false;
464
+ this.producerConnected = false;
163
465
  }
164
466
  async checkJoinConsumerGroupConditions() {
467
+ const logger = getEchoesLogger();
165
468
  const admin = this.kafka.admin();
166
469
  try {
167
470
  await admin.connect();
168
- const groupDescriptions = await admin.describeGroups([this.options.consumer.groupId]);
471
+ const groupId = this.options.consumer.groupId;
472
+ const groupDescriptions = await admin.describeGroups([groupId]);
169
473
  const group = groupDescriptions.groups[0];
170
474
  if (group.state === "Empty") {
171
- import_logger2.logger.info(`Echoes: Consumer group ${this.options.consumer.groupId} is empty, joining`);
475
+ logger.info(`Echoes: Consumer group ${groupId} is empty, joining`);
172
476
  return true;
173
477
  }
174
478
  const topicsMetadata = await admin.fetchTopicMetadata({ topics: this.topics });
@@ -176,23 +480,24 @@ var KafkaManager = class {
176
480
  (acc, topic) => acc + topic.partitions.length,
177
481
  0
178
482
  );
179
- import_logger2.logger.info(
180
- `Echoes: Consumer group ${this.options.consumer.groupId} has ${group.members.length} members and ${totalPartitions} partitions`
483
+ logger.info(
484
+ `Echoes: Consumer group ${groupId} has ${group.members.length} members and ${totalPartitions} partitions`
181
485
  );
182
486
  const partitionsRatio = this.options.membersToPartitionsRatio || DEFAULT_MEMBERS_TO_PARTITIONS_RATIO;
183
487
  const partitionsThreshold = Math.ceil(totalPartitions * partitionsRatio);
184
488
  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`
489
+ logger.info(
490
+ `Echoes: Consumer group ${groupId} has room for more members ${group.members.length}/${partitionsThreshold}, joining`
187
491
  );
188
492
  return true;
189
493
  }
494
+ return false;
190
495
  } catch (error) {
191
- import_logger2.logger.error("Echoes: Error checking consumer group conditions, join", { error });
496
+ logger.error("Echoes: Error checking consumer group conditions, join", { error });
192
497
  return true;
193
498
  } finally {
194
499
  await admin.disconnect().catch((error) => {
195
- import_logger2.logger.error("Echoes: Error disconnecting admin client", { error });
500
+ logger.error("Echoes: Error disconnecting admin client", { error });
196
501
  });
197
502
  }
198
503
  }
@@ -209,67 +514,83 @@ var KafkaManager = class {
209
514
  await this.joinConsumerGroup();
210
515
  return true;
211
516
  }
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();
517
+ return false;
229
518
  }
230
519
  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`);
520
+ const logger = getEchoesLogger();
521
+ const subscription = this.subscriptions.get(params.topic);
522
+ if (!subscription) {
523
+ logger.warn(`Echoes: Received a message for an unknown topic: ${params.topic}, ignoring it`);
234
524
  return;
235
525
  }
236
526
  let intervalsCount = 0;
237
527
  const heartbeatInterval = setInterval(async () => {
238
528
  await params.heartbeat().catch((error) => {
239
- import_logger2.logger.warn(`Echoes: Error sending heartbeat: ${error.message}`);
529
+ logger.warn(`Echoes: Error sending heartbeat: ${error.message}`);
240
530
  });
241
531
  intervalsCount++;
242
532
  if (intervalsCount * HEARTBEAT_INTERVAL_SECONDS % 30 === 0) {
243
- import_logger2.logger.warn(
533
+ logger.warn(
244
534
  `Echoes: Event is taking too long to process: ${params.topic} ${intervalsCount * HEARTBEAT_INTERVAL_SECONDS}s`
245
535
  );
246
536
  }
247
537
  }, HEARTBEAT_INTERVAL_SECONDS * 1e3);
248
538
  try {
249
- await echo2.onMessage(params).catch((error) => this.handleRetries(echo2, params, error));
539
+ const event = this.createReceivedEvent(params);
540
+ await this.onEvent(event);
250
541
  } catch (error) {
251
- import_logger2.logger.error("Echoes: error processing a message", { error, topic: params.topic });
252
- throw error;
542
+ try {
543
+ await this.handleRetries(subscription, params, error);
544
+ } catch (retryError) {
545
+ logger.error("Echoes: error processing a message", {
546
+ error: retryError,
547
+ topic: params.topic
548
+ });
549
+ throw retryError;
550
+ }
253
551
  } finally {
254
552
  clearInterval(heartbeatInterval);
255
553
  }
256
554
  }
257
- async handleRetries(echo2, params, error) {
555
+ createReceivedEvent(params) {
556
+ var _a, _b, _c, _d;
557
+ const { message, topic, partition } = params;
558
+ if (!message.value) {
559
+ throw new Error(`Echoes received an empty Kafka message for ${topic}`);
560
+ }
561
+ const data = deserialize_default(message.value.toString());
562
+ const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
563
+ const timestamp = Number.parseInt(message.timestamp || "", 10);
564
+ return {
565
+ id: ((_d = (_c = message.headers) == null ? void 0 : _c["echoes-event-id"]) == null ? void 0 : _d.toString()) || `kafka:${topic}:${partition}:${message.offset}`,
566
+ topic,
567
+ data,
568
+ transport: "kafka",
569
+ headers: message.headers,
570
+ createdAt: Number.isFinite(timestamp) ? new Date(timestamp) : /* @__PURE__ */ new Date(),
571
+ attempt: retries + 1,
572
+ context: params
573
+ };
574
+ }
575
+ async handleRetries(subscription, params, error) {
258
576
  var _a, _b;
577
+ const logger = getEchoesLogger();
259
578
  const { message, topic } = params;
260
579
  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) {
580
+ if (subscription.attemptsBeforeDeadLetter === void 0 || subscription.attemptsBeforeDeadLetter === null) {
262
581
  throw error;
263
582
  }
264
- const maxRetries = echo2.attemptsBeforeDeadLetter || 0;
583
+ const maxRetries = subscription.attemptsBeforeDeadLetter || 0;
265
584
  const exceededMaxRetries = retries >= maxRetries;
266
585
  const nextTopic = exceededMaxRetries ? `DLQ-${topic}` : topic;
586
+ if (!message.value) throw error;
267
587
  await this.producer.send({
268
588
  topic: nextTopic,
269
589
  messages: [
270
590
  {
271
591
  value: message.value.toString(),
272
592
  headers: {
593
+ ...message.headers,
273
594
  retries: String(retries + 1),
274
595
  error: error.message
275
596
  }
@@ -277,180 +598,339 @@ var KafkaManager = class {
277
598
  ]
278
599
  });
279
600
  if (exceededMaxRetries) {
280
- import_logger2.logger.error(
601
+ logger.error(
281
602
  "Echoes: a message has reached the maximum number of retries, sending it to DLQ",
282
603
  { topic: nextTopic }
283
604
  );
284
605
  } else {
285
- import_logger2.logger.warn("Echoes: a retryable message failed", { error, topic: nextTopic });
606
+ logger.warn("Echoes: a retryable message failed", { error, topic: nextTopic });
286
607
  }
287
608
  }
288
609
  };
289
610
  var KafkaManager_default = KafkaManager;
290
611
 
291
- // src/startService/index.ts
292
- var import_http2 = require("@orion-js/http");
293
- var kafkaManager = null;
294
- async function startService(options) {
295
- config_default.echoes = options.echoes;
296
- if (options.requests) {
297
- config_default.requests = options.requests;
298
- (0, import_http2.registerRoute)(requestsHandler_default(options));
612
+ // src/events/EventBus.ts
613
+ var EventBus = class {
614
+ echoes;
615
+ transports;
616
+ consumeFrom;
617
+ publishTo;
618
+ startedTransports = [];
619
+ started = false;
620
+ closed = false;
621
+ constructor(options) {
622
+ this.echoes = options.echoes;
623
+ this.transports = options.transports;
624
+ this.consumeFrom = [...new Set(options.consumeFrom)];
625
+ this.publishTo = options.publishTo;
299
626
  }
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;
627
+ async start() {
628
+ if (this.started) return;
629
+ const selected = new Set(this.consumeFrom);
630
+ if (this.publishTo) selected.add(this.publishTo);
631
+ for (const name of selected) {
632
+ if (!this.transports[name]) {
633
+ throw new Error(`Echoes event transport "${name}" is selected but not configured`);
634
+ }
635
+ }
636
+ const subscriptions = Object.entries(this.echoes).filter(([, echo2]) => echo2.type === "event").map(([topic, echo2]) => ({
637
+ topic,
638
+ attemptsBeforeDeadLetter: echo2.attemptsBeforeDeadLetter
639
+ }));
640
+ try {
641
+ for (const name of selected) {
642
+ const transport = this.transports[name];
643
+ this.startedTransports.push(transport);
644
+ await transport.start({
645
+ consume: this.consumeFrom.includes(name),
646
+ publish: this.publishTo === name,
647
+ subscriptions,
648
+ onEvent: (event) => this.handleEvent(event)
649
+ });
650
+ }
651
+ this.started = true;
652
+ } catch (error) {
653
+ await this.close().catch(() => void 0);
654
+ throw error;
655
+ }
305
656
  }
306
- }
307
- async function stopService() {
308
- if (kafkaManager) {
309
- console.info("Stoping echoes...");
310
- await kafkaManager.stop();
311
- console.info("Echoes stopped");
657
+ async publish(options) {
658
+ if (!this.started) {
659
+ throw new Error("You must initialize echoes configuration to use publish");
660
+ }
661
+ if (!this.publishTo) {
662
+ throw new Error("Echoes does not have a publish transport configured");
663
+ }
664
+ return await this.transports[this.publishTo].publish(options);
312
665
  }
313
- }
666
+ async close() {
667
+ if (this.closed) return;
668
+ this.closed = true;
669
+ await Promise.all(this.startedTransports.map((transport) => transport.close()));
670
+ }
671
+ async handleEvent(event) {
672
+ const logger = getEchoesLogger();
673
+ const echo2 = this.echoes[event.topic];
674
+ if (!echo2 || echo2.type !== "event") {
675
+ logger.warn(`Echoes: Received a message for an unknown topic: ${event.topic}, ignoring it`);
676
+ return;
677
+ }
678
+ if (event.transport === "kafka" && event.context) {
679
+ await echo2.onMessage(event.context);
680
+ return;
681
+ }
682
+ if (echo2.onEvent) {
683
+ await echo2.onEvent(event);
684
+ return;
685
+ }
686
+ await echo2.resolve(event.data.params, {
687
+ ...event.context || {},
688
+ transport: event.transport,
689
+ eventId: event.id,
690
+ topic: event.topic,
691
+ headers: event.headers,
692
+ createdAt: event.createdAt,
693
+ attempt: event.attempt,
694
+ data: event.data
695
+ });
696
+ }
697
+ };
314
698
 
315
- // src/publish/index.ts
316
- async function publish(options) {
317
- if (!config_default.producer) {
318
- throw new Error("You must initialize echoes configruation to use publish");
699
+ // src/events/PulseManager.ts
700
+ var PulseManager = class {
701
+ name = "pulse";
702
+ options;
703
+ pulse;
704
+ subscriptions = [];
705
+ constructor(options) {
706
+ this.options = options;
707
+ }
708
+ async start(options) {
709
+ let pulseModule;
710
+ try {
711
+ pulseModule = await import("@orion-js/pulse");
712
+ } catch (error) {
713
+ const wrapped = new Error(
714
+ "Echoes Pulse transport requires @orion-js/pulse to be installed in the application"
715
+ );
716
+ wrapped.cause = error;
717
+ throw wrapped;
718
+ }
719
+ const { subscription, ...connectOptions } = this.options;
720
+ this.pulse = pulseModule.connect(connectOptions);
721
+ await this.pulse.awaitConnection();
722
+ if (!options.consume) return;
723
+ this.subscriptions = await Promise.all(
724
+ options.subscriptions.map(
725
+ (definition) => this.pulse.subscribe(
726
+ definition.topic,
727
+ (event) => options.onEvent(this.createReceivedEvent(event)),
728
+ this.getSubscribeOptions(subscription, definition.attemptsBeforeDeadLetter)
729
+ )
730
+ )
731
+ );
732
+ }
733
+ async publish(options) {
734
+ if (!this.pulse) {
735
+ throw new Error("Echoes Pulse client is not connected");
736
+ }
737
+ return await this.pulse.publish({
738
+ topic: options.topic,
739
+ data: { params: options.params }
740
+ });
741
+ }
742
+ async close() {
743
+ var _a;
744
+ await ((_a = this.pulse) == null ? void 0 : _a.close());
745
+ this.subscriptions = [];
746
+ }
747
+ getSubscribeOptions(defaults = {}, attemptsBeforeDeadLetter) {
748
+ return {
749
+ ...defaults,
750
+ ...attemptsBeforeDeadLetter === void 0 ? {} : { maxRetries: attemptsBeforeDeadLetter }
751
+ };
319
752
  }
320
- const payload = {
321
- params: options.params
753
+ createReceivedEvent(event) {
754
+ return {
755
+ id: event.id,
756
+ topic: event.topic,
757
+ data: event.data,
758
+ transport: "pulse",
759
+ headers: event.headers,
760
+ createdAt: event.createdAt,
761
+ attempt: event.attempt,
762
+ context: event
763
+ };
764
+ }
765
+ };
766
+
767
+ // src/events/createEventBus.ts
768
+ function resolveEventsConfig(options) {
769
+ var _a, _b, _c, _d;
770
+ const legacyKafka = options.client ? {
771
+ client: options.client,
772
+ producer: options.producer,
773
+ consumer: options.consumer,
774
+ readTopicsFromBeginning: options.readTopicsFromBeginning,
775
+ partitionsConsumedConcurrently: options.partitionsConsumedConcurrently,
776
+ membersToPartitionsRatio: options.membersToPartitionsRatio
777
+ } : void 0;
778
+ const kafka = ((_a = options.events) == null ? void 0 : _a.kafka) || legacyKafka;
779
+ const pulse = (_b = options.events) == null ? void 0 : _b.pulse;
780
+ if (!kafka && !pulse && !options.events) return void 0;
781
+ const defaultTransport = kafka ? "kafka" : pulse ? "pulse" : void 0;
782
+ return {
783
+ kafka,
784
+ pulse,
785
+ consumeFrom: ((_c = options.events) == null ? void 0 : _c.consumeFrom) || (defaultTransport ? [defaultTransport] : []),
786
+ publishTo: ((_d = options.events) == null ? void 0 : _d.publishTo) || defaultTransport
322
787
  };
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
- ]
788
+ }
789
+ function createEventBus(options) {
790
+ const resolved = resolveEventsConfig(options);
791
+ if (!resolved) return void 0;
792
+ const transports = {};
793
+ if (resolved.kafka) transports.kafka = new KafkaManager_default(resolved.kafka);
794
+ if (resolved.pulse) transports.pulse = new PulseManager(resolved.pulse);
795
+ return new EventBus({
796
+ echoes: options.echoes,
797
+ transports,
798
+ consumeFrom: resolved.consumeFrom,
799
+ publishTo: resolved.publishTo
332
800
  });
333
801
  }
334
802
 
335
- // src/request/getURL.ts
336
- function getURL_default(serviceName) {
337
- var _a, _b;
338
- if (serviceName.startsWith("http")) return serviceName;
339
- const url = (_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.services[serviceName];
340
- if (!url) {
341
- throw new Error(`No URL found in echoes config for service ${serviceName}`);
803
+ // src/requestsHandler/checkSignature.ts
804
+ function checkSignature_default(body, signature) {
805
+ const generatedSignature = getSignature_default(body);
806
+ if (generatedSignature !== signature) {
807
+ throw new Error("Echoes invalid signature");
342
808
  }
343
- return url;
344
809
  }
345
810
 
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");
811
+ // src/requestsHandler/getEcho.ts
812
+ function getEcho_default(method) {
813
+ const echo2 = config_default.echoes[method];
814
+ if (!echo2) {
815
+ throw new Error(`Echo named ${method} not found in this service`);
352
816
  }
817
+ if (echo2.type !== "request") {
818
+ throw new Error(`Echo named ${method} is not of type request`);
819
+ }
820
+ return echo2;
353
821
  }
354
822
 
355
- // src/request/makeRequest.ts
356
- var import_axios = __toESM(require("axios"), 1);
357
- var import_helpers2 = require("@orion-js/helpers");
358
- var makeRequest = async (options) => {
359
- const result = await (0, import_helpers2.executeWithRetries)(
360
- async () => {
361
- return await (0, import_axios.default)({
362
- method: "post",
363
- url: options.url,
364
- timeout: options.timeout,
365
- headers: {
366
- "User-Agent": "Orionjs-Echoes/1.1"
367
- },
368
- data: options.data
369
- });
370
- },
371
- options.retries,
372
- 200
373
- );
374
- return {
375
- data: result.data,
376
- statusCode: result.status
377
- };
378
- };
379
-
380
- // src/request/index.ts
381
- var import_schema = require("@orion-js/schema");
382
- var import_helpers3 = require("@orion-js/helpers");
383
- async function request(options) {
384
- var _a, _b;
385
- const { method, service, params } = options;
386
- const serializedParams = serialize_default(params);
387
- const date = /* @__PURE__ */ new Date();
388
- const body = { method, service, serializedParams, date };
389
- const signature = getSignature_default(body);
390
- try {
391
- const requestMaker = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.makeRequest) || makeRequest;
392
- const requestOptions = {
393
- url: getURL_default(service),
394
- retries: options.retries,
395
- timeout: options.timeout,
396
- data: {
397
- body,
398
- signature
823
+ // src/requestsHandler/index.ts
824
+ var requestsHandler_default = (options) => ({
825
+ method: "post",
826
+ path: options.requests.handlerPath || "/echoes-services",
827
+ bodyLimit: "10mb",
828
+ async handle(requestBody) {
829
+ try {
830
+ const { body, signature } = requestBody;
831
+ checkSignature_default(body, signature);
832
+ const { method, serializedParams } = body;
833
+ const echo2 = getEcho_default(method);
834
+ const result = await echo2.onRequest(serializedParams);
835
+ return { result: serialize_default(result) };
836
+ } catch (error) {
837
+ const caught = error;
838
+ if (!caught.getInfo) {
839
+ getEchoesLogger().error("Error at echo requests handler:", { error: caught });
399
840
  }
400
- };
401
- const result = await requestMaker(requestOptions);
402
- if (result.statusCode !== 200) {
403
- throw new Error(`Wrong status code ${result.statusCode}`);
841
+ return {
842
+ error: caught.message,
843
+ errorInfo: caught.getInfo ? caught.getInfo() : null,
844
+ isValidationError: !!caught.isValidationError,
845
+ isUserError: !!caught.isUserError
846
+ };
404
847
  }
405
- const data = result.data;
406
- if (data.error) {
407
- const info = data.errorInfo;
408
- if (info) {
409
- if (data.isValidationError) {
410
- throw new import_schema.ValidationError(info.validationErrors);
411
- }
412
- if (data.isUserError) {
413
- throw new import_helpers3.UserError(info.error, info.message, info.extra);
414
- }
415
- }
416
- throw new Error(`${data.error}`);
848
+ }
849
+ });
850
+
851
+ // src/startService/index.ts
852
+ var eventBus = null;
853
+ async function startService(options) {
854
+ config_default.echoes = options.echoes;
855
+ if (options.requests) {
856
+ config_default.requests = options.requests;
857
+ const registerHandler = options.requests.registerHandler || getEchoesRuntime().registerRequestHandler;
858
+ if (!registerHandler) {
859
+ throw new Error(
860
+ "Echoes requests require requests.registerHandler in standalone servers. Orion applications can import @orion-js/echoes-orion once during startup."
861
+ );
417
862
  }
418
- const response = deserialize_default(data.result);
419
- return response;
420
- } catch (error) {
421
- if (error.isOrionError) throw error;
422
- throw new Error(`Echoes request network error calling ${service}/${method}: ${error.message}`);
863
+ await registerHandler(requestsHandler_default(options));
864
+ }
865
+ const nextEventBus = createEventBus(options);
866
+ if (nextEventBus) {
867
+ await nextEventBus.start();
868
+ eventBus = nextEventBus;
869
+ config_default.eventBus = eventBus;
870
+ }
871
+ }
872
+ async function stopService() {
873
+ if (eventBus) {
874
+ const logger = getEchoesLogger();
875
+ logger.info("Stopping Echoes...");
876
+ await eventBus.close();
877
+ eventBus = null;
878
+ config_default.eventBus = void 0;
879
+ logger.info("Echoes stopped");
423
880
  }
424
881
  }
425
-
426
- // src/service/index.ts
427
- var import_logger3 = require("@orion-js/logger");
428
882
 
429
883
  // src/echo/index.ts
430
- var import_schema2 = require("@orion-js/schema");
431
884
  var echo = function createNewEcho(options) {
432
885
  const resolve = async (params, context) => {
433
- const cleaned = options.params ? await (0, import_schema2.cleanAndValidate)(options.params, params) : params ?? {};
886
+ const cleaned = options.params ? await parseEchoesSchema(options.params, params) : params ?? {};
434
887
  const result = await options.resolve(cleaned, context);
435
888
  if (options.returns) {
436
- return await (0, import_schema2.clean)(options.returns, result);
889
+ return await cleanEchoesSchema(options.returns, result);
437
890
  }
438
891
  return result;
439
892
  };
893
+ const onEvent = async (event) => {
894
+ const context = {
895
+ ...event.context || {},
896
+ transport: event.transport,
897
+ eventId: event.id,
898
+ topic: event.topic,
899
+ headers: event.headers,
900
+ createdAt: event.createdAt,
901
+ attempt: event.attempt,
902
+ data: event.data
903
+ };
904
+ await resolve(event.data.params, context);
905
+ };
440
906
  return {
441
907
  type: options.type,
442
908
  params: options.params,
443
909
  returns: options.returns,
444
910
  attemptsBeforeDeadLetter: options.type === "event" ? options.attemptsBeforeDeadLetter : void 0,
445
911
  resolve,
912
+ onEvent,
446
913
  onMessage: async (messageData) => {
914
+ var _a, _b, _c, _d;
447
915
  const { message } = messageData;
916
+ if (!message.value) {
917
+ throw new Error(`Echoes received an empty Kafka message for ${messageData.topic}`);
918
+ }
448
919
  const data = deserialize_default(message.value.toString());
449
- const context = {
450
- ...messageData,
451
- data
452
- };
453
- await resolve(data.params, context);
920
+ const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
921
+ const timestamp = Number(message.timestamp);
922
+ const createdAt = Number.isFinite(timestamp) ? new Date(timestamp) : /* @__PURE__ */ new Date();
923
+ 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}`;
924
+ await onEvent({
925
+ id: eventId,
926
+ topic: messageData.topic,
927
+ data,
928
+ transport: "kafka",
929
+ headers: message.headers,
930
+ createdAt,
931
+ attempt: retries + 1,
932
+ context: messageData
933
+ });
454
934
  },
455
935
  onRequest: async (serializedParams) => {
456
936
  const context = {};
@@ -466,15 +946,21 @@ function createEchoEvent(options) {
466
946
  return echo({ ...options, type: "event" });
467
947
  }
468
948
 
949
+ // src/schema.ts
950
+ function typedEchoesSchema(schema) {
951
+ return schema;
952
+ }
953
+
469
954
  // src/service/index.ts
470
- var import_services = require("@orion-js/services");
471
955
  var serviceMetadata = /* @__PURE__ */ new WeakMap();
472
956
  var echoesMetadata = /* @__PURE__ */ new WeakMap();
473
957
  var echoEntriesByClass = /* @__PURE__ */ new Map();
958
+ var standaloneInstances = /* @__PURE__ */ new WeakMap();
474
959
  var pendingEchoEntries = {};
475
960
  function Echoes() {
476
961
  return (target, context) => {
477
- (0, import_services.Service)()(target, context);
962
+ var _a, _b;
963
+ (_b = (_a = getEchoesRuntime()).decorateService) == null ? void 0 : _b.call(_a, target, context);
478
964
  serviceMetadata.set(target, { _serviceType: "echoes" });
479
965
  if (Object.keys(pendingEchoEntries).length > 0) {
480
966
  echoEntriesByClass.set(target, pendingEchoEntries);
@@ -491,7 +977,7 @@ function EchoEvent(options = {}) {
491
977
  return createEchoEvent({
492
978
  ...options,
493
979
  resolve: async (params, contextData) => {
494
- return await (0, import_logger3.runWithOrionAsyncContext)(
980
+ return await runWithEchoesContext(
495
981
  {
496
982
  controllerType: "echo",
497
983
  echoName: propertyKey,
@@ -520,7 +1006,7 @@ function EchoRequest(options = {}) {
520
1006
  return createEchoRequest({
521
1007
  ...options,
522
1008
  resolve: async (params, contextData) => {
523
- return await (0, import_logger3.runWithOrionAsyncContext)(
1009
+ return await runWithEchoesContext(
524
1010
  {
525
1011
  controllerType: "echo",
526
1012
  echoName: propertyKey,
@@ -550,7 +1036,18 @@ function initializeEchoesIfNeeded(instance) {
550
1036
  echoesMetadata.set(instance, echoes);
551
1037
  }
552
1038
  function getServiceEchoes(target) {
553
- const instance = (0, import_services.getInstance)(target);
1039
+ let instance;
1040
+ if (typeof target !== "function") {
1041
+ instance = target;
1042
+ } else if (getEchoesRuntime().getInstance) {
1043
+ instance = getEchoesRuntime().getInstance(target);
1044
+ } else {
1045
+ instance = standaloneInstances.get(target);
1046
+ if (!instance) {
1047
+ instance = new target();
1048
+ standaloneInstances.set(target, instance);
1049
+ }
1050
+ }
554
1051
  if (!serviceMetadata.has(instance.constructor)) {
555
1052
  throw new Error("You must pass a class decorated with @Echoes to getServiceEchoes");
556
1053
  }
@@ -567,13 +1064,25 @@ function getServiceEchoes(target) {
567
1064
  EchoEvent,
568
1065
  EchoRequest,
569
1066
  Echoes,
1067
+ EchoesUserError,
1068
+ EchoesValidationError,
1069
+ cleanEchoesSchema,
1070
+ configureEchoesRuntime,
570
1071
  createEchoEvent,
571
1072
  createEchoRequest,
1073
+ createEchoesUserError,
1074
+ createEchoesValidationError,
572
1075
  echo,
1076
+ getEchoesContext,
1077
+ getEchoesLogger,
1078
+ getEchoesRuntime,
573
1079
  getServiceEchoes,
1080
+ parseEchoesSchema,
574
1081
  publish,
575
1082
  request,
1083
+ runWithEchoesContext,
576
1084
  startService,
577
- stopService
1085
+ stopService,
1086
+ typedEchoesSchema
578
1087
  });
579
1088
  //# sourceMappingURL=index.cjs.map