@orion-js/echoes 4.4.1 → 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/echo/index.d.ts +4 -4
- package/dist/errors.d.ts +26 -0
- package/dist/events/PulseManager.d.ts +2 -2
- package/dist/index.cjs +420 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +395 -175
- package/dist/index.js.map +1 -1
- package/dist/request/getSignature.d.ts +1 -1
- package/dist/requestsHandler/index.d.ts +18 -1
- package/dist/runtime.d.ts +49 -0
- package/dist/schema.d.ts +42 -0
- package/dist/startService/KafkaManager.d.ts +2 -2
- package/dist/types.d.ts +48 -19
- package/package.json +8 -12
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/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,306 @@ 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";
|
|
25
24
|
function serialize_default(data) {
|
|
26
|
-
const
|
|
27
|
-
const serialized = serialize(cloned, { ignoreFunction: true });
|
|
25
|
+
const serialized = serialize(data, { ignoreFunction: true });
|
|
28
26
|
return serialized;
|
|
29
27
|
}
|
|
30
28
|
|
|
29
|
+
// src/runtime.ts
|
|
30
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
31
|
+
import { randomUUID } from "crypto";
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var EchoesUserError = class extends Error {
|
|
35
|
+
isEchoesError = true;
|
|
36
|
+
isOrionError = true;
|
|
37
|
+
isUserError = true;
|
|
38
|
+
code;
|
|
39
|
+
extra;
|
|
40
|
+
constructor(code, message, extra) {
|
|
41
|
+
if (!message) {
|
|
42
|
+
message = code;
|
|
43
|
+
code = "error";
|
|
44
|
+
}
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "EchoesUserError";
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.extra = extra;
|
|
49
|
+
}
|
|
50
|
+
getInfo() {
|
|
51
|
+
return { error: this.code, message: this.message, extra: this.extra };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var EchoesValidationError = class extends Error {
|
|
55
|
+
isEchoesError = true;
|
|
56
|
+
isOrionError = true;
|
|
57
|
+
isValidationError = true;
|
|
58
|
+
code = "validationError";
|
|
59
|
+
validationErrors;
|
|
60
|
+
labels;
|
|
61
|
+
constructor(validationErrors, labels = {}) {
|
|
62
|
+
const printableErrors = Object.entries(validationErrors).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
63
|
+
super(`Validation Error: {${printableErrors}}`);
|
|
64
|
+
this.name = "EchoesValidationError";
|
|
65
|
+
this.validationErrors = validationErrors;
|
|
66
|
+
this.labels = Object.fromEntries(
|
|
67
|
+
Object.keys(validationErrors).filter((key) => labels[key]).map((key) => [key, labels[key]])
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
getInfo() {
|
|
71
|
+
return {
|
|
72
|
+
error: this.code,
|
|
73
|
+
message: "Validation Error",
|
|
74
|
+
validationErrors: this.validationErrors,
|
|
75
|
+
labels: this.labels
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/runtime.ts
|
|
81
|
+
var defaultLogger = {
|
|
82
|
+
debug: (message, metadata) => metadata === void 0 ? console.debug(message) : console.debug(message, metadata),
|
|
83
|
+
info: (message, metadata) => metadata === void 0 ? console.info(message) : console.info(message, metadata),
|
|
84
|
+
warn: (message, metadata) => metadata === void 0 ? console.warn(message) : console.warn(message, metadata),
|
|
85
|
+
error: (message, metadata) => metadata === void 0 ? console.error(message) : console.error(message, metadata)
|
|
86
|
+
};
|
|
87
|
+
var runtime = {};
|
|
88
|
+
var contextStorage = new AsyncLocalStorage();
|
|
89
|
+
function configureEchoesRuntime(adapter) {
|
|
90
|
+
const previous = runtime;
|
|
91
|
+
runtime = { ...runtime, ...adapter };
|
|
92
|
+
return () => {
|
|
93
|
+
runtime = previous;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function getEchoesRuntime() {
|
|
97
|
+
return runtime;
|
|
98
|
+
}
|
|
99
|
+
function getEchoesLogger() {
|
|
100
|
+
return runtime.logger || defaultLogger;
|
|
101
|
+
}
|
|
102
|
+
function getEchoesContext() {
|
|
103
|
+
return contextStorage.getStore();
|
|
104
|
+
}
|
|
105
|
+
async function runWithEchoesContext(context, callback) {
|
|
106
|
+
const contextWithId = { contextId: context.contextId || randomUUID(), ...context };
|
|
107
|
+
return await contextStorage.run(contextWithId, async () => {
|
|
108
|
+
if (runtime.runWithContext) {
|
|
109
|
+
return await runtime.runWithContext(contextWithId, callback);
|
|
110
|
+
}
|
|
111
|
+
return await callback();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function isSimpleSchemaLike(schema) {
|
|
115
|
+
return !!schema && typeof schema === "object" && typeof schema.clean === "function" && typeof schema.validate === "function";
|
|
116
|
+
}
|
|
117
|
+
function cloneValue(value) {
|
|
118
|
+
if (value === null || typeof value !== "object") return value;
|
|
119
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
120
|
+
if (Buffer.isBuffer(value)) return Buffer.from(value);
|
|
121
|
+
if (Array.isArray(value)) return value.map(cloneValue);
|
|
122
|
+
const prototype = Object.getPrototypeOf(value);
|
|
123
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
124
|
+
const result = {};
|
|
125
|
+
for (const [key, child] of Object.entries(value)) {
|
|
126
|
+
result[key] = cloneValue(child);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
async function cleanSimpleSchema(schema, value) {
|
|
131
|
+
const cloned = cloneValue(value);
|
|
132
|
+
return await schema.clean(cloned, { mutate: false });
|
|
133
|
+
}
|
|
134
|
+
async function cleanEchoesSchema(schema, value) {
|
|
135
|
+
if (isSimpleSchemaLike(schema)) {
|
|
136
|
+
return await cleanSimpleSchema(schema, value);
|
|
137
|
+
}
|
|
138
|
+
if (runtime.schema) {
|
|
139
|
+
return await runtime.schema.clean(schema, value);
|
|
140
|
+
}
|
|
141
|
+
throw new Error(
|
|
142
|
+
"Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
async function parseEchoesSchema(schema, value) {
|
|
146
|
+
if (isSimpleSchemaLike(schema)) {
|
|
147
|
+
const cleaned = await cleanSimpleSchema(schema, value);
|
|
148
|
+
await schema.validate(cleaned);
|
|
149
|
+
return cleaned;
|
|
150
|
+
}
|
|
151
|
+
if (runtime.schema) {
|
|
152
|
+
return await runtime.schema.parse(schema, value);
|
|
153
|
+
}
|
|
154
|
+
throw new Error(
|
|
155
|
+
"Echoes received a schema it cannot execute. Use a SimpleSchema-compatible object or configure an Echoes schema adapter."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function createEchoesValidationError(info) {
|
|
159
|
+
return runtime.createValidationError ? runtime.createValidationError(info) : new EchoesValidationError(info.validationErrors || {}, info.labels);
|
|
160
|
+
}
|
|
161
|
+
function createEchoesUserError(info) {
|
|
162
|
+
return runtime.createUserError ? runtime.createUserError(info) : new EchoesUserError(info.error, info.message, info.extra);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/request/getSignature.ts
|
|
166
|
+
import { createHmac } from "crypto";
|
|
167
|
+
|
|
168
|
+
// src/request/getPassword.ts
|
|
169
|
+
function getEchoesPassword() {
|
|
170
|
+
var _a, _b;
|
|
171
|
+
const secret = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.key) || process.env.echoes_password || process.env.ECHOES_PASSWORD;
|
|
172
|
+
if (!secret) {
|
|
173
|
+
getEchoesLogger().warn(
|
|
174
|
+
'Warning: no secret key found for echoes requests. Init echoes or set the env var "echoes_password" or process.env.ECHOES_PASSWORD'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return secret;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/request/getSignature.ts
|
|
181
|
+
function getSignature_default(_body) {
|
|
182
|
+
const password = getEchoesPassword();
|
|
183
|
+
return createHmac("sha1", password || "").update("").digest("hex");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/request/getURL.ts
|
|
187
|
+
function getURL_default(serviceName) {
|
|
188
|
+
var _a, _b, _c;
|
|
189
|
+
if (serviceName.startsWith("http")) return serviceName;
|
|
190
|
+
const url = (_c = (_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.services) == null ? void 0 : _c[serviceName];
|
|
191
|
+
if (!url) {
|
|
192
|
+
throw new Error(`No URL found in echoes config for service ${serviceName}`);
|
|
193
|
+
}
|
|
194
|
+
return url;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/request/makeRequest.ts
|
|
198
|
+
import http from "http";
|
|
199
|
+
import https from "https";
|
|
200
|
+
async function executeWithRetries(callback, retries = 0, delayMs = 200) {
|
|
201
|
+
try {
|
|
202
|
+
return await callback();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (retries <= 0) throw error;
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
206
|
+
return await executeWithRetries(callback, retries - 1, delayMs);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function postJSON(urlValue, data, timeout, redirectsRemaining = 5) {
|
|
210
|
+
const url = new URL(urlValue);
|
|
211
|
+
const body = JSON.stringify(data);
|
|
212
|
+
const transport = url.protocol === "https:" ? https : http;
|
|
213
|
+
return await new Promise((resolve, reject) => {
|
|
214
|
+
const request2 = transport.request(
|
|
215
|
+
url,
|
|
216
|
+
{
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: {
|
|
219
|
+
"User-Agent": "Orionjs-Echoes/1.1",
|
|
220
|
+
"Content-Type": "application/json",
|
|
221
|
+
"Content-Length": Buffer.byteLength(body)
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
(response) => {
|
|
225
|
+
const chunks = [];
|
|
226
|
+
const statusCode = response.statusCode || 0;
|
|
227
|
+
if (statusCode >= 300 && statusCode < 400 && response.headers.location) {
|
|
228
|
+
response.resume();
|
|
229
|
+
if (redirectsRemaining <= 0) {
|
|
230
|
+
reject(new Error("Echoes request exceeded the redirect limit"));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
resolve(
|
|
234
|
+
postJSON(
|
|
235
|
+
new URL(response.headers.location, url).toString(),
|
|
236
|
+
data,
|
|
237
|
+
timeout,
|
|
238
|
+
redirectsRemaining - 1
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
244
|
+
response.on("end", () => {
|
|
245
|
+
try {
|
|
246
|
+
const responseBody = Buffer.concat(chunks).toString("utf8");
|
|
247
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
248
|
+
reject(new Error(`Request failed with status code ${statusCode}`));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
resolve({
|
|
252
|
+
statusCode,
|
|
253
|
+
data: responseBody ? JSON.parse(responseBody) : {}
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
reject(error);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
request2.on("error", reject);
|
|
262
|
+
if (timeout) {
|
|
263
|
+
request2.setTimeout(timeout, () => {
|
|
264
|
+
request2.destroy(new Error(`Echoes request timed out after ${timeout}ms`));
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
request2.end(body);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
var makeRequest = async (options) => {
|
|
271
|
+
return await executeWithRetries(
|
|
272
|
+
() => postJSON(options.url, options.data, options.timeout),
|
|
273
|
+
options.retries || 0
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/request/index.ts
|
|
278
|
+
async function request(options) {
|
|
279
|
+
var _a, _b;
|
|
280
|
+
const { method, service, params } = options;
|
|
281
|
+
const serializedParams = serialize_default(params);
|
|
282
|
+
const date = /* @__PURE__ */ new Date();
|
|
283
|
+
const body = { method, service, serializedParams, date };
|
|
284
|
+
const signature = getSignature_default(body);
|
|
285
|
+
try {
|
|
286
|
+
const requestMaker = ((_b = (_a = config_default) == null ? void 0 : _a.requests) == null ? void 0 : _b.makeRequest) || makeRequest;
|
|
287
|
+
const requestOptions = {
|
|
288
|
+
url: getURL_default(service),
|
|
289
|
+
retries: options.retries,
|
|
290
|
+
timeout: options.timeout,
|
|
291
|
+
data: {
|
|
292
|
+
body,
|
|
293
|
+
signature
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const result = await requestMaker(requestOptions);
|
|
297
|
+
if (result.statusCode !== 200) {
|
|
298
|
+
throw new Error(`Wrong status code ${result.statusCode}`);
|
|
299
|
+
}
|
|
300
|
+
const data = result.data;
|
|
301
|
+
if (data.error) {
|
|
302
|
+
const info = data.errorInfo;
|
|
303
|
+
if (info) {
|
|
304
|
+
if (data.isValidationError) {
|
|
305
|
+
throw createEchoesValidationError(info);
|
|
306
|
+
}
|
|
307
|
+
if (data.isUserError) {
|
|
308
|
+
throw createEchoesUserError(info);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
throw new Error(`${data.error}`);
|
|
312
|
+
}
|
|
313
|
+
const response = deserialize_default(data.result);
|
|
314
|
+
return response;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
const caught = error;
|
|
317
|
+
if (caught.isOrionError || caught.isEchoesError) throw caught;
|
|
318
|
+
throw new Error(`Echoes request network error calling ${service}/${method}: ${caught.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
31
322
|
// src/startService/KafkaManager.ts
|
|
323
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
32
324
|
var HEARTBEAT_INTERVAL_SECONDS = 5;
|
|
33
325
|
var CHECK_JOIN_CONSUMER_INTERVAL_SECONDS = 30;
|
|
34
326
|
var DEFAULT_PARTITIONS_CONSUMED_CONCURRENTLY = 4;
|
|
@@ -46,11 +338,21 @@ var KafkaManager = class {
|
|
|
46
338
|
producerConnected = false;
|
|
47
339
|
interval;
|
|
48
340
|
constructor(options) {
|
|
49
|
-
this.kafka = new Kafka(options.client);
|
|
50
341
|
this.options = options;
|
|
51
342
|
}
|
|
52
343
|
async start(options) {
|
|
53
344
|
var _a;
|
|
345
|
+
let kafkaModule;
|
|
346
|
+
try {
|
|
347
|
+
kafkaModule = await import("kafkajs");
|
|
348
|
+
} catch (error) {
|
|
349
|
+
const wrapped = new Error(
|
|
350
|
+
"Echoes Kafka transport requires kafkajs to be installed in the application"
|
|
351
|
+
);
|
|
352
|
+
wrapped.cause = error;
|
|
353
|
+
throw wrapped;
|
|
354
|
+
}
|
|
355
|
+
this.kafka = new kafkaModule.Kafka(this.options.client);
|
|
54
356
|
this.onEvent = options.onEvent;
|
|
55
357
|
this.subscriptions = new Map(
|
|
56
358
|
options.subscriptions.map((subscription) => [subscription.topic, subscription])
|
|
@@ -68,7 +370,7 @@ var KafkaManager = class {
|
|
|
68
370
|
this.consumer = this.kafka.consumer(this.options.consumer);
|
|
69
371
|
this.consumerStarted = await this.conditionalStart();
|
|
70
372
|
if (this.consumerStarted) return;
|
|
71
|
-
|
|
373
|
+
getEchoesLogger().info("Echoes: Delaying consumer group join, waiting for conditions to be met");
|
|
72
374
|
this.interval = setInterval(async () => {
|
|
73
375
|
this.consumerStarted = await this.conditionalStart();
|
|
74
376
|
if (this.consumerStarted) clearInterval(this.interval);
|
|
@@ -86,7 +388,7 @@ var KafkaManager = class {
|
|
|
86
388
|
{
|
|
87
389
|
value: serialize_default({ params: options.params }),
|
|
88
390
|
headers: {
|
|
89
|
-
"echoes-event-id":
|
|
391
|
+
"echoes-event-id": randomUUID2()
|
|
90
392
|
}
|
|
91
393
|
}
|
|
92
394
|
]
|
|
@@ -94,6 +396,7 @@ var KafkaManager = class {
|
|
|
94
396
|
}
|
|
95
397
|
async close() {
|
|
96
398
|
var _a, _b;
|
|
399
|
+
const logger = getEchoesLogger();
|
|
97
400
|
logger.warn("Echoes: Stopping Kafka transport");
|
|
98
401
|
if (this.interval) clearInterval(this.interval);
|
|
99
402
|
await Promise.all([
|
|
@@ -104,6 +407,7 @@ var KafkaManager = class {
|
|
|
104
407
|
this.producerConnected = false;
|
|
105
408
|
}
|
|
106
409
|
async checkJoinConsumerGroupConditions() {
|
|
410
|
+
const logger = getEchoesLogger();
|
|
107
411
|
const admin = this.kafka.admin();
|
|
108
412
|
try {
|
|
109
413
|
await admin.connect();
|
|
@@ -156,6 +460,7 @@ var KafkaManager = class {
|
|
|
156
460
|
return false;
|
|
157
461
|
}
|
|
158
462
|
async handleMessage(params) {
|
|
463
|
+
const logger = getEchoesLogger();
|
|
159
464
|
const subscription = this.subscriptions.get(params.topic);
|
|
160
465
|
if (!subscription) {
|
|
161
466
|
logger.warn(`Echoes: Received a message for an unknown topic: ${params.topic}, ignoring it`);
|
|
@@ -193,6 +498,9 @@ var KafkaManager = class {
|
|
|
193
498
|
createReceivedEvent(params) {
|
|
194
499
|
var _a, _b, _c, _d;
|
|
195
500
|
const { message, topic, partition } = params;
|
|
501
|
+
if (!message.value) {
|
|
502
|
+
throw new Error(`Echoes received an empty Kafka message for ${topic}`);
|
|
503
|
+
}
|
|
196
504
|
const data = deserialize_default(message.value.toString());
|
|
197
505
|
const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
|
|
198
506
|
const timestamp = Number.parseInt(message.timestamp || "", 10);
|
|
@@ -209,6 +517,7 @@ var KafkaManager = class {
|
|
|
209
517
|
}
|
|
210
518
|
async handleRetries(subscription, params, error) {
|
|
211
519
|
var _a, _b;
|
|
520
|
+
const logger = getEchoesLogger();
|
|
212
521
|
const { message, topic } = params;
|
|
213
522
|
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
523
|
if (subscription.attemptsBeforeDeadLetter === void 0 || subscription.attemptsBeforeDeadLetter === null) {
|
|
@@ -217,6 +526,7 @@ var KafkaManager = class {
|
|
|
217
526
|
const maxRetries = subscription.attemptsBeforeDeadLetter || 0;
|
|
218
527
|
const exceededMaxRetries = retries >= maxRetries;
|
|
219
528
|
const nextTopic = exceededMaxRetries ? `DLQ-${topic}` : topic;
|
|
529
|
+
if (!message.value) throw error;
|
|
220
530
|
await this.producer.send({
|
|
221
531
|
topic: nextTopic,
|
|
222
532
|
messages: [
|
|
@@ -243,7 +553,6 @@ var KafkaManager = class {
|
|
|
243
553
|
var KafkaManager_default = KafkaManager;
|
|
244
554
|
|
|
245
555
|
// src/events/EventBus.ts
|
|
246
|
-
import { logger as logger2 } from "@orion-js/logger";
|
|
247
556
|
var EventBus = class {
|
|
248
557
|
echoes;
|
|
249
558
|
transports;
|
|
@@ -303,9 +612,10 @@ var EventBus = class {
|
|
|
303
612
|
await Promise.all(this.startedTransports.map((transport) => transport.close()));
|
|
304
613
|
}
|
|
305
614
|
async handleEvent(event) {
|
|
615
|
+
const logger = getEchoesLogger();
|
|
306
616
|
const echo2 = this.echoes[event.topic];
|
|
307
617
|
if (!echo2 || echo2.type !== "event") {
|
|
308
|
-
|
|
618
|
+
logger.warn(`Echoes: Received a message for an unknown topic: ${event.topic}, ignoring it`);
|
|
309
619
|
return;
|
|
310
620
|
}
|
|
311
621
|
if (event.transport === "kafka" && event.context) {
|
|
@@ -433,6 +743,14 @@ function createEventBus(options) {
|
|
|
433
743
|
});
|
|
434
744
|
}
|
|
435
745
|
|
|
746
|
+
// src/requestsHandler/checkSignature.ts
|
|
747
|
+
function checkSignature_default(body, signature) {
|
|
748
|
+
const generatedSignature = getSignature_default(body);
|
|
749
|
+
if (generatedSignature !== signature) {
|
|
750
|
+
throw new Error("Echoes invalid signature");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
436
754
|
// src/requestsHandler/getEcho.ts
|
|
437
755
|
function getEcho_default(method) {
|
|
438
756
|
const echo2 = config_default.echoes[method];
|
|
@@ -445,72 +763,29 @@ function getEcho_default(method) {
|
|
|
445
763
|
return echo2;
|
|
446
764
|
}
|
|
447
765
|
|
|
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
766
|
// src/requestsHandler/index.ts
|
|
483
|
-
|
|
484
|
-
var requestsHandler_default = (options) => route({
|
|
767
|
+
var requestsHandler_default = (options) => ({
|
|
485
768
|
method: "post",
|
|
486
769
|
path: options.requests.handlerPath || "/echoes-services",
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
limit: "10mb"
|
|
490
|
-
},
|
|
491
|
-
async resolve(req) {
|
|
770
|
+
bodyLimit: "10mb",
|
|
771
|
+
async handle(requestBody) {
|
|
492
772
|
try {
|
|
493
|
-
const { body, signature } =
|
|
773
|
+
const { body, signature } = requestBody;
|
|
494
774
|
checkSignature_default(body, signature);
|
|
495
775
|
const { method, serializedParams } = body;
|
|
496
776
|
const echo2 = getEcho_default(method);
|
|
497
777
|
const result = await echo2.onRequest(serializedParams);
|
|
498
|
-
return {
|
|
499
|
-
body: {
|
|
500
|
-
result: serialize_default(result)
|
|
501
|
-
}
|
|
502
|
-
};
|
|
778
|
+
return { result: serialize_default(result) };
|
|
503
779
|
} catch (error) {
|
|
504
|
-
|
|
505
|
-
|
|
780
|
+
const caught = error;
|
|
781
|
+
if (!caught.getInfo) {
|
|
782
|
+
getEchoesLogger().error("Error at echo requests handler:", { error: caught });
|
|
506
783
|
}
|
|
507
784
|
return {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
isUserError: !!error.isUserError
|
|
513
|
-
}
|
|
785
|
+
error: caught.message,
|
|
786
|
+
errorInfo: caught.getInfo ? caught.getInfo() : null,
|
|
787
|
+
isValidationError: !!caught.isValidationError,
|
|
788
|
+
isUserError: !!caught.isUserError
|
|
514
789
|
};
|
|
515
790
|
}
|
|
516
791
|
}
|
|
@@ -522,7 +797,13 @@ async function startService(options) {
|
|
|
522
797
|
config_default.echoes = options.echoes;
|
|
523
798
|
if (options.requests) {
|
|
524
799
|
config_default.requests = options.requests;
|
|
525
|
-
|
|
800
|
+
const registerHandler = options.requests.registerHandler || getEchoesRuntime().registerRequestHandler;
|
|
801
|
+
if (!registerHandler) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
"Echoes requests require requests.registerHandler in standalone servers. Orion applications can import @orion-js/echoes-orion once during startup."
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
await registerHandler(requestsHandler_default(options));
|
|
526
807
|
}
|
|
527
808
|
const nextEventBus = createEventBus(options);
|
|
528
809
|
if (nextEventBus) {
|
|
@@ -533,115 +814,22 @@ async function startService(options) {
|
|
|
533
814
|
}
|
|
534
815
|
async function stopService() {
|
|
535
816
|
if (eventBus) {
|
|
536
|
-
|
|
817
|
+
const logger = getEchoesLogger();
|
|
818
|
+
logger.info("Stopping Echoes...");
|
|
537
819
|
await eventBus.close();
|
|
538
820
|
eventBus = null;
|
|
539
821
|
config_default.eventBus = void 0;
|
|
540
|
-
|
|
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}`);
|
|
822
|
+
logger.info("Echoes stopped");
|
|
631
823
|
}
|
|
632
824
|
}
|
|
633
825
|
|
|
634
|
-
// src/service/index.ts
|
|
635
|
-
import { runWithOrionAsyncContext } from "@orion-js/logger";
|
|
636
|
-
|
|
637
826
|
// src/echo/index.ts
|
|
638
|
-
import { clean, cleanAndValidate } from "@orion-js/schema";
|
|
639
827
|
var echo = function createNewEcho(options) {
|
|
640
828
|
const resolve = async (params, context) => {
|
|
641
|
-
const cleaned = options.params ? await
|
|
829
|
+
const cleaned = options.params ? await parseEchoesSchema(options.params, params) : params ?? {};
|
|
642
830
|
const result = await options.resolve(cleaned, context);
|
|
643
831
|
if (options.returns) {
|
|
644
|
-
return await
|
|
832
|
+
return await cleanEchoesSchema(options.returns, result);
|
|
645
833
|
}
|
|
646
834
|
return result;
|
|
647
835
|
};
|
|
@@ -668,6 +856,9 @@ var echo = function createNewEcho(options) {
|
|
|
668
856
|
onMessage: async (messageData) => {
|
|
669
857
|
var _a, _b, _c, _d;
|
|
670
858
|
const { message } = messageData;
|
|
859
|
+
if (!message.value) {
|
|
860
|
+
throw new Error(`Echoes received an empty Kafka message for ${messageData.topic}`);
|
|
861
|
+
}
|
|
671
862
|
const data = deserialize_default(message.value.toString());
|
|
672
863
|
const retries = Number.parseInt(((_b = (_a = message.headers) == null ? void 0 : _a.retries) == null ? void 0 : _b.toString()) || "0", 10);
|
|
673
864
|
const timestamp = Number(message.timestamp);
|
|
@@ -698,15 +889,21 @@ function createEchoEvent(options) {
|
|
|
698
889
|
return echo({ ...options, type: "event" });
|
|
699
890
|
}
|
|
700
891
|
|
|
892
|
+
// src/schema.ts
|
|
893
|
+
function typedEchoesSchema(schema) {
|
|
894
|
+
return schema;
|
|
895
|
+
}
|
|
896
|
+
|
|
701
897
|
// src/service/index.ts
|
|
702
|
-
import { getInstance, Service } from "@orion-js/services";
|
|
703
898
|
var serviceMetadata = /* @__PURE__ */ new WeakMap();
|
|
704
899
|
var echoesMetadata = /* @__PURE__ */ new WeakMap();
|
|
705
900
|
var echoEntriesByClass = /* @__PURE__ */ new Map();
|
|
901
|
+
var standaloneInstances = /* @__PURE__ */ new WeakMap();
|
|
706
902
|
var pendingEchoEntries = {};
|
|
707
903
|
function Echoes() {
|
|
708
904
|
return (target, context) => {
|
|
709
|
-
|
|
905
|
+
var _a, _b;
|
|
906
|
+
(_b = (_a = getEchoesRuntime()).decorateService) == null ? void 0 : _b.call(_a, target, context);
|
|
710
907
|
serviceMetadata.set(target, { _serviceType: "echoes" });
|
|
711
908
|
if (Object.keys(pendingEchoEntries).length > 0) {
|
|
712
909
|
echoEntriesByClass.set(target, pendingEchoEntries);
|
|
@@ -723,7 +920,7 @@ function EchoEvent(options = {}) {
|
|
|
723
920
|
return createEchoEvent({
|
|
724
921
|
...options,
|
|
725
922
|
resolve: async (params, contextData) => {
|
|
726
|
-
return await
|
|
923
|
+
return await runWithEchoesContext(
|
|
727
924
|
{
|
|
728
925
|
controllerType: "echo",
|
|
729
926
|
echoName: propertyKey,
|
|
@@ -752,7 +949,7 @@ function EchoRequest(options = {}) {
|
|
|
752
949
|
return createEchoRequest({
|
|
753
950
|
...options,
|
|
754
951
|
resolve: async (params, contextData) => {
|
|
755
|
-
return await
|
|
952
|
+
return await runWithEchoesContext(
|
|
756
953
|
{
|
|
757
954
|
controllerType: "echo",
|
|
758
955
|
echoName: propertyKey,
|
|
@@ -782,7 +979,18 @@ function initializeEchoesIfNeeded(instance) {
|
|
|
782
979
|
echoesMetadata.set(instance, echoes);
|
|
783
980
|
}
|
|
784
981
|
function getServiceEchoes(target) {
|
|
785
|
-
|
|
982
|
+
let instance;
|
|
983
|
+
if (typeof target !== "function") {
|
|
984
|
+
instance = target;
|
|
985
|
+
} else if (getEchoesRuntime().getInstance) {
|
|
986
|
+
instance = getEchoesRuntime().getInstance(target);
|
|
987
|
+
} else {
|
|
988
|
+
instance = standaloneInstances.get(target);
|
|
989
|
+
if (!instance) {
|
|
990
|
+
instance = new target();
|
|
991
|
+
standaloneInstances.set(target, instance);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
786
994
|
if (!serviceMetadata.has(instance.constructor)) {
|
|
787
995
|
throw new Error("You must pass a class decorated with @Echoes to getServiceEchoes");
|
|
788
996
|
}
|
|
@@ -798,13 +1006,25 @@ export {
|
|
|
798
1006
|
EchoEvent,
|
|
799
1007
|
EchoRequest,
|
|
800
1008
|
Echoes,
|
|
1009
|
+
EchoesUserError,
|
|
1010
|
+
EchoesValidationError,
|
|
1011
|
+
cleanEchoesSchema,
|
|
1012
|
+
configureEchoesRuntime,
|
|
801
1013
|
createEchoEvent,
|
|
802
1014
|
createEchoRequest,
|
|
1015
|
+
createEchoesUserError,
|
|
1016
|
+
createEchoesValidationError,
|
|
803
1017
|
echo,
|
|
1018
|
+
getEchoesContext,
|
|
1019
|
+
getEchoesLogger,
|
|
1020
|
+
getEchoesRuntime,
|
|
804
1021
|
getServiceEchoes,
|
|
1022
|
+
parseEchoesSchema,
|
|
805
1023
|
publish,
|
|
806
1024
|
request,
|
|
1025
|
+
runWithEchoesContext,
|
|
807
1026
|
startService,
|
|
808
|
-
stopService
|
|
1027
|
+
stopService,
|
|
1028
|
+
typedEchoesSchema
|
|
809
1029
|
};
|
|
810
1030
|
//# sourceMappingURL=index.js.map
|