@relaybus/kafka 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -48
- package/dist/index.cjs +595 -443
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -63
- package/dist/index.d.ts +125 -63
- package/dist/index.js +583 -441
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@relaybus/core');
|
|
4
|
+
var internal = require('@relaybus/core/internal');
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
+
// src/errors.ts
|
|
6
7
|
var InvalidKafkaMessageError = class extends core.RelayBusError {
|
|
8
|
+
name = "InvalidKafkaMessageError";
|
|
9
|
+
code = "RELAYBUS_KAFKA_INVALID_MESSAGE";
|
|
7
10
|
};
|
|
8
11
|
var UnencodableEventError = class extends core.NonRetryableError {
|
|
9
12
|
name = "UnencodableEventError";
|
|
13
|
+
code = "RELAYBUS_KAFKA_UNENCODABLE_EVENT";
|
|
14
|
+
};
|
|
15
|
+
var PublishTimeoutError = class extends core.RelayBusError {
|
|
16
|
+
name = "PublishTimeoutError";
|
|
17
|
+
code = "RELAYBUS_KAFKA_PUBLISH_TIMEOUT";
|
|
18
|
+
};
|
|
19
|
+
var TransportClosedError = class extends core.RelayBusError {
|
|
20
|
+
name = "TransportClosedError";
|
|
21
|
+
code = "RELAYBUS_KAFKA_TRANSPORT_CLOSED";
|
|
22
|
+
};
|
|
23
|
+
var GroupJoinTimeoutError = class extends core.RelayBusError {
|
|
24
|
+
name = "GroupJoinTimeoutError";
|
|
25
|
+
code = "RELAYBUS_KAFKA_GROUP_JOIN_TIMEOUT";
|
|
26
|
+
};
|
|
27
|
+
var HandlerFailedError = class extends core.RelayBusError {
|
|
28
|
+
name = "HandlerFailedError";
|
|
29
|
+
code = "RELAYBUS_KAFKA_HANDLER_FAILED";
|
|
10
30
|
};
|
|
11
31
|
var SPEC_VERSION = "1.0";
|
|
12
32
|
var CONTENT_TYPE = "content-type";
|
|
13
33
|
var JSON_MEDIA_TYPE = "application/json";
|
|
34
|
+
var STRUCTURED_MEDIA_TYPE = "application/cloudevents+json";
|
|
14
35
|
var CLOUDEVENTS_PREFIX = "ce_";
|
|
15
36
|
var ESCAPE_PREFIX = "x-relaybus-h-";
|
|
16
37
|
var RESERVED_PREFIXES = [CLOUDEVENTS_PREFIX, "x-relaybus-"];
|
|
@@ -25,6 +46,7 @@ var HEADER = {
|
|
|
25
46
|
causationId: "ce_causationid"
|
|
26
47
|
};
|
|
27
48
|
var PROTOCOL_HEADERS = /* @__PURE__ */ new Set([...Object.values(HEADER), CONTENT_TYPE]);
|
|
49
|
+
var MAXIMUM_RFC3339_YEAR = 9999;
|
|
28
50
|
var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
|
|
29
51
|
var strictUtf8 = new TextDecoder("utf-8", { fatal: true });
|
|
30
52
|
var lenientUtf8 = new TextDecoder("utf-8");
|
|
@@ -52,25 +74,39 @@ function encodeEnvelope(envelope) {
|
|
|
52
74
|
}
|
|
53
75
|
return { key: envelope.key, value, headers: Object.fromEntries(entries) };
|
|
54
76
|
}
|
|
77
|
+
function encodeStructuredEnvelope(envelope) {
|
|
78
|
+
const value = internal.writeStructuredCloudEvent(envelope, {
|
|
79
|
+
time: formatTime(envelope),
|
|
80
|
+
data: serializePayload(envelope)
|
|
81
|
+
});
|
|
82
|
+
const entries = Object.entries(envelope.headers).map(
|
|
83
|
+
([name, headerValue]) => [escapeUserHeaderName(name), headerValue]
|
|
84
|
+
);
|
|
85
|
+
entries.push([CONTENT_TYPE, STRUCTURED_MEDIA_TYPE]);
|
|
86
|
+
return { key: envelope.key, value, headers: Object.fromEntries(entries) };
|
|
87
|
+
}
|
|
55
88
|
function serializePayload(envelope) {
|
|
56
|
-
|
|
89
|
+
return serializeJson(envelope, envelope.payload);
|
|
90
|
+
}
|
|
91
|
+
function serializeJson(envelope, value) {
|
|
92
|
+
let text;
|
|
57
93
|
try {
|
|
58
|
-
|
|
94
|
+
text = JSON.stringify(value);
|
|
59
95
|
} catch (error) {
|
|
60
96
|
throw new UnencodableEventError(
|
|
61
97
|
`Event ${envelope.id} has a payload that JSON cannot represent: ${error.message}`
|
|
62
98
|
);
|
|
63
99
|
}
|
|
64
|
-
if (
|
|
100
|
+
if (text === void 0) {
|
|
65
101
|
throw new UnencodableEventError(
|
|
66
102
|
`Event ${envelope.id} has a payload that JSON cannot represent (undefined, a function or a symbol)`
|
|
67
103
|
);
|
|
68
104
|
}
|
|
69
|
-
return
|
|
105
|
+
return text;
|
|
70
106
|
}
|
|
71
107
|
function formatTime(envelope) {
|
|
72
108
|
const year = envelope.occurredAt.getUTCFullYear();
|
|
73
|
-
if (Number.isNaN(year) || year < 0 || year >
|
|
109
|
+
if (Number.isNaN(year) || year < 0 || year > MAXIMUM_RFC3339_YEAR) {
|
|
74
110
|
throw new UnencodableEventError(
|
|
75
111
|
`Event ${envelope.id} has an occurredAt that is not an RFC 3339 instant (invalid date or year outside 0..9999)`
|
|
76
112
|
);
|
|
@@ -118,11 +154,14 @@ function readHeaders(incoming) {
|
|
|
118
154
|
if (typeof raw !== "string" && !(raw instanceof Uint8Array)) {
|
|
119
155
|
throw invalid(`Header '${name}' is repeated`);
|
|
120
156
|
}
|
|
121
|
-
headers.set(
|
|
157
|
+
headers.set(
|
|
158
|
+
name,
|
|
159
|
+
decodeText(raw, { strict: PROTOCOL_HEADERS.has(name), label: `Header '${name}'` })
|
|
160
|
+
);
|
|
122
161
|
}
|
|
123
162
|
return headers;
|
|
124
163
|
}
|
|
125
|
-
function decodeText(raw, strict, label) {
|
|
164
|
+
function decodeText(raw, { strict, label }) {
|
|
126
165
|
if (typeof raw === "string") {
|
|
127
166
|
return raw;
|
|
128
167
|
}
|
|
@@ -145,19 +184,19 @@ function assertJsonContentType(contentType) {
|
|
|
145
184
|
throw invalid(`${CONTENT_TYPE} must be '${JSON_MEDIA_TYPE}', received '${contentType ?? ""}'`);
|
|
146
185
|
}
|
|
147
186
|
}
|
|
148
|
-
function resolveTime(text,
|
|
187
|
+
function resolveTime(text, recordTimestamp2) {
|
|
149
188
|
if (text !== void 0 && text.length > 0) {
|
|
150
189
|
return parseTime(text);
|
|
151
190
|
}
|
|
152
|
-
const time = readRecordTimestamp(
|
|
191
|
+
const time = readRecordTimestamp(recordTimestamp2);
|
|
153
192
|
if (time === void 0) {
|
|
154
193
|
throw invalid(`${HEADER.time} is missing and the record has no usable timestamp`);
|
|
155
194
|
}
|
|
156
195
|
return time;
|
|
157
196
|
}
|
|
158
|
-
function readRecordTimestamp(
|
|
159
|
-
const time = new Date(Number(
|
|
160
|
-
return
|
|
197
|
+
function readRecordTimestamp(recordTimestamp2) {
|
|
198
|
+
const time = new Date(Number(recordTimestamp2));
|
|
199
|
+
return recordTimestamp2 === void 0 || recordTimestamp2 === "" || Number.isNaN(time.getTime()) ? void 0 : time;
|
|
161
200
|
}
|
|
162
201
|
function parseTime(text, label = HEADER.time) {
|
|
163
202
|
const time = new Date(text);
|
|
@@ -170,7 +209,7 @@ function parsePayload(value) {
|
|
|
170
209
|
if (value === null) {
|
|
171
210
|
throw invalid("Message has no value");
|
|
172
211
|
}
|
|
173
|
-
const text = decodeText(value, true, "Message value");
|
|
212
|
+
const text = decodeText(value, { strict: true, label: "Message value" });
|
|
174
213
|
try {
|
|
175
214
|
return JSON.parse(text);
|
|
176
215
|
} catch {
|
|
@@ -185,7 +224,7 @@ function resolveKey(subject, recordKey) {
|
|
|
185
224
|
return subject ?? keyText;
|
|
186
225
|
}
|
|
187
226
|
function readRecordKey(recordKey) {
|
|
188
|
-
return recordKey === null ? null : decodeText(recordKey, true, "Message key");
|
|
227
|
+
return recordKey === null ? null : decodeText(recordKey, { strict: true, label: "Message key" });
|
|
189
228
|
}
|
|
190
229
|
function collectUserHeaders(headers) {
|
|
191
230
|
const entries = [];
|
|
@@ -214,7 +253,8 @@ function toOutgoingHeaders(incoming) {
|
|
|
214
253
|
function toBufferOrString(value) {
|
|
215
254
|
return typeof value === "string" ? value : Buffer.from(value);
|
|
216
255
|
}
|
|
217
|
-
var
|
|
256
|
+
var { optionalAttribute, requiredAttribute, assertValidType, assertJsonDataContentType } = internal.createCloudEventReaders(invalid);
|
|
257
|
+
var STRUCTURED_MEDIA_TYPE2 = "application/cloudevents+json";
|
|
218
258
|
var exactUtf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
219
259
|
var SPEC_VERSION2 = "1.0";
|
|
220
260
|
var SPEC_VERSION_HEADER = "ce_specversion";
|
|
@@ -234,11 +274,11 @@ var STRUCTURED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
|
234
274
|
]);
|
|
235
275
|
function decodeCloudEvent(record) {
|
|
236
276
|
const headers = readHeaders(record.headers);
|
|
237
|
-
if (mediaTypeOf(headers.get(CONTENT_TYPE)) !==
|
|
277
|
+
if (internal.mediaTypeOf(headers.get(CONTENT_TYPE)) !== STRUCTURED_MEDIA_TYPE2) {
|
|
238
278
|
return decodeEnvelope(record);
|
|
239
279
|
}
|
|
240
280
|
const body = parsePayload(record.value);
|
|
241
|
-
if (!isJsonObject(body)) {
|
|
281
|
+
if (!internal.isJsonObject(body)) {
|
|
242
282
|
throw invalid("A structured CloudEvent must be a JSON object");
|
|
243
283
|
}
|
|
244
284
|
if (body["specversion"] !== SPEC_VERSION2) {
|
|
@@ -269,89 +309,59 @@ function plainJsonDecoder(options) {
|
|
|
269
309
|
}
|
|
270
310
|
return (record) => {
|
|
271
311
|
const headers = readHeadersLeniently(record.headers);
|
|
272
|
-
if (record.headers?.[SPEC_VERSION_HEADER] !== void 0 || mediaTypeOf(headers[CONTENT_TYPE]) ===
|
|
312
|
+
if (record.headers?.[SPEC_VERSION_HEADER] !== void 0 || internal.mediaTypeOf(headers[CONTENT_TYPE]) === STRUCTURED_MEDIA_TYPE2) {
|
|
273
313
|
return decodeCloudEvent(record);
|
|
274
314
|
}
|
|
275
|
-
const plain =
|
|
276
|
-
|
|
277
|
-
partition: record.partition,
|
|
278
|
-
offset: record.offset,
|
|
279
|
-
key: readKeyLeniently(record.key),
|
|
280
|
-
headers,
|
|
281
|
-
payload: parsePayload(record.value),
|
|
282
|
-
timestamp: record.timestamp
|
|
283
|
-
};
|
|
284
|
-
const type = resolveText(options.type, plain, "type");
|
|
315
|
+
const plain = plainRecordOf(record, headers);
|
|
316
|
+
const type = KAFKA_EVENTS.text(options.type, { plain, name: "type" });
|
|
285
317
|
if (type === void 0) {
|
|
286
318
|
throw invalid("Record has no event type: the type option found none");
|
|
287
319
|
}
|
|
288
320
|
assertValidType(type, "The event type");
|
|
289
321
|
return {
|
|
290
|
-
id:
|
|
322
|
+
id: KAFKA_EVENTS.text(options.id, { plain, name: "id" }) ?? `kafka:${record.topic}:${record.partition}:${record.offset}`,
|
|
291
323
|
type,
|
|
292
|
-
source:
|
|
293
|
-
key: options.key === void 0 ? plain.key :
|
|
324
|
+
source: KAFKA_EVENTS.text(options.source, { plain, name: "source" }) ?? record.topic,
|
|
325
|
+
key: options.key === void 0 ? plain.key : KAFKA_EVENTS.text(options.key, { plain, name: "key" }) ?? null,
|
|
294
326
|
payload: plain.payload,
|
|
295
327
|
headers,
|
|
296
328
|
correlationId: null,
|
|
297
329
|
causationId: null,
|
|
298
|
-
occurredAt: resolveOccurredAt(options.occurredAt, plain, record)
|
|
330
|
+
occurredAt: resolveOccurredAt(options.occurredAt, { plain, record })
|
|
299
331
|
};
|
|
300
332
|
};
|
|
301
333
|
}
|
|
302
|
-
function
|
|
303
|
-
if (typeof decoded !== "object" || decoded === null) {
|
|
304
|
-
throw invalid("The decoder must return an event object synchronously");
|
|
305
|
-
}
|
|
306
|
-
if ("then" in decoded) {
|
|
307
|
-
observeRejection(decoded);
|
|
308
|
-
throw invalid("The decoder must return an event object synchronously");
|
|
309
|
-
}
|
|
310
|
-
const event = decoded;
|
|
334
|
+
function plainRecordOf(record, headers) {
|
|
311
335
|
return {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
key:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
causationId: event.causationId ?? null,
|
|
320
|
-
occurredAt: event.occurredAt ?? requireRecordTimestamp(record)
|
|
336
|
+
topic: record.topic,
|
|
337
|
+
partition: record.partition,
|
|
338
|
+
offset: record.offset,
|
|
339
|
+
key: readKeyLeniently(record.key),
|
|
340
|
+
headers,
|
|
341
|
+
payload: parsePayload(record.value),
|
|
342
|
+
timestamp: record.timestamp
|
|
321
343
|
};
|
|
322
344
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
function isJsonObject(value) {
|
|
327
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
328
|
-
}
|
|
329
|
-
function optionalAttribute(body, name) {
|
|
330
|
-
const value = body[name];
|
|
331
|
-
if (value !== void 0 && typeof value !== "string") {
|
|
332
|
-
throw invalid(`Attribute '${name}' must be a string`);
|
|
345
|
+
var KafkaDecodedEvents = class extends internal.DecodedEventReader {
|
|
346
|
+
invalid(reason) {
|
|
347
|
+
return invalid(reason);
|
|
333
348
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const value = optionalAttribute(body, name);
|
|
338
|
-
if (value === void 0 || value.length === 0) {
|
|
339
|
-
throw invalid(`Attribute '${name}' is missing or empty`);
|
|
349
|
+
/** The topic. */
|
|
350
|
+
defaultSource(record) {
|
|
351
|
+
return record.topic;
|
|
340
352
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
core.assertEventType(type);
|
|
346
|
-
} catch (error) {
|
|
347
|
-
throw invalid(`${label} is not a valid event type: ${error.message}`);
|
|
353
|
+
/** The record key as UTF-8 text. */
|
|
354
|
+
defaultKey(record) {
|
|
355
|
+
return readRecordKey(record.key);
|
|
348
356
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (mediaType !== void 0 && mediaType !== "application/json" && mediaType !== "text/json" && !mediaType.endsWith("+json")) {
|
|
353
|
-
throw invalid(`datacontenttype must be JSON, received '${dataContentType}'`);
|
|
357
|
+
/** The record timestamp; a record without a usable one is malformed. */
|
|
358
|
+
defaultTime(record) {
|
|
359
|
+
return requireRecordTimestamp(record);
|
|
354
360
|
}
|
|
361
|
+
};
|
|
362
|
+
var KAFKA_EVENTS = new KafkaDecodedEvents();
|
|
363
|
+
function completeDecodedEvent(decoded, record) {
|
|
364
|
+
return KAFKA_EVENTS.complete(decoded, record);
|
|
355
365
|
}
|
|
356
366
|
function extensionAttributes(body) {
|
|
357
367
|
return Object.fromEntries(
|
|
@@ -398,40 +408,216 @@ function readKeyLeniently(key) {
|
|
|
398
408
|
return Buffer.from(key).toString("base64");
|
|
399
409
|
}
|
|
400
410
|
}
|
|
401
|
-
function
|
|
402
|
-
const value = typeof option === "function" ? option(record) : option;
|
|
403
|
-
if (value === void 0 || value === null) {
|
|
404
|
-
return void 0;
|
|
405
|
-
}
|
|
406
|
-
if (typeof value !== "string") {
|
|
407
|
-
observeRejection(value);
|
|
408
|
-
throw invalid(`The ${name} option returned ${typeof value}, expected a string`);
|
|
409
|
-
}
|
|
410
|
-
return value.length === 0 ? void 0 : value;
|
|
411
|
-
}
|
|
412
|
-
function resolveOccurredAt(option, plain, record) {
|
|
411
|
+
function resolveOccurredAt(option, { plain, record }) {
|
|
413
412
|
const value = option?.(plain);
|
|
414
413
|
if (value === void 0) {
|
|
415
414
|
return requireRecordTimestamp(record);
|
|
416
415
|
}
|
|
417
416
|
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
418
|
-
observeRejection(value);
|
|
417
|
+
internal.observeRejection(value);
|
|
419
418
|
throw invalid("The occurredAt option returned no valid Date");
|
|
420
419
|
}
|
|
421
420
|
return value;
|
|
422
421
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
422
|
+
|
|
423
|
+
// src/codecs.ts
|
|
424
|
+
var cloudEventsBinary = Object.freeze({
|
|
425
|
+
encode: encodeEnvelope,
|
|
426
|
+
decode: decodeEnvelope
|
|
427
|
+
});
|
|
428
|
+
var cloudEventsStructured = Object.freeze({
|
|
429
|
+
encode: encodeStructuredEnvelope,
|
|
430
|
+
decode: decodeCloudEvent
|
|
431
|
+
});
|
|
432
|
+
var RECORD_POSITION = "record-position";
|
|
433
|
+
var EVENT_ID_HEADER = "relaybus-event-id";
|
|
434
|
+
var DEFAULT_CONTENT_TYPE = "application/json";
|
|
435
|
+
function jsonEnvelope(mapping) {
|
|
436
|
+
const format = internal.createJsonEnvelopeFormat(mapping, {
|
|
437
|
+
transportIds: [RECORD_POSITION],
|
|
438
|
+
failures: { invalid, unencodable: (reason) => new UnencodableEventError(reason) }
|
|
439
|
+
});
|
|
440
|
+
const contentType = mapping.contentType ?? DEFAULT_CONTENT_TYPE;
|
|
441
|
+
const stampsTime = mapping.occurredAt === void 0;
|
|
442
|
+
const ownHeaders = (envelope) => mapping.id === RECORD_POSITION ? { [CONTENT_TYPE]: contentType, [EVENT_ID_HEADER]: envelope.id } : { [CONTENT_TYPE]: contentType };
|
|
443
|
+
return {
|
|
444
|
+
encode: (envelope) => encodeJson(envelope, { format, stampsTime, ownHeaders }),
|
|
445
|
+
decode: (record) => decodeJson(record, format)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function encodeJson(envelope, { format, stampsTime, ownHeaders }) {
|
|
449
|
+
assertRepresentable(envelope);
|
|
450
|
+
const { body, headers } = format.write(envelope);
|
|
451
|
+
const taken = [CONTENT_TYPE, EVENT_ID_HEADER].find((name) => Object.hasOwn(headers, name));
|
|
452
|
+
if (taken !== void 0) {
|
|
453
|
+
throw new UnencodableEventError(
|
|
454
|
+
`Event ${envelope.id} has a header '${taken}', which the codec writes itself`
|
|
455
|
+
);
|
|
426
456
|
}
|
|
457
|
+
return {
|
|
458
|
+
key: envelope.key,
|
|
459
|
+
value: serializeJson(envelope, body),
|
|
460
|
+
headers: { ...headers, ...ownHeaders(envelope) },
|
|
461
|
+
...stampsTime ? { timestamp: recordTimestamp(envelope) } : {}
|
|
462
|
+
};
|
|
427
463
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
464
|
+
function decodeJson(record, format) {
|
|
465
|
+
const { [EVENT_ID_HEADER]: eventId, ...headers } = withoutContentType(
|
|
466
|
+
readHeadersLeniently(record.headers)
|
|
467
|
+
);
|
|
468
|
+
const fields = format.read({ body: parsePayload(record.value), headers });
|
|
469
|
+
return {
|
|
470
|
+
...fields,
|
|
471
|
+
id: fields.id ?? (eventId === void 0 || eventId === "" ? `kafka:${record.topic}:${record.partition}:${record.offset}` : eventId)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function withoutContentType(headers) {
|
|
475
|
+
return Object.fromEntries(Object.entries(headers).filter(([name]) => name !== CONTENT_TYPE));
|
|
476
|
+
}
|
|
477
|
+
function assertRepresentable(envelope) {
|
|
478
|
+
const kind = typeof envelope.payload;
|
|
479
|
+
if (kind === "undefined" || kind === "function" || kind === "symbol") {
|
|
480
|
+
throw new UnencodableEventError(
|
|
481
|
+
`Event ${envelope.id} has a payload that JSON cannot represent (undefined, a function or a symbol)`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function recordTimestamp(envelope) {
|
|
486
|
+
formatTime(envelope);
|
|
487
|
+
return String(envelope.occurredAt.getTime());
|
|
488
|
+
}
|
|
489
|
+
function tracksMembership(consumer) {
|
|
490
|
+
return consumer.on !== void 0 && consumer.events?.GROUP_JOIN !== void 0;
|
|
491
|
+
}
|
|
492
|
+
var GroupJoinWatch = class {
|
|
493
|
+
#settings;
|
|
494
|
+
#waiter;
|
|
495
|
+
constructor(settings) {
|
|
496
|
+
this.#settings = settings;
|
|
497
|
+
}
|
|
498
|
+
/** The consumer joined its group. */
|
|
499
|
+
joined() {
|
|
500
|
+
this.#waiter?.resolve();
|
|
501
|
+
}
|
|
502
|
+
/** The consumer crashed while it was joining. */
|
|
503
|
+
failed(error) {
|
|
504
|
+
this.#waiter?.reject(error);
|
|
505
|
+
}
|
|
506
|
+
/** The transport closed while the consumer was joining. */
|
|
507
|
+
closed() {
|
|
508
|
+
this.#waiter?.reject(new TransportClosedError(`Transport '${this.#settings.name}' is closed`));
|
|
509
|
+
}
|
|
510
|
+
/** Connects and subscribes the consumer, runs it and resolves once it joined its group. */
|
|
511
|
+
async start(consumer, {
|
|
512
|
+
topics,
|
|
513
|
+
fromBeginning,
|
|
514
|
+
eachMessage
|
|
515
|
+
}) {
|
|
516
|
+
await consumer.connect();
|
|
517
|
+
await consumer.subscribe({ topics, fromBeginning });
|
|
518
|
+
const joined = this.#wait(consumer);
|
|
519
|
+
try {
|
|
520
|
+
await consumer.run({ eachMessage });
|
|
521
|
+
await joined.promise;
|
|
522
|
+
} finally {
|
|
523
|
+
joined.dispose();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
#wait(consumer) {
|
|
527
|
+
if (!tracksMembership(consumer)) {
|
|
528
|
+
return { promise: Promise.resolve(), dispose: () => void 0 };
|
|
529
|
+
}
|
|
530
|
+
const { name, groupId, timings } = this.#settings;
|
|
531
|
+
let cancel;
|
|
532
|
+
const promise = new Promise((resolve, reject) => {
|
|
533
|
+
this.#waiter = { resolve, reject };
|
|
534
|
+
cancel = internal.schedule(core.systemClock, {
|
|
535
|
+
run: () => {
|
|
536
|
+
reject(
|
|
537
|
+
new GroupJoinTimeoutError(
|
|
538
|
+
`Consumer of transport '${name}' did not join group '${groupId}' within ${timings.startupTimeoutMs}ms`
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
},
|
|
542
|
+
delayMs: timings.startupTimeoutMs
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
promise.catch(() => void 0);
|
|
546
|
+
return {
|
|
547
|
+
promise,
|
|
548
|
+
dispose: () => {
|
|
549
|
+
cancel?.();
|
|
550
|
+
this.#waiter = void 0;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
432
554
|
};
|
|
433
|
-
var
|
|
434
|
-
|
|
555
|
+
var KafkaProducerSession = class {
|
|
556
|
+
#settings;
|
|
557
|
+
/** Whether the transport is closed: then nothing connects any more. */
|
|
558
|
+
#isClosed;
|
|
559
|
+
#producer;
|
|
560
|
+
#connected = false;
|
|
561
|
+
constructor(settings, isClosed) {
|
|
562
|
+
this.#settings = settings;
|
|
563
|
+
this.#isClosed = isClosed;
|
|
564
|
+
}
|
|
565
|
+
/** True once the producer connected, until the transport closes. */
|
|
566
|
+
get isConnected() {
|
|
567
|
+
return this.#connected;
|
|
568
|
+
}
|
|
569
|
+
/** The producer opened so far, if any: what closing must disconnect. */
|
|
570
|
+
get pending() {
|
|
571
|
+
return this.#producer;
|
|
572
|
+
}
|
|
573
|
+
markClosed() {
|
|
574
|
+
this.#connected = false;
|
|
575
|
+
}
|
|
576
|
+
connected() {
|
|
577
|
+
if (this.#isClosed()) {
|
|
578
|
+
return Promise.reject(
|
|
579
|
+
new TransportClosedError(`Transport '${this.#settings.name}' is closed`)
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
this.#producer ??= this.#open();
|
|
583
|
+
return this.#producer;
|
|
584
|
+
}
|
|
585
|
+
/** Sends with the publish timeout; the late outcome of a timed-out send is ignored. */
|
|
586
|
+
async send(producer, record) {
|
|
587
|
+
const { name, timings } = this.#settings;
|
|
588
|
+
let cancel;
|
|
589
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
590
|
+
cancel = internal.schedule(core.systemClock, {
|
|
591
|
+
run: () => {
|
|
592
|
+
reject(
|
|
593
|
+
new PublishTimeoutError(
|
|
594
|
+
`Transport '${name}' got no broker ack for topic '${record.topic}' within ${timings.publishTimeoutMs}ms`
|
|
595
|
+
)
|
|
596
|
+
);
|
|
597
|
+
},
|
|
598
|
+
delayMs: timings.publishTimeoutMs
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
const sending = producer.send(record);
|
|
602
|
+
sending.catch(() => void 0);
|
|
603
|
+
try {
|
|
604
|
+
await Promise.race([sending, timeout]);
|
|
605
|
+
} finally {
|
|
606
|
+
cancel?.();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async #open() {
|
|
610
|
+
const { kafka, producerConfig, maxInFlightRequests } = this.#settings;
|
|
611
|
+
const producer = kafka.producer({ ...producerConfig, idempotent: true, maxInFlightRequests });
|
|
612
|
+
try {
|
|
613
|
+
await producer.connect();
|
|
614
|
+
} catch (error) {
|
|
615
|
+
this.#producer = void 0;
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
this.#connected = !this.#isClosed();
|
|
619
|
+
return producer;
|
|
620
|
+
}
|
|
435
621
|
};
|
|
436
622
|
var DEFAULTS = {
|
|
437
623
|
name: "kafka",
|
|
@@ -445,46 +631,224 @@ var DEFAULTS = {
|
|
|
445
631
|
restartInitialBackoff: "1s",
|
|
446
632
|
restartMaxBackoff: "30s"
|
|
447
633
|
};
|
|
448
|
-
var
|
|
634
|
+
var MAXIMUM_IN_FLIGHT_REQUESTS = 5;
|
|
635
|
+
function resolveKafkaSettings(options) {
|
|
636
|
+
return {
|
|
637
|
+
name: options.name ?? DEFAULTS.name,
|
|
638
|
+
kafka: options.kafka,
|
|
639
|
+
groupId: options.groupId,
|
|
640
|
+
fromBeginning: options.fromBeginning ?? true,
|
|
641
|
+
maxInFlightRequests: internal.BoundedInteger.of(
|
|
642
|
+
options.maxInFlightRequests ?? DEFAULTS.maxInFlightRequests,
|
|
643
|
+
{ name: "maxInFlightRequests", minimum: 1, maximum: MAXIMUM_IN_FLIGHT_REQUESTS }
|
|
644
|
+
),
|
|
645
|
+
producerConfig: options.producerConfig ?? {},
|
|
646
|
+
consumerConfig: options.consumerConfig ?? {},
|
|
647
|
+
invalidPolicy: options.onInvalidMessage ?? "dead-letter",
|
|
648
|
+
codec: resolveCodec(options),
|
|
649
|
+
logger: options.logger ?? console,
|
|
650
|
+
timings: resolveTimings(options)
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function resolveCodec({ codec, decode }) {
|
|
654
|
+
if (decode === void 0) {
|
|
655
|
+
return codec ?? cloudEventsBinary;
|
|
656
|
+
}
|
|
657
|
+
if (codec !== void 0) {
|
|
658
|
+
throw new core.ConfigurationError("Give 'codec' or the deprecated 'decode', not both");
|
|
659
|
+
}
|
|
660
|
+
return { encode: encodeEnvelope, decode };
|
|
661
|
+
}
|
|
662
|
+
function resolveTimings(options) {
|
|
663
|
+
const processing = options.processing ?? {};
|
|
664
|
+
const maxAttempts = internal.PositiveInteger.of(
|
|
665
|
+
processing.maxAttempts ?? DEFAULTS.maxAttempts,
|
|
666
|
+
"processing.maxAttempts"
|
|
667
|
+
);
|
|
668
|
+
return {
|
|
669
|
+
maxAttempts,
|
|
670
|
+
processingBackoff: new internal.ExponentialBackoff({
|
|
671
|
+
initialMs: internal.Milliseconds.of(
|
|
672
|
+
processing.initialBackoff ?? DEFAULTS.initialBackoff,
|
|
673
|
+
"processing.initialBackoff"
|
|
674
|
+
),
|
|
675
|
+
maxMs: internal.Milliseconds.of(processing.maxBackoff ?? DEFAULTS.maxBackoff, "processing.maxBackoff")
|
|
676
|
+
}),
|
|
677
|
+
startupTimeoutMs: internal.Milliseconds.of(
|
|
678
|
+
options.startupTimeout ?? DEFAULTS.startupTimeout,
|
|
679
|
+
"startupTimeout"
|
|
680
|
+
),
|
|
681
|
+
publishTimeoutMs: internal.Milliseconds.of(
|
|
682
|
+
options.publishTimeout ?? DEFAULTS.publishTimeout,
|
|
683
|
+
"publishTimeout"
|
|
684
|
+
),
|
|
685
|
+
heartbeatIntervalMs: Math.max(
|
|
686
|
+
1,
|
|
687
|
+
internal.Milliseconds.of(
|
|
688
|
+
processing.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
|
|
689
|
+
"processing.heartbeatInterval"
|
|
690
|
+
)
|
|
691
|
+
),
|
|
692
|
+
restartBackoff: resolveRestartBackoff(options.restart ?? {})
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function resolveRestartBackoff(restart) {
|
|
696
|
+
return new internal.ExponentialBackoff({
|
|
697
|
+
initialMs: internal.Milliseconds.of(
|
|
698
|
+
restart.initialBackoff ?? DEFAULTS.restartInitialBackoff,
|
|
699
|
+
"restart.initialBackoff"
|
|
700
|
+
),
|
|
701
|
+
maxMs: internal.Milliseconds.of(restart.maxBackoff ?? DEFAULTS.restartMaxBackoff, "restart.maxBackoff")
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function deadLetterTopicFor(policy, sourceTopic) {
|
|
705
|
+
if (policy === "dead-letter") {
|
|
706
|
+
return `${sourceTopic}.dead`;
|
|
707
|
+
}
|
|
708
|
+
if (typeof policy === "object") {
|
|
709
|
+
const { deadLetterTopic } = policy;
|
|
710
|
+
return typeof deadLetterTopic === "function" ? deadLetterTopic(sourceTopic) : deadLetterTopic;
|
|
711
|
+
}
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
function toHandlerFailure(error) {
|
|
715
|
+
const { name, message } = core.toErrorInfo(error);
|
|
716
|
+
return new HandlerFailedError(`${name}: ${message}`);
|
|
717
|
+
}
|
|
718
|
+
function assertCanSubscribe(state, {
|
|
719
|
+
settings,
|
|
720
|
+
destinations
|
|
721
|
+
}) {
|
|
722
|
+
const { name, groupId, invalidPolicy } = settings;
|
|
723
|
+
if (state === "closed") {
|
|
724
|
+
throw new TransportClosedError(`Transport '${name}' is closed`);
|
|
725
|
+
}
|
|
726
|
+
if (state === "subscribed") {
|
|
727
|
+
throw new core.ConfigurationError(
|
|
728
|
+
`Transport '${name}' already subscribed; list every topic in one subscription or use one KafkaTransport per consumer group`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
if (groupId === void 0 || groupId.length === 0) {
|
|
732
|
+
throw new core.ConfigurationError(`Transport '${name}' needs a groupId to subscribe`);
|
|
733
|
+
}
|
|
734
|
+
if (destinations.length === 0) {
|
|
735
|
+
throw new core.ConfigurationError(`Transport '${name}' subscribed to no topics`);
|
|
736
|
+
}
|
|
737
|
+
for (const topic of destinations) {
|
|
738
|
+
const deadLetterTopic = deadLetterTopicFor(invalidPolicy, topic);
|
|
739
|
+
if (deadLetterTopic !== void 0 && destinations.includes(deadLetterTopic)) {
|
|
740
|
+
throw new core.ConfigurationError(
|
|
741
|
+
`Dead letter topic '${deadLetterTopic}' is also consumed, which would loop`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/kafka-records.ts
|
|
748
|
+
function decodeRecord(codec, { topic, partition, message }) {
|
|
749
|
+
if (codec === cloudEventsBinary) {
|
|
750
|
+
return decodeEnvelope(message);
|
|
751
|
+
}
|
|
752
|
+
const record = {
|
|
753
|
+
topic,
|
|
754
|
+
partition,
|
|
755
|
+
offset: message.offset,
|
|
756
|
+
key: message.key,
|
|
757
|
+
value: message.value,
|
|
758
|
+
headers: message.headers,
|
|
759
|
+
timestamp: message.timestamp
|
|
760
|
+
};
|
|
761
|
+
return completeDecodedEvent(codec.decode(record), record);
|
|
762
|
+
}
|
|
763
|
+
function deadLetterRecord({ topic, partition, message }, {
|
|
764
|
+
reason,
|
|
765
|
+
name,
|
|
766
|
+
code,
|
|
767
|
+
deadLetterTopic
|
|
768
|
+
}) {
|
|
769
|
+
const headers = {
|
|
770
|
+
...toOutgoingHeaders(message.headers),
|
|
771
|
+
"relaybus-invalid-reason": reason,
|
|
772
|
+
"relaybus-source-topic": topic,
|
|
773
|
+
"relaybus-source-partition": String(partition),
|
|
774
|
+
"relaybus-source-offset": message.offset,
|
|
775
|
+
"relaybus-error-name": name
|
|
776
|
+
};
|
|
777
|
+
if (code === void 0) {
|
|
778
|
+
delete headers["relaybus-error-code"];
|
|
779
|
+
} else {
|
|
780
|
+
headers["relaybus-error-code"] = code;
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
topic: deadLetterTopic,
|
|
784
|
+
acks: -1,
|
|
785
|
+
messages: [
|
|
786
|
+
{
|
|
787
|
+
key: message.key === null ? null : Buffer.from(message.key),
|
|
788
|
+
value: message.value === null ? null : Buffer.from(message.value),
|
|
789
|
+
headers
|
|
790
|
+
}
|
|
791
|
+
]
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
var WakeableSleeps = class {
|
|
795
|
+
#sleepers = /* @__PURE__ */ new Set();
|
|
796
|
+
sleep(milliseconds) {
|
|
797
|
+
return new Promise((resolve) => {
|
|
798
|
+
const wake = () => {
|
|
799
|
+
cancel();
|
|
800
|
+
this.#sleepers.delete(wake);
|
|
801
|
+
resolve();
|
|
802
|
+
};
|
|
803
|
+
const cancel = internal.schedule(core.systemClock, { run: wake, delayMs: milliseconds });
|
|
804
|
+
this.#sleepers.add(wake);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
wakeAll() {
|
|
808
|
+
for (const wake of this.#sleepers) {
|
|
809
|
+
wake();
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Sleeps `milliseconds` in slices of at most `sliceMs`, running `between` after each, until done
|
|
814
|
+
* or `stopped`: a long backoff then never outlives the session timeout.
|
|
815
|
+
*/
|
|
816
|
+
async sleepInSlices(milliseconds, {
|
|
817
|
+
sliceMs,
|
|
818
|
+
between,
|
|
819
|
+
stopped
|
|
820
|
+
}) {
|
|
821
|
+
let remaining = milliseconds;
|
|
822
|
+
do {
|
|
823
|
+
const slice = Math.min(remaining, sliceMs);
|
|
824
|
+
await this.sleep(slice);
|
|
825
|
+
await between();
|
|
826
|
+
remaining -= slice;
|
|
827
|
+
} while (remaining > 0 && !stopped());
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
// src/kafka-transport.ts
|
|
832
|
+
var KafkaTransport = class extends internal.SubscribingTransport {
|
|
449
833
|
name;
|
|
450
|
-
|
|
451
|
-
#groupId;
|
|
452
|
-
#fromBeginning;
|
|
453
|
-
#maxInFlightRequests;
|
|
454
|
-
#producerConfig;
|
|
455
|
-
#consumerConfig;
|
|
456
|
-
#invalidPolicy;
|
|
457
|
-
#decode;
|
|
458
|
-
#logger;
|
|
834
|
+
system = "kafka";
|
|
459
835
|
#settings;
|
|
460
|
-
#sleepers = /* @__PURE__ */ new Set();
|
|
461
|
-
#state = "idle";
|
|
462
836
|
#producer;
|
|
837
|
+
#groupJoin;
|
|
838
|
+
#sleeps = new WakeableSleeps();
|
|
839
|
+
#state = "idle";
|
|
463
840
|
#consumer;
|
|
464
841
|
#groupJoined = false;
|
|
465
842
|
#consuming = false;
|
|
466
|
-
#producerConnected = false;
|
|
467
843
|
#restartLoop;
|
|
468
844
|
#stalledPartitions = /* @__PURE__ */ new Set();
|
|
469
845
|
#startConsuming;
|
|
470
|
-
#joinWaiter;
|
|
471
846
|
constructor(options) {
|
|
472
|
-
|
|
473
|
-
this.#
|
|
474
|
-
this
|
|
475
|
-
this.#
|
|
476
|
-
this.#
|
|
477
|
-
if (!Number.isInteger(this.#maxInFlightRequests) || this.#maxInFlightRequests < 1 || this.#maxInFlightRequests > 5) {
|
|
478
|
-
throw new core.ConfigurationError(
|
|
479
|
-
`maxInFlightRequests must be an integer from 1 to 5, received ${this.#maxInFlightRequests}`
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
this.#producerConfig = options.producerConfig ?? {};
|
|
483
|
-
this.#consumerConfig = options.consumerConfig ?? {};
|
|
484
|
-
this.#invalidPolicy = options.onInvalidMessage ?? "dead-letter";
|
|
485
|
-
this.#decode = options.decode;
|
|
486
|
-
this.#logger = options.logger ?? console;
|
|
487
|
-
this.#settings = resolveSettings(options);
|
|
847
|
+
super();
|
|
848
|
+
this.#settings = resolveKafkaSettings(options);
|
|
849
|
+
this.name = this.#settings.name;
|
|
850
|
+
this.#producer = new KafkaProducerSession(this.#settings, () => this.#state === "closed");
|
|
851
|
+
this.#groupJoin = new GroupJoinWatch(this.#settings);
|
|
488
852
|
}
|
|
489
853
|
/**
|
|
490
854
|
* True while the consumer is joined to its group, not between a crash or
|
|
@@ -501,22 +865,21 @@ var KafkaTransport = class {
|
|
|
501
865
|
* dead consumer is being restarted.
|
|
502
866
|
*/
|
|
503
867
|
isReady() {
|
|
504
|
-
if (this.#state === "closed" || !this.#
|
|
868
|
+
if (this.#state === "closed" || !this.#producer.isConnected) {
|
|
505
869
|
return false;
|
|
506
870
|
}
|
|
507
871
|
return this.#state === "idle" || this.isConsuming();
|
|
508
872
|
}
|
|
509
873
|
/** Connects the producer. Called by the bus on start; `publish` also connects on demand. */
|
|
510
874
|
async connect() {
|
|
511
|
-
await this.#
|
|
875
|
+
await this.#producer.connected();
|
|
512
876
|
}
|
|
513
877
|
async publish(envelope, destination) {
|
|
514
|
-
const producer = await this.#
|
|
515
|
-
|
|
516
|
-
await this.#send(producer, {
|
|
878
|
+
const producer = await this.#producer.connected();
|
|
879
|
+
await this.#producer.send(producer, {
|
|
517
880
|
topic: destination,
|
|
518
881
|
acks: -1,
|
|
519
|
-
messages: [
|
|
882
|
+
messages: [this.#settings.codec.encode(envelope)]
|
|
520
883
|
});
|
|
521
884
|
}
|
|
522
885
|
/**
|
|
@@ -526,13 +889,21 @@ var KafkaTransport = class {
|
|
|
526
889
|
* @throws ConfigurationError when `groupId` is missing, `destinations` is empty
|
|
527
890
|
* or the transport already subscribed (create one transport per subscription).
|
|
528
891
|
*/
|
|
529
|
-
|
|
530
|
-
this.#
|
|
892
|
+
openSubscription(subscription) {
|
|
893
|
+
return this.#subscribe(subscription);
|
|
894
|
+
}
|
|
895
|
+
async #subscribe({ destinations, onMessage, hooks }) {
|
|
896
|
+
assertCanSubscribe(this.#state, { settings: this.#settings, destinations });
|
|
531
897
|
this.#state = "subscribed";
|
|
532
|
-
const
|
|
898
|
+
const { kafka, consumerConfig, groupId } = this.#settings;
|
|
899
|
+
const consumer = kafka.consumer({ ...consumerConfig, groupId });
|
|
533
900
|
this.#consumer = consumer;
|
|
534
901
|
this.#observeConsumer(consumer);
|
|
535
|
-
const start = () => this.#
|
|
902
|
+
const start = () => this.#groupJoin.start(consumer, {
|
|
903
|
+
topics: [...destinations],
|
|
904
|
+
fromBeginning: this.#settings.fromBeginning,
|
|
905
|
+
eachMessage: (payload) => this.#handleMessage(payload, { onMessage, hooks })
|
|
906
|
+
});
|
|
536
907
|
this.#startConsuming = start;
|
|
537
908
|
try {
|
|
538
909
|
await start();
|
|
@@ -544,34 +915,21 @@ var KafkaTransport = class {
|
|
|
544
915
|
throw error;
|
|
545
916
|
}
|
|
546
917
|
this.#consuming = true;
|
|
547
|
-
this.#logger.log(
|
|
548
|
-
`[KafkaTransport](subscribe) Consuming | transport=${this.name} group=${
|
|
918
|
+
this.#settings.logger.log(
|
|
919
|
+
`[KafkaTransport](subscribe) Consuming | transport=${this.name} group=${groupId} topics=${destinations.join(",")}`
|
|
549
920
|
);
|
|
550
921
|
}
|
|
551
|
-
async #startConsumer(consumer, destinations, onMessage) {
|
|
552
|
-
await consumer.connect();
|
|
553
|
-
await consumer.subscribe({ topics: destinations, fromBeginning: this.#fromBeginning });
|
|
554
|
-
const joined = this.#waitForGroupJoin(consumer);
|
|
555
|
-
try {
|
|
556
|
-
await consumer.run({ eachMessage: (payload) => this.#handleMessage(payload, onMessage) });
|
|
557
|
-
await joined.promise;
|
|
558
|
-
} finally {
|
|
559
|
-
joined.dispose();
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
922
|
/** Stops consuming and disconnects the consumer and the producer. Safe to call twice. */
|
|
563
923
|
async close() {
|
|
564
924
|
if (this.#state === "closed") {
|
|
565
925
|
return;
|
|
566
926
|
}
|
|
567
927
|
this.#state = "closed";
|
|
568
|
-
const pendingProducer = this.#producer;
|
|
928
|
+
const pendingProducer = this.#producer.pending;
|
|
569
929
|
this.#groupJoined = false;
|
|
570
|
-
this.#
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
this.#joinWaiter?.reject(new core.RelayBusError(`Transport '${this.name}' is closed`));
|
|
930
|
+
this.#producer.markClosed();
|
|
931
|
+
this.#sleeps.wakeAll();
|
|
932
|
+
this.#groupJoin.closed();
|
|
575
933
|
await this.#restartLoop;
|
|
576
934
|
const producer = await pendingProducer?.catch(() => void 0);
|
|
577
935
|
const results = await Promise.allSettled([
|
|
@@ -583,130 +941,45 @@ var KafkaTransport = class {
|
|
|
583
941
|
throw failure.reason;
|
|
584
942
|
}
|
|
585
943
|
}
|
|
586
|
-
#
|
|
587
|
-
if (this.#state === "closed") {
|
|
588
|
-
throw new core.RelayBusError(`Transport '${this.name}' is closed`);
|
|
589
|
-
}
|
|
590
|
-
if (this.#state === "subscribed") {
|
|
591
|
-
throw new core.ConfigurationError(
|
|
592
|
-
`Transport '${this.name}' already subscribed; list every topic in one subscription or use one KafkaTransport per consumer group`
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
if (this.#groupId === void 0 || this.#groupId.length === 0) {
|
|
596
|
-
throw new core.ConfigurationError(`Transport '${this.name}' needs a groupId to subscribe`);
|
|
597
|
-
}
|
|
598
|
-
if (destinations.length === 0) {
|
|
599
|
-
throw new core.ConfigurationError(`Transport '${this.name}' subscribed to no topics`);
|
|
600
|
-
}
|
|
601
|
-
for (const topic of destinations) {
|
|
602
|
-
const deadLetterTopic = this.#deadLetterTopicFor(topic);
|
|
603
|
-
if (deadLetterTopic !== void 0 && destinations.includes(deadLetterTopic)) {
|
|
604
|
-
throw new core.ConfigurationError(
|
|
605
|
-
`Dead letter topic '${deadLetterTopic}' is also consumed, which would loop`
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
/** The dead-letter topic for records of `sourceTopic`, or undefined when the policy does not use one. */
|
|
611
|
-
#deadLetterTopicFor(sourceTopic) {
|
|
612
|
-
const policy = this.#invalidPolicy;
|
|
613
|
-
if (policy === "dead-letter") {
|
|
614
|
-
return `${sourceTopic}.dead`;
|
|
615
|
-
}
|
|
616
|
-
if (typeof policy === "object") {
|
|
617
|
-
const { deadLetterTopic } = policy;
|
|
618
|
-
return typeof deadLetterTopic === "function" ? deadLetterTopic(sourceTopic) : deadLetterTopic;
|
|
619
|
-
}
|
|
620
|
-
return void 0;
|
|
621
|
-
}
|
|
622
|
-
/** Sends with the publish timeout; the late outcome of a timed-out send is ignored. */
|
|
623
|
-
async #send(producer, record) {
|
|
624
|
-
let timer;
|
|
625
|
-
const timeout = new Promise((_resolve, reject) => {
|
|
626
|
-
timer = setTimeout(() => {
|
|
627
|
-
reject(
|
|
628
|
-
new PublishTimeoutError(
|
|
629
|
-
`Transport '${this.name}' got no broker ack for topic '${record.topic}' within ${this.#settings.publishTimeoutMs}ms`
|
|
630
|
-
)
|
|
631
|
-
);
|
|
632
|
-
}, this.#settings.publishTimeoutMs);
|
|
633
|
-
});
|
|
634
|
-
const sending = producer.send(record);
|
|
635
|
-
sending.catch(() => void 0);
|
|
636
|
-
try {
|
|
637
|
-
await Promise.race([sending, timeout]);
|
|
638
|
-
} finally {
|
|
639
|
-
clearTimeout(timer);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
#connectedProducer() {
|
|
643
|
-
if (this.#state === "closed") {
|
|
644
|
-
return Promise.reject(new core.RelayBusError(`Transport '${this.name}' is closed`));
|
|
645
|
-
}
|
|
646
|
-
this.#producer ??= this.#openProducer();
|
|
647
|
-
return this.#producer;
|
|
648
|
-
}
|
|
649
|
-
async #openProducer() {
|
|
650
|
-
const producer = this.#kafka.producer({
|
|
651
|
-
...this.#producerConfig,
|
|
652
|
-
idempotent: true,
|
|
653
|
-
maxInFlightRequests: this.#maxInFlightRequests
|
|
654
|
-
});
|
|
655
|
-
try {
|
|
656
|
-
await producer.connect();
|
|
657
|
-
} catch (error) {
|
|
658
|
-
this.#producer = void 0;
|
|
659
|
-
throw error;
|
|
660
|
-
}
|
|
661
|
-
this.#producerConnected = this.#state !== "closed";
|
|
662
|
-
return producer;
|
|
663
|
-
}
|
|
664
|
-
async #handleMessage(payload, onMessage) {
|
|
944
|
+
async #handleMessage(payload, route) {
|
|
665
945
|
let envelope;
|
|
666
946
|
try {
|
|
667
|
-
envelope = this.#
|
|
947
|
+
envelope = decodeRecord(this.#settings.codec, payload);
|
|
668
948
|
} catch (error) {
|
|
669
|
-
if (this.#
|
|
949
|
+
if (this.#settings.codec === cloudEventsBinary && !(error instanceof InvalidKafkaMessageError)) {
|
|
670
950
|
throw toHandlerFailure(error);
|
|
671
951
|
}
|
|
672
|
-
|
|
673
|
-
await this.#withRetries(payload,
|
|
952
|
+
route.hooks?.onInvalidMessage({ destination: payload.topic, error });
|
|
953
|
+
await this.#withRetries(payload, {
|
|
954
|
+
method: "handleInvalid",
|
|
955
|
+
operation: () => this.#handleInvalid(payload, error)
|
|
956
|
+
});
|
|
674
957
|
return;
|
|
675
958
|
}
|
|
676
|
-
await this.#withRetries(payload,
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
959
|
+
await this.#withRetries(payload, {
|
|
960
|
+
method: "deliver",
|
|
961
|
+
operation: async () => {
|
|
962
|
+
try {
|
|
963
|
+
await route.onMessage(envelope, { destination: payload.topic });
|
|
964
|
+
} catch (error) {
|
|
965
|
+
if (!(error instanceof core.NonRetryableError)) {
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
968
|
+
await this.#handleInvalid(payload, error);
|
|
682
969
|
}
|
|
683
|
-
await this.#handleInvalid(payload, error.message);
|
|
684
970
|
}
|
|
685
971
|
});
|
|
686
972
|
}
|
|
687
|
-
#
|
|
688
|
-
if (this.#decode === void 0) {
|
|
689
|
-
return decodeEnvelope(message);
|
|
690
|
-
}
|
|
691
|
-
const record = {
|
|
692
|
-
topic,
|
|
693
|
-
partition,
|
|
694
|
-
offset: message.offset,
|
|
695
|
-
key: message.key,
|
|
696
|
-
value: message.value,
|
|
697
|
-
headers: message.headers,
|
|
698
|
-
timestamp: message.timestamp
|
|
699
|
-
};
|
|
700
|
-
return completeDecodedEvent(this.#decode(record), record);
|
|
701
|
-
}
|
|
702
|
-
async #handleInvalid(payload, reason) {
|
|
973
|
+
async #handleInvalid(payload, error) {
|
|
703
974
|
const { topic, partition, message } = payload;
|
|
975
|
+
const { message: reason, name, code } = core.toErrorInfo(error);
|
|
704
976
|
const location = `topic=${topic} partition=${partition} offset=${message.offset}`;
|
|
705
|
-
const
|
|
706
|
-
const
|
|
977
|
+
const errorFields = `errorName=${name}${code === void 0 ? "" : ` code=${code}`}`;
|
|
978
|
+
const policy = this.#settings.invalidPolicy;
|
|
979
|
+
const deadLetterTopic = deadLetterTopicFor(policy, topic);
|
|
707
980
|
if (policy === "skip") {
|
|
708
|
-
this.#logger.error(
|
|
709
|
-
`[KafkaTransport](handleInvalid) Skipping malformed message | ${location} reason=${reason}`
|
|
981
|
+
this.#settings.logger.error(
|
|
982
|
+
`[KafkaTransport](handleInvalid) Skipping malformed message | ${location} reason=${reason} ${errorFields}`
|
|
710
983
|
);
|
|
711
984
|
} else if (typeof policy === "function") {
|
|
712
985
|
await policy({
|
|
@@ -718,26 +991,16 @@ var KafkaTransport = class {
|
|
|
718
991
|
value: message.value
|
|
719
992
|
});
|
|
720
993
|
} else {
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
{
|
|
727
|
-
key: message.key === null ? null : Buffer.from(message.key),
|
|
728
|
-
value: message.value === null ? null : Buffer.from(message.value),
|
|
729
|
-
headers: {
|
|
730
|
-
...toOutgoingHeaders(message.headers),
|
|
731
|
-
"relaybus-invalid-reason": reason,
|
|
732
|
-
"relaybus-source-topic": topic,
|
|
733
|
-
"relaybus-source-partition": String(partition),
|
|
734
|
-
"relaybus-source-offset": message.offset
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
]
|
|
994
|
+
const record = deadLetterRecord(payload, {
|
|
995
|
+
reason,
|
|
996
|
+
name,
|
|
997
|
+
code,
|
|
998
|
+
deadLetterTopic
|
|
738
999
|
});
|
|
739
|
-
this.#
|
|
740
|
-
|
|
1000
|
+
const producer = await this.#producer.connected();
|
|
1001
|
+
await this.#producer.send(producer, record);
|
|
1002
|
+
this.#settings.logger.error(
|
|
1003
|
+
`[KafkaTransport](handleInvalid) Dead-lettered malformed message | ${location} deadLetterTopic=${deadLetterTopic} reason=${reason} ${errorFields}`
|
|
741
1004
|
);
|
|
742
1005
|
}
|
|
743
1006
|
}
|
|
@@ -746,8 +1009,9 @@ var KafkaTransport = class {
|
|
|
746
1009
|
* last error is rethrown so the client leaves the offset uncommitted and
|
|
747
1010
|
* redelivers the record; heartbeats between attempts keep the session alive.
|
|
748
1011
|
*/
|
|
749
|
-
async #withRetries(payload, method, operation) {
|
|
750
|
-
const { maxAttempts,
|
|
1012
|
+
async #withRetries(payload, { method, operation }) {
|
|
1013
|
+
const { maxAttempts, processingBackoff } = this.#settings.timings;
|
|
1014
|
+
const { logger } = this.#settings;
|
|
751
1015
|
const location = `topic=${payload.topic} partition=${payload.partition} offset=${payload.message.offset}`;
|
|
752
1016
|
const partitionKey = `${payload.topic}:${payload.partition}`;
|
|
753
1017
|
for (let attempt = 1; ; attempt += 1) {
|
|
@@ -759,51 +1023,28 @@ var KafkaTransport = class {
|
|
|
759
1023
|
const reason = core.toErrorInfo(error).message;
|
|
760
1024
|
if (attempt >= maxAttempts || this.#state === "closed") {
|
|
761
1025
|
this.#stalledPartitions.add(partitionKey);
|
|
762
|
-
|
|
763
|
-
`[KafkaTransport](
|
|
1026
|
+
logger.error(
|
|
1027
|
+
`[KafkaTransport](withRetries) Giving up for now, the record stays uncommitted and will be redelivered | operation=${method} ${location} attempts=${attempt} error=${reason}`
|
|
764
1028
|
);
|
|
765
1029
|
throw toHandlerFailure(error);
|
|
766
1030
|
}
|
|
767
|
-
|
|
768
|
-
`[KafkaTransport](
|
|
769
|
-
);
|
|
770
|
-
await this.#sleepWithHeartbeats(
|
|
771
|
-
Math.min(initialBackoffMs * 2 ** (attempt - 1), maxBackoffMs),
|
|
772
|
-
payload
|
|
1031
|
+
logger.warn(
|
|
1032
|
+
`[KafkaTransport](withRetries) Attempt failed, retrying | operation=${method} ${location} attempt=${attempt} error=${reason}`
|
|
773
1033
|
);
|
|
1034
|
+
await this.#sleeps.sleepInSlices(processingBackoff.delayFor(attempt), {
|
|
1035
|
+
sliceMs: this.#settings.timings.heartbeatIntervalMs,
|
|
1036
|
+
between: () => payload.heartbeat(),
|
|
1037
|
+
stopped: () => this.#state === "closed"
|
|
1038
|
+
});
|
|
774
1039
|
}
|
|
775
1040
|
}
|
|
776
1041
|
}
|
|
777
|
-
/** Sleeps in slices and heartbeats after each, so a long backoff never outlives the session timeout. */
|
|
778
|
-
async #sleepWithHeartbeats(milliseconds, payload) {
|
|
779
|
-
let remaining = milliseconds;
|
|
780
|
-
do {
|
|
781
|
-
const slice = Math.min(remaining, this.#settings.heartbeatIntervalMs);
|
|
782
|
-
await this.#sleep(slice);
|
|
783
|
-
await payload.heartbeat();
|
|
784
|
-
remaining -= slice;
|
|
785
|
-
} while (remaining > 0 && this.#state !== "closed");
|
|
786
|
-
}
|
|
787
|
-
#sleep(milliseconds) {
|
|
788
|
-
return new Promise((resolve) => {
|
|
789
|
-
const wake = () => {
|
|
790
|
-
clearTimeout(timer);
|
|
791
|
-
this.#sleepers.delete(wake);
|
|
792
|
-
resolve();
|
|
793
|
-
};
|
|
794
|
-
const timer = setTimeout(wake, milliseconds);
|
|
795
|
-
this.#sleepers.add(wake);
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
/** Whether the client exposes the instrumentation needed to follow the group membership. */
|
|
799
|
-
#tracksMembership(consumer) {
|
|
800
|
-
return consumer.on !== void 0 && consumer.events?.GROUP_JOIN !== void 0;
|
|
801
|
-
}
|
|
802
1042
|
#observeConsumer(consumer) {
|
|
803
|
-
if (!
|
|
1043
|
+
if (!tracksMembership(consumer)) {
|
|
804
1044
|
this.#groupJoined = true;
|
|
805
1045
|
return;
|
|
806
1046
|
}
|
|
1047
|
+
const { logger, groupId } = this.#settings;
|
|
807
1048
|
const names = consumer.events;
|
|
808
1049
|
const listen = (name, listener) => {
|
|
809
1050
|
if (name !== void 0) {
|
|
@@ -812,29 +1053,28 @@ var KafkaTransport = class {
|
|
|
812
1053
|
};
|
|
813
1054
|
listen(names.GROUP_JOIN, () => {
|
|
814
1055
|
this.#groupJoined = true;
|
|
815
|
-
this.#
|
|
816
|
-
|
|
1056
|
+
this.#groupJoin.joined();
|
|
1057
|
+
logger.log(`[KafkaTransport](observeConsumer) Joined group | group=${groupId}`);
|
|
817
1058
|
});
|
|
818
1059
|
listen(names.REBALANCING, () => {
|
|
819
1060
|
this.#groupJoined = false;
|
|
820
1061
|
this.#stalledPartitions.clear();
|
|
821
|
-
|
|
1062
|
+
logger.warn(`[KafkaTransport](observeConsumer) Rebalancing | group=${groupId}`);
|
|
822
1063
|
});
|
|
823
1064
|
listen(names.CRASH, (event) => this.#handleCrash(event));
|
|
824
|
-
|
|
1065
|
+
const leftGroup = () => {
|
|
825
1066
|
this.#groupJoined = false;
|
|
826
|
-
}
|
|
827
|
-
listen(names.
|
|
828
|
-
|
|
829
|
-
});
|
|
1067
|
+
};
|
|
1068
|
+
listen(names.STOP, leftGroup);
|
|
1069
|
+
listen(names.DISCONNECT, leftGroup);
|
|
830
1070
|
}
|
|
831
1071
|
#handleCrash(event) {
|
|
832
1072
|
const { error, restart } = event.payload ?? {};
|
|
833
1073
|
this.#groupJoined = false;
|
|
834
|
-
this.#logger.error(
|
|
835
|
-
`[KafkaTransport](handleCrash) Consumer crashed | group=${this.#groupId} restart=${String(restart)} error=${core.toErrorInfo(error).message}`
|
|
1074
|
+
this.#settings.logger.error(
|
|
1075
|
+
`[KafkaTransport](handleCrash) Consumer crashed | group=${this.#settings.groupId} restart=${String(restart)} error=${core.toErrorInfo(error).message}`
|
|
836
1076
|
);
|
|
837
|
-
this.#
|
|
1077
|
+
this.#groupJoin.failed(error);
|
|
838
1078
|
if (restart === false && this.#consuming && this.#restartLoop === void 0) {
|
|
839
1079
|
this.#restartLoop = this.#restartConsumer().finally(() => {
|
|
840
1080
|
this.#restartLoop = void 0;
|
|
@@ -846,138 +1086,50 @@ var KafkaTransport = class {
|
|
|
846
1086
|
* again, with bounded backoff, until it joined or the transport closed.
|
|
847
1087
|
*/
|
|
848
1088
|
async #restartConsumer() {
|
|
849
|
-
const {
|
|
1089
|
+
const { logger, groupId, timings } = this.#settings;
|
|
850
1090
|
const consumer = this.#consumer;
|
|
851
1091
|
for (let attempt = 1; this.#state === "subscribed"; attempt += 1) {
|
|
852
|
-
await this.#sleep(
|
|
853
|
-
Math.min(restartInitialBackoffMs * 2 ** (attempt - 1), restartMaxBackoffMs)
|
|
854
|
-
);
|
|
1092
|
+
await this.#sleeps.sleep(timings.restartBackoff.delayFor(attempt));
|
|
855
1093
|
if (this.#state !== "subscribed") {
|
|
856
1094
|
return;
|
|
857
1095
|
}
|
|
858
1096
|
await consumer.disconnect().catch(() => void 0);
|
|
859
1097
|
try {
|
|
860
1098
|
await this.#startConsuming();
|
|
861
|
-
|
|
862
|
-
`[KafkaTransport](restartConsumer) Consumer restarted | group=${
|
|
1099
|
+
logger.log(
|
|
1100
|
+
`[KafkaTransport](restartConsumer) Consumer restarted | group=${groupId} attempt=${attempt}`
|
|
863
1101
|
);
|
|
864
1102
|
return;
|
|
865
1103
|
} catch (error) {
|
|
866
|
-
|
|
867
|
-
`[KafkaTransport](restartConsumer) Restart failed, will retry | group=${
|
|
1104
|
+
logger.error(
|
|
1105
|
+
`[KafkaTransport](restartConsumer) Restart failed, will retry | group=${groupId} attempt=${attempt} error=${core.toErrorInfo(error).message}`
|
|
868
1106
|
);
|
|
869
1107
|
}
|
|
870
1108
|
}
|
|
871
1109
|
}
|
|
872
|
-
#waitForGroupJoin(consumer) {
|
|
873
|
-
if (!this.#tracksMembership(consumer)) {
|
|
874
|
-
return { promise: Promise.resolve(), dispose: () => void 0 };
|
|
875
|
-
}
|
|
876
|
-
let timer;
|
|
877
|
-
const promise = new Promise((resolve, reject) => {
|
|
878
|
-
this.#joinWaiter = { resolve, reject };
|
|
879
|
-
timer = setTimeout(() => {
|
|
880
|
-
reject(
|
|
881
|
-
new core.RelayBusError(
|
|
882
|
-
`Consumer of transport '${this.name}' did not join group '${this.#groupId}' within ${this.#settings.startupTimeoutMs}ms`
|
|
883
|
-
)
|
|
884
|
-
);
|
|
885
|
-
}, this.#settings.startupTimeoutMs);
|
|
886
|
-
});
|
|
887
|
-
promise.catch(() => void 0);
|
|
888
|
-
return {
|
|
889
|
-
promise,
|
|
890
|
-
dispose: () => {
|
|
891
|
-
clearTimeout(timer);
|
|
892
|
-
this.#joinWaiter = void 0;
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
1110
|
};
|
|
897
|
-
function resolveSettings(options) {
|
|
898
|
-
const maxAttempts = options.processing?.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
899
|
-
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
|
|
900
|
-
throw new core.ConfigurationError(
|
|
901
|
-
`processing.maxAttempts must be a positive integer, received ${maxAttempts}`
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
return {
|
|
905
|
-
maxAttempts,
|
|
906
|
-
initialBackoffMs: core.toMilliseconds(
|
|
907
|
-
options.processing?.initialBackoff ?? DEFAULTS.initialBackoff,
|
|
908
|
-
"processing.initialBackoff"
|
|
909
|
-
),
|
|
910
|
-
maxBackoffMs: core.toMilliseconds(
|
|
911
|
-
options.processing?.maxBackoff ?? DEFAULTS.maxBackoff,
|
|
912
|
-
"processing.maxBackoff"
|
|
913
|
-
),
|
|
914
|
-
startupTimeoutMs: core.toMilliseconds(
|
|
915
|
-
options.startupTimeout ?? DEFAULTS.startupTimeout,
|
|
916
|
-
"startupTimeout"
|
|
917
|
-
),
|
|
918
|
-
publishTimeoutMs: core.toMilliseconds(
|
|
919
|
-
options.publishTimeout ?? DEFAULTS.publishTimeout,
|
|
920
|
-
"publishTimeout"
|
|
921
|
-
),
|
|
922
|
-
heartbeatIntervalMs: Math.max(
|
|
923
|
-
1,
|
|
924
|
-
core.toMilliseconds(
|
|
925
|
-
options.processing?.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
|
|
926
|
-
"processing.heartbeatInterval"
|
|
927
|
-
)
|
|
928
|
-
),
|
|
929
|
-
restartInitialBackoffMs: core.toMilliseconds(
|
|
930
|
-
options.restart?.initialBackoff ?? DEFAULTS.restartInitialBackoff,
|
|
931
|
-
"restart.initialBackoff"
|
|
932
|
-
),
|
|
933
|
-
restartMaxBackoffMs: core.toMilliseconds(
|
|
934
|
-
options.restart?.maxBackoff ?? DEFAULTS.restartMaxBackoff,
|
|
935
|
-
"restart.maxBackoff"
|
|
936
|
-
)
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
function toHandlerFailure(error) {
|
|
940
|
-
const { name, message } = core.toErrorInfo(error);
|
|
941
|
-
return new HandlerFailedError(`${name}: ${message}`);
|
|
942
|
-
}
|
|
943
|
-
function fromHeader(name) {
|
|
944
|
-
if (name.length === 0) {
|
|
945
|
-
throw new core.ConfigurationError("fromHeader needs a header name");
|
|
946
|
-
}
|
|
947
|
-
return (message) => Object.hasOwn(message.headers, name) ? message.headers[name] : void 0;
|
|
948
|
-
}
|
|
949
|
-
function fromPayloadField(path) {
|
|
950
|
-
const segments = path.split(".");
|
|
951
|
-
if (segments.some((segment) => segment.length === 0)) {
|
|
952
|
-
throw new core.ConfigurationError(
|
|
953
|
-
`fromPayloadField needs a dotted path of non-empty names, received '${path}'`
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
return (message) => {
|
|
957
|
-
let value = message.payload;
|
|
958
|
-
for (const segment of segments) {
|
|
959
|
-
if (typeof value !== "object" || value === null || !Object.hasOwn(value, segment)) {
|
|
960
|
-
return void 0;
|
|
961
|
-
}
|
|
962
|
-
value = value[segment];
|
|
963
|
-
}
|
|
964
|
-
if (typeof value === "string") {
|
|
965
|
-
return value;
|
|
966
|
-
}
|
|
967
|
-
return Number.isSafeInteger(value) ? String(value) : void 0;
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
1111
|
|
|
1112
|
+
Object.defineProperty(exports, "fromHeader", {
|
|
1113
|
+
enumerable: true,
|
|
1114
|
+
get: function () { return internal.fromHeader; }
|
|
1115
|
+
});
|
|
1116
|
+
Object.defineProperty(exports, "fromPayloadField", {
|
|
1117
|
+
enumerable: true,
|
|
1118
|
+
get: function () { return internal.fromPayloadField; }
|
|
1119
|
+
});
|
|
1120
|
+
exports.GroupJoinTimeoutError = GroupJoinTimeoutError;
|
|
971
1121
|
exports.HandlerFailedError = HandlerFailedError;
|
|
972
1122
|
exports.InvalidKafkaMessageError = InvalidKafkaMessageError;
|
|
973
1123
|
exports.KafkaTransport = KafkaTransport;
|
|
974
1124
|
exports.PublishTimeoutError = PublishTimeoutError;
|
|
1125
|
+
exports.TransportClosedError = TransportClosedError;
|
|
975
1126
|
exports.UnencodableEventError = UnencodableEventError;
|
|
1127
|
+
exports.cloudEventsBinary = cloudEventsBinary;
|
|
1128
|
+
exports.cloudEventsStructured = cloudEventsStructured;
|
|
976
1129
|
exports.decodeCloudEvent = decodeCloudEvent;
|
|
977
1130
|
exports.decodeEnvelope = decodeEnvelope;
|
|
978
1131
|
exports.encodeEnvelope = encodeEnvelope;
|
|
979
|
-
exports.
|
|
980
|
-
exports.fromPayloadField = fromPayloadField;
|
|
1132
|
+
exports.jsonEnvelope = jsonEnvelope;
|
|
981
1133
|
exports.plainJsonDecoder = plainJsonDecoder;
|
|
982
1134
|
//# sourceMappingURL=index.cjs.map
|
|
983
1135
|
//# sourceMappingURL=index.cjs.map
|