@loadstrike/loadstrike-sdk 0.1.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/README.md +73 -0
- package/dist/cjs/cluster.js +410 -0
- package/dist/cjs/contracts.js +2 -0
- package/dist/cjs/correlation.js +1009 -0
- package/dist/cjs/index.js +71 -0
- package/dist/cjs/local.js +1884 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/reporting.js +1250 -0
- package/dist/cjs/runtime.js +7013 -0
- package/dist/cjs/sinks.js +2675 -0
- package/dist/cjs/transports.js +3695 -0
- package/dist/esm/cluster.js +403 -0
- package/dist/esm/contracts.js +1 -0
- package/dist/esm/correlation.js +999 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/local.js +1844 -0
- package/dist/esm/reporting.js +1241 -0
- package/dist/esm/runtime.js +6992 -0
- package/dist/esm/sinks.js +2657 -0
- package/dist/esm/transports.js +3658 -0
- package/dist/types/cluster.d.ts +112 -0
- package/dist/types/contracts.d.ts +439 -0
- package/dist/types/correlation.d.ts +234 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/local.d.ts +30 -0
- package/dist/types/reporting.d.ts +6 -0
- package/dist/types/runtime.d.ts +1052 -0
- package/dist/types/sinks.d.ts +497 -0
- package/dist/types/transports.d.ts +745 -0
- package/package.json +110 -0
|
@@ -0,0 +1,3658 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { TrackingFieldSelector } from "./correlation.js";
|
|
3
|
+
class TrafficEndpointDefinitionModel {
|
|
4
|
+
get JsonSerializerSettings() {
|
|
5
|
+
return this.JsonSettings;
|
|
6
|
+
}
|
|
7
|
+
set JsonSerializerSettings(value) {
|
|
8
|
+
this.JsonSettings = value;
|
|
9
|
+
}
|
|
10
|
+
constructor(initial) {
|
|
11
|
+
this.Mode = "Produce";
|
|
12
|
+
this.Name = "";
|
|
13
|
+
this.TrackingField = "";
|
|
14
|
+
this.AutoGenerateTrackingIdWhenMissing = true;
|
|
15
|
+
this.PollInterval = 0.25;
|
|
16
|
+
this.MessageHeaders = {};
|
|
17
|
+
if (new.target === TrafficEndpointDefinitionModel) {
|
|
18
|
+
throw new TypeError("TrafficEndpointDefinition is abstract and cannot be instantiated directly.");
|
|
19
|
+
}
|
|
20
|
+
initializeTrafficEndpointDefinitionModel(this, initial);
|
|
21
|
+
}
|
|
22
|
+
Validate() {
|
|
23
|
+
validateTrafficEndpointDefinitionModel(this);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class HttpOAuth2ClientCredentialsOptionsModel {
|
|
27
|
+
constructor(initial) {
|
|
28
|
+
this.TokenEndpoint = "";
|
|
29
|
+
this.ClientId = "";
|
|
30
|
+
this.ClientSecret = "";
|
|
31
|
+
this.Scopes = [];
|
|
32
|
+
this.AdditionalFormFields = {};
|
|
33
|
+
initializeHttpOAuth2ClientCredentialsOptionsModel(this, initial);
|
|
34
|
+
}
|
|
35
|
+
Validate() {
|
|
36
|
+
validateHttpOAuth2ClientCredentialsOptionsModel(this);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class HttpAuthOptionsModel {
|
|
40
|
+
constructor(initial) {
|
|
41
|
+
this.Type = "None";
|
|
42
|
+
this.AdditionalFormFields = {};
|
|
43
|
+
initializeHttpAuthOptionsModel(this, initial);
|
|
44
|
+
}
|
|
45
|
+
Validate() {
|
|
46
|
+
validateHttpAuthOptionsModel(this);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
class KafkaSaslOptionsModel {
|
|
50
|
+
constructor(initial) {
|
|
51
|
+
this.Mechanism = "Plain";
|
|
52
|
+
this.AdditionalSettings = {};
|
|
53
|
+
initializeKafkaSaslOptionsModel(this, initial);
|
|
54
|
+
}
|
|
55
|
+
Validate() {
|
|
56
|
+
validateKafkaSaslOptionsModel(this);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class HttpEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
60
|
+
constructor(initial) {
|
|
61
|
+
super(initial);
|
|
62
|
+
this.Kind = "Http";
|
|
63
|
+
this.Url = "";
|
|
64
|
+
this.Method = "GET";
|
|
65
|
+
this.BodyType = "Json";
|
|
66
|
+
this.TrackingPayloadSource = "Request";
|
|
67
|
+
this.ConsumeJsonArrayResponse = false;
|
|
68
|
+
this.RequestTimeout = 30;
|
|
69
|
+
this.Auth = new HttpAuthOptionsModel();
|
|
70
|
+
this.TokenRequestHeaders = {};
|
|
71
|
+
initializeHttpEndpointDefinitionModel(this, initial);
|
|
72
|
+
}
|
|
73
|
+
Validate() {
|
|
74
|
+
super.Validate();
|
|
75
|
+
const url = requireNonEmptyString(this.Url, "Url must be provided for HTTP endpoint.");
|
|
76
|
+
try {
|
|
77
|
+
new URL(url);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new Error("Url must be an absolute URI for HTTP endpoint.");
|
|
81
|
+
}
|
|
82
|
+
if (this.RequestTimeout <= 0) {
|
|
83
|
+
throw new RangeError("RequestTimeout must be greater than zero.");
|
|
84
|
+
}
|
|
85
|
+
toHttpAuthOptionsModel(this.Auth).Validate();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
class KafkaEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
89
|
+
constructor(initial) {
|
|
90
|
+
super(initial);
|
|
91
|
+
this.Kind = "Kafka";
|
|
92
|
+
this.BootstrapServers = "";
|
|
93
|
+
this.Topic = "";
|
|
94
|
+
this.SecurityProtocol = "Plaintext";
|
|
95
|
+
this.ConfluentSettings = {};
|
|
96
|
+
this.StartFromEarliest = true;
|
|
97
|
+
initializeKafkaEndpointDefinitionModel(this, initial);
|
|
98
|
+
}
|
|
99
|
+
Validate() {
|
|
100
|
+
super.Validate();
|
|
101
|
+
requireNonEmptyString(this.BootstrapServers, "BootstrapServers must be provided for Kafka endpoint.");
|
|
102
|
+
requireNonEmptyString(this.Topic, "Topic must be provided for Kafka endpoint.");
|
|
103
|
+
if (this.Mode === "Consume" && !String(this.ConsumerGroupId ?? "").trim()) {
|
|
104
|
+
throw new Error("ConsumerGroupId must be provided when Kafka endpoint mode is Consume.");
|
|
105
|
+
}
|
|
106
|
+
const securityProtocol = normalizeToken(this.SecurityProtocol);
|
|
107
|
+
if (securityProtocol === "saslplaintext" || securityProtocol === "saslssl") {
|
|
108
|
+
if (!this.Sasl) {
|
|
109
|
+
throw new Error("Sasl options must be provided for SASL Kafka security protocol.");
|
|
110
|
+
}
|
|
111
|
+
toKafkaSaslOptionsModel(this.Sasl).Validate();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
class RabbitMqEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
116
|
+
constructor(initial) {
|
|
117
|
+
super(initial);
|
|
118
|
+
this.Kind = "RabbitMq";
|
|
119
|
+
this.HostName = "";
|
|
120
|
+
this.Port = 5672;
|
|
121
|
+
this.VirtualHost = "/";
|
|
122
|
+
this.UserName = "";
|
|
123
|
+
this.Password = "";
|
|
124
|
+
this.Exchange = "";
|
|
125
|
+
this.RoutingKey = "";
|
|
126
|
+
this.QueueName = "";
|
|
127
|
+
this.Durable = true;
|
|
128
|
+
this.AutoAck = false;
|
|
129
|
+
this.UseSsl = false;
|
|
130
|
+
this.ClientProperties = {};
|
|
131
|
+
initializeRabbitMqEndpointDefinitionModel(this, initial);
|
|
132
|
+
}
|
|
133
|
+
Validate() {
|
|
134
|
+
super.Validate();
|
|
135
|
+
requireNonEmptyString(this.HostName, "HostName must be provided for RabbitMQ endpoint.");
|
|
136
|
+
if (this.Port <= 0) {
|
|
137
|
+
throw new RangeError("Port must be greater than zero.");
|
|
138
|
+
}
|
|
139
|
+
requireNonEmptyString(this.UserName, "UserName must be provided for RabbitMQ endpoint.");
|
|
140
|
+
if (this.Password == null) {
|
|
141
|
+
throw new Error("Password must be provided for RabbitMQ endpoint.");
|
|
142
|
+
}
|
|
143
|
+
if (this.Mode === "Produce" && !String(this.RoutingKey).trim() && !String(this.QueueName).trim()) {
|
|
144
|
+
throw new Error("Either RoutingKey or QueueName must be provided for RabbitMQ producer endpoint.");
|
|
145
|
+
}
|
|
146
|
+
if (this.Mode === "Consume" && !String(this.QueueName).trim()) {
|
|
147
|
+
throw new Error("QueueName must be provided for RabbitMQ consumer endpoint.");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
class NatsEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
152
|
+
constructor(initial) {
|
|
153
|
+
super(initial);
|
|
154
|
+
this.Kind = "Nats";
|
|
155
|
+
this.ServerUrl = "";
|
|
156
|
+
this.Subject = "";
|
|
157
|
+
this.MaxReconnectAttempts = 60;
|
|
158
|
+
initializeNatsEndpointDefinitionModel(this, initial);
|
|
159
|
+
}
|
|
160
|
+
Validate() {
|
|
161
|
+
super.Validate();
|
|
162
|
+
requireNonEmptyString(this.ServerUrl, "ServerUrl must be provided for NATS endpoint.");
|
|
163
|
+
requireNonEmptyString(this.Subject, "Subject must be provided for NATS endpoint.");
|
|
164
|
+
if (String(this.UserName ?? "").trim() && this.Password == null) {
|
|
165
|
+
throw new Error("Password must be provided when UserName is configured for NATS endpoint.");
|
|
166
|
+
}
|
|
167
|
+
if (this.MaxReconnectAttempts < 0) {
|
|
168
|
+
throw new RangeError("MaxReconnectAttempts must be zero or greater.");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
class RedisStreamsEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
173
|
+
constructor(initial) {
|
|
174
|
+
super(initial);
|
|
175
|
+
this.Kind = "RedisStreams";
|
|
176
|
+
this.ConnectionString = "";
|
|
177
|
+
this.StreamKey = "";
|
|
178
|
+
this.StartFromEarliest = true;
|
|
179
|
+
this.ReadCount = 50;
|
|
180
|
+
initializeRedisStreamsEndpointDefinitionModel(this, initial);
|
|
181
|
+
}
|
|
182
|
+
Validate() {
|
|
183
|
+
super.Validate();
|
|
184
|
+
requireNonEmptyString(this.ConnectionString, "ConnectionString must be provided for Redis Streams endpoint.");
|
|
185
|
+
requireNonEmptyString(this.StreamKey, "StreamKey must be provided for Redis Streams endpoint.");
|
|
186
|
+
if (this.Mode === "Consume" && !String(this.ConsumerGroup ?? "").trim()) {
|
|
187
|
+
throw new Error("ConsumerGroup must be provided when Redis Streams endpoint mode is Consume.");
|
|
188
|
+
}
|
|
189
|
+
if (this.Mode === "Consume" && !String(this.ConsumerName ?? "").trim()) {
|
|
190
|
+
throw new Error("ConsumerName must be provided when Redis Streams endpoint mode is Consume.");
|
|
191
|
+
}
|
|
192
|
+
if (this.ReadCount <= 0) {
|
|
193
|
+
throw new RangeError("ReadCount must be greater than zero.");
|
|
194
|
+
}
|
|
195
|
+
if (this.MaxLength != null && this.MaxLength <= 0) {
|
|
196
|
+
throw new RangeError("MaxLength must be greater than zero when configured.");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
class AzureEventHubsEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
201
|
+
constructor(initial) {
|
|
202
|
+
super(initial);
|
|
203
|
+
this.Kind = "AzureEventHubs";
|
|
204
|
+
this.ConnectionString = "";
|
|
205
|
+
this.EventHubName = "";
|
|
206
|
+
this.ConsumerGroup = "$Default";
|
|
207
|
+
this.StartFromEarliest = false;
|
|
208
|
+
initializeAzureEventHubsEndpointDefinitionModel(this, initial);
|
|
209
|
+
}
|
|
210
|
+
Validate() {
|
|
211
|
+
super.Validate();
|
|
212
|
+
requireNonEmptyString(this.ConnectionString, "ConnectionString must be provided for Azure Event Hubs endpoint.");
|
|
213
|
+
requireNonEmptyString(this.EventHubName, "EventHubName must be provided for Azure Event Hubs endpoint.");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
class DelegateStreamEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
217
|
+
constructor(initial) {
|
|
218
|
+
super(initial);
|
|
219
|
+
this.Kind = "DelegateStream";
|
|
220
|
+
this.ConnectionMetadata = {};
|
|
221
|
+
initializeDelegateStreamEndpointDefinitionModel(this, initial);
|
|
222
|
+
}
|
|
223
|
+
Validate() {
|
|
224
|
+
super.Validate();
|
|
225
|
+
if (this.Mode === "Produce" && typeof this.ProduceAsync !== "function") {
|
|
226
|
+
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
227
|
+
}
|
|
228
|
+
if (this.Mode === "Consume" && typeof this.ConsumeAsync !== "function") {
|
|
229
|
+
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
class PushDiffusionEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
234
|
+
constructor(initial) {
|
|
235
|
+
super(initial);
|
|
236
|
+
this.Kind = "PushDiffusion";
|
|
237
|
+
this.ServerUrl = "";
|
|
238
|
+
this.TopicPath = "";
|
|
239
|
+
this.ConnectionProperties = {};
|
|
240
|
+
initializePushDiffusionEndpointDefinitionModel(this, initial);
|
|
241
|
+
}
|
|
242
|
+
Validate() {
|
|
243
|
+
super.Validate();
|
|
244
|
+
requireNonEmptyString(this.ServerUrl, "ServerUrl must be provided for Push Diffusion endpoint.");
|
|
245
|
+
requireNonEmptyString(this.TopicPath, "TopicPath must be provided for Push Diffusion endpoint.");
|
|
246
|
+
if (this.Mode === "Produce" && typeof this.PublishAsync !== "function") {
|
|
247
|
+
throw new Error("PublishAsync delegate must be provided for Push Diffusion producer endpoint.");
|
|
248
|
+
}
|
|
249
|
+
if (this.Mode === "Consume" && typeof this.SubscribeAsync !== "function") {
|
|
250
|
+
throw new Error("SubscribeAsync delegate must be provided for Push Diffusion consumer endpoint.");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export const TrafficEndpointDefinition = TrafficEndpointDefinitionModel;
|
|
255
|
+
export const HttpEndpointDefinition = HttpEndpointDefinitionModel;
|
|
256
|
+
export const KafkaEndpointDefinition = KafkaEndpointDefinitionModel;
|
|
257
|
+
export const RabbitMqEndpointDefinition = RabbitMqEndpointDefinitionModel;
|
|
258
|
+
export const NatsEndpointDefinition = NatsEndpointDefinitionModel;
|
|
259
|
+
export const RedisStreamsEndpointDefinition = RedisStreamsEndpointDefinitionModel;
|
|
260
|
+
export const AzureEventHubsEndpointDefinition = AzureEventHubsEndpointDefinitionModel;
|
|
261
|
+
export const DelegateStreamEndpointDefinition = DelegateStreamEndpointDefinitionModel;
|
|
262
|
+
export const PushDiffusionEndpointDefinition = PushDiffusionEndpointDefinitionModel;
|
|
263
|
+
export const HttpOAuth2ClientCredentialsOptions = HttpOAuth2ClientCredentialsOptionsModel;
|
|
264
|
+
export const HttpAuthOptions = HttpAuthOptionsModel;
|
|
265
|
+
export const KafkaSaslOptions = KafkaSaslOptionsModel;
|
|
266
|
+
function initializeTrafficEndpointDefinitionModel(target, initial) {
|
|
267
|
+
const raw = asRecordOrEmpty(initial);
|
|
268
|
+
const mode = pickOptionalEndpointString(raw, "Mode", "mode");
|
|
269
|
+
if (mode) {
|
|
270
|
+
target.Mode = mode;
|
|
271
|
+
}
|
|
272
|
+
const name = pickOptionalEndpointString(raw, "Name", "name");
|
|
273
|
+
if (name) {
|
|
274
|
+
target.Name = name;
|
|
275
|
+
}
|
|
276
|
+
const trackingField = pickOptionalTrackingFieldSelectorString(raw, "TrackingField", "trackingField");
|
|
277
|
+
if (trackingField != null) {
|
|
278
|
+
target.TrackingField = trackingField;
|
|
279
|
+
}
|
|
280
|
+
if (hasAnyEndpointField(raw, ["GatherByField", "gatherByField"])) {
|
|
281
|
+
target.GatherByField = pickOptionalTrackingFieldSelectorString(raw, "GatherByField", "gatherByField");
|
|
282
|
+
}
|
|
283
|
+
const autoGenerate = pickEndpointBoolean(raw, "AutoGenerateTrackingIdWhenMissing", "autoGenerateTrackingIdWhenMissing");
|
|
284
|
+
if (autoGenerate != null) {
|
|
285
|
+
target.AutoGenerateTrackingIdWhenMissing = autoGenerate;
|
|
286
|
+
}
|
|
287
|
+
const pollInterval = pickOptionalEndpointNumber(raw, "PollInterval", "pollInterval");
|
|
288
|
+
const pollIntervalMs = pickOptionalEndpointNumber(raw, "PollIntervalMs", "pollIntervalMs");
|
|
289
|
+
const pollIntervalSeconds = pickOptionalEndpointNumber(raw, "PollIntervalSeconds", "pollIntervalSeconds");
|
|
290
|
+
if (pollIntervalMs != null) {
|
|
291
|
+
target.PollInterval = pollIntervalMs / 1000;
|
|
292
|
+
}
|
|
293
|
+
else if (pollIntervalSeconds != null) {
|
|
294
|
+
target.PollInterval = pollIntervalSeconds;
|
|
295
|
+
}
|
|
296
|
+
else if (pollInterval != null) {
|
|
297
|
+
target.PollInterval = pollInterval;
|
|
298
|
+
}
|
|
299
|
+
if (hasAnyEndpointField(raw, ["MessageHeaders", "messageHeaders"])) {
|
|
300
|
+
target.MessageHeaders = pickEndpointStringRecord(raw, "MessageHeaders", "messageHeaders");
|
|
301
|
+
}
|
|
302
|
+
if (hasAnyEndpointField(raw, ["MessagePayload", "messagePayload"])) {
|
|
303
|
+
target.MessagePayload = pickEndpointValue(raw, "MessagePayload", "messagePayload");
|
|
304
|
+
}
|
|
305
|
+
if (hasAnyEndpointField(raw, ["MessagePayloadType", "messagePayloadType"])) {
|
|
306
|
+
target.MessagePayloadType = pickOptionalEndpointString(raw, "MessagePayloadType", "messagePayloadType");
|
|
307
|
+
}
|
|
308
|
+
if (hasAnyEndpointField(raw, ["JsonSettings", "jsonSettings", "JsonSerializerSettings", "jsonSerializerSettings"])) {
|
|
309
|
+
target.JsonSettings = pickEndpointRecord(raw, "JsonSettings", "jsonSettings", "JsonSerializerSettings", "jsonSerializerSettings");
|
|
310
|
+
}
|
|
311
|
+
if (hasAnyEndpointField(raw, ["JsonConvertSettings", "jsonConvertSettings"])) {
|
|
312
|
+
target.JsonConvertSettings = pickEndpointRecord(raw, "JsonConvertSettings", "jsonConvertSettings");
|
|
313
|
+
}
|
|
314
|
+
if (hasAnyEndpointField(raw, ["ContentType", "contentType"])) {
|
|
315
|
+
target.ContentType = pickOptionalEndpointString(raw, "ContentType", "contentType");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function initializeHttpOAuth2ClientCredentialsOptionsModel(target, initial) {
|
|
319
|
+
const raw = asRecordOrEmpty(initial);
|
|
320
|
+
const tokenEndpoint = pickOptionalEndpointString(raw, "TokenEndpoint", "tokenEndpoint", "TokenUrl", "tokenUrl");
|
|
321
|
+
if (tokenEndpoint) {
|
|
322
|
+
target.TokenEndpoint = tokenEndpoint;
|
|
323
|
+
}
|
|
324
|
+
const clientId = pickOptionalEndpointString(raw, "ClientId", "clientId");
|
|
325
|
+
if (clientId) {
|
|
326
|
+
target.ClientId = clientId;
|
|
327
|
+
}
|
|
328
|
+
const clientSecret = pickOptionalEndpointString(raw, "ClientSecret", "clientSecret");
|
|
329
|
+
if (clientSecret) {
|
|
330
|
+
target.ClientSecret = clientSecret;
|
|
331
|
+
}
|
|
332
|
+
const scopes = pickEndpointStringArray(raw, "Scopes", "scopes");
|
|
333
|
+
if (scopes) {
|
|
334
|
+
target.Scopes = scopes;
|
|
335
|
+
}
|
|
336
|
+
if (hasAnyEndpointField(raw, ["AdditionalFormFields", "additionalFormFields"])) {
|
|
337
|
+
target.AdditionalFormFields = pickEndpointStringRecord(raw, "AdditionalFormFields", "additionalFormFields");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function initializeHttpAuthOptionsModel(target, initial) {
|
|
341
|
+
const raw = asRecordOrEmpty(initial);
|
|
342
|
+
const type = pickOptionalEndpointString(raw, "Type", "type", "Mode", "mode");
|
|
343
|
+
if (type) {
|
|
344
|
+
target.Type = type;
|
|
345
|
+
}
|
|
346
|
+
if (hasAnyEndpointField(raw, ["Username", "username"])) {
|
|
347
|
+
target.Username = pickOptionalEndpointString(raw, "Username", "username");
|
|
348
|
+
}
|
|
349
|
+
if (hasAnyEndpointField(raw, ["Password", "password"])) {
|
|
350
|
+
target.Password = pickOptionalEndpointStringAllowEmpty(raw, "Password", "password");
|
|
351
|
+
}
|
|
352
|
+
if (hasAnyEndpointField(raw, ["BearerToken", "bearerToken"])) {
|
|
353
|
+
target.BearerToken = pickOptionalEndpointString(raw, "BearerToken", "bearerToken");
|
|
354
|
+
}
|
|
355
|
+
if (hasAnyEndpointField(raw, ["TokenUrl", "tokenUrl"])) {
|
|
356
|
+
target.TokenUrl = pickOptionalEndpointString(raw, "TokenUrl", "tokenUrl");
|
|
357
|
+
}
|
|
358
|
+
if (hasAnyEndpointField(raw, ["ClientId", "clientId"])) {
|
|
359
|
+
target.ClientId = pickOptionalEndpointString(raw, "ClientId", "clientId");
|
|
360
|
+
}
|
|
361
|
+
if (hasAnyEndpointField(raw, ["ClientSecret", "clientSecret"])) {
|
|
362
|
+
target.ClientSecret = pickOptionalEndpointString(raw, "ClientSecret", "clientSecret");
|
|
363
|
+
}
|
|
364
|
+
if (hasAnyEndpointField(raw, ["Scope", "scope"])) {
|
|
365
|
+
target.Scope = pickOptionalEndpointString(raw, "Scope", "scope");
|
|
366
|
+
}
|
|
367
|
+
const scopes = pickEndpointStringArray(raw, "Scopes", "scopes");
|
|
368
|
+
if (scopes) {
|
|
369
|
+
target.Scopes = scopes;
|
|
370
|
+
}
|
|
371
|
+
if (hasAnyEndpointField(raw, ["Audience", "audience"])) {
|
|
372
|
+
target.Audience = pickOptionalEndpointString(raw, "Audience", "audience");
|
|
373
|
+
}
|
|
374
|
+
if (hasAnyEndpointField(raw, ["TokenHeaderName", "tokenHeaderName"])) {
|
|
375
|
+
target.TokenHeaderName = pickOptionalEndpointString(raw, "TokenHeaderName", "tokenHeaderName");
|
|
376
|
+
}
|
|
377
|
+
if (hasAnyEndpointField(raw, ["AdditionalFormFields", "additionalFormFields"])) {
|
|
378
|
+
target.AdditionalFormFields = pickEndpointStringRecord(raw, "AdditionalFormFields", "additionalFormFields");
|
|
379
|
+
}
|
|
380
|
+
if (hasAnyEndpointField(raw, ["OAuth2ClientCredentials", "oauth2ClientCredentials"])) {
|
|
381
|
+
target.OAuth2ClientCredentials = toHttpOAuth2ClientCredentialsOptionsModel(pickEndpointRecord(raw, "OAuth2ClientCredentials", "oauth2ClientCredentials"));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function initializeKafkaSaslOptionsModel(target, initial) {
|
|
385
|
+
const raw = asRecordOrEmpty(initial);
|
|
386
|
+
const mechanism = pickOptionalEndpointString(raw, "Mechanism", "mechanism");
|
|
387
|
+
if (mechanism) {
|
|
388
|
+
target.Mechanism = mechanism;
|
|
389
|
+
}
|
|
390
|
+
if (hasAnyEndpointField(raw, ["Username", "username"])) {
|
|
391
|
+
target.Username = pickOptionalEndpointString(raw, "Username", "username");
|
|
392
|
+
}
|
|
393
|
+
if (hasAnyEndpointField(raw, ["Password", "password"])) {
|
|
394
|
+
target.Password = pickOptionalEndpointStringAllowEmpty(raw, "Password", "password");
|
|
395
|
+
}
|
|
396
|
+
if (hasAnyEndpointField(raw, ["OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint"])) {
|
|
397
|
+
const tokenEndpoint = pickOptionalEndpointString(raw, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint");
|
|
398
|
+
target.OAuthBearerTokenEndpointUrl = tokenEndpoint;
|
|
399
|
+
target.TokenEndpoint = tokenEndpoint;
|
|
400
|
+
}
|
|
401
|
+
if (hasAnyEndpointField(raw, ["AccessToken", "accessToken"])) {
|
|
402
|
+
target.AccessToken = pickOptionalEndpointString(raw, "AccessToken", "accessToken");
|
|
403
|
+
}
|
|
404
|
+
if (hasAnyEndpointField(raw, ["OAuthBearerToken", "oauthBearerToken"])) {
|
|
405
|
+
target.OAuthBearerToken = pickOptionalEndpointString(raw, "OAuthBearerToken", "oauthBearerToken");
|
|
406
|
+
}
|
|
407
|
+
if (hasAnyEndpointField(raw, ["AdditionalSettings", "additionalSettings"])) {
|
|
408
|
+
target.AdditionalSettings = pickEndpointStringRecord(raw, "AdditionalSettings", "additionalSettings");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function initializeHttpEndpointDefinitionModel(target, initial) {
|
|
412
|
+
const raw = asRecordOrEmpty(initial);
|
|
413
|
+
const url = pickOptionalEndpointString(raw, "Url", "url");
|
|
414
|
+
if (url) {
|
|
415
|
+
target.Url = url;
|
|
416
|
+
}
|
|
417
|
+
const method = pickOptionalEndpointString(raw, "Method", "method");
|
|
418
|
+
if (method) {
|
|
419
|
+
target.Method = method;
|
|
420
|
+
}
|
|
421
|
+
const consumePoll = pickEndpointFunction(raw, "ConsumePoll", "consumePoll");
|
|
422
|
+
if (consumePoll) {
|
|
423
|
+
target.ConsumePoll = consumePoll;
|
|
424
|
+
}
|
|
425
|
+
const bodyType = pickOptionalEndpointString(raw, "BodyType", "bodyType");
|
|
426
|
+
if (bodyType) {
|
|
427
|
+
target.BodyType = bodyType;
|
|
428
|
+
}
|
|
429
|
+
const responseSource = pickOptionalEndpointString(raw, "ResponseSource", "responseSource");
|
|
430
|
+
if (responseSource) {
|
|
431
|
+
target.ResponseSource = responseSource;
|
|
432
|
+
}
|
|
433
|
+
const trackingPayloadSource = pickOptionalEndpointString(raw, "TrackingPayloadSource", "trackingPayloadSource");
|
|
434
|
+
if (trackingPayloadSource) {
|
|
435
|
+
target.TrackingPayloadSource = canonicalizeHttpTrackingPayloadSource(trackingPayloadSource);
|
|
436
|
+
if (!responseSource) {
|
|
437
|
+
const inferredResponseSource = inferLegacyHttpResponseSource(trackingPayloadSource);
|
|
438
|
+
if (inferredResponseSource) {
|
|
439
|
+
target.ResponseSource = inferredResponseSource;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (hasAnyEndpointField(raw, ["ConsumeArrayPath", "consumeArrayPath"])) {
|
|
444
|
+
target.ConsumeArrayPath = pickOptionalEndpointString(raw, "ConsumeArrayPath", "consumeArrayPath");
|
|
445
|
+
}
|
|
446
|
+
const consumeJsonArrayResponse = pickEndpointBoolean(raw, "ConsumeJsonArrayResponse", "consumeJsonArrayResponse");
|
|
447
|
+
if (consumeJsonArrayResponse != null) {
|
|
448
|
+
target.ConsumeJsonArrayResponse = consumeJsonArrayResponse;
|
|
449
|
+
}
|
|
450
|
+
const requestTimeout = pickOptionalEndpointNumber(raw, "RequestTimeout", "requestTimeout", "RequestTimeoutSeconds", "requestTimeoutSeconds");
|
|
451
|
+
if (requestTimeout != null) {
|
|
452
|
+
target.RequestTimeout = requestTimeout;
|
|
453
|
+
}
|
|
454
|
+
if (hasAnyEndpointField(raw, ["Auth", "auth"])) {
|
|
455
|
+
target.Auth = toHttpAuthOptionsModel(pickEndpointRecord(raw, "Auth", "auth"));
|
|
456
|
+
}
|
|
457
|
+
if (hasAnyEndpointField(raw, ["TokenRequestHeaders", "tokenRequestHeaders"])) {
|
|
458
|
+
target.TokenRequestHeaders = pickEndpointStringRecord(raw, "TokenRequestHeaders", "tokenRequestHeaders");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function initializeKafkaEndpointDefinitionModel(target, initial) {
|
|
462
|
+
const raw = asRecordOrEmpty(initial);
|
|
463
|
+
const bootstrapServers = pickOptionalEndpointString(raw, "BootstrapServers", "bootstrapServers");
|
|
464
|
+
if (bootstrapServers) {
|
|
465
|
+
target.BootstrapServers = bootstrapServers;
|
|
466
|
+
}
|
|
467
|
+
const topic = pickOptionalEndpointString(raw, "Topic", "topic");
|
|
468
|
+
if (topic) {
|
|
469
|
+
target.Topic = topic;
|
|
470
|
+
}
|
|
471
|
+
if (hasAnyEndpointField(raw, ["ConsumerGroupId", "consumerGroupId"])) {
|
|
472
|
+
target.ConsumerGroupId = pickOptionalEndpointString(raw, "ConsumerGroupId", "consumerGroupId");
|
|
473
|
+
}
|
|
474
|
+
const securityProtocol = pickOptionalEndpointString(raw, "SecurityProtocol", "securityProtocol");
|
|
475
|
+
if (securityProtocol) {
|
|
476
|
+
target.SecurityProtocol = securityProtocol;
|
|
477
|
+
}
|
|
478
|
+
if (hasAnyEndpointField(raw, ["Sasl", "sasl"])) {
|
|
479
|
+
target.Sasl = toKafkaSaslOptionsModel(pickEndpointRecord(raw, "Sasl", "sasl"));
|
|
480
|
+
}
|
|
481
|
+
if (hasAnyEndpointField(raw, ["ConfluentSettings", "confluentSettings"])) {
|
|
482
|
+
target.ConfluentSettings = pickEndpointStringRecord(raw, "ConfluentSettings", "confluentSettings");
|
|
483
|
+
}
|
|
484
|
+
const startFromEarliest = pickEndpointBoolean(raw, "StartFromEarliest", "startFromEarliest");
|
|
485
|
+
if (startFromEarliest != null) {
|
|
486
|
+
target.StartFromEarliest = startFromEarliest;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function initializeRabbitMqEndpointDefinitionModel(target, initial) {
|
|
490
|
+
const raw = asRecordOrEmpty(initial);
|
|
491
|
+
const hostName = pickOptionalEndpointString(raw, "HostName", "hostName");
|
|
492
|
+
if (hostName) {
|
|
493
|
+
target.HostName = hostName;
|
|
494
|
+
}
|
|
495
|
+
const port = pickOptionalEndpointNumber(raw, "Port", "port");
|
|
496
|
+
if (port != null) {
|
|
497
|
+
target.Port = port;
|
|
498
|
+
}
|
|
499
|
+
const virtualHost = pickOptionalEndpointString(raw, "VirtualHost", "virtualHost");
|
|
500
|
+
if (virtualHost) {
|
|
501
|
+
target.VirtualHost = virtualHost;
|
|
502
|
+
}
|
|
503
|
+
const userName = pickOptionalEndpointString(raw, "UserName", "userName");
|
|
504
|
+
if (userName) {
|
|
505
|
+
target.UserName = userName;
|
|
506
|
+
}
|
|
507
|
+
if (hasAnyEndpointField(raw, ["Password", "password"])) {
|
|
508
|
+
target.Password = pickOptionalEndpointStringAllowEmpty(raw, "Password", "password") ?? "";
|
|
509
|
+
}
|
|
510
|
+
const exchange = pickOptionalEndpointString(raw, "Exchange", "exchange");
|
|
511
|
+
if (exchange) {
|
|
512
|
+
target.Exchange = exchange;
|
|
513
|
+
}
|
|
514
|
+
const routingKey = pickOptionalEndpointString(raw, "RoutingKey", "routingKey");
|
|
515
|
+
if (routingKey) {
|
|
516
|
+
target.RoutingKey = routingKey;
|
|
517
|
+
}
|
|
518
|
+
const queueName = pickOptionalEndpointString(raw, "QueueName", "queueName");
|
|
519
|
+
if (queueName) {
|
|
520
|
+
target.QueueName = queueName;
|
|
521
|
+
}
|
|
522
|
+
const durable = pickEndpointBoolean(raw, "Durable", "durable");
|
|
523
|
+
if (durable != null) {
|
|
524
|
+
target.Durable = durable;
|
|
525
|
+
}
|
|
526
|
+
const autoAck = pickEndpointBoolean(raw, "AutoAck", "autoAck");
|
|
527
|
+
if (autoAck != null) {
|
|
528
|
+
target.AutoAck = autoAck;
|
|
529
|
+
}
|
|
530
|
+
const useSsl = pickEndpointBoolean(raw, "UseSsl", "useSsl");
|
|
531
|
+
if (useSsl != null) {
|
|
532
|
+
target.UseSsl = useSsl;
|
|
533
|
+
}
|
|
534
|
+
if (hasAnyEndpointField(raw, ["ClientProperties", "clientProperties"])) {
|
|
535
|
+
target.ClientProperties = pickEndpointStringRecord(raw, "ClientProperties", "clientProperties");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function initializeNatsEndpointDefinitionModel(target, initial) {
|
|
539
|
+
const raw = asRecordOrEmpty(initial);
|
|
540
|
+
const serverUrl = pickOptionalEndpointString(raw, "ServerUrl", "serverUrl");
|
|
541
|
+
if (serverUrl) {
|
|
542
|
+
target.ServerUrl = serverUrl;
|
|
543
|
+
}
|
|
544
|
+
const subject = pickOptionalEndpointString(raw, "Subject", "subject");
|
|
545
|
+
if (subject) {
|
|
546
|
+
target.Subject = subject;
|
|
547
|
+
}
|
|
548
|
+
if (hasAnyEndpointField(raw, ["QueueGroup", "queueGroup"])) {
|
|
549
|
+
target.QueueGroup = pickOptionalEndpointString(raw, "QueueGroup", "queueGroup");
|
|
550
|
+
}
|
|
551
|
+
if (hasAnyEndpointField(raw, ["UserName", "userName"])) {
|
|
552
|
+
target.UserName = pickOptionalEndpointString(raw, "UserName", "userName");
|
|
553
|
+
}
|
|
554
|
+
if (hasAnyEndpointField(raw, ["Password", "password"])) {
|
|
555
|
+
target.Password = pickOptionalEndpointStringAllowEmpty(raw, "Password", "password");
|
|
556
|
+
}
|
|
557
|
+
if (hasAnyEndpointField(raw, ["Token", "token"])) {
|
|
558
|
+
target.Token = pickOptionalEndpointString(raw, "Token", "token");
|
|
559
|
+
}
|
|
560
|
+
if (hasAnyEndpointField(raw, ["ConnectionName", "connectionName"])) {
|
|
561
|
+
target.ConnectionName = pickOptionalEndpointString(raw, "ConnectionName", "connectionName");
|
|
562
|
+
}
|
|
563
|
+
const maxReconnectAttempts = pickOptionalEndpointNumber(raw, "MaxReconnectAttempts", "maxReconnectAttempts");
|
|
564
|
+
if (maxReconnectAttempts != null) {
|
|
565
|
+
target.MaxReconnectAttempts = maxReconnectAttempts;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function initializeRedisStreamsEndpointDefinitionModel(target, initial) {
|
|
569
|
+
const raw = asRecordOrEmpty(initial);
|
|
570
|
+
const connectionString = pickOptionalEndpointString(raw, "ConnectionString", "connectionString");
|
|
571
|
+
if (connectionString) {
|
|
572
|
+
target.ConnectionString = connectionString;
|
|
573
|
+
}
|
|
574
|
+
const streamKey = pickOptionalEndpointString(raw, "StreamKey", "streamKey");
|
|
575
|
+
if (streamKey) {
|
|
576
|
+
target.StreamKey = streamKey;
|
|
577
|
+
}
|
|
578
|
+
if (hasAnyEndpointField(raw, ["ConsumerGroup", "consumerGroup"])) {
|
|
579
|
+
target.ConsumerGroup = pickOptionalEndpointString(raw, "ConsumerGroup", "consumerGroup");
|
|
580
|
+
}
|
|
581
|
+
if (hasAnyEndpointField(raw, ["ConsumerName", "consumerName"])) {
|
|
582
|
+
target.ConsumerName = pickOptionalEndpointString(raw, "ConsumerName", "consumerName");
|
|
583
|
+
}
|
|
584
|
+
const startFromEarliest = pickEndpointBoolean(raw, "StartFromEarliest", "startFromEarliest");
|
|
585
|
+
if (startFromEarliest != null) {
|
|
586
|
+
target.StartFromEarliest = startFromEarliest;
|
|
587
|
+
}
|
|
588
|
+
const readCount = pickOptionalEndpointNumber(raw, "ReadCount", "readCount");
|
|
589
|
+
if (readCount != null) {
|
|
590
|
+
target.ReadCount = readCount;
|
|
591
|
+
}
|
|
592
|
+
if (hasAnyEndpointField(raw, ["MaxLength", "maxLength"])) {
|
|
593
|
+
target.MaxLength = pickOptionalEndpointNumber(raw, "MaxLength", "maxLength");
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function initializeAzureEventHubsEndpointDefinitionModel(target, initial) {
|
|
597
|
+
const raw = asRecordOrEmpty(initial);
|
|
598
|
+
const connectionString = pickOptionalEndpointString(raw, "ConnectionString", "connectionString");
|
|
599
|
+
if (connectionString) {
|
|
600
|
+
target.ConnectionString = connectionString;
|
|
601
|
+
}
|
|
602
|
+
const eventHubName = pickOptionalEndpointString(raw, "EventHubName", "eventHubName");
|
|
603
|
+
if (eventHubName) {
|
|
604
|
+
target.EventHubName = eventHubName;
|
|
605
|
+
}
|
|
606
|
+
const consumerGroup = pickOptionalEndpointString(raw, "ConsumerGroup", "consumerGroup");
|
|
607
|
+
if (consumerGroup) {
|
|
608
|
+
target.ConsumerGroup = consumerGroup;
|
|
609
|
+
}
|
|
610
|
+
const startFromEarliest = pickEndpointBoolean(raw, "StartFromEarliest", "startFromEarliest");
|
|
611
|
+
if (startFromEarliest != null) {
|
|
612
|
+
target.StartFromEarliest = startFromEarliest;
|
|
613
|
+
}
|
|
614
|
+
if (hasAnyEndpointField(raw, ["PartitionId", "partitionId"])) {
|
|
615
|
+
target.PartitionId = pickOptionalEndpointString(raw, "PartitionId", "partitionId");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function initializeDelegateStreamEndpointDefinitionModel(target, initial) {
|
|
619
|
+
const raw = asRecordOrEmpty(initial);
|
|
620
|
+
const produce = pickEndpointFunction(raw, "Produce", "produce");
|
|
621
|
+
if (produce) {
|
|
622
|
+
target.Produce = produce;
|
|
623
|
+
}
|
|
624
|
+
const consume = pickEndpointFunction(raw, "Consume", "consume");
|
|
625
|
+
if (consume) {
|
|
626
|
+
target.Consume = consume;
|
|
627
|
+
}
|
|
628
|
+
const produceAsync = pickEndpointFunction(raw, "ProduceAsync", "produceAsync");
|
|
629
|
+
if (produceAsync) {
|
|
630
|
+
target.ProduceAsync = produceAsync;
|
|
631
|
+
}
|
|
632
|
+
const consumeAsync = pickEndpointFunction(raw, "ConsumeAsync", "consumeAsync");
|
|
633
|
+
if (consumeAsync) {
|
|
634
|
+
target.ConsumeAsync = consumeAsync;
|
|
635
|
+
}
|
|
636
|
+
if (hasAnyEndpointField(raw, ["ConnectionMetadata", "connectionMetadata"])) {
|
|
637
|
+
target.ConnectionMetadata = pickEndpointStringRecord(raw, "ConnectionMetadata", "connectionMetadata");
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function initializePushDiffusionEndpointDefinitionModel(target, initial) {
|
|
641
|
+
const raw = asRecordOrEmpty(initial);
|
|
642
|
+
const serverUrl = pickOptionalEndpointString(raw, "ServerUrl", "serverUrl");
|
|
643
|
+
if (serverUrl) {
|
|
644
|
+
target.ServerUrl = serverUrl;
|
|
645
|
+
}
|
|
646
|
+
const topicPath = pickOptionalEndpointString(raw, "TopicPath", "topicPath");
|
|
647
|
+
if (topicPath) {
|
|
648
|
+
target.TopicPath = topicPath;
|
|
649
|
+
}
|
|
650
|
+
if (hasAnyEndpointField(raw, ["Principal", "principal"])) {
|
|
651
|
+
target.Principal = pickOptionalEndpointString(raw, "Principal", "principal");
|
|
652
|
+
}
|
|
653
|
+
if (hasAnyEndpointField(raw, ["Password", "password"])) {
|
|
654
|
+
target.Password = pickOptionalEndpointStringAllowEmpty(raw, "Password", "password");
|
|
655
|
+
}
|
|
656
|
+
if (hasAnyEndpointField(raw, ["ConnectionProperties", "connectionProperties"])) {
|
|
657
|
+
target.ConnectionProperties = pickEndpointStringRecord(raw, "ConnectionProperties", "connectionProperties");
|
|
658
|
+
}
|
|
659
|
+
const publishAsync = pickEndpointFunction(raw, "PublishAsync", "publishAsync");
|
|
660
|
+
if (publishAsync) {
|
|
661
|
+
target.PublishAsync = publishAsync;
|
|
662
|
+
}
|
|
663
|
+
const subscribeAsync = pickEndpointFunction(raw, "SubscribeAsync", "subscribeAsync");
|
|
664
|
+
if (subscribeAsync) {
|
|
665
|
+
target.SubscribeAsync = subscribeAsync;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function validateTrafficEndpointDefinitionModel(target) {
|
|
669
|
+
requireNonEmptyString(target.Name, "Endpoint name must be provided.");
|
|
670
|
+
if (target.Mode !== "Produce" && target.Mode !== "Consume") {
|
|
671
|
+
throw new Error(`Unsupported endpoint mode: ${String(target.Mode)}.`);
|
|
672
|
+
}
|
|
673
|
+
if (!String(target.TrackingField ?? "").trim()) {
|
|
674
|
+
throw new Error("TrackingField must be provided.");
|
|
675
|
+
}
|
|
676
|
+
validateTrackingSelectorPath(String(target.TrackingField ?? ""), "TrackingField");
|
|
677
|
+
if (target.GatherByField != null) {
|
|
678
|
+
validateTrackingSelectorPath(String(target.GatherByField), "GatherByField");
|
|
679
|
+
}
|
|
680
|
+
if (target.PollInterval <= 0) {
|
|
681
|
+
throw new RangeError("PollInterval must be greater than zero.");
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function validateHttpOAuth2ClientCredentialsOptionsModel(target) {
|
|
685
|
+
requireNonEmptyString(target.TokenEndpoint, "TokenEndpoint must be provided for OAuth2 client credentials flow.");
|
|
686
|
+
requireNonEmptyString(target.ClientId, "ClientId must be provided for OAuth2 client credentials flow.");
|
|
687
|
+
requireNonEmptyString(target.ClientSecret, "ClientSecret must be provided for OAuth2 client credentials flow.");
|
|
688
|
+
}
|
|
689
|
+
function validateHttpAuthOptionsModel(target) {
|
|
690
|
+
switch (normalizeToken(target.Type)) {
|
|
691
|
+
case "none":
|
|
692
|
+
return;
|
|
693
|
+
case "basic":
|
|
694
|
+
if (!String(target.Username ?? "").trim()) {
|
|
695
|
+
throw new Error("Username must be provided for basic auth.");
|
|
696
|
+
}
|
|
697
|
+
if (target.Password == null) {
|
|
698
|
+
throw new Error("Password must be provided for basic auth.");
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
701
|
+
case "bearer":
|
|
702
|
+
if (!String(target.BearerToken ?? "").trim()) {
|
|
703
|
+
throw new Error("BearerToken must be provided for bearer auth.");
|
|
704
|
+
}
|
|
705
|
+
return;
|
|
706
|
+
case "oauth2clientcredentials":
|
|
707
|
+
if (!target.OAuth2ClientCredentials) {
|
|
708
|
+
throw new Error("OAuth2ClientCredentials must be provided for OAuth2 client-credentials auth.");
|
|
709
|
+
}
|
|
710
|
+
toHttpOAuth2ClientCredentialsOptionsModel(target.OAuth2ClientCredentials).Validate();
|
|
711
|
+
return;
|
|
712
|
+
default:
|
|
713
|
+
throw new RangeError("Unsupported HTTP auth type.");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function validateKafkaSaslOptionsModel(target) {
|
|
717
|
+
switch (normalizeToken(target.Mechanism)) {
|
|
718
|
+
case "oauthbearer":
|
|
719
|
+
if (!String(target.OAuthBearerTokenEndpointUrl ?? "").trim()) {
|
|
720
|
+
throw new Error("OAuthBearerTokenEndpointUrl must be provided when SASL mechanism is OAuthBearer.");
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
default:
|
|
724
|
+
if (!String(target.Username ?? "").trim()) {
|
|
725
|
+
throw new Error("Username must be provided for SASL authentication.");
|
|
726
|
+
}
|
|
727
|
+
if (target.Password == null) {
|
|
728
|
+
throw new Error("Password must be provided for SASL authentication.");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function toHttpOAuth2ClientCredentialsOptionsModel(value) {
|
|
733
|
+
return value instanceof HttpOAuth2ClientCredentialsOptionsModel
|
|
734
|
+
? value
|
|
735
|
+
: new HttpOAuth2ClientCredentialsOptionsModel(value);
|
|
736
|
+
}
|
|
737
|
+
function toHttpAuthOptionsModel(value) {
|
|
738
|
+
return value instanceof HttpAuthOptionsModel ? value : new HttpAuthOptionsModel(value);
|
|
739
|
+
}
|
|
740
|
+
function toKafkaSaslOptionsModel(value) {
|
|
741
|
+
return value instanceof KafkaSaslOptionsModel ? value : new KafkaSaslOptionsModel(value);
|
|
742
|
+
}
|
|
743
|
+
const protocolBus = new class InMemoryProtocolBus {
|
|
744
|
+
constructor() {
|
|
745
|
+
this.kafkaTopics = new Map();
|
|
746
|
+
this.kafkaOffsets = new Map();
|
|
747
|
+
this.rabbitQueues = new Map();
|
|
748
|
+
this.natsSubjects = new Map();
|
|
749
|
+
this.natsOffsets = new Map();
|
|
750
|
+
this.redisStreams = new Map();
|
|
751
|
+
this.redisGroupOffsets = new Map();
|
|
752
|
+
this.redisCounters = new Map();
|
|
753
|
+
this.eventHubs = new Map();
|
|
754
|
+
this.eventHubOffsets = new Map();
|
|
755
|
+
this.pushTopics = new Map();
|
|
756
|
+
this.pushOffsets = new Map();
|
|
757
|
+
}
|
|
758
|
+
reset() {
|
|
759
|
+
this.kafkaTopics.clear();
|
|
760
|
+
this.kafkaOffsets.clear();
|
|
761
|
+
this.rabbitQueues.clear();
|
|
762
|
+
this.natsSubjects.clear();
|
|
763
|
+
this.natsOffsets.clear();
|
|
764
|
+
this.redisStreams.clear();
|
|
765
|
+
this.redisGroupOffsets.clear();
|
|
766
|
+
this.redisCounters.clear();
|
|
767
|
+
this.eventHubs.clear();
|
|
768
|
+
this.eventHubOffsets.clear();
|
|
769
|
+
this.pushTopics.clear();
|
|
770
|
+
this.pushOffsets.clear();
|
|
771
|
+
}
|
|
772
|
+
produceKafka(endpoint, payload) {
|
|
773
|
+
const options = endpoint.kafka ?? {};
|
|
774
|
+
const topic = optionString(options, "Topic", "topic") || endpoint.name;
|
|
775
|
+
const topicRows = this.kafkaTopics.get(topic) ?? [];
|
|
776
|
+
topicRows.push({ payload: clonePayload(payload), timestampMs: Date.now() });
|
|
777
|
+
this.kafkaTopics.set(topic, topicRows);
|
|
778
|
+
return clonePayload(payload);
|
|
779
|
+
}
|
|
780
|
+
consumeKafka(endpoint) {
|
|
781
|
+
const options = endpoint.kafka ?? {};
|
|
782
|
+
const topic = optionString(options, "Topic", "topic") || endpoint.name;
|
|
783
|
+
const topicRows = this.kafkaTopics.get(topic) ?? [];
|
|
784
|
+
const groupId = optionString(options, "ConsumerGroupId", "consumerGroupId")
|
|
785
|
+
|| `${endpoint.name}-group`;
|
|
786
|
+
const startFromEarliest = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
|
|
787
|
+
const cursorKey = `${topic}|${groupId}`;
|
|
788
|
+
let offset = this.kafkaOffsets.get(cursorKey);
|
|
789
|
+
if (offset == null) {
|
|
790
|
+
offset = startFromEarliest ? 0 : topicRows.length;
|
|
791
|
+
}
|
|
792
|
+
if (offset >= topicRows.length) {
|
|
793
|
+
this.kafkaOffsets.set(cursorKey, offset);
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
const next = topicRows[offset];
|
|
797
|
+
this.kafkaOffsets.set(cursorKey, offset + 1);
|
|
798
|
+
return clonePayload(next.payload);
|
|
799
|
+
}
|
|
800
|
+
produceRabbit(endpoint, payload) {
|
|
801
|
+
const options = endpoint.rabbitMq ?? {};
|
|
802
|
+
const queueName = optionString(options, "QueueName", "queueName")
|
|
803
|
+
|| optionString(options, "RoutingKey", "routingKey")
|
|
804
|
+
|| endpoint.name;
|
|
805
|
+
const queue = this.rabbitQueues.get(queueName) ?? [];
|
|
806
|
+
queue.push({ payload: clonePayload(payload), timestampMs: Date.now() });
|
|
807
|
+
this.rabbitQueues.set(queueName, queue);
|
|
808
|
+
return clonePayload(payload);
|
|
809
|
+
}
|
|
810
|
+
consumeRabbit(endpoint) {
|
|
811
|
+
const options = endpoint.rabbitMq ?? {};
|
|
812
|
+
const queueName = optionString(options, "QueueName", "queueName")
|
|
813
|
+
|| optionString(options, "RoutingKey", "routingKey")
|
|
814
|
+
|| endpoint.name;
|
|
815
|
+
const queue = this.rabbitQueues.get(queueName) ?? [];
|
|
816
|
+
if (!queue.length) {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
const next = queue.shift() ?? null;
|
|
820
|
+
this.rabbitQueues.set(queueName, queue);
|
|
821
|
+
return next ? clonePayload(next.payload) : null;
|
|
822
|
+
}
|
|
823
|
+
produceNats(endpoint, payload) {
|
|
824
|
+
const options = endpoint.nats ?? {};
|
|
825
|
+
const subject = optionString(options, "Subject", "subject") || endpoint.name;
|
|
826
|
+
const rows = this.natsSubjects.get(subject) ?? [];
|
|
827
|
+
rows.push({ payload: clonePayload(payload), timestampMs: Date.now() });
|
|
828
|
+
this.natsSubjects.set(subject, rows);
|
|
829
|
+
return clonePayload(payload);
|
|
830
|
+
}
|
|
831
|
+
consumeNats(endpoint) {
|
|
832
|
+
const options = endpoint.nats ?? {};
|
|
833
|
+
const subject = optionString(options, "Subject", "subject") || endpoint.name;
|
|
834
|
+
const rows = this.natsSubjects.get(subject) ?? [];
|
|
835
|
+
const queueGroup = optionString(options, "QueueGroup", "queueGroup");
|
|
836
|
+
const startFromEarliest = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
|
|
837
|
+
const cursorKey = queueGroup
|
|
838
|
+
? `queue:${subject}:${queueGroup}`
|
|
839
|
+
: `sub:${subject}:${endpoint.name}`;
|
|
840
|
+
let offset = this.natsOffsets.get(cursorKey);
|
|
841
|
+
if (offset == null) {
|
|
842
|
+
offset = startFromEarliest ? 0 : rows.length;
|
|
843
|
+
}
|
|
844
|
+
if (offset >= rows.length) {
|
|
845
|
+
this.natsOffsets.set(cursorKey, offset);
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
const next = rows[offset];
|
|
849
|
+
this.natsOffsets.set(cursorKey, offset + 1);
|
|
850
|
+
return clonePayload(next.payload);
|
|
851
|
+
}
|
|
852
|
+
produceRedis(endpoint, payload) {
|
|
853
|
+
const options = endpoint.redisStreams ?? {};
|
|
854
|
+
const streamKey = optionString(options, "StreamKey", "streamKey") || endpoint.name;
|
|
855
|
+
const rows = this.redisStreams.get(streamKey) ?? [];
|
|
856
|
+
const counter = (this.redisCounters.get(streamKey) ?? 0) + 1;
|
|
857
|
+
this.redisCounters.set(streamKey, counter);
|
|
858
|
+
const id = `${Date.now()}-${counter}`;
|
|
859
|
+
rows.push({ id, payload: clonePayload(payload), timestampMs: Date.now() });
|
|
860
|
+
const maxLength = optionNumber(options, "MaxLength", "maxLength");
|
|
861
|
+
if (maxLength > 0 && rows.length > maxLength) {
|
|
862
|
+
rows.splice(0, rows.length - Math.trunc(maxLength));
|
|
863
|
+
}
|
|
864
|
+
this.redisStreams.set(streamKey, rows);
|
|
865
|
+
const withId = clonePayload(payload);
|
|
866
|
+
withId.headers = {
|
|
867
|
+
...(withId.headers ?? {}),
|
|
868
|
+
"x-stream-id": id
|
|
869
|
+
};
|
|
870
|
+
return withId;
|
|
871
|
+
}
|
|
872
|
+
consumeRedis(endpoint) {
|
|
873
|
+
const options = endpoint.redisStreams ?? {};
|
|
874
|
+
const streamKey = optionString(options, "StreamKey", "streamKey") || endpoint.name;
|
|
875
|
+
const rows = this.redisStreams.get(streamKey) ?? [];
|
|
876
|
+
const consumerGroup = optionString(options, "ConsumerGroup", "consumerGroup")
|
|
877
|
+
|| `${endpoint.name}-group`;
|
|
878
|
+
const startFromEarliest = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
|
|
879
|
+
const cursorKey = `${streamKey}|${consumerGroup}`;
|
|
880
|
+
let offset = this.redisGroupOffsets.get(cursorKey);
|
|
881
|
+
if (offset == null) {
|
|
882
|
+
offset = startFromEarliest ? 0 : rows.length;
|
|
883
|
+
}
|
|
884
|
+
if (offset >= rows.length) {
|
|
885
|
+
this.redisGroupOffsets.set(cursorKey, offset);
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
const next = rows[offset];
|
|
889
|
+
this.redisGroupOffsets.set(cursorKey, offset + 1);
|
|
890
|
+
const payload = clonePayload(next.payload);
|
|
891
|
+
payload.headers = {
|
|
892
|
+
...(payload.headers ?? {}),
|
|
893
|
+
"x-stream-id": next.id
|
|
894
|
+
};
|
|
895
|
+
return payload;
|
|
896
|
+
}
|
|
897
|
+
produceEventHub(endpoint, payload) {
|
|
898
|
+
const options = endpoint.azureEventHubs ?? {};
|
|
899
|
+
const hubName = optionString(options, "EventHubName", "eventHubName") || endpoint.name;
|
|
900
|
+
const partitionId = optionString(options, "PartitionId", "partitionId")
|
|
901
|
+
|| partitionFromKey(optionString(options, "PartitionKey", "partitionKey"), optionNumber(options, "PartitionCount", "partitionCount") || 4);
|
|
902
|
+
const rows = this.eventHubs.get(hubName) ?? [];
|
|
903
|
+
rows.push({
|
|
904
|
+
partitionId,
|
|
905
|
+
payload: clonePayload(payload),
|
|
906
|
+
timestampMs: Date.now()
|
|
907
|
+
});
|
|
908
|
+
this.eventHubs.set(hubName, rows);
|
|
909
|
+
return clonePayload(payload);
|
|
910
|
+
}
|
|
911
|
+
consumeEventHub(endpoint) {
|
|
912
|
+
const options = endpoint.azureEventHubs ?? {};
|
|
913
|
+
const hubName = optionString(options, "EventHubName", "eventHubName") || endpoint.name;
|
|
914
|
+
const rows = this.eventHubs.get(hubName) ?? [];
|
|
915
|
+
const consumerGroup = optionString(options, "ConsumerGroup", "consumerGroup") || "$Default";
|
|
916
|
+
const partitionFilter = optionString(options, "PartitionId", "partitionId") || "*";
|
|
917
|
+
const startFromEarliest = optionBoolean(options, false, "StartFromEarliest", "startFromEarliest");
|
|
918
|
+
const cursorKey = `${hubName}|${consumerGroup}|${partitionFilter}`;
|
|
919
|
+
let offset = this.eventHubOffsets.get(cursorKey);
|
|
920
|
+
if (offset == null) {
|
|
921
|
+
offset = startFromEarliest ? 0 : rows.length;
|
|
922
|
+
}
|
|
923
|
+
for (let i = offset; i < rows.length; i += 1) {
|
|
924
|
+
const row = rows[i];
|
|
925
|
+
if (partitionFilter !== "*" && row.partitionId !== partitionFilter) {
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
this.eventHubOffsets.set(cursorKey, i + 1);
|
|
929
|
+
return clonePayload(row.payload);
|
|
930
|
+
}
|
|
931
|
+
this.eventHubOffsets.set(cursorKey, rows.length);
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
producePush(endpoint, payload) {
|
|
935
|
+
const options = endpoint.pushDiffusion ?? {};
|
|
936
|
+
const topic = optionString(options, "TopicPath", "topicPath")
|
|
937
|
+
|| optionString(options, "Channel", "channel")
|
|
938
|
+
|| endpoint.name;
|
|
939
|
+
const rows = this.pushTopics.get(topic) ?? [];
|
|
940
|
+
rows.push({ payload: clonePayload(payload), timestampMs: Date.now() });
|
|
941
|
+
this.pushTopics.set(topic, rows);
|
|
942
|
+
return clonePayload(payload);
|
|
943
|
+
}
|
|
944
|
+
consumePush(endpoint) {
|
|
945
|
+
const options = endpoint.pushDiffusion ?? {};
|
|
946
|
+
const topic = optionString(options, "TopicPath", "topicPath")
|
|
947
|
+
|| optionString(options, "Channel", "channel")
|
|
948
|
+
|| endpoint.name;
|
|
949
|
+
const rows = this.pushTopics.get(topic) ?? [];
|
|
950
|
+
const subscriberId = optionString(options, "SubscriberId", "subscriberId") || endpoint.name;
|
|
951
|
+
const startFromEarliest = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
|
|
952
|
+
const cursorKey = `${topic}|${subscriberId}`;
|
|
953
|
+
let offset = this.pushOffsets.get(cursorKey);
|
|
954
|
+
if (offset == null) {
|
|
955
|
+
offset = startFromEarliest ? 0 : rows.length;
|
|
956
|
+
}
|
|
957
|
+
if (offset >= rows.length) {
|
|
958
|
+
this.pushOffsets.set(cursorKey, offset);
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
const next = rows[offset];
|
|
962
|
+
this.pushOffsets.set(cursorKey, offset + 1);
|
|
963
|
+
return clonePayload(next.payload);
|
|
964
|
+
}
|
|
965
|
+
}();
|
|
966
|
+
class CallbackAdapter {
|
|
967
|
+
constructor(endpoint) {
|
|
968
|
+
this.consumeQueue = [];
|
|
969
|
+
this.consumeStreamStarted = false;
|
|
970
|
+
this.endpoint = endpoint;
|
|
971
|
+
}
|
|
972
|
+
async produce(payload) {
|
|
973
|
+
if (this.endpoint.mode !== "Produce") {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
977
|
+
const structuredDelegate = resolveStructuredProduceDelegate(this.endpoint);
|
|
978
|
+
if (structuredDelegate) {
|
|
979
|
+
return await invokeStructuredProduceDelegate(this.endpoint, resolved, structuredDelegate);
|
|
980
|
+
}
|
|
981
|
+
const delegate = resolveProduceDelegate(this.endpoint);
|
|
982
|
+
if (delegate) {
|
|
983
|
+
const next = await delegate(clonePayload(resolved));
|
|
984
|
+
return next == null ? null : normalizeTrackingPayload(next);
|
|
985
|
+
}
|
|
986
|
+
return clonePayload(resolved);
|
|
987
|
+
}
|
|
988
|
+
async consume() {
|
|
989
|
+
if (this.endpoint.mode !== "Consume") {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
if (this.consumeQueue.length > 0) {
|
|
993
|
+
return this.consumeQueue.shift() ?? null;
|
|
994
|
+
}
|
|
995
|
+
const structuredDelegate = resolveStructuredConsumeDelegate(this.endpoint);
|
|
996
|
+
if (structuredDelegate) {
|
|
997
|
+
const next = await structuredDelegate();
|
|
998
|
+
return normalizeConsumedDelegatePayload(next);
|
|
999
|
+
}
|
|
1000
|
+
const streamDelegate = resolveStructuredConsumeStreamDelegate(this.endpoint);
|
|
1001
|
+
if (streamDelegate) {
|
|
1002
|
+
this.ensureConsumeStreamStarted(streamDelegate);
|
|
1003
|
+
return this.consumeQueue.shift() ?? null;
|
|
1004
|
+
}
|
|
1005
|
+
const delegate = resolveConsumeDelegate(this.endpoint);
|
|
1006
|
+
if (delegate) {
|
|
1007
|
+
const next = await delegate();
|
|
1008
|
+
return next == null ? null : normalizeTrackingPayload(next);
|
|
1009
|
+
}
|
|
1010
|
+
return this.defaultPayload();
|
|
1011
|
+
}
|
|
1012
|
+
async dispose() { }
|
|
1013
|
+
defaultPayload() {
|
|
1014
|
+
return createDefaultPayload(this.endpoint);
|
|
1015
|
+
}
|
|
1016
|
+
ensureConsumeStreamStarted(streamDelegate) {
|
|
1017
|
+
if (this.consumeStreamStarted) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
this.consumeStreamStarted = true;
|
|
1021
|
+
void Promise.resolve(streamDelegate(async (message) => {
|
|
1022
|
+
const normalized = normalizeConsumedDelegatePayload(message);
|
|
1023
|
+
if (normalized) {
|
|
1024
|
+
this.consumeQueue.push(normalized);
|
|
1025
|
+
}
|
|
1026
|
+
})).catch(() => {
|
|
1027
|
+
// Consume stream failures are best-effort at the adapter surface.
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
class HttpEndpointAdapter extends CallbackAdapter {
|
|
1032
|
+
constructor() {
|
|
1033
|
+
super(...arguments);
|
|
1034
|
+
this.httpConsumeQueue = [];
|
|
1035
|
+
this.oauthToken = null;
|
|
1036
|
+
}
|
|
1037
|
+
async produce(payload) {
|
|
1038
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
const resolved = payload ?? this.defaultPayload();
|
|
1042
|
+
const httpOptions = this.endpoint.http;
|
|
1043
|
+
if (!httpOptions?.url) {
|
|
1044
|
+
return super.produce(resolved);
|
|
1045
|
+
}
|
|
1046
|
+
const method = httpOptions.method ?? "GET";
|
|
1047
|
+
const resolvedContentType = resolveHttpContentType(this.endpoint.contentType, httpOptions.bodyType);
|
|
1048
|
+
const headers = {
|
|
1049
|
+
...(resolved.headers ?? {}),
|
|
1050
|
+
"Content-Type": resolvedContentType
|
|
1051
|
+
};
|
|
1052
|
+
await applyHttpAuthHeaders(headers, httpOptions, async () => this.resolveOAuthToken(httpOptions));
|
|
1053
|
+
const body = buildHttpRequestBody(resolved.body, httpOptions.bodyType ?? "Json", headers["Content-Type"] ?? resolvedContentType);
|
|
1054
|
+
const produceTimeout = createTimeoutSignal(httpOptions.requestTimeoutSeconds);
|
|
1055
|
+
const response = await fetch(httpOptions.url, {
|
|
1056
|
+
method,
|
|
1057
|
+
headers,
|
|
1058
|
+
body: body,
|
|
1059
|
+
signal: produceTimeout.signal
|
|
1060
|
+
}).finally(() => produceTimeout.clear());
|
|
1061
|
+
if (!response.ok) {
|
|
1062
|
+
throw new Error(`HTTP adapter request failed with status ${response.status}.`);
|
|
1063
|
+
}
|
|
1064
|
+
const responseClone = response.clone();
|
|
1065
|
+
let result = await buildHttpProduceResult(resolved, response, httpOptions.responseSource);
|
|
1066
|
+
if (canonicalizeHttpTrackingPayloadSource(String(httpOptions.trackingPayloadSource ?? "Request")) === "Response" &&
|
|
1067
|
+
!extractTrackingValue(result, this.endpoint.trackingField)) {
|
|
1068
|
+
result = await buildHttpProduceResult(resolved, responseClone, "ResponseBody");
|
|
1069
|
+
}
|
|
1070
|
+
return result;
|
|
1071
|
+
}
|
|
1072
|
+
async consume() {
|
|
1073
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
const httpOptions = this.endpoint.http;
|
|
1077
|
+
if (this.httpConsumeQueue.length > 0) {
|
|
1078
|
+
return this.httpConsumeQueue.shift() ?? null;
|
|
1079
|
+
}
|
|
1080
|
+
if (httpOptions?.consumePoll) {
|
|
1081
|
+
return await httpOptions.consumePoll();
|
|
1082
|
+
}
|
|
1083
|
+
if (!httpOptions?.url) {
|
|
1084
|
+
return super.consume();
|
|
1085
|
+
}
|
|
1086
|
+
const method = httpOptions.method ?? "GET";
|
|
1087
|
+
const headers = {};
|
|
1088
|
+
await applyHttpAuthHeaders(headers, httpOptions, async () => this.resolveOAuthToken(httpOptions));
|
|
1089
|
+
const consumeTimeout = createTimeoutSignal(httpOptions.requestTimeoutSeconds);
|
|
1090
|
+
const response = await fetch(httpOptions.url, {
|
|
1091
|
+
method,
|
|
1092
|
+
headers,
|
|
1093
|
+
signal: consumeTimeout.signal
|
|
1094
|
+
}).finally(() => consumeTimeout.clear());
|
|
1095
|
+
if (!response.ok) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
const rawText = await response.text();
|
|
1099
|
+
const parsed = parseMaybeJson(rawText);
|
|
1100
|
+
const consumeArrayPath = httpOptions.consumeArrayPath
|
|
1101
|
+
?? (httpOptions.consumeJsonArrayResponse ? "$" : undefined);
|
|
1102
|
+
const rows = normalizeConsumedPayloadRows(parsed, consumeArrayPath);
|
|
1103
|
+
if (!rows.length) {
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
this.httpConsumeQueue.push(...rows.slice(1));
|
|
1107
|
+
return rows[0];
|
|
1108
|
+
}
|
|
1109
|
+
async resolveOAuthToken(httpOptions) {
|
|
1110
|
+
const auth = httpOptions.auth;
|
|
1111
|
+
const mode = normalizeToken(auth?.mode ?? auth?.type ?? "None");
|
|
1112
|
+
if (mode !== "oauth2clientcredentials") {
|
|
1113
|
+
return "";
|
|
1114
|
+
}
|
|
1115
|
+
const now = Date.now();
|
|
1116
|
+
if (this.oauthToken && this.oauthToken.expiresAtMs > now + 5000) {
|
|
1117
|
+
return this.oauthToken.value;
|
|
1118
|
+
}
|
|
1119
|
+
const oauthOptions = auth?.oauth2ClientCredentials;
|
|
1120
|
+
const tokenUrl = requireNonEmptyString(auth?.tokenUrl ?? oauthOptions?.tokenEndpoint, "Http auth tokenUrl is required for OAuth2 client credentials.");
|
|
1121
|
+
const clientId = requireNonEmptyString(auth?.clientId ?? oauthOptions?.clientId, "Http auth clientId is required for OAuth2 client credentials.");
|
|
1122
|
+
const clientSecret = requireNonEmptyString(auth?.clientSecret ?? oauthOptions?.clientSecret, "Http auth clientSecret is required for OAuth2 client credentials.");
|
|
1123
|
+
const body = new URLSearchParams();
|
|
1124
|
+
body.set("grant_type", "client_credentials");
|
|
1125
|
+
body.set("client_id", clientId);
|
|
1126
|
+
body.set("client_secret", clientSecret);
|
|
1127
|
+
const scopes = Array.isArray(oauthOptions?.scopes)
|
|
1128
|
+
? oauthOptions.scopes.map((x) => String(x).trim()).filter((x) => x.length > 0)
|
|
1129
|
+
: Array.isArray(auth?.scopes)
|
|
1130
|
+
? auth.scopes.map((x) => String(x).trim()).filter((x) => x.length > 0)
|
|
1131
|
+
: [];
|
|
1132
|
+
const scopeText = typeof auth?.scope === "string" && auth.scope.trim()
|
|
1133
|
+
? auth.scope.trim()
|
|
1134
|
+
: (scopes.length ? scopes.join(" ") : "");
|
|
1135
|
+
if (scopeText) {
|
|
1136
|
+
body.set("scope", scopeText);
|
|
1137
|
+
}
|
|
1138
|
+
if (typeof auth?.audience === "string" && auth.audience.trim()) {
|
|
1139
|
+
body.set("audience", auth.audience.trim());
|
|
1140
|
+
}
|
|
1141
|
+
for (const [key, value] of Object.entries({
|
|
1142
|
+
...(auth?.additionalFormFields ?? {}),
|
|
1143
|
+
...(oauthOptions?.additionalFormFields ?? {})
|
|
1144
|
+
})) {
|
|
1145
|
+
if (key.trim() && typeof value === "string") {
|
|
1146
|
+
body.set(key.trim(), value);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const headers = {
|
|
1150
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1151
|
+
};
|
|
1152
|
+
for (const [key, value] of Object.entries(httpOptions.tokenRequestHeaders ?? {})) {
|
|
1153
|
+
if (key.trim() && typeof value === "string") {
|
|
1154
|
+
headers[key] = value;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const tokenTimeout = createTimeoutSignal(httpOptions.requestTimeoutSeconds);
|
|
1158
|
+
const response = await fetch(tokenUrl, {
|
|
1159
|
+
method: "POST",
|
|
1160
|
+
headers,
|
|
1161
|
+
body: body.toString(),
|
|
1162
|
+
signal: tokenTimeout.signal
|
|
1163
|
+
}).finally(() => tokenTimeout.clear());
|
|
1164
|
+
if (!response.ok) {
|
|
1165
|
+
throw new Error(`OAuth2 token request failed with status ${response.status}.`);
|
|
1166
|
+
}
|
|
1167
|
+
const payload = parseMaybeJson(await response.text());
|
|
1168
|
+
if (!isRecord(payload)) {
|
|
1169
|
+
throw new Error("OAuth2 token response payload was not a JSON object.");
|
|
1170
|
+
}
|
|
1171
|
+
const accessToken = requireNonEmptyString(payload.access_token, "OAuth2 token response did not contain access_token.");
|
|
1172
|
+
const expiresInSeconds = optionNumber(payload, "expires_in", "expiresIn", "ExpiresIn");
|
|
1173
|
+
this.oauthToken = {
|
|
1174
|
+
value: accessToken,
|
|
1175
|
+
expiresAtMs: now + (Math.max(expiresInSeconds, 1) * 1000)
|
|
1176
|
+
};
|
|
1177
|
+
return accessToken;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
class KafkaEndpointAdapter extends CallbackAdapter {
|
|
1181
|
+
constructor() {
|
|
1182
|
+
super(...arguments);
|
|
1183
|
+
this.producerPromise = null;
|
|
1184
|
+
this.consumerPromise = null;
|
|
1185
|
+
this.consumerStartPromise = null;
|
|
1186
|
+
this.queue = [];
|
|
1187
|
+
}
|
|
1188
|
+
async produce(payload) {
|
|
1189
|
+
if (resolveProduceDelegate(this.endpoint)) {
|
|
1190
|
+
return super.produce(payload);
|
|
1191
|
+
}
|
|
1192
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
const producer = await this.getProducer();
|
|
1196
|
+
const options = this.endpoint.kafka ?? {};
|
|
1197
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1198
|
+
const wire = toWirePayload(resolved, this.endpoint);
|
|
1199
|
+
await producer.send({
|
|
1200
|
+
topic: optionString(options, "Topic", "topic"),
|
|
1201
|
+
messages: [
|
|
1202
|
+
{
|
|
1203
|
+
value: Buffer.from(wire.body),
|
|
1204
|
+
headers: toKafkaHeadersWithContentType(wire.headers, wire.contentType)
|
|
1205
|
+
}
|
|
1206
|
+
]
|
|
1207
|
+
});
|
|
1208
|
+
return clonePayload(resolved);
|
|
1209
|
+
}
|
|
1210
|
+
async consume() {
|
|
1211
|
+
if (resolveConsumeDelegate(this.endpoint)) {
|
|
1212
|
+
return super.consume();
|
|
1213
|
+
}
|
|
1214
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
await this.ensureConsumerStarted();
|
|
1218
|
+
return this.queue.shift() ?? null;
|
|
1219
|
+
}
|
|
1220
|
+
async dispose() {
|
|
1221
|
+
const consumer = await this.consumerPromise?.catch(() => null);
|
|
1222
|
+
const producer = await this.producerPromise?.catch(() => null);
|
|
1223
|
+
await consumer?.disconnect?.().catch(() => { });
|
|
1224
|
+
await producer?.disconnect?.().catch(() => { });
|
|
1225
|
+
}
|
|
1226
|
+
async getProducer() {
|
|
1227
|
+
if (!this.producerPromise) {
|
|
1228
|
+
this.producerPromise = (async () => {
|
|
1229
|
+
const kafka = await createKafkaClient(this.endpoint);
|
|
1230
|
+
const producer = kafka.producer();
|
|
1231
|
+
await producer.connect();
|
|
1232
|
+
return producer;
|
|
1233
|
+
})();
|
|
1234
|
+
}
|
|
1235
|
+
return this.producerPromise;
|
|
1236
|
+
}
|
|
1237
|
+
async ensureConsumerStarted() {
|
|
1238
|
+
if (!this.consumerStartPromise) {
|
|
1239
|
+
this.consumerStartPromise = (async () => {
|
|
1240
|
+
const options = this.endpoint.kafka ?? {};
|
|
1241
|
+
const useConfluentClient = shouldUseConfluentKafkaClient(this.endpoint);
|
|
1242
|
+
const kafka = await createKafkaClient(this.endpoint);
|
|
1243
|
+
const consumer = kafka.consumer(buildKafkaConsumerOptions(this.endpoint));
|
|
1244
|
+
await consumer.connect();
|
|
1245
|
+
if (useConfluentClient) {
|
|
1246
|
+
await consumer.subscribe({
|
|
1247
|
+
topics: [optionString(options, "Topic", "topic")]
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
else {
|
|
1251
|
+
await consumer.subscribe({
|
|
1252
|
+
topic: optionString(options, "Topic", "topic"),
|
|
1253
|
+
fromBeginning: optionBoolean(options, true, "StartFromEarliest", "startFromEarliest")
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
await consumer.run({
|
|
1257
|
+
eachMessage: async ({ message }) => {
|
|
1258
|
+
this.queue.push(createBrokerPayload(fromKafkaHeaders(message.headers), bufferToUint8Array(message.value), this.endpoint, headerValue(message.headers, "content-type")));
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
this.consumerPromise = Promise.resolve(consumer);
|
|
1262
|
+
})();
|
|
1263
|
+
}
|
|
1264
|
+
await this.consumerStartPromise;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
class RabbitMqEndpointAdapter extends CallbackAdapter {
|
|
1268
|
+
constructor() {
|
|
1269
|
+
super(...arguments);
|
|
1270
|
+
this.connectionPromise = null;
|
|
1271
|
+
this.channelPromise = null;
|
|
1272
|
+
}
|
|
1273
|
+
async produce(payload) {
|
|
1274
|
+
if (resolveProduceDelegate(this.endpoint)) {
|
|
1275
|
+
return super.produce(payload);
|
|
1276
|
+
}
|
|
1277
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
const options = this.endpoint.rabbitMq ?? {};
|
|
1281
|
+
const channel = await this.getChannel();
|
|
1282
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1283
|
+
const wire = toWirePayload(resolved, this.endpoint);
|
|
1284
|
+
const queueName = optionString(options, "QueueName", "queueName");
|
|
1285
|
+
const routingKey = optionString(options, "RoutingKey", "routingKey") || queueName;
|
|
1286
|
+
const exchange = optionString(options, "Exchange", "exchange");
|
|
1287
|
+
if (queueName) {
|
|
1288
|
+
await channel.assertQueue(queueName, {
|
|
1289
|
+
durable: optionBoolean(options, true, "Durable", "durable"),
|
|
1290
|
+
autoDelete: false,
|
|
1291
|
+
exclusive: false
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
channel.publish(exchange || "", routingKey, Buffer.from(wire.body), {
|
|
1295
|
+
persistent: optionBoolean(options, true, "Durable", "durable"),
|
|
1296
|
+
contentType: wire.contentType,
|
|
1297
|
+
headers: { ...wire.headers }
|
|
1298
|
+
});
|
|
1299
|
+
return clonePayload(resolved);
|
|
1300
|
+
}
|
|
1301
|
+
async consume() {
|
|
1302
|
+
if (resolveConsumeDelegate(this.endpoint)) {
|
|
1303
|
+
return super.consume();
|
|
1304
|
+
}
|
|
1305
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
const channel = await this.getChannel();
|
|
1309
|
+
const options = this.endpoint.rabbitMq ?? {};
|
|
1310
|
+
const queueName = optionString(options, "QueueName", "queueName");
|
|
1311
|
+
const autoAck = optionBoolean(options, false, "AutoAck", "autoAck");
|
|
1312
|
+
const message = await channel.get(queueName, { noAck: autoAck });
|
|
1313
|
+
if (!message) {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
if (!autoAck) {
|
|
1317
|
+
channel.ack(message);
|
|
1318
|
+
}
|
|
1319
|
+
return createBrokerPayload(toHeaderRecord(message.properties?.headers), bufferToUint8Array(message.content), this.endpoint, typeof message.properties?.contentType === "string" ? message.properties.contentType : undefined);
|
|
1320
|
+
}
|
|
1321
|
+
async dispose() {
|
|
1322
|
+
const channel = await this.channelPromise?.catch(() => null);
|
|
1323
|
+
const connection = await this.connectionPromise?.catch(() => null);
|
|
1324
|
+
await channel?.close?.().catch(() => { });
|
|
1325
|
+
await connection?.close?.().catch(() => { });
|
|
1326
|
+
}
|
|
1327
|
+
async getChannel() {
|
|
1328
|
+
if (!this.channelPromise) {
|
|
1329
|
+
this.channelPromise = (async () => {
|
|
1330
|
+
const amqplib = await import("amqplib");
|
|
1331
|
+
const options = this.endpoint.rabbitMq ?? {};
|
|
1332
|
+
const connection = await amqplib.default.connect({
|
|
1333
|
+
protocol: optionBoolean(options, false, "UseSsl", "useSsl") ? "amqps" : "amqp",
|
|
1334
|
+
hostname: optionString(options, "HostName", "hostName"),
|
|
1335
|
+
port: optionNumber(options, "Port", "port") || 5672,
|
|
1336
|
+
username: optionString(options, "UserName", "userName"),
|
|
1337
|
+
password: optionString(options, "Password", "password"),
|
|
1338
|
+
vhost: optionString(options, "VirtualHost", "virtualHost") || "/",
|
|
1339
|
+
clientProperties: toStringRecord(pickProtocolValue(options, "ClientProperties", "clientProperties"))
|
|
1340
|
+
});
|
|
1341
|
+
this.connectionPromise = Promise.resolve(connection);
|
|
1342
|
+
const channel = await connection.createChannel();
|
|
1343
|
+
const queueName = optionString(options, "QueueName", "queueName");
|
|
1344
|
+
if (queueName) {
|
|
1345
|
+
await channel.assertQueue(queueName, {
|
|
1346
|
+
durable: optionBoolean(options, true, "Durable", "durable"),
|
|
1347
|
+
autoDelete: false,
|
|
1348
|
+
exclusive: false
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return channel;
|
|
1352
|
+
})();
|
|
1353
|
+
}
|
|
1354
|
+
return this.channelPromise;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
class NatsEndpointAdapter extends CallbackAdapter {
|
|
1358
|
+
constructor() {
|
|
1359
|
+
super(...arguments);
|
|
1360
|
+
this.connectionPromise = null;
|
|
1361
|
+
this.subscriptionPromise = null;
|
|
1362
|
+
this.subscription = null;
|
|
1363
|
+
this.queue = [];
|
|
1364
|
+
}
|
|
1365
|
+
async produce(payload) {
|
|
1366
|
+
if (resolveProduceDelegate(this.endpoint)) {
|
|
1367
|
+
return super.produce(payload);
|
|
1368
|
+
}
|
|
1369
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
const connection = await this.getConnection();
|
|
1373
|
+
const options = this.endpoint.nats ?? {};
|
|
1374
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1375
|
+
const wire = toWirePayload(resolved, this.endpoint);
|
|
1376
|
+
const subject = optionString(options, "Subject", "subject");
|
|
1377
|
+
const headers = await createNatsHeaders(wire.headers, wire.contentType);
|
|
1378
|
+
connection.publish(subject, Buffer.from(wire.body), headers ? { headers } : undefined);
|
|
1379
|
+
await connection.flush();
|
|
1380
|
+
return clonePayload(resolved);
|
|
1381
|
+
}
|
|
1382
|
+
async consume() {
|
|
1383
|
+
if (resolveConsumeDelegate(this.endpoint)) {
|
|
1384
|
+
return super.consume();
|
|
1385
|
+
}
|
|
1386
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
await this.ensureSubscriptionStarted();
|
|
1390
|
+
return this.queue.shift() ?? null;
|
|
1391
|
+
}
|
|
1392
|
+
async dispose() {
|
|
1393
|
+
this.subscription?.unsubscribe?.();
|
|
1394
|
+
const connection = await this.connectionPromise?.catch(() => null);
|
|
1395
|
+
await connection?.drain?.().catch(() => { });
|
|
1396
|
+
await connection?.close?.().catch(() => { });
|
|
1397
|
+
}
|
|
1398
|
+
async getConnection() {
|
|
1399
|
+
if (!this.connectionPromise) {
|
|
1400
|
+
this.connectionPromise = (async () => {
|
|
1401
|
+
const { connect } = await import("nats");
|
|
1402
|
+
const options = this.endpoint.nats ?? {};
|
|
1403
|
+
return connect({
|
|
1404
|
+
servers: optionString(options, "ServerUrl", "serverUrl"),
|
|
1405
|
+
user: optionString(options, "UserName", "userName") || undefined,
|
|
1406
|
+
pass: optionString(options, "Password", "password") || undefined,
|
|
1407
|
+
token: optionString(options, "Token", "token") || undefined,
|
|
1408
|
+
name: optionString(options, "ConnectionName", "connectionName") || this.endpoint.name,
|
|
1409
|
+
maxReconnectAttempts: optionNumber(options, "MaxReconnectAttempts", "maxReconnectAttempts") || 60
|
|
1410
|
+
});
|
|
1411
|
+
})();
|
|
1412
|
+
}
|
|
1413
|
+
return this.connectionPromise;
|
|
1414
|
+
}
|
|
1415
|
+
async ensureSubscriptionStarted() {
|
|
1416
|
+
if (!this.subscriptionPromise) {
|
|
1417
|
+
this.subscriptionPromise = (async () => {
|
|
1418
|
+
const connection = await this.getConnection();
|
|
1419
|
+
const options = this.endpoint.nats ?? {};
|
|
1420
|
+
const subject = optionString(options, "Subject", "subject");
|
|
1421
|
+
const queueGroup = optionString(options, "QueueGroup", "queueGroup") || undefined;
|
|
1422
|
+
this.subscription = connection.subscribe(subject, queueGroup ? { queue: queueGroup } : undefined);
|
|
1423
|
+
await connection.flush();
|
|
1424
|
+
void (async () => {
|
|
1425
|
+
for await (const message of this.subscription) {
|
|
1426
|
+
this.queue.push(createBrokerPayload(toHeaderRecord(message.headers), bufferToUint8Array(message.data), this.endpoint, toHeaderRecord(message.headers)["content-type"]));
|
|
1427
|
+
}
|
|
1428
|
+
})();
|
|
1429
|
+
})();
|
|
1430
|
+
}
|
|
1431
|
+
await this.subscriptionPromise;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
class RedisStreamsEndpointAdapter extends CallbackAdapter {
|
|
1435
|
+
constructor() {
|
|
1436
|
+
super(...arguments);
|
|
1437
|
+
this.clientPromise = null;
|
|
1438
|
+
this.groupReady = false;
|
|
1439
|
+
}
|
|
1440
|
+
async produce(payload) {
|
|
1441
|
+
if (resolveProduceDelegate(this.endpoint)) {
|
|
1442
|
+
return super.produce(payload);
|
|
1443
|
+
}
|
|
1444
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
const client = await this.getClient();
|
|
1448
|
+
const options = this.endpoint.redisStreams ?? {};
|
|
1449
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1450
|
+
const wire = toWirePayload(resolved, this.endpoint);
|
|
1451
|
+
const streamKey = optionString(options, "StreamKey", "streamKey");
|
|
1452
|
+
const args = ["XADD", streamKey];
|
|
1453
|
+
const maxLength = optionNumber(options, "MaxLength", "maxLength");
|
|
1454
|
+
if (maxLength > 0) {
|
|
1455
|
+
args.push("MAXLEN", "~", String(Math.trunc(maxLength)));
|
|
1456
|
+
}
|
|
1457
|
+
args.push("*", "body", Buffer.from(wire.body));
|
|
1458
|
+
if (wire.contentType) {
|
|
1459
|
+
args.push("content-type", wire.contentType);
|
|
1460
|
+
}
|
|
1461
|
+
for (const [key, value] of Object.entries(wire.headers)) {
|
|
1462
|
+
args.push(`header:${key}`, value);
|
|
1463
|
+
}
|
|
1464
|
+
await client.sendCommand(args);
|
|
1465
|
+
return clonePayload(resolved);
|
|
1466
|
+
}
|
|
1467
|
+
async consume() {
|
|
1468
|
+
if (resolveConsumeDelegate(this.endpoint)) {
|
|
1469
|
+
return super.consume();
|
|
1470
|
+
}
|
|
1471
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
const client = await this.getClient();
|
|
1475
|
+
const options = this.endpoint.redisStreams ?? {};
|
|
1476
|
+
await this.ensureConsumerGroup();
|
|
1477
|
+
const streamKey = optionString(options, "StreamKey", "streamKey");
|
|
1478
|
+
const consumerGroup = optionString(options, "ConsumerGroup", "consumerGroup");
|
|
1479
|
+
const consumerName = optionString(options, "ConsumerName", "consumerName");
|
|
1480
|
+
const readCount = optionNumber(options, "ReadCount", "readCount") || 50;
|
|
1481
|
+
const blockMs = Math.max(this.endpoint.pollIntervalMs ?? 250, 1);
|
|
1482
|
+
const { commandOptions } = await import("redis");
|
|
1483
|
+
const rows = await client.sendCommand(commandOptions({ returnBuffers: true }), [
|
|
1484
|
+
"XREADGROUP",
|
|
1485
|
+
"GROUP",
|
|
1486
|
+
consumerGroup,
|
|
1487
|
+
consumerName,
|
|
1488
|
+
"COUNT",
|
|
1489
|
+
String(Math.trunc(readCount)),
|
|
1490
|
+
"BLOCK",
|
|
1491
|
+
String(Math.trunc(blockMs)),
|
|
1492
|
+
"STREAMS",
|
|
1493
|
+
streamKey,
|
|
1494
|
+
">"
|
|
1495
|
+
]);
|
|
1496
|
+
const entry = readFirstRedisStreamEntry(rows);
|
|
1497
|
+
if (!entry) {
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
await client.sendCommand(["XACK", streamKey, consumerGroup, entry.id]);
|
|
1501
|
+
const payload = createRedisStreamPayload(entry.fields, this.endpoint);
|
|
1502
|
+
payload.headers = {
|
|
1503
|
+
...(payload.headers ?? {}),
|
|
1504
|
+
"x-stream-id": entry.id
|
|
1505
|
+
};
|
|
1506
|
+
return payload;
|
|
1507
|
+
}
|
|
1508
|
+
async dispose() {
|
|
1509
|
+
const client = await this.clientPromise?.catch(() => null);
|
|
1510
|
+
await client?.quit?.().catch(async () => {
|
|
1511
|
+
await client?.disconnect?.().catch(() => { });
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
async getClient() {
|
|
1515
|
+
if (!this.clientPromise) {
|
|
1516
|
+
this.clientPromise = (async () => {
|
|
1517
|
+
const { createClient } = await import("redis");
|
|
1518
|
+
const options = this.endpoint.redisStreams ?? {};
|
|
1519
|
+
const client = createClient({
|
|
1520
|
+
url: optionString(options, "ConnectionString", "connectionString")
|
|
1521
|
+
});
|
|
1522
|
+
client.on?.("error", () => { });
|
|
1523
|
+
await client.connect();
|
|
1524
|
+
return client;
|
|
1525
|
+
})();
|
|
1526
|
+
}
|
|
1527
|
+
return this.clientPromise;
|
|
1528
|
+
}
|
|
1529
|
+
async ensureConsumerGroup() {
|
|
1530
|
+
if (this.groupReady) {
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const client = await this.getClient();
|
|
1534
|
+
const options = this.endpoint.redisStreams ?? {};
|
|
1535
|
+
const streamKey = optionString(options, "StreamKey", "streamKey");
|
|
1536
|
+
const consumerGroup = optionString(options, "ConsumerGroup", "consumerGroup");
|
|
1537
|
+
const startPosition = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest") ? "0-0" : "$";
|
|
1538
|
+
try {
|
|
1539
|
+
await client.sendCommand(["XGROUP", "CREATE", streamKey, consumerGroup, startPosition, "MKSTREAM"]);
|
|
1540
|
+
}
|
|
1541
|
+
catch (error) {
|
|
1542
|
+
if (!String(error ?? "").toUpperCase().includes("BUSYGROUP")) {
|
|
1543
|
+
throw error;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
this.groupReady = true;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
class AzureEventHubsEndpointAdapter extends CallbackAdapter {
|
|
1550
|
+
constructor() {
|
|
1551
|
+
super(...arguments);
|
|
1552
|
+
this.producerPromise = null;
|
|
1553
|
+
this.consumerPromise = null;
|
|
1554
|
+
this.subscriptionPromise = null;
|
|
1555
|
+
this.subscription = null;
|
|
1556
|
+
this.queue = [];
|
|
1557
|
+
}
|
|
1558
|
+
async produce(payload) {
|
|
1559
|
+
if (resolveProduceDelegate(this.endpoint)) {
|
|
1560
|
+
return super.produce(payload);
|
|
1561
|
+
}
|
|
1562
|
+
if (this.endpoint.mode !== "Produce") {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
const producer = await this.getProducer();
|
|
1566
|
+
const options = this.endpoint.azureEventHubs ?? {};
|
|
1567
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1568
|
+
const wire = toWirePayload(resolved, this.endpoint);
|
|
1569
|
+
const batch = await producer.createBatch();
|
|
1570
|
+
const eventData = {
|
|
1571
|
+
body: Buffer.from(wire.body),
|
|
1572
|
+
contentType: wire.contentType,
|
|
1573
|
+
properties: { ...wire.headers }
|
|
1574
|
+
};
|
|
1575
|
+
const partitionId = optionString(options, "PartitionId", "partitionId");
|
|
1576
|
+
if (partitionId) {
|
|
1577
|
+
eventData.partitionId = partitionId;
|
|
1578
|
+
}
|
|
1579
|
+
if (!batch.tryAdd(eventData)) {
|
|
1580
|
+
throw new Error("Azure Event Hubs payload is too large for a single batch.");
|
|
1581
|
+
}
|
|
1582
|
+
await producer.sendBatch(batch);
|
|
1583
|
+
return clonePayload(resolved);
|
|
1584
|
+
}
|
|
1585
|
+
async consume() {
|
|
1586
|
+
if (resolveConsumeDelegate(this.endpoint)) {
|
|
1587
|
+
return super.consume();
|
|
1588
|
+
}
|
|
1589
|
+
if (this.endpoint.mode !== "Consume") {
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
await this.ensureSubscriptionStarted();
|
|
1593
|
+
return this.queue.shift() ?? null;
|
|
1594
|
+
}
|
|
1595
|
+
async dispose() {
|
|
1596
|
+
await this.subscription?.close?.().catch(() => { });
|
|
1597
|
+
const consumer = await this.consumerPromise?.catch(() => null);
|
|
1598
|
+
const producer = await this.producerPromise?.catch(() => null);
|
|
1599
|
+
await consumer?.close?.().catch(() => { });
|
|
1600
|
+
await producer?.close?.().catch(() => { });
|
|
1601
|
+
}
|
|
1602
|
+
async getProducer() {
|
|
1603
|
+
if (!this.producerPromise) {
|
|
1604
|
+
this.producerPromise = (async () => {
|
|
1605
|
+
const { EventHubProducerClient } = await import("@azure/event-hubs");
|
|
1606
|
+
const options = this.endpoint.azureEventHubs ?? {};
|
|
1607
|
+
return new EventHubProducerClient(optionString(options, "ConnectionString", "connectionString"), optionString(options, "EventHubName", "eventHubName"));
|
|
1608
|
+
})();
|
|
1609
|
+
}
|
|
1610
|
+
return this.producerPromise;
|
|
1611
|
+
}
|
|
1612
|
+
async getConsumer() {
|
|
1613
|
+
if (!this.consumerPromise) {
|
|
1614
|
+
this.consumerPromise = (async () => {
|
|
1615
|
+
const { EventHubConsumerClient } = await import("@azure/event-hubs");
|
|
1616
|
+
const options = this.endpoint.azureEventHubs ?? {};
|
|
1617
|
+
return new EventHubConsumerClient(optionString(options, "ConsumerGroup", "consumerGroup") || "$Default", optionString(options, "ConnectionString", "connectionString"), optionString(options, "EventHubName", "eventHubName"));
|
|
1618
|
+
})();
|
|
1619
|
+
}
|
|
1620
|
+
return this.consumerPromise;
|
|
1621
|
+
}
|
|
1622
|
+
async ensureSubscriptionStarted() {
|
|
1623
|
+
if (!this.subscriptionPromise) {
|
|
1624
|
+
this.subscriptionPromise = (async () => {
|
|
1625
|
+
const consumer = await this.getConsumer();
|
|
1626
|
+
const options = this.endpoint.azureEventHubs ?? {};
|
|
1627
|
+
const startPosition = optionBoolean(options, false, "StartFromEarliest", "startFromEarliest")
|
|
1628
|
+
? { enqueuedOn: new Date(0) }
|
|
1629
|
+
: { latest: true };
|
|
1630
|
+
const partitionId = optionString(options, "PartitionId", "partitionId");
|
|
1631
|
+
this.subscription = consumer.subscribe(partitionId ? {
|
|
1632
|
+
processEvents: async (events) => {
|
|
1633
|
+
for (const event of events) {
|
|
1634
|
+
this.queue.push(createBrokerPayload(toHeaderRecord(event.properties), bufferToUint8Array(event.body), this.endpoint, typeof event.contentType === "string" ? event.contentType : undefined));
|
|
1635
|
+
}
|
|
1636
|
+
},
|
|
1637
|
+
processError: async () => { }
|
|
1638
|
+
} : {
|
|
1639
|
+
processEvents: async (events) => {
|
|
1640
|
+
for (const event of events) {
|
|
1641
|
+
this.queue.push(createBrokerPayload(toHeaderRecord(event.properties), bufferToUint8Array(event.body), this.endpoint, typeof event.contentType === "string" ? event.contentType : undefined));
|
|
1642
|
+
}
|
|
1643
|
+
},
|
|
1644
|
+
processError: async () => { }
|
|
1645
|
+
}, partitionId ? { startPosition, partitionId } : { startPosition });
|
|
1646
|
+
})();
|
|
1647
|
+
}
|
|
1648
|
+
await this.subscriptionPromise;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
class PushDiffusionEndpointAdapter extends CallbackAdapter {
|
|
1652
|
+
async produce(payload) {
|
|
1653
|
+
return super.produce(payload);
|
|
1654
|
+
}
|
|
1655
|
+
async consume() {
|
|
1656
|
+
return super.consume();
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
class DelegateStreamEndpointAdapter extends CallbackAdapter {
|
|
1660
|
+
}
|
|
1661
|
+
export class EndpointAdapterFactory {
|
|
1662
|
+
static create(endpoint) {
|
|
1663
|
+
if (endpoint instanceof TrafficEndpointDefinitionModel) {
|
|
1664
|
+
endpoint.Validate();
|
|
1665
|
+
}
|
|
1666
|
+
const normalized = normalizeEndpointDefinition(endpoint);
|
|
1667
|
+
validateEndpointDefinition(normalized);
|
|
1668
|
+
switch (normalized.kind) {
|
|
1669
|
+
case "Http":
|
|
1670
|
+
return new HttpEndpointAdapter(normalized);
|
|
1671
|
+
case "Kafka":
|
|
1672
|
+
return new KafkaEndpointAdapter(normalized);
|
|
1673
|
+
case "RabbitMq":
|
|
1674
|
+
return new RabbitMqEndpointAdapter(normalized);
|
|
1675
|
+
case "Nats":
|
|
1676
|
+
return new NatsEndpointAdapter(normalized);
|
|
1677
|
+
case "RedisStreams":
|
|
1678
|
+
return new RedisStreamsEndpointAdapter(normalized);
|
|
1679
|
+
case "AzureEventHubs":
|
|
1680
|
+
return new AzureEventHubsEndpointAdapter(normalized);
|
|
1681
|
+
case "PushDiffusion":
|
|
1682
|
+
return new PushDiffusionEndpointAdapter(normalized);
|
|
1683
|
+
case "DelegateStream":
|
|
1684
|
+
return new DelegateStreamEndpointAdapter(normalized);
|
|
1685
|
+
default:
|
|
1686
|
+
throw new Error(`Unsupported endpoint kind: ${String(normalized?.kind ?? "")}.`);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const HTTP_ENDPOINT_FLAT_KEYS = [
|
|
1691
|
+
"Url",
|
|
1692
|
+
"url",
|
|
1693
|
+
"Method",
|
|
1694
|
+
"method",
|
|
1695
|
+
"ConsumePoll",
|
|
1696
|
+
"consumePoll",
|
|
1697
|
+
"BodyType",
|
|
1698
|
+
"bodyType",
|
|
1699
|
+
"ResponseSource",
|
|
1700
|
+
"responseSource",
|
|
1701
|
+
"TrackingPayloadSource",
|
|
1702
|
+
"trackingPayloadSource",
|
|
1703
|
+
"ConsumeArrayPath",
|
|
1704
|
+
"consumeArrayPath",
|
|
1705
|
+
"ConsumeJsonArrayResponse",
|
|
1706
|
+
"consumeJsonArrayResponse",
|
|
1707
|
+
"RequestTimeout",
|
|
1708
|
+
"requestTimeout",
|
|
1709
|
+
"RequestTimeoutSeconds",
|
|
1710
|
+
"requestTimeoutSeconds",
|
|
1711
|
+
"Auth",
|
|
1712
|
+
"auth",
|
|
1713
|
+
"TokenRequestHeaders",
|
|
1714
|
+
"tokenRequestHeaders"
|
|
1715
|
+
];
|
|
1716
|
+
const KAFKA_ENDPOINT_FLAT_KEYS = [
|
|
1717
|
+
"BootstrapServers",
|
|
1718
|
+
"bootstrapServers",
|
|
1719
|
+
"Topic",
|
|
1720
|
+
"topic",
|
|
1721
|
+
"ConsumerGroupId",
|
|
1722
|
+
"consumerGroupId",
|
|
1723
|
+
"SecurityProtocol",
|
|
1724
|
+
"securityProtocol",
|
|
1725
|
+
"Sasl",
|
|
1726
|
+
"sasl",
|
|
1727
|
+
"ConfluentSettings",
|
|
1728
|
+
"confluentSettings",
|
|
1729
|
+
"StartFromEarliest",
|
|
1730
|
+
"startFromEarliest"
|
|
1731
|
+
];
|
|
1732
|
+
const RABBITMQ_ENDPOINT_FLAT_KEYS = [
|
|
1733
|
+
"HostName",
|
|
1734
|
+
"hostName",
|
|
1735
|
+
"Port",
|
|
1736
|
+
"port",
|
|
1737
|
+
"VirtualHost",
|
|
1738
|
+
"virtualHost",
|
|
1739
|
+
"UserName",
|
|
1740
|
+
"userName",
|
|
1741
|
+
"Password",
|
|
1742
|
+
"password",
|
|
1743
|
+
"Exchange",
|
|
1744
|
+
"exchange",
|
|
1745
|
+
"QueueName",
|
|
1746
|
+
"queueName",
|
|
1747
|
+
"RoutingKey",
|
|
1748
|
+
"routingKey",
|
|
1749
|
+
"Durable",
|
|
1750
|
+
"durable",
|
|
1751
|
+
"AutoAck",
|
|
1752
|
+
"autoAck",
|
|
1753
|
+
"UseSsl",
|
|
1754
|
+
"useSsl",
|
|
1755
|
+
"ClientProperties",
|
|
1756
|
+
"clientProperties"
|
|
1757
|
+
];
|
|
1758
|
+
const NATS_ENDPOINT_FLAT_KEYS = [
|
|
1759
|
+
"ServerUrl",
|
|
1760
|
+
"serverUrl",
|
|
1761
|
+
"Subject",
|
|
1762
|
+
"subject",
|
|
1763
|
+
"QueueGroup",
|
|
1764
|
+
"queueGroup",
|
|
1765
|
+
"UserName",
|
|
1766
|
+
"userName",
|
|
1767
|
+
"Password",
|
|
1768
|
+
"password",
|
|
1769
|
+
"Token",
|
|
1770
|
+
"token",
|
|
1771
|
+
"ConnectionName",
|
|
1772
|
+
"connectionName",
|
|
1773
|
+
"MaxReconnectAttempts",
|
|
1774
|
+
"maxReconnectAttempts"
|
|
1775
|
+
];
|
|
1776
|
+
const REDIS_STREAMS_ENDPOINT_FLAT_KEYS = [
|
|
1777
|
+
"ConnectionString",
|
|
1778
|
+
"connectionString",
|
|
1779
|
+
"StreamKey",
|
|
1780
|
+
"streamKey",
|
|
1781
|
+
"ConsumerGroup",
|
|
1782
|
+
"consumerGroup",
|
|
1783
|
+
"ConsumerName",
|
|
1784
|
+
"consumerName",
|
|
1785
|
+
"StartFromEarliest",
|
|
1786
|
+
"startFromEarliest",
|
|
1787
|
+
"ReadCount",
|
|
1788
|
+
"readCount",
|
|
1789
|
+
"MaxLength",
|
|
1790
|
+
"maxLength"
|
|
1791
|
+
];
|
|
1792
|
+
const AZURE_EVENT_HUBS_ENDPOINT_FLAT_KEYS = [
|
|
1793
|
+
"ConnectionString",
|
|
1794
|
+
"connectionString",
|
|
1795
|
+
"EventHubName",
|
|
1796
|
+
"eventHubName",
|
|
1797
|
+
"ConsumerGroup",
|
|
1798
|
+
"consumerGroup",
|
|
1799
|
+
"PartitionId",
|
|
1800
|
+
"partitionId",
|
|
1801
|
+
"StartFromEarliest",
|
|
1802
|
+
"startFromEarliest"
|
|
1803
|
+
];
|
|
1804
|
+
const PUSH_DIFFUSION_ENDPOINT_FLAT_KEYS = [
|
|
1805
|
+
"ServerUrl",
|
|
1806
|
+
"serverUrl",
|
|
1807
|
+
"TopicPath",
|
|
1808
|
+
"topicPath",
|
|
1809
|
+
"Principal",
|
|
1810
|
+
"principal",
|
|
1811
|
+
"Password",
|
|
1812
|
+
"password",
|
|
1813
|
+
"ConnectionProperties",
|
|
1814
|
+
"connectionProperties",
|
|
1815
|
+
"PublishAsync",
|
|
1816
|
+
"publishAsync",
|
|
1817
|
+
"SubscribeAsync",
|
|
1818
|
+
"subscribeAsync"
|
|
1819
|
+
];
|
|
1820
|
+
const DELEGATE_STREAM_ENDPOINT_FLAT_KEYS = [
|
|
1821
|
+
"Produce",
|
|
1822
|
+
"produce",
|
|
1823
|
+
"Consume",
|
|
1824
|
+
"consume",
|
|
1825
|
+
"ProduceAsync",
|
|
1826
|
+
"produceAsync",
|
|
1827
|
+
"ConsumeAsync",
|
|
1828
|
+
"consumeAsync",
|
|
1829
|
+
"ConnectionMetadata",
|
|
1830
|
+
"connectionMetadata"
|
|
1831
|
+
];
|
|
1832
|
+
function normalizeEndpointDefinition(endpoint) {
|
|
1833
|
+
if (!endpoint || typeof endpoint !== "object") {
|
|
1834
|
+
return endpoint;
|
|
1835
|
+
}
|
|
1836
|
+
const raw = endpoint;
|
|
1837
|
+
const kind = resolveEndpointKind(raw);
|
|
1838
|
+
const pollInterval = pickOptionalEndpointNumber(raw, "PollInterval", "pollInterval");
|
|
1839
|
+
const pollIntervalMs = pickOptionalEndpointNumber(raw, "PollIntervalMs", "pollIntervalMs");
|
|
1840
|
+
const pollIntervalSeconds = pickOptionalEndpointNumber(raw, "PollIntervalSeconds", "pollIntervalSeconds");
|
|
1841
|
+
return {
|
|
1842
|
+
kind: (kind ?? pickEndpointString(raw, "kind", "Kind")),
|
|
1843
|
+
mode: pickEndpointString(raw, "mode", "Mode"),
|
|
1844
|
+
name: pickEndpointString(raw, "name", "Name"),
|
|
1845
|
+
trackingField: pickTrackingFieldSelectorString(raw, "trackingField", "TrackingField"),
|
|
1846
|
+
gatherByField: pickOptionalTrackingFieldSelectorString(raw, "gatherByField", "GatherByField"),
|
|
1847
|
+
autoGenerateTrackingIdWhenMissing: pickEndpointBoolean(raw, "autoGenerateTrackingIdWhenMissing", "AutoGenerateTrackingIdWhenMissing"),
|
|
1848
|
+
pollIntervalMs: pollIntervalMs ?? (pollIntervalSeconds != null
|
|
1849
|
+
? pollIntervalSeconds * 1000
|
|
1850
|
+
: (pollInterval != null ? pollInterval * 1000 : undefined)),
|
|
1851
|
+
messageHeaders: pickEndpointStringRecord(raw, "messageHeaders", "MessageHeaders"),
|
|
1852
|
+
messagePayload: pickEndpointValue(raw, "messagePayload", "MessagePayload"),
|
|
1853
|
+
messagePayloadType: pickOptionalEndpointString(raw, "messagePayloadType", "MessagePayloadType"),
|
|
1854
|
+
jsonSerializerSettings: pickEndpointRecord(raw, "jsonSerializerSettings", "JsonSettings", "JsonSerializerSettings"),
|
|
1855
|
+
jsonConvertSettings: pickEndpointRecord(raw, "jsonConvertSettings", "JsonConvertSettings"),
|
|
1856
|
+
contentType: pickOptionalEndpointString(raw, "contentType", "ContentType"),
|
|
1857
|
+
connectionMetadata: pickEndpointStringRecord(raw, "connectionMetadata", "ConnectionMetadata"),
|
|
1858
|
+
http: normalizeHttpEndpointOptions(pickEndpointTransportRecord(raw, kind, "Http", ["http", "Http"], HTTP_ENDPOINT_FLAT_KEYS)),
|
|
1859
|
+
kafka: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "Kafka", ["kafka", "Kafka"], KAFKA_ENDPOINT_FLAT_KEYS)),
|
|
1860
|
+
rabbitMq: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "RabbitMq", ["rabbitMq", "RabbitMq"], RABBITMQ_ENDPOINT_FLAT_KEYS)),
|
|
1861
|
+
nats: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "Nats", ["nats", "Nats"], NATS_ENDPOINT_FLAT_KEYS)),
|
|
1862
|
+
redisStreams: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "RedisStreams", ["redisStreams", "RedisStreams"], REDIS_STREAMS_ENDPOINT_FLAT_KEYS)),
|
|
1863
|
+
azureEventHubs: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "AzureEventHubs", ["azureEventHubs", "AzureEventHubs"], AZURE_EVENT_HUBS_ENDPOINT_FLAT_KEYS)),
|
|
1864
|
+
pushDiffusion: normalizeProtocolOptions(pickEndpointTransportRecord(raw, kind, "PushDiffusion", ["pushDiffusion", "PushDiffusion"], PUSH_DIFFUSION_ENDPOINT_FLAT_KEYS)),
|
|
1865
|
+
delegate: normalizeDelegateEndpointOptions(pickEndpointTransportRecord(raw, kind, "DelegateStream", ["delegate", "Delegate", "DelegateStream"], DELEGATE_STREAM_ENDPOINT_FLAT_KEYS))
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function resolveEndpointKind(raw) {
|
|
1869
|
+
const explicitKind = pickOptionalEndpointString(raw, "kind", "Kind");
|
|
1870
|
+
if (explicitKind) {
|
|
1871
|
+
return explicitKind;
|
|
1872
|
+
}
|
|
1873
|
+
if (Object.keys(pickEndpointRecord(raw, "http", "Http")).length || hasAnyEndpointField(raw, ["Url", "url"])) {
|
|
1874
|
+
return "Http";
|
|
1875
|
+
}
|
|
1876
|
+
if (Object.keys(pickEndpointRecord(raw, "kafka", "Kafka")).length || hasAnyEndpointField(raw, ["BootstrapServers", "bootstrapServers", "Topic", "topic"])) {
|
|
1877
|
+
return "Kafka";
|
|
1878
|
+
}
|
|
1879
|
+
if (Object.keys(pickEndpointRecord(raw, "rabbitMq", "RabbitMq")).length || hasAnyEndpointField(raw, [
|
|
1880
|
+
"HostName",
|
|
1881
|
+
"hostName",
|
|
1882
|
+
"VirtualHost",
|
|
1883
|
+
"virtualHost",
|
|
1884
|
+
"Exchange",
|
|
1885
|
+
"exchange",
|
|
1886
|
+
"QueueName",
|
|
1887
|
+
"queueName",
|
|
1888
|
+
"RoutingKey",
|
|
1889
|
+
"routingKey",
|
|
1890
|
+
"UseSsl",
|
|
1891
|
+
"useSsl",
|
|
1892
|
+
"ClientProperties",
|
|
1893
|
+
"clientProperties"
|
|
1894
|
+
])) {
|
|
1895
|
+
return "RabbitMq";
|
|
1896
|
+
}
|
|
1897
|
+
if (Object.keys(pickEndpointRecord(raw, "redisStreams", "RedisStreams")).length || hasAnyEndpointField(raw, [
|
|
1898
|
+
"StreamKey",
|
|
1899
|
+
"streamKey",
|
|
1900
|
+
"ConsumerName",
|
|
1901
|
+
"consumerName",
|
|
1902
|
+
"ReadCount",
|
|
1903
|
+
"readCount",
|
|
1904
|
+
"MaxLength",
|
|
1905
|
+
"maxLength"
|
|
1906
|
+
])) {
|
|
1907
|
+
return "RedisStreams";
|
|
1908
|
+
}
|
|
1909
|
+
if (Object.keys(pickEndpointRecord(raw, "azureEventHubs", "AzureEventHubs")).length || hasAnyEndpointField(raw, [
|
|
1910
|
+
"EventHubName",
|
|
1911
|
+
"eventHubName",
|
|
1912
|
+
"PartitionId",
|
|
1913
|
+
"partitionId"
|
|
1914
|
+
])) {
|
|
1915
|
+
return "AzureEventHubs";
|
|
1916
|
+
}
|
|
1917
|
+
if (Object.keys(pickEndpointRecord(raw, "pushDiffusion", "PushDiffusion")).length || hasAnyEndpointField(raw, [
|
|
1918
|
+
"TopicPath",
|
|
1919
|
+
"topicPath",
|
|
1920
|
+
"ConnectionProperties",
|
|
1921
|
+
"connectionProperties",
|
|
1922
|
+
"PublishAsync",
|
|
1923
|
+
"publishAsync",
|
|
1924
|
+
"SubscribeAsync",
|
|
1925
|
+
"subscribeAsync",
|
|
1926
|
+
"Principal",
|
|
1927
|
+
"principal"
|
|
1928
|
+
])) {
|
|
1929
|
+
return "PushDiffusion";
|
|
1930
|
+
}
|
|
1931
|
+
if (Object.keys(pickEndpointRecord(raw, "delegate", "Delegate", "DelegateStream")).length || hasAnyEndpointField(raw, [
|
|
1932
|
+
"Produce",
|
|
1933
|
+
"produce",
|
|
1934
|
+
"Consume",
|
|
1935
|
+
"consume",
|
|
1936
|
+
"ProduceAsync",
|
|
1937
|
+
"produceAsync",
|
|
1938
|
+
"ConsumeAsync",
|
|
1939
|
+
"consumeAsync",
|
|
1940
|
+
"ConnectionMetadata",
|
|
1941
|
+
"connectionMetadata"
|
|
1942
|
+
])) {
|
|
1943
|
+
return "DelegateStream";
|
|
1944
|
+
}
|
|
1945
|
+
if (Object.keys(pickEndpointRecord(raw, "nats", "Nats")).length || hasAnyEndpointField(raw, [
|
|
1946
|
+
"Subject",
|
|
1947
|
+
"subject",
|
|
1948
|
+
"QueueGroup",
|
|
1949
|
+
"queueGroup",
|
|
1950
|
+
"Token",
|
|
1951
|
+
"token",
|
|
1952
|
+
"ConnectionName",
|
|
1953
|
+
"connectionName",
|
|
1954
|
+
"MaxReconnectAttempts",
|
|
1955
|
+
"maxReconnectAttempts"
|
|
1956
|
+
])) {
|
|
1957
|
+
return "Nats";
|
|
1958
|
+
}
|
|
1959
|
+
return undefined;
|
|
1960
|
+
}
|
|
1961
|
+
function pickEndpointTransportRecord(raw, resolvedKind, expectedKind, nestedKeys, flatKeys) {
|
|
1962
|
+
const nested = pickEndpointRecord(raw, ...nestedKeys);
|
|
1963
|
+
if (Object.keys(nested).length) {
|
|
1964
|
+
return nested;
|
|
1965
|
+
}
|
|
1966
|
+
if (resolvedKind !== expectedKind) {
|
|
1967
|
+
return {};
|
|
1968
|
+
}
|
|
1969
|
+
return pickFlatEndpointRecord(raw, flatKeys);
|
|
1970
|
+
}
|
|
1971
|
+
function pickFlatEndpointRecord(raw, keys) {
|
|
1972
|
+
const result = {};
|
|
1973
|
+
for (const key of keys) {
|
|
1974
|
+
if (key in raw) {
|
|
1975
|
+
result[key] = raw[key];
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return result;
|
|
1979
|
+
}
|
|
1980
|
+
function hasAnyEndpointField(raw, keys) {
|
|
1981
|
+
return keys.some((key) => key in raw);
|
|
1982
|
+
}
|
|
1983
|
+
function normalizeHttpEndpointOptions(options) {
|
|
1984
|
+
if (!Object.keys(options).length) {
|
|
1985
|
+
return undefined;
|
|
1986
|
+
}
|
|
1987
|
+
return {
|
|
1988
|
+
url: pickOptionalEndpointString(options, "url", "Url") ?? "",
|
|
1989
|
+
method: pickOptionalEndpointString(options, "method", "Method") ?? "GET",
|
|
1990
|
+
consumePoll: pickEndpointFunction(options, "consumePoll", "ConsumePoll"),
|
|
1991
|
+
bodyType: pickOptionalEndpointString(options, "bodyType", "BodyType") ?? "Json",
|
|
1992
|
+
responseSource: (() => {
|
|
1993
|
+
const explicitResponseSource = pickOptionalEndpointString(options, "responseSource", "ResponseSource");
|
|
1994
|
+
if (explicitResponseSource) {
|
|
1995
|
+
return explicitResponseSource;
|
|
1996
|
+
}
|
|
1997
|
+
const rawTrackingPayloadSource = pickOptionalEndpointString(options, "TrackingPayloadSource", "trackingPayloadSource");
|
|
1998
|
+
return inferLegacyHttpResponseSource(rawTrackingPayloadSource);
|
|
1999
|
+
})(),
|
|
2000
|
+
trackingPayloadSource: (() => {
|
|
2001
|
+
const rawTrackingPayloadSource = pickOptionalEndpointString(options, "TrackingPayloadSource", "trackingPayloadSource");
|
|
2002
|
+
return rawTrackingPayloadSource
|
|
2003
|
+
? canonicalizeHttpTrackingPayloadSource(rawTrackingPayloadSource)
|
|
2004
|
+
: undefined;
|
|
2005
|
+
})(),
|
|
2006
|
+
consumeArrayPath: pickOptionalEndpointString(options, "consumeArrayPath", "ConsumeArrayPath"),
|
|
2007
|
+
consumeJsonArrayResponse: pickEndpointBoolean(options, "consumeJsonArrayResponse", "ConsumeJsonArrayResponse"),
|
|
2008
|
+
requestTimeoutSeconds: pickOptionalEndpointNumber(options, "requestTimeoutSeconds", "RequestTimeoutSeconds", "requestTimeout", "RequestTimeout") ?? 30,
|
|
2009
|
+
auth: normalizeHttpAuthOptions(pickEndpointRecord(options, "auth", "Auth")),
|
|
2010
|
+
tokenRequestHeaders: pickEndpointStringRecord(options, "tokenRequestHeaders", "TokenRequestHeaders")
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
function normalizeHttpAuthOptions(options) {
|
|
2014
|
+
if (!Object.keys(options).length) {
|
|
2015
|
+
return undefined;
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
mode: pickOptionalEndpointString(options, "mode", "Mode"),
|
|
2019
|
+
type: pickOptionalEndpointString(options, "type", "Type"),
|
|
2020
|
+
username: pickOptionalEndpointString(options, "username", "Username"),
|
|
2021
|
+
password: pickOptionalEndpointStringAllowEmpty(options, "password", "Password"),
|
|
2022
|
+
bearerToken: pickOptionalEndpointString(options, "bearerToken", "BearerToken"),
|
|
2023
|
+
tokenUrl: pickOptionalEndpointString(options, "tokenUrl", "TokenUrl"),
|
|
2024
|
+
clientId: pickOptionalEndpointString(options, "clientId", "ClientId"),
|
|
2025
|
+
clientSecret: pickOptionalEndpointString(options, "clientSecret", "ClientSecret"),
|
|
2026
|
+
scope: pickOptionalEndpointString(options, "scope", "Scope"),
|
|
2027
|
+
scopes: pickEndpointStringArray(options, "scopes", "Scopes"),
|
|
2028
|
+
audience: pickOptionalEndpointString(options, "audience", "Audience"),
|
|
2029
|
+
tokenHeaderName: pickOptionalEndpointString(options, "tokenHeaderName", "TokenHeaderName"),
|
|
2030
|
+
additionalFormFields: pickEndpointStringRecord(options, "additionalFormFields", "AdditionalFormFields"),
|
|
2031
|
+
oauth2ClientCredentials: normalizeOAuth2ClientCredentialsOptions(pickEndpointRecord(options, "oauth2ClientCredentials", "OAuth2ClientCredentials"))
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
function normalizeOAuth2ClientCredentialsOptions(options) {
|
|
2035
|
+
if (!Object.keys(options).length) {
|
|
2036
|
+
return undefined;
|
|
2037
|
+
}
|
|
2038
|
+
return {
|
|
2039
|
+
tokenEndpoint: pickOptionalEndpointString(options, "tokenEndpoint", "TokenEndpoint"),
|
|
2040
|
+
clientId: pickOptionalEndpointString(options, "clientId", "ClientId"),
|
|
2041
|
+
clientSecret: pickOptionalEndpointString(options, "clientSecret", "ClientSecret"),
|
|
2042
|
+
scopes: pickEndpointStringArray(options, "scopes", "Scopes"),
|
|
2043
|
+
additionalFormFields: pickEndpointStringRecord(options, "additionalFormFields", "AdditionalFormFields")
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function normalizeDelegateEndpointOptions(options) {
|
|
2047
|
+
if (!Object.keys(options).length) {
|
|
2048
|
+
return undefined;
|
|
2049
|
+
}
|
|
2050
|
+
return {
|
|
2051
|
+
produce: pickEndpointFunction(options, "produce", "Produce"),
|
|
2052
|
+
consume: pickEndpointFunction(options, "consume", "Consume"),
|
|
2053
|
+
produceAsync: pickEndpointFunction(options, "produceAsync", "ProduceAsync"),
|
|
2054
|
+
consumeAsync: pickEndpointFunction(options, "consumeAsync", "ConsumeAsync"),
|
|
2055
|
+
connectionMetadata: pickEndpointStringRecord(options, "connectionMetadata", "ConnectionMetadata")
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
function normalizeProtocolOptions(options) {
|
|
2059
|
+
return Object.keys(options).length ? options : undefined;
|
|
2060
|
+
}
|
|
2061
|
+
function pickEndpointValue(record, ...keys) {
|
|
2062
|
+
return pickProtocolValue(record, ...keys);
|
|
2063
|
+
}
|
|
2064
|
+
function pickEndpointString(record, ...keys) {
|
|
2065
|
+
return optionString(record, ...keys);
|
|
2066
|
+
}
|
|
2067
|
+
function pickOptionalEndpointString(record, ...keys) {
|
|
2068
|
+
const value = optionString(record, ...keys).trim();
|
|
2069
|
+
return value ? value : undefined;
|
|
2070
|
+
}
|
|
2071
|
+
function pickOptionalEndpointStringAllowEmpty(record, ...keys) {
|
|
2072
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2073
|
+
return typeof value === "string" ? value.trim() : undefined;
|
|
2074
|
+
}
|
|
2075
|
+
function pickTrackingFieldSelectorString(record, ...keys) {
|
|
2076
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2077
|
+
const normalized = normalizeTrackingFieldSelectorInput(value, keys[0] ?? "TrackingField");
|
|
2078
|
+
return normalized ?? "";
|
|
2079
|
+
}
|
|
2080
|
+
function pickOptionalTrackingFieldSelectorString(record, ...keys) {
|
|
2081
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2082
|
+
return normalizeTrackingFieldSelectorInput(value, keys[0] ?? "TrackingField");
|
|
2083
|
+
}
|
|
2084
|
+
function pickEndpointBoolean(record, ...keys) {
|
|
2085
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2086
|
+
if (typeof value === "boolean") {
|
|
2087
|
+
return value;
|
|
2088
|
+
}
|
|
2089
|
+
if (typeof value === "number") {
|
|
2090
|
+
return value !== 0;
|
|
2091
|
+
}
|
|
2092
|
+
if (typeof value === "string") {
|
|
2093
|
+
const normalized = value.trim().toLowerCase();
|
|
2094
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") {
|
|
2098
|
+
return false;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return undefined;
|
|
2102
|
+
}
|
|
2103
|
+
function pickOptionalEndpointNumber(record, ...keys) {
|
|
2104
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2105
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2106
|
+
return value;
|
|
2107
|
+
}
|
|
2108
|
+
if (typeof value === "string" && value.trim()) {
|
|
2109
|
+
const parsed = Number(value);
|
|
2110
|
+
if (Number.isFinite(parsed)) {
|
|
2111
|
+
return parsed;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return undefined;
|
|
2115
|
+
}
|
|
2116
|
+
function pickEndpointRecord(record, ...keys) {
|
|
2117
|
+
return asRecordOrEmpty(pickEndpointValue(record, ...keys));
|
|
2118
|
+
}
|
|
2119
|
+
function pickEndpointStringRecord(record, ...keys) {
|
|
2120
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2121
|
+
return value == null ? {} : toStringRecord(value);
|
|
2122
|
+
}
|
|
2123
|
+
function pickEndpointStringArray(record, ...keys) {
|
|
2124
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2125
|
+
if (!Array.isArray(value)) {
|
|
2126
|
+
return undefined;
|
|
2127
|
+
}
|
|
2128
|
+
const values = value
|
|
2129
|
+
.map((entry) => String(entry ?? "").trim())
|
|
2130
|
+
.filter((entry) => entry.length > 0);
|
|
2131
|
+
return values.length ? values : undefined;
|
|
2132
|
+
}
|
|
2133
|
+
function pickEndpointFunction(record, ...keys) {
|
|
2134
|
+
const value = pickEndpointValue(record, ...keys);
|
|
2135
|
+
return typeof value === "function" ? value : undefined;
|
|
2136
|
+
}
|
|
2137
|
+
function normalizeTrackingFieldSelectorInput(value, fieldName) {
|
|
2138
|
+
if (value == null) {
|
|
2139
|
+
return undefined;
|
|
2140
|
+
}
|
|
2141
|
+
if (typeof value === "string") {
|
|
2142
|
+
return value.trim();
|
|
2143
|
+
}
|
|
2144
|
+
if (value instanceof TrackingFieldSelector) {
|
|
2145
|
+
return value.toString();
|
|
2146
|
+
}
|
|
2147
|
+
if (typeof value === "object") {
|
|
2148
|
+
const raw = value;
|
|
2149
|
+
const location = raw.Location ?? raw.location ?? raw.Kind ?? raw.kind;
|
|
2150
|
+
if (location != null || raw.Path != null || raw.path != null) {
|
|
2151
|
+
return new TrackingFieldSelector(location, String(raw.Path ?? raw.path ?? "")).toString();
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
throw new TypeError(`${fieldName} must be provided as a string or TrackingFieldSelector.`);
|
|
2155
|
+
}
|
|
2156
|
+
function validateEndpointDefinition(endpoint) {
|
|
2157
|
+
if (!endpoint || typeof endpoint !== "object") {
|
|
2158
|
+
throw new Error("Endpoint definition must be provided.");
|
|
2159
|
+
}
|
|
2160
|
+
const kind = requireNonEmptyString(endpoint.kind, "Endpoint kind must be provided.");
|
|
2161
|
+
const mode = requireNonEmptyString(endpoint.mode, "Endpoint mode must be provided.");
|
|
2162
|
+
requireNonEmptyString(endpoint.name, "Endpoint name must be provided.");
|
|
2163
|
+
requireNonEmptyString(endpoint.trackingField, "Endpoint trackingField must be provided.");
|
|
2164
|
+
validateTrackingSelectorPath(endpoint.trackingField, "TrackingField");
|
|
2165
|
+
if (endpoint.gatherByField != null) {
|
|
2166
|
+
validateTrackingSelectorPath(String(endpoint.gatherByField), "GatherByField");
|
|
2167
|
+
}
|
|
2168
|
+
if (endpoint.pollIntervalMs != null && endpoint.pollIntervalMs <= 0) {
|
|
2169
|
+
throw new RangeError("PollInterval must be greater than zero.");
|
|
2170
|
+
}
|
|
2171
|
+
if (mode !== "Produce" && mode !== "Consume") {
|
|
2172
|
+
throw new Error(`Unsupported endpoint mode: ${mode}.`);
|
|
2173
|
+
}
|
|
2174
|
+
const hasProduceDelegate = typeof endpoint.delegate?.produce === "function"
|
|
2175
|
+
|| typeof endpoint.delegate?.produceAsync === "function";
|
|
2176
|
+
const hasConsumeDelegate = typeof endpoint.delegate?.consume === "function"
|
|
2177
|
+
|| typeof endpoint.delegate?.consumeAsync === "function";
|
|
2178
|
+
const hasModeDelegate = mode === "Produce" ? hasProduceDelegate : hasConsumeDelegate;
|
|
2179
|
+
switch (kind) {
|
|
2180
|
+
case "Http":
|
|
2181
|
+
validateHttpEndpoint(endpoint, mode, hasModeDelegate);
|
|
2182
|
+
return;
|
|
2183
|
+
case "Kafka":
|
|
2184
|
+
validateKafkaEndpoint(endpoint, mode, hasModeDelegate);
|
|
2185
|
+
return;
|
|
2186
|
+
case "RabbitMq":
|
|
2187
|
+
validateRabbitMqEndpoint(endpoint, mode, hasModeDelegate);
|
|
2188
|
+
return;
|
|
2189
|
+
case "Nats":
|
|
2190
|
+
validateNatsEndpoint(endpoint, mode, hasModeDelegate);
|
|
2191
|
+
return;
|
|
2192
|
+
case "RedisStreams":
|
|
2193
|
+
validateRedisStreamsEndpoint(endpoint, mode, hasModeDelegate);
|
|
2194
|
+
return;
|
|
2195
|
+
case "AzureEventHubs":
|
|
2196
|
+
validateAzureEventHubsEndpoint(endpoint, mode, hasModeDelegate);
|
|
2197
|
+
return;
|
|
2198
|
+
case "PushDiffusion":
|
|
2199
|
+
validatePushDiffusionEndpoint(endpoint, mode);
|
|
2200
|
+
return;
|
|
2201
|
+
case "DelegateStream":
|
|
2202
|
+
validateDelegateStreamEndpoint(endpoint, mode);
|
|
2203
|
+
return;
|
|
2204
|
+
default:
|
|
2205
|
+
throw new Error(`Unsupported endpoint kind: ${kind}.`);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function validateTrackingSelectorPath(expression, fieldName) {
|
|
2209
|
+
const normalized = String(expression ?? "").trim();
|
|
2210
|
+
if (!normalized) {
|
|
2211
|
+
if (fieldName === "TrackingField") {
|
|
2212
|
+
throw new Error("TrackingField must be provided.");
|
|
2213
|
+
}
|
|
2214
|
+
throw new Error("GatherByField path must be provided when GatherByField is configured.");
|
|
2215
|
+
}
|
|
2216
|
+
const separatorIndex = normalized.indexOf(":");
|
|
2217
|
+
if (separatorIndex >= 0) {
|
|
2218
|
+
const location = normalized.slice(0, separatorIndex).trim().toLowerCase();
|
|
2219
|
+
const path = normalized.slice(separatorIndex + 1).trim();
|
|
2220
|
+
if ((location === "header" || location === "json") && !path) {
|
|
2221
|
+
throw new Error(`${fieldName} path must be provided when ${fieldName} is configured.`);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function validateHttpEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2226
|
+
const options = endpoint.http;
|
|
2227
|
+
if (!options && !hasModeDelegate) {
|
|
2228
|
+
throw new Error(`Http endpoint in ${mode} mode requires http options or delegate callback.`);
|
|
2229
|
+
}
|
|
2230
|
+
if (options) {
|
|
2231
|
+
const bodyType = canonicalizeHttpBodyType(String(options.bodyType ?? "Json"));
|
|
2232
|
+
if (!["Json", "PlainText", "Xml", "FormUrlEncoded", "Binary"].includes(bodyType)) {
|
|
2233
|
+
throw new Error(`Unsupported Http bodyType: ${String(options.bodyType ?? "")}.`);
|
|
2234
|
+
}
|
|
2235
|
+
const responseSource = canonicalizeHttpResponseSource(String(options.responseSource ?? "None"));
|
|
2236
|
+
if (!["None", "ResponseBody", "ResponseHeaders", "ResponseStatusCode"].includes(responseSource)) {
|
|
2237
|
+
throw new Error(`Unsupported Http responseSource: ${String(options.responseSource ?? "")}.`);
|
|
2238
|
+
}
|
|
2239
|
+
const trackingPayloadSource = canonicalizeHttpTrackingPayloadSource(String(options.trackingPayloadSource ?? "Request"));
|
|
2240
|
+
if (!["Request", "Response"].includes(trackingPayloadSource)) {
|
|
2241
|
+
throw new Error(`Unsupported Http trackingPayloadSource: ${String(options.trackingPayloadSource ?? "")}.`);
|
|
2242
|
+
}
|
|
2243
|
+
options.bodyType = bodyType;
|
|
2244
|
+
options.responseSource = responseSource;
|
|
2245
|
+
options.trackingPayloadSource = trackingPayloadSource;
|
|
2246
|
+
if (options.url && String(options.url).trim()) {
|
|
2247
|
+
try {
|
|
2248
|
+
new URL(String(options.url).trim());
|
|
2249
|
+
}
|
|
2250
|
+
catch {
|
|
2251
|
+
throw new Error("Url must be an absolute URI for HTTP endpoint.");
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
if (options.requestTimeoutSeconds != null && options.requestTimeoutSeconds <= 0) {
|
|
2255
|
+
throw new RangeError("RequestTimeout must be greater than zero.");
|
|
2256
|
+
}
|
|
2257
|
+
validateHttpAuthOptions(options);
|
|
2258
|
+
}
|
|
2259
|
+
if (mode === "Produce" && !hasModeDelegate) {
|
|
2260
|
+
if (!options?.url || !String(options.url).trim()) {
|
|
2261
|
+
throw new Error("Url must be provided for HTTP endpoint.");
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
else if (mode === "Consume" && !hasModeDelegate) {
|
|
2265
|
+
if (!options?.url || !String(options.url).trim()) {
|
|
2266
|
+
throw new Error("Url must be provided for HTTP endpoint.");
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function validateDelegateStreamEndpoint(endpoint, mode) {
|
|
2271
|
+
const delegate = endpoint.delegate;
|
|
2272
|
+
if (mode === "Produce" && typeof delegate?.produce !== "function" && typeof delegate?.produceAsync !== "function") {
|
|
2273
|
+
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
2274
|
+
}
|
|
2275
|
+
if (mode === "Consume" && typeof delegate?.consume !== "function" && typeof delegate?.consumeAsync !== "function") {
|
|
2276
|
+
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
function validateHttpAuthOptions(options) {
|
|
2280
|
+
const auth = options.auth;
|
|
2281
|
+
if (!auth) {
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const mode = normalizeToken(auth.mode ?? auth.type ?? "None");
|
|
2285
|
+
const oauthOptions = auth.oauth2ClientCredentials;
|
|
2286
|
+
switch (mode) {
|
|
2287
|
+
case "none":
|
|
2288
|
+
return;
|
|
2289
|
+
case "basic":
|
|
2290
|
+
requireNonEmptyString(auth.username, "Username must be provided for basic auth.");
|
|
2291
|
+
if ((auth.password ?? null) == null) {
|
|
2292
|
+
throw new Error("Password must be provided for basic auth.");
|
|
2293
|
+
}
|
|
2294
|
+
return;
|
|
2295
|
+
case "bearer":
|
|
2296
|
+
requireNonEmptyString(auth.bearerToken, "BearerToken must be provided for bearer auth.");
|
|
2297
|
+
return;
|
|
2298
|
+
case "oauth2clientcredentials":
|
|
2299
|
+
requireNonEmptyString(auth.tokenUrl ?? oauthOptions?.tokenEndpoint, "TokenEndpoint must be provided for OAuth2 client credentials flow.");
|
|
2300
|
+
requireNonEmptyString(auth.clientId ?? oauthOptions?.clientId, "ClientId must be provided for OAuth2 client credentials flow.");
|
|
2301
|
+
requireNonEmptyString(auth.clientSecret ?? oauthOptions?.clientSecret, "ClientSecret must be provided for OAuth2 client credentials flow.");
|
|
2302
|
+
return;
|
|
2303
|
+
default:
|
|
2304
|
+
throw new RangeError("Unsupported HTTP auth type.");
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
function validateKafkaEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2308
|
+
const options = endpoint.kafka;
|
|
2309
|
+
if ((!options || typeof options !== "object") && !hasModeDelegate) {
|
|
2310
|
+
throw new Error(`Kafka endpoint in ${mode} mode requires kafka options or delegate callback.`);
|
|
2311
|
+
}
|
|
2312
|
+
if (!options || typeof options !== "object") {
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
requireNonEmptyString(optionString(options, "BootstrapServers", "bootstrapServers"), "BootstrapServers must be provided for Kafka endpoint.");
|
|
2316
|
+
requireNonEmptyString(optionString(options, "Topic", "topic"), "Topic must be provided for Kafka endpoint.");
|
|
2317
|
+
if (!hasModeDelegate && mode === "Consume") {
|
|
2318
|
+
requireNonEmptyString(optionString(options, "ConsumerGroupId", "consumerGroupId"), "ConsumerGroupId must be provided when Kafka endpoint mode is Consume.");
|
|
2319
|
+
}
|
|
2320
|
+
const securityProtocol = optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext";
|
|
2321
|
+
if (/^sasl/i.test(securityProtocol)) {
|
|
2322
|
+
const sasl = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
|
|
2323
|
+
validateKafkaSaslOptions(sasl);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
function validateKafkaSaslOptions(options) {
|
|
2327
|
+
const mechanism = optionString(options, "Mechanism", "mechanism") || "Plain";
|
|
2328
|
+
if (mechanism.toLowerCase() === "oauthbearer") {
|
|
2329
|
+
requireNonEmptyString(optionString(options, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(asRecordOrEmpty(pickProtocolValue(options, "AdditionalSettings", "additionalSettings")), "TokenEndpoint", "tokenEndpoint"), "OAuthBearerTokenEndpointUrl must be provided when SASL mechanism is OAuthBearer.");
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
requireNonEmptyString(optionString(options, "Username", "username"), "Username must be provided for SASL authentication.");
|
|
2333
|
+
if (pickProtocolValue(options, "Password", "password") == null) {
|
|
2334
|
+
throw new Error("Password must be provided for SASL authentication.");
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
function validateRabbitMqEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2338
|
+
const options = endpoint.rabbitMq;
|
|
2339
|
+
if ((!options || typeof options !== "object") && !hasModeDelegate) {
|
|
2340
|
+
throw new Error(`RabbitMq endpoint in ${mode} mode requires rabbitMq options or delegate callback.`);
|
|
2341
|
+
}
|
|
2342
|
+
if (!options || typeof options !== "object") {
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
requireNonEmptyString(optionString(options, "HostName", "hostName"), "HostName must be provided for RabbitMQ endpoint.");
|
|
2346
|
+
const port = optionNumber(options, "Port", "port") || 5672;
|
|
2347
|
+
if (port <= 0) {
|
|
2348
|
+
throw new RangeError("Port must be greater than zero.");
|
|
2349
|
+
}
|
|
2350
|
+
requireNonEmptyString(optionString(options, "UserName", "userName"), "UserName must be provided for RabbitMQ endpoint.");
|
|
2351
|
+
if (pickProtocolValue(options, "Password", "password") == null) {
|
|
2352
|
+
throw new Error("Password must be provided for RabbitMQ endpoint.");
|
|
2353
|
+
}
|
|
2354
|
+
const routingKey = optionString(options, "RoutingKey", "routingKey");
|
|
2355
|
+
const queueName = optionString(options, "QueueName", "queueName");
|
|
2356
|
+
if (!hasModeDelegate && mode === "Produce" && !routingKey && !queueName) {
|
|
2357
|
+
throw new Error("Either RoutingKey or QueueName must be provided for RabbitMQ producer endpoint.");
|
|
2358
|
+
}
|
|
2359
|
+
if (!hasModeDelegate && mode === "Consume" && !queueName) {
|
|
2360
|
+
throw new Error("QueueName must be provided for RabbitMQ consumer endpoint.");
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
function validateNatsEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2364
|
+
const options = endpoint.nats;
|
|
2365
|
+
if ((!options || typeof options !== "object") && !hasModeDelegate) {
|
|
2366
|
+
throw new Error(`Nats endpoint in ${mode} mode requires nats options or delegate callback.`);
|
|
2367
|
+
}
|
|
2368
|
+
if (!options || typeof options !== "object") {
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
requireNonEmptyString(optionString(options, "ServerUrl", "serverUrl"), "ServerUrl must be provided for NATS endpoint.");
|
|
2372
|
+
requireNonEmptyString(optionString(options, "Subject", "subject"), "Subject must be provided for NATS endpoint.");
|
|
2373
|
+
if (optionString(options, "UserName", "userName") && pickProtocolValue(options, "Password", "password") == null) {
|
|
2374
|
+
throw new Error("Password must be provided when UserName is configured for NATS endpoint.");
|
|
2375
|
+
}
|
|
2376
|
+
const reconnect = optionNumber(options, "MaxReconnectAttempts", "maxReconnectAttempts");
|
|
2377
|
+
if (reconnect < 0) {
|
|
2378
|
+
throw new RangeError("MaxReconnectAttempts must be zero or greater.");
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function validateRedisStreamsEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2382
|
+
const options = endpoint.redisStreams;
|
|
2383
|
+
if ((!options || typeof options !== "object") && !hasModeDelegate) {
|
|
2384
|
+
throw new Error(`RedisStreams endpoint in ${mode} mode requires redisStreams options or delegate callback.`);
|
|
2385
|
+
}
|
|
2386
|
+
if (!options || typeof options !== "object") {
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
requireNonEmptyString(optionString(options, "ConnectionString", "connectionString"), "ConnectionString must be provided for Redis Streams endpoint.");
|
|
2390
|
+
requireNonEmptyString(optionString(options, "StreamKey", "streamKey"), "StreamKey must be provided for Redis Streams endpoint.");
|
|
2391
|
+
if (!hasModeDelegate && mode === "Consume") {
|
|
2392
|
+
requireNonEmptyString(optionString(options, "ConsumerGroup", "consumerGroup"), "ConsumerGroup must be provided when Redis Streams endpoint mode is Consume.");
|
|
2393
|
+
requireNonEmptyString(optionString(options, "ConsumerName", "consumerName"), "ConsumerName must be provided when Redis Streams endpoint mode is Consume.");
|
|
2394
|
+
}
|
|
2395
|
+
const readCount = optionNumber(options, "ReadCount", "readCount");
|
|
2396
|
+
if (readCount !== 0 && readCount <= 0) {
|
|
2397
|
+
throw new RangeError("ReadCount must be greater than zero.");
|
|
2398
|
+
}
|
|
2399
|
+
const maxLength = optionNumber(options, "MaxLength", "maxLength");
|
|
2400
|
+
if (maxLength !== 0 && maxLength <= 0) {
|
|
2401
|
+
throw new RangeError("MaxLength must be greater than zero when configured.");
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
function validateAzureEventHubsEndpoint(endpoint, mode, hasModeDelegate) {
|
|
2405
|
+
const options = endpoint.azureEventHubs;
|
|
2406
|
+
if ((!options || typeof options !== "object") && !hasModeDelegate) {
|
|
2407
|
+
throw new Error(`AzureEventHubs endpoint in ${mode} mode requires azureEventHubs options or delegate callback.`);
|
|
2408
|
+
}
|
|
2409
|
+
if (!options || typeof options !== "object") {
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
requireNonEmptyString(optionString(options, "ConnectionString", "connectionString"), "ConnectionString must be provided for Azure Event Hubs endpoint.");
|
|
2413
|
+
requireNonEmptyString(optionString(options, "EventHubName", "eventHubName"), "EventHubName must be provided for Azure Event Hubs endpoint.");
|
|
2414
|
+
}
|
|
2415
|
+
function validatePushDiffusionEndpoint(endpoint, mode) {
|
|
2416
|
+
const options = endpoint.pushDiffusion;
|
|
2417
|
+
if (!options || typeof options !== "object") {
|
|
2418
|
+
throw new Error(`PushDiffusion endpoint in ${mode} mode requires pushDiffusion options.`);
|
|
2419
|
+
}
|
|
2420
|
+
requireNonEmptyString(optionString(options, "ServerUrl", "serverUrl"), "ServerUrl must be provided for Push Diffusion endpoint.");
|
|
2421
|
+
requireNonEmptyString(optionString(options, "TopicPath", "topicPath"), "TopicPath must be provided for Push Diffusion endpoint.");
|
|
2422
|
+
if (mode === "Produce" && !resolveStructuredProduceDelegate(endpoint) && !resolveProduceDelegate(endpoint)) {
|
|
2423
|
+
throw new Error("PublishAsync delegate must be provided for Push Diffusion producer endpoint.");
|
|
2424
|
+
}
|
|
2425
|
+
if (mode === "Consume"
|
|
2426
|
+
&& !resolveStructuredConsumeDelegate(endpoint)
|
|
2427
|
+
&& !resolveStructuredConsumeStreamDelegate(endpoint)
|
|
2428
|
+
&& !resolveConsumeDelegate(endpoint)) {
|
|
2429
|
+
throw new Error("SubscribeAsync delegate must be provided for Push Diffusion consumer endpoint.");
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
async function applyHttpAuthHeaders(headers, options, resolveOAuthToken) {
|
|
2433
|
+
const auth = options.auth;
|
|
2434
|
+
if (!auth) {
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
const headerName = auth.tokenHeaderName?.trim() || "Authorization";
|
|
2438
|
+
const mode = normalizeToken(auth.mode ?? auth.type ?? "None");
|
|
2439
|
+
if (mode === "basic") {
|
|
2440
|
+
const username = requireNonEmptyString(auth.username, "Http Basic auth requires username.");
|
|
2441
|
+
if (auth.password == null) {
|
|
2442
|
+
throw new Error("Http Basic auth requires password.");
|
|
2443
|
+
}
|
|
2444
|
+
headers[headerName] = `Basic ${Buffer.from(`${username}:${String(auth.password)}`).toString("base64")}`;
|
|
2445
|
+
}
|
|
2446
|
+
else if (mode === "bearer") {
|
|
2447
|
+
headers[headerName] = `Bearer ${requireNonEmptyString(auth.bearerToken, "Http Bearer auth requires bearerToken.")}`;
|
|
2448
|
+
}
|
|
2449
|
+
else if (mode === "oauth2clientcredentials") {
|
|
2450
|
+
headers[headerName] = `Bearer ${await resolveOAuthToken()}`;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function buildHttpRequestBody(body, bodyType, contentType) {
|
|
2454
|
+
if (body == null) {
|
|
2455
|
+
return undefined;
|
|
2456
|
+
}
|
|
2457
|
+
if (body instanceof Uint8Array) {
|
|
2458
|
+
return new Uint8Array(body);
|
|
2459
|
+
}
|
|
2460
|
+
if (body instanceof ArrayBuffer) {
|
|
2461
|
+
return body;
|
|
2462
|
+
}
|
|
2463
|
+
const normalized = normalizeToken(canonicalizeHttpBodyType(String(bodyType)));
|
|
2464
|
+
if (normalized === "plaintext" || normalized === "text" || normalized === "xml") {
|
|
2465
|
+
return String(body);
|
|
2466
|
+
}
|
|
2467
|
+
if (normalized === "binary") {
|
|
2468
|
+
return new TextEncoder().encode(String(body));
|
|
2469
|
+
}
|
|
2470
|
+
if (normalized === "formurlencoded") {
|
|
2471
|
+
if (isRecord(body)) {
|
|
2472
|
+
const form = new URLSearchParams();
|
|
2473
|
+
for (const [key, value] of Object.entries(body)) {
|
|
2474
|
+
form.set(String(key), value == null ? "" : String(value));
|
|
2475
|
+
}
|
|
2476
|
+
return form.toString();
|
|
2477
|
+
}
|
|
2478
|
+
return String(body);
|
|
2479
|
+
}
|
|
2480
|
+
if (typeof body === "string" && !isLikelyJsonContentType(contentType)) {
|
|
2481
|
+
return body;
|
|
2482
|
+
}
|
|
2483
|
+
return JSON.stringify(body);
|
|
2484
|
+
}
|
|
2485
|
+
async function buildHttpProduceResult(requestPayload, response, responseSource) {
|
|
2486
|
+
const mode = normalizeToken(canonicalizeHttpResponseSource(String(responseSource ?? "None")));
|
|
2487
|
+
if (mode === "none") {
|
|
2488
|
+
return requestPayload;
|
|
2489
|
+
}
|
|
2490
|
+
if (mode === "responseheaders") {
|
|
2491
|
+
return attachPayloadHelpers({
|
|
2492
|
+
headers: {
|
|
2493
|
+
...(requestPayload.headers ?? {}),
|
|
2494
|
+
...headersToRecord(response.headers)
|
|
2495
|
+
},
|
|
2496
|
+
body: requestPayload.body,
|
|
2497
|
+
producedUtc: requestPayload.producedUtc,
|
|
2498
|
+
contentType: requestPayload.contentType,
|
|
2499
|
+
messagePayloadType: requestPayload.messagePayloadType,
|
|
2500
|
+
jsonSettings: requestPayload.jsonSettings,
|
|
2501
|
+
jsonConvertSettings: requestPayload.jsonConvertSettings
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
if (mode === "responsestatuscode") {
|
|
2505
|
+
return attachPayloadHelpers({
|
|
2506
|
+
headers: { ...(requestPayload.headers ?? {}) },
|
|
2507
|
+
body: {
|
|
2508
|
+
statusCode: response.status,
|
|
2509
|
+
statusText: response.statusText
|
|
2510
|
+
},
|
|
2511
|
+
producedUtc: requestPayload.producedUtc,
|
|
2512
|
+
contentType: requestPayload.contentType,
|
|
2513
|
+
messagePayloadType: requestPayload.messagePayloadType,
|
|
2514
|
+
jsonSettings: requestPayload.jsonSettings,
|
|
2515
|
+
jsonConvertSettings: requestPayload.jsonConvertSettings
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
const raw = await response.text();
|
|
2519
|
+
return attachPayloadHelpers({
|
|
2520
|
+
headers: {
|
|
2521
|
+
...(requestPayload.headers ?? {}),
|
|
2522
|
+
...headersToRecord(response.headers)
|
|
2523
|
+
},
|
|
2524
|
+
body: parseMaybeJson(raw),
|
|
2525
|
+
producedUtc: requestPayload.producedUtc,
|
|
2526
|
+
contentType: response.headers.get("content-type") ?? requestPayload.contentType,
|
|
2527
|
+
messagePayloadType: requestPayload.messagePayloadType,
|
|
2528
|
+
jsonSettings: requestPayload.jsonSettings,
|
|
2529
|
+
jsonConvertSettings: requestPayload.jsonConvertSettings
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
function buildProducedMessageRequest(endpoint, payload) {
|
|
2533
|
+
const clonedPayload = clonePayload(payload);
|
|
2534
|
+
const request = attachPayloadHelpers(clonePayload(clonedPayload));
|
|
2535
|
+
request.endpoint = endpoint;
|
|
2536
|
+
request.payload = attachPayloadHelpers(clonePayload(clonedPayload));
|
|
2537
|
+
request.trackingId = extractTrackingValue(clonedPayload, endpoint.trackingField) ?? "";
|
|
2538
|
+
request.connectionMetadata = resolveConnectionMetadata(endpoint);
|
|
2539
|
+
return attachStructuredProducedMessageRequestAliases(request, endpoint);
|
|
2540
|
+
}
|
|
2541
|
+
async function invokeStructuredProduceDelegate(endpoint, payload, delegate) {
|
|
2542
|
+
const request = buildProducedMessageRequest(endpoint, payload);
|
|
2543
|
+
const callbackResult = await delegate(request);
|
|
2544
|
+
return normalizeStructuredProduceResult(endpoint, payload, callbackResult, request.trackingId);
|
|
2545
|
+
}
|
|
2546
|
+
function normalizeStructuredProduceResult(endpoint, requestPayload, callbackResult, requestTrackingId) {
|
|
2547
|
+
if (callbackResult == null) {
|
|
2548
|
+
throw new Error("Structured producer returned null result.");
|
|
2549
|
+
}
|
|
2550
|
+
if (isStructuredProduceResult(callbackResult)) {
|
|
2551
|
+
const structured = callbackResult;
|
|
2552
|
+
const isSuccess = structured.isSuccess ?? structured.IsSuccess;
|
|
2553
|
+
const timestampUtc = (structured.timestampUtc ?? structured.TimestampUtc);
|
|
2554
|
+
const timestampUtcMs = (structured.timestampUtcMs ?? structured.TimestampUtcMs);
|
|
2555
|
+
if (isSuccess === false) {
|
|
2556
|
+
throw new Error(String(structured.errorMessage ?? structured.ErrorMessage ?? "").trim()
|
|
2557
|
+
|| "Structured producer reported failure.");
|
|
2558
|
+
}
|
|
2559
|
+
const payload = normalizeTrackingPayload(structured.responsePayload
|
|
2560
|
+
?? structured.ResponsePayload
|
|
2561
|
+
?? requestPayload);
|
|
2562
|
+
payload.producedUtc = payload.producedUtc ?? normalizeDelegateTimestamp(timestampUtc, timestampUtcMs);
|
|
2563
|
+
if (requestTrackingId && !extractTrackingValue(payload, endpoint.trackingField)) {
|
|
2564
|
+
injectTrackingValue(payload, endpoint.trackingField, requestTrackingId);
|
|
2565
|
+
}
|
|
2566
|
+
return payload;
|
|
2567
|
+
}
|
|
2568
|
+
const payload = normalizeTrackingPayload(callbackResult);
|
|
2569
|
+
payload.producedUtc = payload.producedUtc ?? normalizeDelegateTimestamp();
|
|
2570
|
+
if (requestTrackingId && !extractTrackingValue(payload, endpoint.trackingField)) {
|
|
2571
|
+
injectTrackingValue(payload, endpoint.trackingField, requestTrackingId);
|
|
2572
|
+
}
|
|
2573
|
+
return payload;
|
|
2574
|
+
}
|
|
2575
|
+
function normalizeConsumedDelegatePayload(message) {
|
|
2576
|
+
if (message == null) {
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
if (isStructuredConsumedMessage(message)) {
|
|
2580
|
+
const structured = message;
|
|
2581
|
+
const timestampUtc = (structured.timestampUtc ?? structured.TimestampUtc);
|
|
2582
|
+
const timestampUtcMs = (structured.timestampUtcMs ?? structured.TimestampUtcMs);
|
|
2583
|
+
const payload = normalizeTrackingPayload(structured.payload
|
|
2584
|
+
?? structured.Payload
|
|
2585
|
+
?? structured);
|
|
2586
|
+
payload.producedUtc = payload.producedUtc ?? normalizeDelegateTimestamp(timestampUtc, timestampUtcMs);
|
|
2587
|
+
return payload;
|
|
2588
|
+
}
|
|
2589
|
+
return normalizeTrackingPayload(message);
|
|
2590
|
+
}
|
|
2591
|
+
function normalizeDelegateTimestamp(value, valueMs) {
|
|
2592
|
+
if (typeof valueMs === "number" && Number.isFinite(valueMs)) {
|
|
2593
|
+
return new Date(valueMs).toISOString();
|
|
2594
|
+
}
|
|
2595
|
+
if (value instanceof Date) {
|
|
2596
|
+
return value.toISOString();
|
|
2597
|
+
}
|
|
2598
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2599
|
+
return new Date(value).toISOString();
|
|
2600
|
+
}
|
|
2601
|
+
if (typeof value === "string" && value.trim()) {
|
|
2602
|
+
const parsed = Date.parse(value);
|
|
2603
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : value.trim();
|
|
2604
|
+
}
|
|
2605
|
+
return new Date().toISOString();
|
|
2606
|
+
}
|
|
2607
|
+
function normalizeConsumedPayloadRows(value, arrayPath) {
|
|
2608
|
+
const selected = extractJsonPath(value, arrayPath);
|
|
2609
|
+
if (Array.isArray(selected)) {
|
|
2610
|
+
return selected.map((entry) => normalizeTrackingPayload(entry));
|
|
2611
|
+
}
|
|
2612
|
+
if (selected == null) {
|
|
2613
|
+
return [];
|
|
2614
|
+
}
|
|
2615
|
+
return [normalizeTrackingPayload(selected)];
|
|
2616
|
+
}
|
|
2617
|
+
function normalizeTrackingPayload(value) {
|
|
2618
|
+
if (isRecord(value) && ("headers" in value || "body" in value || "Headers" in value || "Body" in value)) {
|
|
2619
|
+
const headers = toStringRecord(value.headers ?? value.Headers);
|
|
2620
|
+
const jsonSettings = value.jsonSettings ?? value.JsonSettings;
|
|
2621
|
+
const jsonConvertSettings = value.jsonConvertSettings ?? value.JsonConvertSettings;
|
|
2622
|
+
return attachPayloadHelpers({
|
|
2623
|
+
headers,
|
|
2624
|
+
body: cloneBody(value.body ?? value.Body),
|
|
2625
|
+
producedUtc: typeof (value.producedUtc ?? value.ProducedUtc) === "string"
|
|
2626
|
+
? String(value.producedUtc ?? value.ProducedUtc)
|
|
2627
|
+
: undefined,
|
|
2628
|
+
contentType: typeof (value.contentType ?? value.ContentType) === "string"
|
|
2629
|
+
? String(value.contentType ?? value.ContentType)
|
|
2630
|
+
: undefined,
|
|
2631
|
+
messagePayloadType: typeof (value.messagePayloadType ?? value.MessagePayloadType) === "string"
|
|
2632
|
+
? String(value.messagePayloadType ?? value.MessagePayloadType)
|
|
2633
|
+
: undefined,
|
|
2634
|
+
jsonSettings: isRecord(jsonSettings) ? { ...jsonSettings } : undefined,
|
|
2635
|
+
jsonConvertSettings: isRecord(jsonConvertSettings) ? { ...jsonConvertSettings } : undefined
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
return attachPayloadHelpers({
|
|
2639
|
+
headers: {},
|
|
2640
|
+
body: cloneBody(value),
|
|
2641
|
+
producedUtc: undefined
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
function extractJsonPath(value, arrayPath) {
|
|
2645
|
+
if (!arrayPath || !arrayPath.trim()) {
|
|
2646
|
+
return value;
|
|
2647
|
+
}
|
|
2648
|
+
let current = value;
|
|
2649
|
+
const normalized = arrayPath.trim().replace(/^\$\./, "").replace(/^\$/, "");
|
|
2650
|
+
if (!normalized) {
|
|
2651
|
+
return value;
|
|
2652
|
+
}
|
|
2653
|
+
const tokens = normalized.replace(/\[(\d+)\]/g, ".$1").split(".").filter((x) => x.length > 0);
|
|
2654
|
+
for (const token of tokens) {
|
|
2655
|
+
if (current == null) {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
if (Array.isArray(current)) {
|
|
2659
|
+
const index = Number(token);
|
|
2660
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) {
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
current = current[index];
|
|
2664
|
+
continue;
|
|
2665
|
+
}
|
|
2666
|
+
if (!isRecord(current) || !(token in current)) {
|
|
2667
|
+
return null;
|
|
2668
|
+
}
|
|
2669
|
+
current = current[token];
|
|
2670
|
+
}
|
|
2671
|
+
return current;
|
|
2672
|
+
}
|
|
2673
|
+
function parseMaybeJson(value) {
|
|
2674
|
+
const text = value.trim();
|
|
2675
|
+
if (!text) {
|
|
2676
|
+
return null;
|
|
2677
|
+
}
|
|
2678
|
+
try {
|
|
2679
|
+
return JSON.parse(text);
|
|
2680
|
+
}
|
|
2681
|
+
catch {
|
|
2682
|
+
return value;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
function headersToRecord(value) {
|
|
2686
|
+
const headers = {};
|
|
2687
|
+
value.forEach((headerValue, key) => {
|
|
2688
|
+
headers[key] = headerValue;
|
|
2689
|
+
});
|
|
2690
|
+
return headers;
|
|
2691
|
+
}
|
|
2692
|
+
function toStringRecord(value) {
|
|
2693
|
+
if (!isRecord(value)) {
|
|
2694
|
+
return {};
|
|
2695
|
+
}
|
|
2696
|
+
const mapped = {};
|
|
2697
|
+
for (const [key, row] of Object.entries(value)) {
|
|
2698
|
+
mapped[key] = row == null ? "" : String(row);
|
|
2699
|
+
}
|
|
2700
|
+
return mapped;
|
|
2701
|
+
}
|
|
2702
|
+
function isLikelyJsonContentType(contentType) {
|
|
2703
|
+
return contentType.toLowerCase().includes("json");
|
|
2704
|
+
}
|
|
2705
|
+
function normalizeToken(value) {
|
|
2706
|
+
return String(value ?? "").trim().toLowerCase();
|
|
2707
|
+
}
|
|
2708
|
+
function createTimeoutSignal(timeoutSeconds) {
|
|
2709
|
+
const timeoutMs = Number(timeoutSeconds ?? 0);
|
|
2710
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
2711
|
+
return { signal: undefined, clear: () => undefined };
|
|
2712
|
+
}
|
|
2713
|
+
const controller = new AbortController();
|
|
2714
|
+
const timer = setTimeout(() => controller.abort(), Math.trunc(timeoutMs * 1000));
|
|
2715
|
+
return {
|
|
2716
|
+
signal: controller.signal,
|
|
2717
|
+
clear: () => clearTimeout(timer)
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
function isRecord(value) {
|
|
2721
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2722
|
+
}
|
|
2723
|
+
function requireNonEmptyString(value, message) {
|
|
2724
|
+
if (typeof value === "string" && value.trim()) {
|
|
2725
|
+
return value.trim();
|
|
2726
|
+
}
|
|
2727
|
+
throw new Error(message);
|
|
2728
|
+
}
|
|
2729
|
+
function clonePayload(payload) {
|
|
2730
|
+
return attachPayloadHelpers({
|
|
2731
|
+
headers: { ...(payload.headers ?? {}) },
|
|
2732
|
+
body: cloneBody(payload.body),
|
|
2733
|
+
producedUtc: payload.producedUtc,
|
|
2734
|
+
contentType: payload.contentType,
|
|
2735
|
+
messagePayloadType: payload.messagePayloadType,
|
|
2736
|
+
jsonSettings: isRecord(payload.jsonSettings) ? { ...payload.jsonSettings } : undefined,
|
|
2737
|
+
jsonConvertSettings: isRecord(payload.jsonConvertSettings) ? { ...payload.jsonConvertSettings } : undefined
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
function cloneBody(value) {
|
|
2741
|
+
if (value == null) {
|
|
2742
|
+
return value;
|
|
2743
|
+
}
|
|
2744
|
+
if (value instanceof Uint8Array) {
|
|
2745
|
+
return new Uint8Array(value);
|
|
2746
|
+
}
|
|
2747
|
+
if (Array.isArray(value)) {
|
|
2748
|
+
return value.map((x) => cloneBody(x));
|
|
2749
|
+
}
|
|
2750
|
+
if (typeof value === "object") {
|
|
2751
|
+
return { ...value };
|
|
2752
|
+
}
|
|
2753
|
+
return value;
|
|
2754
|
+
}
|
|
2755
|
+
function optionString(options, ...keys) {
|
|
2756
|
+
for (const key of keys) {
|
|
2757
|
+
const value = options[key];
|
|
2758
|
+
if (typeof value === "string" && value.trim()) {
|
|
2759
|
+
return value.trim();
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return "";
|
|
2763
|
+
}
|
|
2764
|
+
function optionNumber(options, ...keys) {
|
|
2765
|
+
for (const key of keys) {
|
|
2766
|
+
const value = options[key];
|
|
2767
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2768
|
+
return value;
|
|
2769
|
+
}
|
|
2770
|
+
if (typeof value === "string" && value.trim()) {
|
|
2771
|
+
const parsed = Number(value);
|
|
2772
|
+
if (Number.isFinite(parsed)) {
|
|
2773
|
+
return parsed;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
return 0;
|
|
2778
|
+
}
|
|
2779
|
+
function optionBoolean(options, fallback, ...keys) {
|
|
2780
|
+
for (const key of keys) {
|
|
2781
|
+
const value = options[key];
|
|
2782
|
+
if (typeof value === "boolean") {
|
|
2783
|
+
return value;
|
|
2784
|
+
}
|
|
2785
|
+
if (typeof value === "number") {
|
|
2786
|
+
return value !== 0;
|
|
2787
|
+
}
|
|
2788
|
+
if (typeof value === "string" && value.trim()) {
|
|
2789
|
+
const normalized = value.trim().toLowerCase();
|
|
2790
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
|
2791
|
+
return true;
|
|
2792
|
+
}
|
|
2793
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return fallback;
|
|
2799
|
+
}
|
|
2800
|
+
function partitionFromKey(value, partitionCount) {
|
|
2801
|
+
if (!value) {
|
|
2802
|
+
return "0";
|
|
2803
|
+
}
|
|
2804
|
+
const count = Math.max(Math.trunc(partitionCount), 1);
|
|
2805
|
+
let hash = 0;
|
|
2806
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
2807
|
+
hash = ((hash << 5) - hash) + value.charCodeAt(i);
|
|
2808
|
+
hash |= 0;
|
|
2809
|
+
}
|
|
2810
|
+
const normalized = Math.abs(hash) % count;
|
|
2811
|
+
return String(normalized);
|
|
2812
|
+
}
|
|
2813
|
+
function attachPayloadHelpers(payload) {
|
|
2814
|
+
const target = {
|
|
2815
|
+
...payload,
|
|
2816
|
+
getBodyAsUtf8: () => payloadBodyAsUtf8(target.body)
|
|
2817
|
+
};
|
|
2818
|
+
return attachDotNetTrackingPayloadAliases(target);
|
|
2819
|
+
}
|
|
2820
|
+
function attachDotNetTrackingPayloadAliases(payload) {
|
|
2821
|
+
const target = payload;
|
|
2822
|
+
definePascalCaseAlias(target, "Headers", () => target.headers, (value) => { target.headers = toStringRecord(value); });
|
|
2823
|
+
definePascalCaseAlias(target, "Body", () => target.body, (value) => { target.body = cloneBody(value); });
|
|
2824
|
+
definePascalCaseAlias(target, "ProducedUtc", () => target.producedUtc, (value) => {
|
|
2825
|
+
target.producedUtc = typeof value === "string" ? value : undefined;
|
|
2826
|
+
});
|
|
2827
|
+
definePascalCaseAlias(target, "ConsumedUtc", () => target.consumedUtc, (value) => {
|
|
2828
|
+
target.consumedUtc = typeof value === "string" ? value : undefined;
|
|
2829
|
+
});
|
|
2830
|
+
definePascalCaseAlias(target, "ContentType", () => target.contentType, (value) => {
|
|
2831
|
+
target.contentType = typeof value === "string" ? value : undefined;
|
|
2832
|
+
});
|
|
2833
|
+
definePascalCaseAlias(target, "MessagePayloadType", () => target.messagePayloadType, (value) => {
|
|
2834
|
+
target.messagePayloadType = typeof value === "string" ? value : undefined;
|
|
2835
|
+
});
|
|
2836
|
+
definePascalCaseAlias(target, "JsonSettings", () => target.jsonSettings, (value) => {
|
|
2837
|
+
target.jsonSettings = isRecord(value) ? { ...value } : undefined;
|
|
2838
|
+
});
|
|
2839
|
+
definePascalCaseAlias(target, "JsonConvertSettings", () => target.jsonConvertSettings, (value) => {
|
|
2840
|
+
target.jsonConvertSettings = isRecord(value) ? { ...value } : undefined;
|
|
2841
|
+
});
|
|
2842
|
+
definePascalCaseAlias(target, "GetBodyAsUtf8", () => target.getBodyAsUtf8);
|
|
2843
|
+
return payload;
|
|
2844
|
+
}
|
|
2845
|
+
function attachStructuredProducedMessageRequestAliases(request, endpoint) {
|
|
2846
|
+
const target = request;
|
|
2847
|
+
const endpointView = createDelegateRequestEndpointView(endpoint);
|
|
2848
|
+
definePascalCaseAlias(target, "Endpoint", () => endpointView);
|
|
2849
|
+
definePascalCaseAlias(target, "Payload", () => target.payload);
|
|
2850
|
+
definePascalCaseAlias(target, "TrackingId", () => target.trackingId, (value) => {
|
|
2851
|
+
target.trackingId = String(value ?? "");
|
|
2852
|
+
});
|
|
2853
|
+
definePascalCaseAlias(target, "ConnectionMetadata", () => target.connectionMetadata, (value) => {
|
|
2854
|
+
target.connectionMetadata = toStringRecord(value);
|
|
2855
|
+
});
|
|
2856
|
+
return request;
|
|
2857
|
+
}
|
|
2858
|
+
function createDelegateRequestEndpointView(endpoint) {
|
|
2859
|
+
switch (endpoint.kind) {
|
|
2860
|
+
case "Http":
|
|
2861
|
+
return new HttpEndpointDefinition(endpoint);
|
|
2862
|
+
case "Kafka":
|
|
2863
|
+
return new KafkaEndpointDefinition(endpoint);
|
|
2864
|
+
case "RabbitMq":
|
|
2865
|
+
return new RabbitMqEndpointDefinition(endpoint);
|
|
2866
|
+
case "Nats":
|
|
2867
|
+
return new NatsEndpointDefinition(endpoint);
|
|
2868
|
+
case "RedisStreams":
|
|
2869
|
+
return new RedisStreamsEndpointDefinition(endpoint);
|
|
2870
|
+
case "AzureEventHubs":
|
|
2871
|
+
return new AzureEventHubsEndpointDefinition(endpoint);
|
|
2872
|
+
case "PushDiffusion":
|
|
2873
|
+
return new PushDiffusionEndpointDefinition(endpoint);
|
|
2874
|
+
default:
|
|
2875
|
+
return new DelegateStreamEndpointDefinition(endpoint);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
function definePascalCaseAlias(target, key, getter, setter) {
|
|
2879
|
+
if (Object.getOwnPropertyDescriptor(target, key)) {
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
Object.defineProperty(target, key, {
|
|
2883
|
+
configurable: true,
|
|
2884
|
+
enumerable: true,
|
|
2885
|
+
get: getter,
|
|
2886
|
+
set: setter
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
function createDefaultPayload(endpoint) {
|
|
2890
|
+
const contentType = endpoint.contentType
|
|
2891
|
+
?? (endpoint.messagePayload != null ? "application/json" : undefined);
|
|
2892
|
+
return attachPayloadHelpers({
|
|
2893
|
+
headers: { ...(endpoint.messageHeaders ?? {}) },
|
|
2894
|
+
body: cloneBody(endpoint.messagePayload),
|
|
2895
|
+
contentType,
|
|
2896
|
+
messagePayloadType: endpoint.messagePayloadType,
|
|
2897
|
+
jsonSettings: endpoint.jsonSerializerSettings ? { ...endpoint.jsonSerializerSettings } : undefined,
|
|
2898
|
+
jsonConvertSettings: endpoint.jsonConvertSettings ? { ...endpoint.jsonConvertSettings } : undefined
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
function prepareProducedPayload(endpoint, payload) {
|
|
2902
|
+
const base = payload
|
|
2903
|
+
? attachPayloadHelpers({
|
|
2904
|
+
headers: {
|
|
2905
|
+
...(endpoint.messageHeaders ?? {}),
|
|
2906
|
+
...(payload.headers ?? {})
|
|
2907
|
+
},
|
|
2908
|
+
body: payload.body ?? cloneBody(endpoint.messagePayload),
|
|
2909
|
+
producedUtc: payload.producedUtc,
|
|
2910
|
+
contentType: payload.contentType ?? endpoint.contentType ?? (payload.body != null || endpoint.messagePayload != null ? "application/json" : undefined),
|
|
2911
|
+
messagePayloadType: payload.messagePayloadType ?? endpoint.messagePayloadType,
|
|
2912
|
+
jsonSettings: payload.jsonSettings ?? endpoint.jsonSerializerSettings,
|
|
2913
|
+
jsonConvertSettings: payload.jsonConvertSettings ?? endpoint.jsonConvertSettings
|
|
2914
|
+
})
|
|
2915
|
+
: createDefaultPayload(endpoint);
|
|
2916
|
+
const trackingId = extractTrackingValue(base, endpoint.trackingField);
|
|
2917
|
+
if (trackingId) {
|
|
2918
|
+
return base;
|
|
2919
|
+
}
|
|
2920
|
+
if (endpoint.autoGenerateTrackingIdWhenMissing === false) {
|
|
2921
|
+
throw new Error(`Tracking id could not be extracted using selector \`${endpoint.trackingField}\` and AutoGenerateTrackingIdWhenMissing is disabled for endpoint \`${endpoint.name}\`.`);
|
|
2922
|
+
}
|
|
2923
|
+
const generated = randomUUID().replace(/-/g, "");
|
|
2924
|
+
if (!injectTrackingValue(base, endpoint.trackingField, generated)) {
|
|
2925
|
+
throw new Error(`Tracking id could not be injected using selector \`${endpoint.trackingField}\` for endpoint \`${endpoint.name}\`. Verify the selector and payload shape.`);
|
|
2926
|
+
}
|
|
2927
|
+
return base;
|
|
2928
|
+
}
|
|
2929
|
+
function toWirePayload(payload, endpoint) {
|
|
2930
|
+
return {
|
|
2931
|
+
headers: { ...(payload.headers ?? {}) },
|
|
2932
|
+
body: serializePayloadBody(payload.body, payload.contentType ?? endpoint.contentType),
|
|
2933
|
+
contentType: payload.contentType ?? endpoint.contentType
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
function serializePayloadBody(body, contentType) {
|
|
2937
|
+
if (body == null) {
|
|
2938
|
+
return new Uint8Array();
|
|
2939
|
+
}
|
|
2940
|
+
if (body instanceof Uint8Array) {
|
|
2941
|
+
return new Uint8Array(body);
|
|
2942
|
+
}
|
|
2943
|
+
if (body instanceof ArrayBuffer) {
|
|
2944
|
+
return new Uint8Array(body);
|
|
2945
|
+
}
|
|
2946
|
+
if (typeof body === "string") {
|
|
2947
|
+
if (isLikelyJsonContentType(contentType ?? "")) {
|
|
2948
|
+
try {
|
|
2949
|
+
JSON.parse(body);
|
|
2950
|
+
return new TextEncoder().encode(body);
|
|
2951
|
+
}
|
|
2952
|
+
catch {
|
|
2953
|
+
return new TextEncoder().encode(JSON.stringify(body));
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
return new TextEncoder().encode(body);
|
|
2957
|
+
}
|
|
2958
|
+
return new TextEncoder().encode(JSON.stringify(body));
|
|
2959
|
+
}
|
|
2960
|
+
function createBrokerPayload(headers, body, endpoint, contentType) {
|
|
2961
|
+
const normalizedHeaders = { ...headers };
|
|
2962
|
+
const resolvedContentType = contentType ?? normalizedHeaders["content-type"] ?? endpoint.contentType;
|
|
2963
|
+
return attachPayloadHelpers({
|
|
2964
|
+
headers: normalizedHeaders,
|
|
2965
|
+
body: deserializeBrokerPayloadBody(body, resolvedContentType, endpoint.messagePayloadType),
|
|
2966
|
+
contentType: resolvedContentType,
|
|
2967
|
+
messagePayloadType: endpoint.messagePayloadType,
|
|
2968
|
+
jsonSettings: endpoint.jsonSerializerSettings ? { ...endpoint.jsonSerializerSettings } : undefined,
|
|
2969
|
+
jsonConvertSettings: endpoint.jsonConvertSettings ? { ...endpoint.jsonConvertSettings } : undefined
|
|
2970
|
+
});
|
|
2971
|
+
}
|
|
2972
|
+
function deserializeBrokerPayloadBody(body, contentType, messagePayloadType) {
|
|
2973
|
+
if (!body.length) {
|
|
2974
|
+
return null;
|
|
2975
|
+
}
|
|
2976
|
+
const payloadType = String(messagePayloadType ?? "").trim().toLowerCase();
|
|
2977
|
+
if (payloadType === "binary") {
|
|
2978
|
+
return new Uint8Array(body);
|
|
2979
|
+
}
|
|
2980
|
+
const text = new TextDecoder().decode(body);
|
|
2981
|
+
if (payloadType === "json" || isLikelyJsonContentType(contentType ?? "")) {
|
|
2982
|
+
return parseMaybeJson(text);
|
|
2983
|
+
}
|
|
2984
|
+
if (payloadType) {
|
|
2985
|
+
return text;
|
|
2986
|
+
}
|
|
2987
|
+
if (typeof contentType === "string" && contentType.trim()) {
|
|
2988
|
+
return text;
|
|
2989
|
+
}
|
|
2990
|
+
return new Uint8Array(body);
|
|
2991
|
+
}
|
|
2992
|
+
function extractTrackingValue(payload, selector) {
|
|
2993
|
+
const normalized = selector.trim().toLowerCase();
|
|
2994
|
+
if (normalized.startsWith("header:")) {
|
|
2995
|
+
const headerName = selector.slice("header:".length).trim().toLowerCase();
|
|
2996
|
+
for (const [key, value] of Object.entries(payload.headers ?? {})) {
|
|
2997
|
+
const resolved = String(value ?? "").trim();
|
|
2998
|
+
if (key.toLowerCase() === headerName && resolved) {
|
|
2999
|
+
return resolved;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return null;
|
|
3003
|
+
}
|
|
3004
|
+
if (normalized.startsWith("json:")) {
|
|
3005
|
+
const body = parseBodyObject(payload.body);
|
|
3006
|
+
if (!body) {
|
|
3007
|
+
return null;
|
|
3008
|
+
}
|
|
3009
|
+
let current = body;
|
|
3010
|
+
for (const segment of selector.slice("json:".length).trim().replace(/^\$\./, "").split(".").filter(Boolean)) {
|
|
3011
|
+
if (!isRecord(current)) {
|
|
3012
|
+
return null;
|
|
3013
|
+
}
|
|
3014
|
+
current = current[segment];
|
|
3015
|
+
}
|
|
3016
|
+
return current == null ? null : String(current);
|
|
3017
|
+
}
|
|
3018
|
+
return null;
|
|
3019
|
+
}
|
|
3020
|
+
function injectTrackingValue(payload, selector, value) {
|
|
3021
|
+
const normalized = selector.trim().toLowerCase();
|
|
3022
|
+
if (normalized.startsWith("header:")) {
|
|
3023
|
+
const headerName = selector.slice("header:".length).trim();
|
|
3024
|
+
if (!headerName) {
|
|
3025
|
+
return false;
|
|
3026
|
+
}
|
|
3027
|
+
payload.headers = {
|
|
3028
|
+
...(payload.headers ?? {}),
|
|
3029
|
+
[headerName]: value
|
|
3030
|
+
};
|
|
3031
|
+
return true;
|
|
3032
|
+
}
|
|
3033
|
+
if (normalized.startsWith("json:")) {
|
|
3034
|
+
const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
|
|
3035
|
+
payload.body = setJsonBodyValue(payload.body, path, value);
|
|
3036
|
+
if (!payload.contentType) {
|
|
3037
|
+
payload.contentType = "application/json";
|
|
3038
|
+
}
|
|
3039
|
+
return true;
|
|
3040
|
+
}
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
function setJsonBodyValue(body, path, value) {
|
|
3044
|
+
const target = parseBodyObject(body) ?? {};
|
|
3045
|
+
const clone = cloneBody(target);
|
|
3046
|
+
const segments = path.split(".").filter(Boolean);
|
|
3047
|
+
if (!segments.length) {
|
|
3048
|
+
return clone;
|
|
3049
|
+
}
|
|
3050
|
+
let current = clone;
|
|
3051
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
3052
|
+
const segment = segments[i];
|
|
3053
|
+
const next = current[segment];
|
|
3054
|
+
if (!isRecord(next)) {
|
|
3055
|
+
current[segment] = {};
|
|
3056
|
+
}
|
|
3057
|
+
current = current[segment];
|
|
3058
|
+
}
|
|
3059
|
+
current[segments[segments.length - 1]] = value;
|
|
3060
|
+
return clone;
|
|
3061
|
+
}
|
|
3062
|
+
function parseBodyObject(body) {
|
|
3063
|
+
if (body instanceof Uint8Array) {
|
|
3064
|
+
return parseBodyObject(new TextDecoder().decode(body));
|
|
3065
|
+
}
|
|
3066
|
+
if (isRecord(body)) {
|
|
3067
|
+
return body;
|
|
3068
|
+
}
|
|
3069
|
+
if (typeof body !== "string" || !body.trim()) {
|
|
3070
|
+
return null;
|
|
3071
|
+
}
|
|
3072
|
+
try {
|
|
3073
|
+
const parsed = JSON.parse(body);
|
|
3074
|
+
return isRecord(parsed) ? parsed : null;
|
|
3075
|
+
}
|
|
3076
|
+
catch {
|
|
3077
|
+
return null;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
function resolveStructuredProduceDelegate(endpoint) {
|
|
3081
|
+
return endpoint.delegate?.produceAsync
|
|
3082
|
+
?? endpoint.pushDiffusion?.PublishAsync
|
|
3083
|
+
?? endpoint.pushDiffusion?.publishAsync;
|
|
3084
|
+
}
|
|
3085
|
+
function resolveStructuredConsumeDelegate(endpoint) {
|
|
3086
|
+
const delegate = endpoint.delegate?.consumeAsync
|
|
3087
|
+
?? endpoint.pushDiffusion?.SubscribeAsync
|
|
3088
|
+
?? endpoint.pushDiffusion?.subscribeAsync;
|
|
3089
|
+
return typeof delegate === "function" && delegate.length === 0
|
|
3090
|
+
? delegate
|
|
3091
|
+
: undefined;
|
|
3092
|
+
}
|
|
3093
|
+
function resolveStructuredConsumeStreamDelegate(endpoint) {
|
|
3094
|
+
const delegate = endpoint.delegate?.consumeAsync
|
|
3095
|
+
?? endpoint.pushDiffusion?.SubscribeAsync
|
|
3096
|
+
?? endpoint.pushDiffusion?.subscribeAsync;
|
|
3097
|
+
return typeof delegate === "function" && delegate.length > 0
|
|
3098
|
+
? delegate
|
|
3099
|
+
: undefined;
|
|
3100
|
+
}
|
|
3101
|
+
function resolveProduceDelegate(endpoint) {
|
|
3102
|
+
return endpoint.delegate?.produce;
|
|
3103
|
+
}
|
|
3104
|
+
function resolveConsumeDelegate(endpoint) {
|
|
3105
|
+
return endpoint.delegate?.consume;
|
|
3106
|
+
}
|
|
3107
|
+
function resolveConnectionMetadata(endpoint) {
|
|
3108
|
+
return {
|
|
3109
|
+
...(endpoint.connectionMetadata ?? {}),
|
|
3110
|
+
...(endpoint.delegate?.connectionMetadata ?? {}),
|
|
3111
|
+
...toStringRecord(endpoint.pushDiffusion?.ConnectionProperties ?? endpoint.pushDiffusion?.connectionProperties)
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
function isStructuredProduceResult(value) {
|
|
3115
|
+
return isRecord(value) && ("isSuccess" in value
|
|
3116
|
+
|| "IsSuccess" in value
|
|
3117
|
+
|| "errorMessage" in value
|
|
3118
|
+
|| "ErrorMessage" in value
|
|
3119
|
+
|| "responsePayload" in value
|
|
3120
|
+
|| "ResponsePayload" in value
|
|
3121
|
+
|| "timestampUtc" in value
|
|
3122
|
+
|| "TimestampUtc" in value
|
|
3123
|
+
|| "timestampUtcMs" in value
|
|
3124
|
+
|| "TimestampUtcMs" in value);
|
|
3125
|
+
}
|
|
3126
|
+
function isStructuredConsumedMessage(value) {
|
|
3127
|
+
return isRecord(value) && ("payload" in value
|
|
3128
|
+
|| "Payload" in value
|
|
3129
|
+
|| "timestampUtc" in value
|
|
3130
|
+
|| "TimestampUtc" in value
|
|
3131
|
+
|| "timestampUtcMs" in value
|
|
3132
|
+
|| "TimestampUtcMs" in value);
|
|
3133
|
+
}
|
|
3134
|
+
function resolveHttpContentType(configuredContentType, bodyType) {
|
|
3135
|
+
if (typeof configuredContentType === "string" && configuredContentType.trim()) {
|
|
3136
|
+
return configuredContentType.trim();
|
|
3137
|
+
}
|
|
3138
|
+
switch (canonicalizeHttpBodyType(String(bodyType ?? "Json"))) {
|
|
3139
|
+
case "PlainText":
|
|
3140
|
+
return "text/plain";
|
|
3141
|
+
case "Xml":
|
|
3142
|
+
return "application/xml";
|
|
3143
|
+
case "FormUrlEncoded":
|
|
3144
|
+
return "application/x-www-form-urlencoded";
|
|
3145
|
+
case "Binary":
|
|
3146
|
+
return "application/octet-stream";
|
|
3147
|
+
default:
|
|
3148
|
+
return "application/json";
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
function canonicalizeHttpBodyType(value) {
|
|
3152
|
+
const normalized = normalizeToken(value).replace(/_/g, "");
|
|
3153
|
+
switch (normalized) {
|
|
3154
|
+
case "":
|
|
3155
|
+
case "json":
|
|
3156
|
+
return "Json";
|
|
3157
|
+
case "text":
|
|
3158
|
+
case "plaintext":
|
|
3159
|
+
return "PlainText";
|
|
3160
|
+
case "xml":
|
|
3161
|
+
return "Xml";
|
|
3162
|
+
case "form":
|
|
3163
|
+
case "formurlencoded":
|
|
3164
|
+
return "FormUrlEncoded";
|
|
3165
|
+
case "binary":
|
|
3166
|
+
return "Binary";
|
|
3167
|
+
default:
|
|
3168
|
+
return value;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function canonicalizeHttpResponseSource(value) {
|
|
3172
|
+
const normalized = normalizeToken(value).replace(/_/g, "");
|
|
3173
|
+
switch (normalized) {
|
|
3174
|
+
case "":
|
|
3175
|
+
case "none":
|
|
3176
|
+
case "request":
|
|
3177
|
+
return "None";
|
|
3178
|
+
case "response":
|
|
3179
|
+
case "body":
|
|
3180
|
+
case "responsebody":
|
|
3181
|
+
return "ResponseBody";
|
|
3182
|
+
case "headers":
|
|
3183
|
+
case "responseheaders":
|
|
3184
|
+
return "ResponseHeaders";
|
|
3185
|
+
case "status":
|
|
3186
|
+
case "statuscode":
|
|
3187
|
+
case "responsestatuscode":
|
|
3188
|
+
return "ResponseStatusCode";
|
|
3189
|
+
default:
|
|
3190
|
+
return value;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function canonicalizeHttpTrackingPayloadSource(value) {
|
|
3194
|
+
const normalized = normalizeToken(value).replace(/_/g, "");
|
|
3195
|
+
switch (normalized) {
|
|
3196
|
+
case "":
|
|
3197
|
+
case "request":
|
|
3198
|
+
return "Request";
|
|
3199
|
+
case "response":
|
|
3200
|
+
case "body":
|
|
3201
|
+
case "responsebody":
|
|
3202
|
+
case "headers":
|
|
3203
|
+
case "responseheaders":
|
|
3204
|
+
case "status":
|
|
3205
|
+
case "statuscode":
|
|
3206
|
+
case "responsestatuscode":
|
|
3207
|
+
return "Response";
|
|
3208
|
+
default:
|
|
3209
|
+
return value;
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
function inferLegacyHttpResponseSource(value) {
|
|
3213
|
+
const normalized = normalizeToken(String(value ?? "")).replace(/_/g, "");
|
|
3214
|
+
switch (normalized) {
|
|
3215
|
+
case "body":
|
|
3216
|
+
case "responsebody":
|
|
3217
|
+
return "ResponseBody";
|
|
3218
|
+
case "headers":
|
|
3219
|
+
case "responseheaders":
|
|
3220
|
+
return "ResponseHeaders";
|
|
3221
|
+
case "status":
|
|
3222
|
+
case "statuscode":
|
|
3223
|
+
case "responsestatuscode":
|
|
3224
|
+
return "ResponseStatusCode";
|
|
3225
|
+
default:
|
|
3226
|
+
return undefined;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
function asRecordOrEmpty(value) {
|
|
3230
|
+
return isRecord(value) ? value : {};
|
|
3231
|
+
}
|
|
3232
|
+
function pickProtocolValue(options, ...keys) {
|
|
3233
|
+
for (const key of keys) {
|
|
3234
|
+
if (key in options) {
|
|
3235
|
+
return options[key];
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
return undefined;
|
|
3239
|
+
}
|
|
3240
|
+
function buildKafkaClientOptions(endpoint) {
|
|
3241
|
+
const options = endpoint.kafka ?? {};
|
|
3242
|
+
const securityProtocol = (optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext").toLowerCase();
|
|
3243
|
+
const bootstrapServers = optionString(options, "BootstrapServers", "bootstrapServers")
|
|
3244
|
+
.split(",")
|
|
3245
|
+
.map((value) => value.trim())
|
|
3246
|
+
.filter((value) => value.length > 0);
|
|
3247
|
+
const clientOptions = {
|
|
3248
|
+
clientId: `${endpoint.name}-${randomUUID().slice(0, 8)}`,
|
|
3249
|
+
brokers: bootstrapServers,
|
|
3250
|
+
ssl: securityProtocol === "ssl" || securityProtocol === "saslssl"
|
|
3251
|
+
};
|
|
3252
|
+
if (securityProtocol === "saslplaintext" || securityProtocol === "saslssl") {
|
|
3253
|
+
const saslOptions = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
|
|
3254
|
+
const mechanism = (optionString(saslOptions, "Mechanism", "mechanism") || "Plain").toLowerCase();
|
|
3255
|
+
if (mechanism === "oauthbearer") {
|
|
3256
|
+
clientOptions.sasl = {
|
|
3257
|
+
mechanism: "oauthbearer",
|
|
3258
|
+
oauthBearerProvider: async () => ({
|
|
3259
|
+
value: await resolveKafkaOAuthBearerToken(saslOptions)
|
|
3260
|
+
})
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
else {
|
|
3264
|
+
clientOptions.sasl = {
|
|
3265
|
+
mechanism: mapKafkaSaslMechanism(mechanism),
|
|
3266
|
+
username: optionString(saslOptions, "Username", "username"),
|
|
3267
|
+
password: optionString(saslOptions, "Password", "password")
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
return clientOptions;
|
|
3272
|
+
}
|
|
3273
|
+
async function createKafkaClient(endpoint) {
|
|
3274
|
+
if (shouldUseConfluentKafkaClient(endpoint)) {
|
|
3275
|
+
const { KafkaJS } = await import("@confluentinc/kafka-javascript");
|
|
3276
|
+
return new KafkaJS.Kafka(buildConfluentKafkaClientOptions(endpoint));
|
|
3277
|
+
}
|
|
3278
|
+
const { Kafka } = await import("kafkajs");
|
|
3279
|
+
return new Kafka(buildKafkaClientOptions(endpoint));
|
|
3280
|
+
}
|
|
3281
|
+
function buildKafkaConsumerOptions(endpoint) {
|
|
3282
|
+
const options = endpoint.kafka ?? {};
|
|
3283
|
+
const groupId = optionString(options, "ConsumerGroupId", "consumerGroupId");
|
|
3284
|
+
const fromBeginning = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
|
|
3285
|
+
if (shouldUseConfluentKafkaClient(endpoint)) {
|
|
3286
|
+
return {
|
|
3287
|
+
"group.id": groupId,
|
|
3288
|
+
"auto.offset.reset": fromBeginning ? "earliest" : "latest",
|
|
3289
|
+
"enable.auto.commit": true
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
return { groupId };
|
|
3293
|
+
}
|
|
3294
|
+
function shouldUseConfluentKafkaClient(endpoint) {
|
|
3295
|
+
const options = endpoint.kafka ?? {};
|
|
3296
|
+
const securityProtocol = (optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext").toLowerCase();
|
|
3297
|
+
if (securityProtocol !== "saslplaintext" && securityProtocol !== "saslssl") {
|
|
3298
|
+
return Object.keys(toStringRecord(pickProtocolValue(options, "ConfluentSettings", "confluentSettings"))).length > 0;
|
|
3299
|
+
}
|
|
3300
|
+
const saslOptions = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
|
|
3301
|
+
const mechanism = (optionString(saslOptions, "Mechanism", "mechanism") || "Plain").toLowerCase();
|
|
3302
|
+
const confluentSettings = toStringRecord(pickProtocolValue(options, "ConfluentSettings", "confluentSettings"));
|
|
3303
|
+
const additionalSettings = toStringRecord(pickProtocolValue(saslOptions, "AdditionalSettings", "additionalSettings"));
|
|
3304
|
+
const hasDirectOAuthToken = mechanism === "oauthbearer" && Boolean(optionString(saslOptions, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken"));
|
|
3305
|
+
return mechanism === "gssapi"
|
|
3306
|
+
|| Object.keys(confluentSettings).length > 0
|
|
3307
|
+
|| (Object.keys(additionalSettings).length > 0 && !hasDirectOAuthToken);
|
|
3308
|
+
}
|
|
3309
|
+
function buildConfluentKafkaClientOptions(endpoint) {
|
|
3310
|
+
const options = endpoint.kafka ?? {};
|
|
3311
|
+
const securityProtocol = optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext";
|
|
3312
|
+
const base = {
|
|
3313
|
+
"bootstrap.servers": optionString(options, "BootstrapServers", "bootstrapServers"),
|
|
3314
|
+
"client.id": `${endpoint.name}-${randomUUID().slice(0, 8)}`,
|
|
3315
|
+
"security.protocol": mapConfluentKafkaSecurityProtocol(securityProtocol)
|
|
3316
|
+
};
|
|
3317
|
+
if (/^sasl/i.test(securityProtocol)) {
|
|
3318
|
+
const saslOptions = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
|
|
3319
|
+
const mechanism = optionString(saslOptions, "Mechanism", "mechanism") || "Plain";
|
|
3320
|
+
const additionalSettings = toStringRecord(pickProtocolValue(saslOptions, "AdditionalSettings", "additionalSettings"));
|
|
3321
|
+
base["sasl.mechanism"] = mapConfluentKafkaSaslMechanism(mechanism);
|
|
3322
|
+
if (mechanism.toLowerCase() === "oauthbearer") {
|
|
3323
|
+
const tokenEndpoint = optionString(saslOptions, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(additionalSettings, "TokenEndpoint", "tokenEndpoint");
|
|
3324
|
+
if (tokenEndpoint) {
|
|
3325
|
+
base["sasl.oauthbearer.method"] = "oidc";
|
|
3326
|
+
base["sasl.oauthbearer.token.endpoint.url"] = tokenEndpoint;
|
|
3327
|
+
}
|
|
3328
|
+
const clientId = optionString(additionalSettings, "ClientId", "clientId");
|
|
3329
|
+
if (clientId) {
|
|
3330
|
+
base["sasl.oauthbearer.client.id"] = clientId;
|
|
3331
|
+
}
|
|
3332
|
+
const clientSecret = optionString(additionalSettings, "ClientSecret", "clientSecret");
|
|
3333
|
+
if (clientSecret) {
|
|
3334
|
+
base["sasl.oauthbearer.client.secret"] = clientSecret;
|
|
3335
|
+
}
|
|
3336
|
+
const scope = optionString(additionalSettings, "Scope", "scope");
|
|
3337
|
+
if (scope) {
|
|
3338
|
+
base["sasl.oauthbearer.scope"] = scope;
|
|
3339
|
+
}
|
|
3340
|
+
const grantType = optionString(additionalSettings, "GrantType", "grantType");
|
|
3341
|
+
if (grantType) {
|
|
3342
|
+
base["sasl.oauthbearer.grant.type"] = grantType;
|
|
3343
|
+
}
|
|
3344
|
+
const extensions = optionString(additionalSettings, "Extensions", "extensions");
|
|
3345
|
+
if (extensions) {
|
|
3346
|
+
base["sasl.oauthbearer.extensions"] = extensions;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
else {
|
|
3350
|
+
base["sasl.username"] = optionString(saslOptions, "Username", "username");
|
|
3351
|
+
base["sasl.password"] = optionString(saslOptions, "Password", "password");
|
|
3352
|
+
}
|
|
3353
|
+
for (const [key, value] of Object.entries(additionalSettings)) {
|
|
3354
|
+
if (key === "ClientId"
|
|
3355
|
+
|| key === "clientId"
|
|
3356
|
+
|| key === "ClientSecret"
|
|
3357
|
+
|| key === "clientSecret"
|
|
3358
|
+
|| key === "Scope"
|
|
3359
|
+
|| key === "scope"
|
|
3360
|
+
|| key === "GrantType"
|
|
3361
|
+
|| key === "grantType"
|
|
3362
|
+
|| key === "Extensions"
|
|
3363
|
+
|| key === "extensions"
|
|
3364
|
+
|| key === "TokenEndpoint"
|
|
3365
|
+
|| key === "tokenEndpoint") {
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
base[key] = value;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
for (const [key, value] of Object.entries(toStringRecord(pickProtocolValue(options, "ConfluentSettings", "confluentSettings")))) {
|
|
3372
|
+
base[key] = value;
|
|
3373
|
+
}
|
|
3374
|
+
return base;
|
|
3375
|
+
}
|
|
3376
|
+
async function resolveKafkaOAuthBearerToken(options) {
|
|
3377
|
+
const directToken = optionString(options, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken");
|
|
3378
|
+
if (directToken) {
|
|
3379
|
+
return directToken;
|
|
3380
|
+
}
|
|
3381
|
+
const additional = asRecordOrEmpty(pickProtocolValue(options, "AdditionalSettings", "additionalSettings"));
|
|
3382
|
+
const tokenEndpoint = optionString(options, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(additional, "TokenEndpoint", "tokenEndpoint");
|
|
3383
|
+
const clientId = optionString(additional, "ClientId", "clientId");
|
|
3384
|
+
const clientSecret = optionString(additional, "ClientSecret", "clientSecret");
|
|
3385
|
+
if (!tokenEndpoint || !clientId || !clientSecret) {
|
|
3386
|
+
throw new Error("Kafka OAuthBearer requires a direct access token or token endpoint with client credentials.");
|
|
3387
|
+
}
|
|
3388
|
+
const form = new URLSearchParams();
|
|
3389
|
+
form.set("grant_type", "client_credentials");
|
|
3390
|
+
form.set("client_id", clientId);
|
|
3391
|
+
form.set("client_secret", clientSecret);
|
|
3392
|
+
const scope = optionString(additional, "Scope", "scope");
|
|
3393
|
+
if (scope) {
|
|
3394
|
+
form.set("scope", scope);
|
|
3395
|
+
}
|
|
3396
|
+
const response = await fetch(tokenEndpoint, {
|
|
3397
|
+
method: "POST",
|
|
3398
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3399
|
+
body: form.toString()
|
|
3400
|
+
});
|
|
3401
|
+
if (!response.ok) {
|
|
3402
|
+
throw new Error(`Kafka OAuthBearer token request failed with status ${response.status}.`);
|
|
3403
|
+
}
|
|
3404
|
+
const payload = parseMaybeJson(await response.text());
|
|
3405
|
+
if (!isRecord(payload) || !payload.access_token) {
|
|
3406
|
+
throw new Error("Kafka OAuthBearer token response did not contain access_token.");
|
|
3407
|
+
}
|
|
3408
|
+
return String(payload.access_token);
|
|
3409
|
+
}
|
|
3410
|
+
function mapKafkaSaslMechanism(mechanism) {
|
|
3411
|
+
switch (mechanism) {
|
|
3412
|
+
case "plain":
|
|
3413
|
+
return "plain";
|
|
3414
|
+
case "scramsha256":
|
|
3415
|
+
case "scram-sha-256":
|
|
3416
|
+
return "scram-sha-256";
|
|
3417
|
+
case "scramsha512":
|
|
3418
|
+
case "scram-sha-512":
|
|
3419
|
+
return "scram-sha-512";
|
|
3420
|
+
default:
|
|
3421
|
+
throw new Error(`Unsupported Kafka SASL mechanism: ${mechanism}.`);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
function mapConfluentKafkaSecurityProtocol(protocol) {
|
|
3425
|
+
switch (protocol.toLowerCase()) {
|
|
3426
|
+
case "plaintext":
|
|
3427
|
+
return "plaintext";
|
|
3428
|
+
case "ssl":
|
|
3429
|
+
return "ssl";
|
|
3430
|
+
case "saslplaintext":
|
|
3431
|
+
case "sasl_plaintext":
|
|
3432
|
+
case "sasl-plaintext":
|
|
3433
|
+
return "sasl_plaintext";
|
|
3434
|
+
case "saslssl":
|
|
3435
|
+
case "sasl_ssl":
|
|
3436
|
+
case "sasl-ssl":
|
|
3437
|
+
return "sasl_ssl";
|
|
3438
|
+
default:
|
|
3439
|
+
throw new Error(`Unsupported Kafka security protocol: ${protocol}.`);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function mapConfluentKafkaSaslMechanism(mechanism) {
|
|
3443
|
+
switch (mechanism.toLowerCase()) {
|
|
3444
|
+
case "plain":
|
|
3445
|
+
return "PLAIN";
|
|
3446
|
+
case "scramsha256":
|
|
3447
|
+
case "scram-sha-256":
|
|
3448
|
+
return "SCRAM-SHA-256";
|
|
3449
|
+
case "scramsha512":
|
|
3450
|
+
case "scram-sha-512":
|
|
3451
|
+
return "SCRAM-SHA-512";
|
|
3452
|
+
case "gssapi":
|
|
3453
|
+
return "GSSAPI";
|
|
3454
|
+
case "oauthbearer":
|
|
3455
|
+
return "OAUTHBEARER";
|
|
3456
|
+
default:
|
|
3457
|
+
throw new Error(`Unsupported Kafka SASL mechanism: ${mechanism}.`);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
function toKafkaHeadersWithContentType(headers, contentType) {
|
|
3461
|
+
const resolvedHeaders = { ...headers };
|
|
3462
|
+
if (contentType) {
|
|
3463
|
+
resolvedHeaders["content-type"] = contentType;
|
|
3464
|
+
}
|
|
3465
|
+
return toKafkaHeaders(resolvedHeaders);
|
|
3466
|
+
}
|
|
3467
|
+
function toKafkaHeaders(headers) {
|
|
3468
|
+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, Buffer.from(String(value ?? ""), "utf8")]));
|
|
3469
|
+
}
|
|
3470
|
+
function fromKafkaHeaders(headers) {
|
|
3471
|
+
const result = {};
|
|
3472
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
3473
|
+
if (value == null) {
|
|
3474
|
+
result[key] = "";
|
|
3475
|
+
continue;
|
|
3476
|
+
}
|
|
3477
|
+
if (Array.isArray(value)) {
|
|
3478
|
+
const last = value[value.length - 1];
|
|
3479
|
+
result[key] = last == null
|
|
3480
|
+
? ""
|
|
3481
|
+
: typeof last === "string"
|
|
3482
|
+
? last
|
|
3483
|
+
: Buffer.from(last).toString("utf8");
|
|
3484
|
+
continue;
|
|
3485
|
+
}
|
|
3486
|
+
if (typeof value === "string") {
|
|
3487
|
+
result[key] = value;
|
|
3488
|
+
continue;
|
|
3489
|
+
}
|
|
3490
|
+
result[key] = Buffer.from(value).toString("utf8");
|
|
3491
|
+
}
|
|
3492
|
+
return result;
|
|
3493
|
+
}
|
|
3494
|
+
function headerValue(headers, expected) {
|
|
3495
|
+
const normalized = expected.toLowerCase();
|
|
3496
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
3497
|
+
if (key.toLowerCase() !== normalized) {
|
|
3498
|
+
continue;
|
|
3499
|
+
}
|
|
3500
|
+
if (Array.isArray(value)) {
|
|
3501
|
+
const last = value[value.length - 1];
|
|
3502
|
+
if (typeof last === "string") {
|
|
3503
|
+
return last;
|
|
3504
|
+
}
|
|
3505
|
+
if (last != null) {
|
|
3506
|
+
return Buffer.from(last).toString("utf8");
|
|
3507
|
+
}
|
|
3508
|
+
return undefined;
|
|
3509
|
+
}
|
|
3510
|
+
if (typeof value === "string") {
|
|
3511
|
+
return value;
|
|
3512
|
+
}
|
|
3513
|
+
if (value != null) {
|
|
3514
|
+
return Buffer.from(value).toString("utf8");
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
return undefined;
|
|
3518
|
+
}
|
|
3519
|
+
async function createNatsHeaders(headers, contentType) {
|
|
3520
|
+
const entries = Object.entries(headers);
|
|
3521
|
+
if (!entries.length && !contentType) {
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
const { headers: createHeaders } = await import("nats");
|
|
3525
|
+
const natsHeaders = createHeaders();
|
|
3526
|
+
for (const [key, value] of entries) {
|
|
3527
|
+
natsHeaders.set(key, value);
|
|
3528
|
+
}
|
|
3529
|
+
if (contentType) {
|
|
3530
|
+
natsHeaders.set("content-type", contentType);
|
|
3531
|
+
}
|
|
3532
|
+
return natsHeaders;
|
|
3533
|
+
}
|
|
3534
|
+
function toHeaderRecord(value) {
|
|
3535
|
+
if (!value) {
|
|
3536
|
+
return {};
|
|
3537
|
+
}
|
|
3538
|
+
if (typeof value.entries === "function") {
|
|
3539
|
+
const result = {};
|
|
3540
|
+
for (const [key, entry] of value.entries()) {
|
|
3541
|
+
if (Array.isArray(entry)) {
|
|
3542
|
+
result[key] = String(entry[0] ?? "");
|
|
3543
|
+
}
|
|
3544
|
+
else {
|
|
3545
|
+
result[key] = String(entry ?? "");
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
return result;
|
|
3549
|
+
}
|
|
3550
|
+
if (typeof value.keys === "function"
|
|
3551
|
+
&& typeof value.get === "function") {
|
|
3552
|
+
const result = {};
|
|
3553
|
+
for (const key of value.keys()) {
|
|
3554
|
+
const entry = value.get(key);
|
|
3555
|
+
if (Array.isArray(entry)) {
|
|
3556
|
+
result[key] = String(entry[0] ?? "");
|
|
3557
|
+
}
|
|
3558
|
+
else {
|
|
3559
|
+
result[key] = String(entry ?? "");
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
return result;
|
|
3563
|
+
}
|
|
3564
|
+
if (typeof value.forEach === "function") {
|
|
3565
|
+
const result = {};
|
|
3566
|
+
value.forEach((entry, key) => {
|
|
3567
|
+
if (Array.isArray(entry)) {
|
|
3568
|
+
result[key] = String(entry[0] ?? "");
|
|
3569
|
+
}
|
|
3570
|
+
else {
|
|
3571
|
+
result[key] = String(entry ?? "");
|
|
3572
|
+
}
|
|
3573
|
+
});
|
|
3574
|
+
return result;
|
|
3575
|
+
}
|
|
3576
|
+
if (isRecord(value)) {
|
|
3577
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
3578
|
+
if (entry instanceof Uint8Array || entry instanceof Buffer) {
|
|
3579
|
+
return [key, Buffer.from(entry).toString("utf8")];
|
|
3580
|
+
}
|
|
3581
|
+
return [key, String(entry ?? "")];
|
|
3582
|
+
}));
|
|
3583
|
+
}
|
|
3584
|
+
return {};
|
|
3585
|
+
}
|
|
3586
|
+
function bufferToUint8Array(value) {
|
|
3587
|
+
if (value instanceof Uint8Array) {
|
|
3588
|
+
return new Uint8Array(value);
|
|
3589
|
+
}
|
|
3590
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
3591
|
+
return Uint8Array.from(value);
|
|
3592
|
+
}
|
|
3593
|
+
if (value instanceof ArrayBuffer) {
|
|
3594
|
+
return new Uint8Array(value);
|
|
3595
|
+
}
|
|
3596
|
+
if (value == null) {
|
|
3597
|
+
return new Uint8Array();
|
|
3598
|
+
}
|
|
3599
|
+
return new TextEncoder().encode(String(value));
|
|
3600
|
+
}
|
|
3601
|
+
function readFirstRedisStreamEntry(value) {
|
|
3602
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
3603
|
+
return null;
|
|
3604
|
+
}
|
|
3605
|
+
const stream = value[0];
|
|
3606
|
+
if (!Array.isArray(stream) || stream.length < 2 || !Array.isArray(stream[1]) || stream[1].length === 0) {
|
|
3607
|
+
return null;
|
|
3608
|
+
}
|
|
3609
|
+
const message = stream[1][0];
|
|
3610
|
+
if (!Array.isArray(message) || message.length < 2 || !Array.isArray(message[1])) {
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
const id = Buffer.from(message[0]).toString("utf8");
|
|
3614
|
+
const fieldEntries = message[1];
|
|
3615
|
+
const fields = {};
|
|
3616
|
+
for (let index = 0; index < fieldEntries.length; index += 2) {
|
|
3617
|
+
const key = Buffer.from(fieldEntries[index]).toString("utf8");
|
|
3618
|
+
const raw = fieldEntries[index + 1];
|
|
3619
|
+
fields[key] = bufferToUint8Array(raw);
|
|
3620
|
+
}
|
|
3621
|
+
return { id, fields };
|
|
3622
|
+
}
|
|
3623
|
+
function createRedisStreamPayload(fields, endpoint) {
|
|
3624
|
+
const headers = {};
|
|
3625
|
+
let body = new Uint8Array(0);
|
|
3626
|
+
let contentType;
|
|
3627
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3628
|
+
if (key.toLowerCase() === "body") {
|
|
3629
|
+
body = bufferToUint8Array(value);
|
|
3630
|
+
continue;
|
|
3631
|
+
}
|
|
3632
|
+
if (key.toLowerCase() === "content-type") {
|
|
3633
|
+
contentType = Buffer.from(value).toString("utf8");
|
|
3634
|
+
continue;
|
|
3635
|
+
}
|
|
3636
|
+
if (key.toLowerCase().startsWith("header:")) {
|
|
3637
|
+
headers[key.slice("header:".length)] = Buffer.from(value).toString("utf8");
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
return createBrokerPayload(headers, body, endpoint, contentType);
|
|
3641
|
+
}
|
|
3642
|
+
function payloadBodyAsUtf8(body) {
|
|
3643
|
+
if (body instanceof Uint8Array) {
|
|
3644
|
+
return new TextDecoder().decode(body);
|
|
3645
|
+
}
|
|
3646
|
+
if (typeof body === "string") {
|
|
3647
|
+
return body;
|
|
3648
|
+
}
|
|
3649
|
+
if (body == null) {
|
|
3650
|
+
return "";
|
|
3651
|
+
}
|
|
3652
|
+
try {
|
|
3653
|
+
return JSON.stringify(body);
|
|
3654
|
+
}
|
|
3655
|
+
catch {
|
|
3656
|
+
return String(body);
|
|
3657
|
+
}
|
|
3658
|
+
}
|