@orion-js/echoes 4.4.1 → 4.5.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,14 +1,14 @@
1
- // src/startService/index.ts
2
- import { registerRoute } from "@orion-js/http";
3
-
4
1
  // src/config.ts
5
2
  var config = {};
6
3
  var config_default = config;
7
4
 
8
- // src/startService/KafkaManager.ts
9
- import { randomUUID } from "crypto";
10
- import { logger } from "@orion-js/logger";
11
- import { Kafka } from "kafkajs";
5
+ // src/publish/index.ts
6
+ async function publish(options) {
7
+ if (!config_default.eventBus) {
8
+ throw new Error("You must initialize echoes configuration to use publish");
9
+ }
10
+ return await config_default.eventBus.publish(options);
11
+ }
12
12
 
13
13
  // src/echo/deserialize.ts
14
14
  function deserialize_default(serializedJavascript) {
@@ -21,14 +21,426 @@ function deserialize_default(serializedJavascript) {
21
21
 
22
22
  // src/publish/serialize.ts
23
23
  import serialize from "serialize-javascript";
24
- import { clone } from "@orion-js/helpers";
24
+ function clonePropertyDescriptor(descriptor, references) {
25
+ const clonedDescriptor = { ...descriptor, configurable: true };
26
+ if ("value" in clonedDescriptor) {
27
+ clonedDescriptor.value = cloneForSerialization(clonedDescriptor.value, references);
28
+ }
29
+ return clonedDescriptor;
30
+ }
31
+ function copyOwnProperties(source, target, references, shouldCopy = () => true) {
32
+ for (const key of Reflect.ownKeys(source)) {
33
+ if (!shouldCopy(key)) continue;
34
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
35
+ if (!descriptor) continue;
36
+ Object.defineProperty(target, key, clonePropertyDescriptor(descriptor, references));
37
+ }
38
+ }
39
+ function preserveCustomToJSON(source, target, references) {
40
+ const toJSON = source.toJSON;
41
+ if (typeof toJSON !== "function") return;
42
+ Object.defineProperty(target, "toJSON", {
43
+ configurable: true,
44
+ enumerable: false,
45
+ writable: true,
46
+ value(key) {
47
+ return cloneForSerialization(toJSON.call(source, key), references);
48
+ }
49
+ });
50
+ }
51
+ function cloneArrayBufferView(value, references) {
52
+ const sourceBuffer = value.buffer;
53
+ let clonedBuffer = references.get(sourceBuffer);
54
+ const shouldCopyBufferProperties = !clonedBuffer;
55
+ if (!clonedBuffer) {
56
+ clonedBuffer = sourceBuffer.slice(0);
57
+ references.set(sourceBuffer, clonedBuffer);
58
+ }
59
+ const clone = value instanceof DataView ? new DataView(clonedBuffer, value.byteOffset, value.byteLength) : new value.constructor(clonedBuffer, value.byteOffset, value.length);
60
+ references.set(value, clone);
61
+ if (shouldCopyBufferProperties) {
62
+ copyOwnProperties(sourceBuffer, clonedBuffer, references);
63
+ }
64
+ copyOwnProperties(value, clone, references, (key) => {
65
+ if (typeof key !== "string") return true;
66
+ const index = Number(key);
67
+ return !(Number.isInteger(index) && index >= 0 && index < value.length && String(index) === key);
68
+ });
69
+ return clone;
70
+ }
71
+ function cloneForSerialization(value, references = /* @__PURE__ */ new WeakMap()) {
72
+ if (value === null || typeof value !== "object" && typeof value !== "function") return value;
73
+ if (typeof value === "function") return value;
74
+ const source = value;
75
+ if (references.has(source)) return references.get(source);
76
+ if (value instanceof Date) {
77
+ const clone2 = new Date(value.getTime());
78
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
79
+ references.set(source, clone2);
80
+ return clone2;
81
+ }
82
+ if (value instanceof RegExp) {
83
+ const clone2 = new RegExp(value.source, value.flags);
84
+ clone2.lastIndex = value.lastIndex;
85
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
86
+ references.set(source, clone2);
87
+ return clone2;
88
+ }
89
+ if (value instanceof Map) {
90
+ const clone2 = /* @__PURE__ */ new Map();
91
+ references.set(source, clone2);
92
+ for (const [key, entry] of value) {
93
+ Map.prototype.set.call(
94
+ clone2,
95
+ cloneForSerialization(key, references),
96
+ cloneForSerialization(entry, references)
97
+ );
98
+ }
99
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
100
+ return clone2;
101
+ }
102
+ if (value instanceof Set) {
103
+ const clone2 = /* @__PURE__ */ new Set();
104
+ references.set(source, clone2);
105
+ for (const entry of value) {
106
+ Set.prototype.add.call(clone2, cloneForSerialization(entry, references));
107
+ }
108
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
109
+ return clone2;
110
+ }
111
+ if (value instanceof URL) {
112
+ const clone2 = new URL(value.toString());
113
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
114
+ references.set(source, clone2);
115
+ return clone2;
116
+ }
117
+ if (Buffer.isBuffer(value)) {
118
+ const clone2 = Buffer.from(value);
119
+ references.set(source, clone2);
120
+ return clone2;
121
+ }
122
+ if (ArrayBuffer.isView(value)) {
123
+ return cloneArrayBufferView(value, references);
124
+ }
125
+ if (value instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer) {
126
+ const clone2 = value.slice(0);
127
+ references.set(source, clone2);
128
+ copyOwnProperties(value, clone2, references);
129
+ return clone2;
130
+ }
131
+ if (Array.isArray(value)) {
132
+ const clone2 = new Array(value.length);
133
+ Object.setPrototypeOf(clone2, Object.getPrototypeOf(value));
134
+ references.set(source, clone2);
135
+ copyOwnProperties(value, clone2, references, (key) => key !== "length");
136
+ return clone2;
137
+ }
138
+ const clone = Object.create(Object.getPrototypeOf(value));
139
+ references.set(source, clone);
140
+ copyOwnProperties(value, clone, references);
141
+ preserveCustomToJSON(source, clone, references);
142
+ return clone;
143
+ }
25
144
  function serialize_default(data) {
26
- const cloned = clone(data);
27
- const serialized = serialize(cloned, { ignoreFunction: true });
145
+ const serialized = serialize(cloneForSerialization(data), { ignoreFunction: true });
28
146
  return serialized;
29
147
  }
30
148
 
149
+ // src/runtime.ts
150
+ import { AsyncLocalStorage } from "async_hooks";
151
+ import { randomUUID } from "crypto";
152
+
153
+ // src/errors.ts
154
+ var EchoesUserError = class extends Error {
155
+ isEchoesError = true;
156
+ isOrionError = true;
157
+ isUserError = true;
158
+ code;
159
+ extra;
160
+ constructor(code, message, extra) {
161
+ if (!message) {
162
+ message = code;
163
+ code = "error";
164
+ }
165
+ super(message);
166
+ this.name = "EchoesUserError";
167
+ this.code = code;
168
+ this.extra = extra;
169
+ }
170
+ getInfo() {
171
+ return { error: this.code, message: this.message, extra: this.extra };
172
+ }
173
+ };
174
+ var EchoesValidationError = class extends Error {
175
+ isEchoesError = true;
176
+ isOrionError = true;
177
+ isValidationError = true;
178
+ code = "validationError";
179
+ validationErrors;
180
+ labels;
181
+ constructor(validationErrors, labels = {}) {
182
+ const printableErrors = Object.entries(validationErrors).map(([key, value]) => `${key}: ${value}`).join(", ");
183
+ super(`Validation Error: {${printableErrors}}`);
184
+ this.name = "EchoesValidationError";
185
+ this.validationErrors = validationErrors;
186
+ this.labels = Object.fromEntries(
187
+ Object.keys(validationErrors).filter((key) => labels[key]).map((key) => [key, labels[key]])
188
+ );
189
+ }
190
+ getInfo() {
191
+ return {
192
+ error: this.code,
193
+ message: "Validation Error",
194
+ validationErrors: this.validationErrors,
195
+ labels: this.labels
196
+ };
197
+ }
198
+ };
199
+
200
+ // src/runtime.ts
201
+ var defaultLogger = {
202
+ debug: (message, metadata) => metadata === void 0 ? console.debug(message) : console.debug(message, metadata),
203
+ info: (message, metadata) => metadata === void 0 ? console.info(message) : console.info(message, metadata),
204
+ warn: (message, metadata) => metadata === void 0 ? console.warn(message) : console.warn(message, metadata),
205
+ error: (message, metadata) => metadata === void 0 ? console.error(message) : console.error(message, metadata)
206
+ };
207
+ var runtime = {};
208
+ var contextStorage = new AsyncLocalStorage();
209
+ function configureEchoesRuntime(adapter) {
210
+ const previous = runtime;
211
+ runtime = { ...runtime, ...adapter };
212
+ return () => {
213
+ runtime = previous;
214
+ };
215
+ }
216
+ function getEchoesRuntime() {
217
+ return runtime;
218
+ }
219
+ function getEchoesLogger() {
220
+ return runtime.logger || defaultLogger;
221
+ }
222
+ function getEchoesContext() {
223
+ return contextStorage.getStore();
224
+ }
225
+ async function runWithEchoesContext(context, callback) {
226
+ const contextWithId = { contextId: context.contextId || randomUUID(), ...context };
227
+ return await contextStorage.run(contextWithId, async () => {
228
+ if (runtime.runWithContext) {
229
+ return await runtime.runWithContext(contextWithId, callback);
230
+ }
231
+ return await callback();
232
+ });
233
+ }
234
+ function isSimpleSchemaLike(schema) {
235
+ return !!schema && typeof schema === "object" && typeof schema.clean === "function" && typeof schema.validate === "function";
236
+ }
237
+ function cloneValue(value) {
238
+ if (value === null || typeof value !== "object") return value;
239
+ if (value instanceof Date) return new Date(value.getTime());
240
+ if (Buffer.isBuffer(value)) return Buffer.from(value);
241
+ if (Array.isArray(value)) return value.map(cloneValue);
242
+ const prototype = Object.getPrototypeOf(value);
243
+ if (prototype !== Object.prototype && prototype !== null) return value;
244
+ const result = {};
245
+ for (const [key, child] of Object.entries(value)) {
246
+ result[key] = cloneValue(child);
247
+ }
248
+ return result;
249
+ }
250
+ async function cleanSimpleSchema(schema, value) {
251
+ const cloned = cloneValue(value);
252
+ return await schema.clean(cloned, { mutate: false });
253
+ }
254
+ async function cleanEchoesSchema(schema, value) {
255
+ if (isSimpleSchemaLike(schema)) {
256
+ return await cleanSimpleSchema(schema, value);
257
+ }
258
+ if (runtime.schema) {
259
+ return await runtime.schema.clean(schema, value);
260
+ }
261
+ throw new Error(
262
+ "Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
263
+ );
264
+ }
265
+ async function parseEchoesSchema(schema, value) {
266
+ if (isSimpleSchemaLike(schema)) {
267
+ const cleaned = await cleanSimpleSchema(schema, value);
268
+ await schema.validate(cleaned);
269
+ return cleaned;
270
+ }
271
+ if (runtime.schema) {
272
+ return await runtime.schema.parse(schema, value);
273
+ }
274
+ throw new Error(
275
+ "Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
276
+ );
277
+ }
278
+ function createEchoesValidationError(info) {
279
+ return runtime.createValidationError ? runtime.createValidationError(info) : new EchoesValidationError(info.validationErrors || {}, info.labels);
280
+ }
281
+ function createEchoesUserError(info) {
282
+ return runtime.createUserError ? runtime.createUserError(info) : new EchoesUserError(info.error, info.message, info.extra);
283
+ }
284
+
285
+ // src/request/getSignature.ts
286
+ import { createHmac } from "crypto";
287
+
288
+ // src/request/getPassword.ts
289
+ function getEchoesPassword() {
290
+ var _a, _b;
291
+ const secret = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.key) || process.env.echoes_password || process.env.ECHOES_PASSWORD;
292
+ if (!secret) {
293
+ getEchoesLogger().warn(
294
+ 'Warning: no secret key found for echoes requests. Init echoes or set the env var "echoes_password" or process.env.ECHOES_PASSWORD'
295
+ );
296
+ }
297
+ return secret;
298
+ }
299
+
300
+ // src/request/getSignature.ts
301
+ function getSignature_default(_body) {
302
+ const password = getEchoesPassword();
303
+ return createHmac("sha1", password || "").update("").digest("hex");
304
+ }
305
+
306
+ // src/request/getURL.ts
307
+ function getURL_default(serviceName) {
308
+ var _a, _b, _c;
309
+ if (serviceName.startsWith("http")) return serviceName;
310
+ const url = (_c = (_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.services) == null ? void 0 : _c[serviceName];
311
+ if (!url) {
312
+ throw new Error(`No URL found in echoes config for service ${serviceName}`);
313
+ }
314
+ return url;
315
+ }
316
+
317
+ // src/request/makeRequest.ts
318
+ import http from "http";
319
+ import https from "https";
320
+ async function executeWithRetries(callback, retries = 0, delayMs = 200) {
321
+ try {
322
+ return await callback();
323
+ } catch (error) {
324
+ if (retries <= 0) throw error;
325
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
326
+ return await executeWithRetries(callback, retries - 1, delayMs);
327
+ }
328
+ }
329
+ async function postJSON(urlValue, data, timeout, redirectsRemaining = 5) {
330
+ const url = new URL(urlValue);
331
+ const body = JSON.stringify(data);
332
+ const transport = url.protocol === "https:" ? https : http;
333
+ return await new Promise((resolve, reject) => {
334
+ const request2 = transport.request(
335
+ url,
336
+ {
337
+ method: "POST",
338
+ headers: {
339
+ "User-Agent": "Orionjs-Echoes/1.1",
340
+ "Content-Type": "application/json",
341
+ "Content-Length": Buffer.byteLength(body)
342
+ }
343
+ },
344
+ (response) => {
345
+ const chunks = [];
346
+ const statusCode = response.statusCode || 0;
347
+ if (statusCode >= 300 && statusCode < 400 && response.headers.location) {
348
+ response.resume();
349
+ if (redirectsRemaining <= 0) {
350
+ reject(new Error("Echoes request exceeded the redirect limit"));
351
+ return;
352
+ }
353
+ resolve(
354
+ postJSON(
355
+ new URL(response.headers.location, url).toString(),
356
+ data,
357
+ timeout,
358
+ redirectsRemaining - 1
359
+ )
360
+ );
361
+ return;
362
+ }
363
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
364
+ response.on("end", () => {
365
+ try {
366
+ const responseBody = Buffer.concat(chunks).toString("utf8");
367
+ if (statusCode < 200 || statusCode >= 300) {
368
+ reject(new Error(`Request failed with status code ${statusCode}`));
369
+ return;
370
+ }
371
+ resolve({
372
+ statusCode,
373
+ data: responseBody ? JSON.parse(responseBody) : {}
374
+ });
375
+ } catch (error) {
376
+ reject(error);
377
+ }
378
+ });
379
+ }
380
+ );
381
+ request2.on("error", reject);
382
+ if (timeout) {
383
+ request2.setTimeout(timeout, () => {
384
+ request2.destroy(new Error(`Echoes request timed out after ${timeout}ms`));
385
+ });
386
+ }
387
+ request2.end(body);
388
+ });
389
+ }
390
+ var makeRequest = async (options) => {
391
+ return await executeWithRetries(
392
+ () => postJSON(options.url, options.data, options.timeout),
393
+ options.retries || 0
394
+ );
395
+ };
396
+
397
+ // src/request/index.ts
398
+ async function request(options) {
399
+ var _a, _b;
400
+ const { method, service, params } = options;
401
+ const serializedParams = serialize_default(params);
402
+ const date = /* @__PURE__ */ new Date();
403
+ const body = { method, service, serializedParams, date };
404
+ const signature = getSignature_default(body);
405
+ try {
406
+ const requestMaker = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.makeRequest) || makeRequest;
407
+ const requestOptions = {
408
+ url: getURL_default(service),
409
+ retries: options.retries,
410
+ timeout: options.timeout,
411
+ data: {
412
+ body,
413
+ signature
414
+ }
415
+ };
416
+ const result = await requestMaker(requestOptions);
417
+ if (result.statusCode !== 200) {
418
+ throw new Error(`Wrong status code ${result.statusCode}`);
419
+ }
420
+ const data = result.data;
421
+ if (data.error) {
422
+ const info = data.errorInfo;
423
+ if (info) {
424
+ if (data.isValidationError) {
425
+ throw createEchoesValidationError(info);
426
+ }
427
+ if (data.isUserError) {
428
+ throw createEchoesUserError(info);
429
+ }
430
+ }
431
+ throw new Error(`${data.error}`);
432
+ }
433
+ const response = deserialize_default(data.result);
434
+ return response;
435
+ } catch (error) {
436
+ const caught = error;
437
+ if (caught.isOrionError || caught.isEchoesError) throw caught;
438
+ throw new Error(`Echoes request network error calling ${service}/${method}: ${caught.message}`);
439
+ }
440
+ }
441
+
31
442
  // src/startService/KafkaManager.ts
443
+ import { randomUUID as randomUUID2 } from "crypto";
32
444
  var HEARTBEAT_INTERVAL_SECONDS = 5;
33
445
  var CHECK_JOIN_CONSUMER_INTERVAL_SECONDS = 30;
34
446
  var DEFAULT_PARTITIONS_CONSUMED_CONCURRENTLY = 4;
@@ -46,11 +458,21 @@ var KafkaManager = class {
46
458
  producerConnected = false;
47
459
  interval;
48
460
  constructor(options) {
49
- this.kafka = new Kafka(options.client);
50
461
  this.options = options;
51
462
  }
52
463
  async start(options) {
53
464
  var _a;
465
+ let kafkaModule;
466
+ try {
467
+ kafkaModule = await import("kafkajs");
468
+ } catch (error) {
469
+ const wrapped = new Error(
470
+ "Echoes Kafka transport requires kafkajs to be installed in the application"
471
+ );
472
+ wrapped.cause = error;
473
+ throw wrapped;
474
+ }
475
+ this.kafka = new kafkaModule.Kafka(this.options.client);
54
476
  this.onEvent = options.onEvent;
55
477
  this.subscriptions = new Map(
56
478
  options.subscriptions.map((subscription) => [subscription.topic, subscription])
@@ -68,7 +490,7 @@ var KafkaManager = class {
68
490
  this.consumer = this.kafka.consumer(this.options.consumer);
69
491
  this.consumerStarted = await this.conditionalStart();
70
492
  if (this.consumerStarted) return;
71
- logger.info("Echoes: Delaying consumer group join, waiting for conditions to be met");
493
+ getEchoesLogger().info("Echoes: Delaying consumer group join, waiting for conditions to be met");
72
494
  this.interval = setInterval(async () => {
73
495
  this.consumerStarted = await this.conditionalStart();
74
496
  if (this.consumerStarted) clearInterval(this.interval);
@@ -86,7 +508,7 @@ var KafkaManager = class {
86
508
  {
87
509
  value: serialize_default({ params: options.params }),
88
510
  headers: {
89
- "echoes-event-id": randomUUID()
511
+ "echoes-event-id": randomUUID2()
90
512
  }
91
513
  }
92
514
  ]
@@ -94,6 +516,7 @@ var KafkaManager = class {
94
516
  }
95
517
  async close() {
96
518
  var _a, _b;
519
+ const logger = getEchoesLogger();
97
520
  logger.warn("Echoes: Stopping Kafka transport");
98
521
  if (this.interval) clearInterval(this.interval);
99
522
  await Promise.all([
@@ -104,6 +527,7 @@ var KafkaManager = class {
104
527
  this.producerConnected = false;
105
528
  }
106
529
  async checkJoinConsumerGroupConditions() {
530
+ const logger = getEchoesLogger();
107
531
  const admin = this.kafka.admin();
108
532
  try {
109
533
  await admin.connect();
@@ -156,6 +580,7 @@ var KafkaManager = class {
156
580
  return false;
157
581
  }
158
582
  async handleMessage(params) {
583
+ const logger = getEchoesLogger();
159
584
  const subscription = this.subscriptions.get(params.topic);
160
585
  if (!subscription) {
161
586
  logger.warn(`Echoes: Received a message for an unknown topic: ${params.topic}, ignoring it`);
@@ -193,6 +618,9 @@ var KafkaManager = class {
193
618
  createReceivedEvent(params) {
194
619
  var _a, _b, _c, _d;
195
620
  const { message, topic, partition } = params;
621
+ if (!message.value) {
622
+ throw new Error(`Echoes received an empty Kafka message for ${topic}`);
623
+ }
196
624
  const data = deserialize_default(message.value.toString());
197
625
  const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
198
626
  const timestamp = Number.parseInt(message.timestamp || "", 10);
@@ -209,6 +637,7 @@ var KafkaManager = class {
209
637
  }
210
638
  async handleRetries(subscription, params, error) {
211
639
  var _a, _b;
640
+ const logger = getEchoesLogger();
212
641
  const { message, topic } = params;
213
642
  const retries = Number.parseInt(((_b = (_a = message == null ? void 0 : message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
214
643
  if (subscription.attemptsBeforeDeadLetter === void 0 || subscription.attemptsBeforeDeadLetter === null) {
@@ -217,6 +646,7 @@ var KafkaManager = class {
217
646
  const maxRetries = subscription.attemptsBeforeDeadLetter || 0;
218
647
  const exceededMaxRetries = retries >= maxRetries;
219
648
  const nextTopic = exceededMaxRetries ? `DLQ-${topic}` : topic;
649
+ if (!message.value) throw error;
220
650
  await this.producer.send({
221
651
  topic: nextTopic,
222
652
  messages: [
@@ -243,7 +673,6 @@ var KafkaManager = class {
243
673
  var KafkaManager_default = KafkaManager;
244
674
 
245
675
  // src/events/EventBus.ts
246
- import { logger as logger2 } from "@orion-js/logger";
247
676
  var EventBus = class {
248
677
  echoes;
249
678
  transports;
@@ -303,9 +732,10 @@ var EventBus = class {
303
732
  await Promise.all(this.startedTransports.map((transport) => transport.close()));
304
733
  }
305
734
  async handleEvent(event) {
735
+ const logger = getEchoesLogger();
306
736
  const echo2 = this.echoes[event.topic];
307
737
  if (!echo2 || echo2.type !== "event") {
308
- logger2.warn(`Echoes: Received a message for an unknown topic: ${event.topic}, ignoring it`);
738
+ logger.warn(`Echoes: Received a message for an unknown topic: ${event.topic}, ignoring it`);
309
739
  return;
310
740
  }
311
741
  if (event.transport === "kafka" && event.context) {
@@ -433,6 +863,14 @@ function createEventBus(options) {
433
863
  });
434
864
  }
435
865
 
866
+ // src/requestsHandler/checkSignature.ts
867
+ function checkSignature_default(body, signature) {
868
+ const generatedSignature = getSignature_default(body);
869
+ if (generatedSignature !== signature) {
870
+ throw new Error("Echoes invalid signature");
871
+ }
872
+ }
873
+
436
874
  // src/requestsHandler/getEcho.ts
437
875
  function getEcho_default(method) {
438
876
  const echo2 = config_default.echoes[method];
@@ -445,72 +883,29 @@ function getEcho_default(method) {
445
883
  return echo2;
446
884
  }
447
885
 
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
886
  // src/requestsHandler/index.ts
483
- import { route } from "@orion-js/http";
484
- var requestsHandler_default = (options) => route({
887
+ var requestsHandler_default = (options) => ({
485
888
  method: "post",
486
889
  path: options.requests.handlerPath || "/echoes-services",
487
- bodyParser: "json",
488
- bodyParserOptions: {
489
- limit: "10mb"
490
- },
491
- async resolve(req) {
890
+ bodyLimit: "10mb",
891
+ async handle(requestBody) {
492
892
  try {
493
- const { body, signature } = req.body;
893
+ const { body, signature } = requestBody;
494
894
  checkSignature_default(body, signature);
495
895
  const { method, serializedParams } = body;
496
896
  const echo2 = getEcho_default(method);
497
897
  const result = await echo2.onRequest(serializedParams);
498
- return {
499
- body: {
500
- result: serialize_default(result)
501
- }
502
- };
898
+ return { result: serialize_default(result) };
503
899
  } catch (error) {
504
- if (!error.getInfo) {
505
- console.error("Error at echo requests handler:", error);
900
+ const caught = error;
901
+ if (!caught.getInfo) {
902
+ getEchoesLogger().error("Error at echo requests handler:", { error: caught });
506
903
  }
507
904
  return {
508
- body: {
509
- error: error.message,
510
- errorInfo: error.getInfo ? error.getInfo() : null,
511
- isValidationError: !!error.isValidationError,
512
- isUserError: !!error.isUserError
513
- }
905
+ error: caught.message,
906
+ errorInfo: caught.getInfo ? caught.getInfo() : null,
907
+ isValidationError: !!caught.isValidationError,
908
+ isUserError: !!caught.isUserError
514
909
  };
515
910
  }
516
911
  }
@@ -522,7 +917,13 @@ async function startService(options) {
522
917
  config_default.echoes = options.echoes;
523
918
  if (options.requests) {
524
919
  config_default.requests = options.requests;
525
- registerRoute(requestsHandler_default(options));
920
+ const registerHandler = options.requests.registerHandler || getEchoesRuntime().registerRequestHandler;
921
+ if (!registerHandler) {
922
+ throw new Error(
923
+ "Echoes requests require requests.registerHandler in standalone servers. Orion applications can import @orion-js/echoes-orion once during startup."
924
+ );
925
+ }
926
+ await registerHandler(requestsHandler_default(options));
526
927
  }
527
928
  const nextEventBus = createEventBus(options);
528
929
  if (nextEventBus) {
@@ -533,115 +934,22 @@ async function startService(options) {
533
934
  }
534
935
  async function stopService() {
535
936
  if (eventBus) {
536
- console.info("Stoping echoes...");
937
+ const logger = getEchoesLogger();
938
+ logger.info("Stopping Echoes...");
537
939
  await eventBus.close();
538
940
  eventBus = null;
539
941
  config_default.eventBus = void 0;
540
- console.info("Echoes stopped");
541
- }
542
- }
543
-
544
- // src/publish/index.ts
545
- async function publish(options) {
546
- if (!config_default.eventBus) {
547
- throw new Error("You must initialize echoes configuration to use publish");
548
- }
549
- return await config_default.eventBus.publish(options);
550
- }
551
-
552
- // src/request/getURL.ts
553
- function getURL_default(serviceName) {
554
- var _a, _b;
555
- if (serviceName.startsWith("http")) return serviceName;
556
- const url = (_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.services[serviceName];
557
- if (!url) {
558
- throw new Error(`No URL found in echoes config for service ${serviceName}`);
559
- }
560
- return url;
561
- }
562
-
563
- // src/request/makeRequest.ts
564
- import axios from "axios";
565
- import { executeWithRetries } from "@orion-js/helpers";
566
- var makeRequest = async (options) => {
567
- const result = await executeWithRetries(
568
- async () => {
569
- return await axios({
570
- method: "post",
571
- url: options.url,
572
- timeout: options.timeout,
573
- headers: {
574
- "User-Agent": "Orionjs-Echoes/1.1"
575
- },
576
- data: options.data
577
- });
578
- },
579
- options.retries,
580
- 200
581
- );
582
- return {
583
- data: result.data,
584
- statusCode: result.status
585
- };
586
- };
587
-
588
- // src/request/index.ts
589
- import { ValidationError } from "@orion-js/schema";
590
- import { UserError } from "@orion-js/helpers";
591
- async function request(options) {
592
- var _a, _b;
593
- const { method, service, params } = options;
594
- const serializedParams = serialize_default(params);
595
- const date = /* @__PURE__ */ new Date();
596
- const body = { method, service, serializedParams, date };
597
- const signature = getSignature_default(body);
598
- try {
599
- const requestMaker = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.makeRequest) || makeRequest;
600
- const requestOptions = {
601
- url: getURL_default(service),
602
- retries: options.retries,
603
- timeout: options.timeout,
604
- data: {
605
- body,
606
- signature
607
- }
608
- };
609
- const result = await requestMaker(requestOptions);
610
- if (result.statusCode !== 200) {
611
- throw new Error(`Wrong status code ${result.statusCode}`);
612
- }
613
- const data = result.data;
614
- if (data.error) {
615
- const info = data.errorInfo;
616
- if (info) {
617
- if (data.isValidationError) {
618
- throw new ValidationError(info.validationErrors);
619
- }
620
- if (data.isUserError) {
621
- throw new UserError(info.error, info.message, info.extra);
622
- }
623
- }
624
- throw new Error(`${data.error}`);
625
- }
626
- const response = deserialize_default(data.result);
627
- return response;
628
- } catch (error) {
629
- if (error.isOrionError) throw error;
630
- throw new Error(`Echoes request network error calling ${service}/${method}: ${error.message}`);
942
+ logger.info("Echoes stopped");
631
943
  }
632
944
  }
633
945
 
634
- // src/service/index.ts
635
- import { runWithOrionAsyncContext } from "@orion-js/logger";
636
-
637
946
  // src/echo/index.ts
638
- import { clean, cleanAndValidate } from "@orion-js/schema";
639
947
  var echo = function createNewEcho(options) {
640
948
  const resolve = async (params, context) => {
641
- const cleaned = options.params ? await cleanAndValidate(options.params, params) : params ?? {};
949
+ const cleaned = options.params ? await parseEchoesSchema(options.params, params) : params ?? {};
642
950
  const result = await options.resolve(cleaned, context);
643
951
  if (options.returns) {
644
- return await clean(options.returns, result);
952
+ return await cleanEchoesSchema(options.returns, result);
645
953
  }
646
954
  return result;
647
955
  };
@@ -668,6 +976,9 @@ var echo = function createNewEcho(options) {
668
976
  onMessage: async (messageData) => {
669
977
  var _a, _b, _c, _d;
670
978
  const { message } = messageData;
979
+ if (!message.value) {
980
+ throw new Error(`Echoes received an empty Kafka message for ${messageData.topic}`);
981
+ }
671
982
  const data = deserialize_default(message.value.toString());
672
983
  const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
673
984
  const timestamp = Number(message.timestamp);
@@ -698,15 +1009,21 @@ function createEchoEvent(options) {
698
1009
  return echo({ ...options, type: "event" });
699
1010
  }
700
1011
 
1012
+ // src/schema.ts
1013
+ function typedEchoesSchema(schema) {
1014
+ return schema;
1015
+ }
1016
+
701
1017
  // src/service/index.ts
702
- import { getInstance, Service } from "@orion-js/services";
703
1018
  var serviceMetadata = /* @__PURE__ */ new WeakMap();
704
1019
  var echoesMetadata = /* @__PURE__ */ new WeakMap();
705
1020
  var echoEntriesByClass = /* @__PURE__ */ new Map();
1021
+ var standaloneInstances = /* @__PURE__ */ new WeakMap();
706
1022
  var pendingEchoEntries = {};
707
1023
  function Echoes() {
708
1024
  return (target, context) => {
709
- Service()(target, context);
1025
+ var _a, _b;
1026
+ (_b = (_a = getEchoesRuntime()).decorateService) == null ? void 0 : _b.call(_a, target, context);
710
1027
  serviceMetadata.set(target, { _serviceType: "echoes" });
711
1028
  if (Object.keys(pendingEchoEntries).length > 0) {
712
1029
  echoEntriesByClass.set(target, pendingEchoEntries);
@@ -723,7 +1040,7 @@ function EchoEvent(options = {}) {
723
1040
  return createEchoEvent({
724
1041
  ...options,
725
1042
  resolve: async (params, contextData) => {
726
- return await runWithOrionAsyncContext(
1043
+ return await runWithEchoesContext(
727
1044
  {
728
1045
  controllerType: "echo",
729
1046
  echoName: propertyKey,
@@ -752,7 +1069,7 @@ function EchoRequest(options = {}) {
752
1069
  return createEchoRequest({
753
1070
  ...options,
754
1071
  resolve: async (params, contextData) => {
755
- return await runWithOrionAsyncContext(
1072
+ return await runWithEchoesContext(
756
1073
  {
757
1074
  controllerType: "echo",
758
1075
  echoName: propertyKey,
@@ -782,7 +1099,18 @@ function initializeEchoesIfNeeded(instance) {
782
1099
  echoesMetadata.set(instance, echoes);
783
1100
  }
784
1101
  function getServiceEchoes(target) {
785
- const instance = getInstance(target);
1102
+ let instance;
1103
+ if (typeof target !== "function") {
1104
+ instance = target;
1105
+ } else if (getEchoesRuntime().getInstance) {
1106
+ instance = getEchoesRuntime().getInstance(target);
1107
+ } else {
1108
+ instance = standaloneInstances.get(target);
1109
+ if (!instance) {
1110
+ instance = new target();
1111
+ standaloneInstances.set(target, instance);
1112
+ }
1113
+ }
786
1114
  if (!serviceMetadata.has(instance.constructor)) {
787
1115
  throw new Error("You must pass a class decorated with @Echoes to getServiceEchoes");
788
1116
  }
@@ -798,13 +1126,25 @@ export {
798
1126
  EchoEvent,
799
1127
  EchoRequest,
800
1128
  Echoes,
1129
+ EchoesUserError,
1130
+ EchoesValidationError,
1131
+ cleanEchoesSchema,
1132
+ configureEchoesRuntime,
801
1133
  createEchoEvent,
802
1134
  createEchoRequest,
1135
+ createEchoesUserError,
1136
+ createEchoesValidationError,
803
1137
  echo,
1138
+ getEchoesContext,
1139
+ getEchoesLogger,
1140
+ getEchoesRuntime,
804
1141
  getServiceEchoes,
1142
+ parseEchoesSchema,
805
1143
  publish,
806
1144
  request,
1145
+ runWithEchoesContext,
807
1146
  startService,
808
- stopService
1147
+ stopService,
1148
+ typedEchoesSchema
809
1149
  };
810
1150
  //# sourceMappingURL=index.js.map