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