@horizon-republic/nestjs-jetstream 2.9.0 → 2.10.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 +15 -2
- package/dist/index.cjs +2016 -441
- package/dist/index.d.cts +803 -389
- package/dist/index.d.ts +803 -389
- package/dist/index.js +1998 -426
- package/package.json +35 -11
package/dist/index.cjs
CHANGED
|
@@ -7,11 +7,11 @@ var __export = (target, all) => {
|
|
|
7
7
|
for (var name in all)
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
|
-
var __copyProps = (to,
|
|
11
|
-
if (
|
|
12
|
-
for (let key of __getOwnPropNames(
|
|
10
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
11
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from2))
|
|
13
13
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () =>
|
|
14
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
@@ -29,14 +29,25 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
ConsumeKind: () => ConsumeKind,
|
|
33
|
+
DEFAULT_BROADCAST_CONSUMER_CONFIG: () => DEFAULT_BROADCAST_CONSUMER_CONFIG,
|
|
34
|
+
DEFAULT_BROADCAST_STREAM_CONFIG: () => DEFAULT_BROADCAST_STREAM_CONFIG,
|
|
35
|
+
DEFAULT_COMMAND_CONSUMER_CONFIG: () => DEFAULT_COMMAND_CONSUMER_CONFIG,
|
|
36
|
+
DEFAULT_COMMAND_STREAM_CONFIG: () => DEFAULT_COMMAND_STREAM_CONFIG,
|
|
37
|
+
DEFAULT_DLQ_STREAM_CONFIG: () => DEFAULT_DLQ_STREAM_CONFIG,
|
|
38
|
+
DEFAULT_EVENT_CONSUMER_CONFIG: () => DEFAULT_EVENT_CONSUMER_CONFIG,
|
|
39
|
+
DEFAULT_EVENT_STREAM_CONFIG: () => DEFAULT_EVENT_STREAM_CONFIG,
|
|
40
|
+
DEFAULT_JETSTREAM_RPC_TIMEOUT: () => DEFAULT_JETSTREAM_RPC_TIMEOUT,
|
|
32
41
|
DEFAULT_METADATA_BUCKET: () => DEFAULT_METADATA_BUCKET,
|
|
33
42
|
DEFAULT_METADATA_HISTORY: () => DEFAULT_METADATA_HISTORY,
|
|
34
43
|
DEFAULT_METADATA_REPLICAS: () => DEFAULT_METADATA_REPLICAS,
|
|
35
44
|
DEFAULT_METADATA_TTL: () => DEFAULT_METADATA_TTL,
|
|
36
|
-
|
|
45
|
+
DEFAULT_ORDERED_STREAM_CONFIG: () => DEFAULT_ORDERED_STREAM_CONFIG,
|
|
46
|
+
DEFAULT_RPC_TIMEOUT: () => DEFAULT_RPC_TIMEOUT,
|
|
47
|
+
DEFAULT_SHUTDOWN_TIMEOUT: () => DEFAULT_SHUTDOWN_TIMEOUT,
|
|
48
|
+
DEFAULT_TRACES: () => DEFAULT_TRACES,
|
|
37
49
|
JETSTREAM_CODEC: () => JETSTREAM_CODEC,
|
|
38
50
|
JETSTREAM_CONNECTION: () => JETSTREAM_CONNECTION,
|
|
39
|
-
JETSTREAM_EVENT_BUS: () => JETSTREAM_EVENT_BUS,
|
|
40
51
|
JETSTREAM_OPTIONS: () => JETSTREAM_OPTIONS,
|
|
41
52
|
JetstreamClient: () => JetstreamClient,
|
|
42
53
|
JetstreamDlqHeader: () => JetstreamDlqHeader,
|
|
@@ -46,12 +57,18 @@ __export(index_exports, {
|
|
|
46
57
|
JetstreamRecord: () => JetstreamRecord,
|
|
47
58
|
JetstreamRecordBuilder: () => JetstreamRecordBuilder,
|
|
48
59
|
JetstreamStrategy: () => JetstreamStrategy,
|
|
60
|
+
JetstreamTrace: () => JetstreamTrace,
|
|
49
61
|
JsonCodec: () => JsonCodec,
|
|
50
62
|
MIN_METADATA_TTL: () => MIN_METADATA_TTL,
|
|
51
63
|
MessageKind: () => MessageKind,
|
|
64
|
+
MsgpackCodec: () => MsgpackCodec,
|
|
65
|
+
NatsErrorCode: () => NatsErrorCode,
|
|
52
66
|
PatternPrefix: () => PatternPrefix,
|
|
67
|
+
PublishKind: () => PublishKind,
|
|
68
|
+
RESERVED_HEADERS: () => RESERVED_HEADERS,
|
|
53
69
|
RpcContext: () => RpcContext,
|
|
54
70
|
StreamKind: () => StreamKind,
|
|
71
|
+
TRACER_NAME: () => TRACER_NAME,
|
|
55
72
|
TransportEvent: () => TransportEvent,
|
|
56
73
|
buildBroadcastSubject: () => buildBroadcastSubject,
|
|
57
74
|
buildSubject: () => buildSubject,
|
|
@@ -68,13 +85,14 @@ __export(index_exports, {
|
|
|
68
85
|
module.exports = __toCommonJS(index_exports);
|
|
69
86
|
|
|
70
87
|
// src/jetstream.module.ts
|
|
71
|
-
var
|
|
88
|
+
var import_common18 = require("@nestjs/common");
|
|
72
89
|
|
|
73
90
|
// src/client/jetstream.client.ts
|
|
74
|
-
var
|
|
91
|
+
var import_common5 = require("@nestjs/common");
|
|
75
92
|
var import_microservices = require("@nestjs/microservices");
|
|
76
|
-
var
|
|
93
|
+
var import_api8 = require("@opentelemetry/api");
|
|
77
94
|
var import_nuid = require("@nats-io/nuid");
|
|
95
|
+
var import_transport_node = require("@nats-io/transport-node");
|
|
78
96
|
|
|
79
97
|
// src/interfaces/hooks.interface.ts
|
|
80
98
|
var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
|
|
@@ -264,6 +282,907 @@ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
|
|
|
264
282
|
var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
|
|
265
283
|
var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
|
|
266
284
|
|
|
285
|
+
// src/otel/constants.ts
|
|
286
|
+
var TRACER_NAME = "@horizon-republic/nestjs-jetstream";
|
|
287
|
+
|
|
288
|
+
// src/otel/attribute-keys.ts
|
|
289
|
+
var ATTR_MESSAGING_SYSTEM = "messaging.system";
|
|
290
|
+
var ATTR_MESSAGING_DESTINATION_NAME = "messaging.destination.name";
|
|
291
|
+
var ATTR_MESSAGING_DESTINATION_TEMPLATE = "messaging.destination.template";
|
|
292
|
+
var ATTR_MESSAGING_CLIENT_ID = "messaging.client.id";
|
|
293
|
+
var ATTR_MESSAGING_OPERATION_NAME = "messaging.operation.name";
|
|
294
|
+
var ATTR_MESSAGING_OPERATION_TYPE = "messaging.operation.type";
|
|
295
|
+
var ATTR_MESSAGING_MESSAGE_BODY_SIZE = "messaging.message.body.size";
|
|
296
|
+
var ATTR_MESSAGING_MESSAGE_ID = "messaging.message.id";
|
|
297
|
+
var ATTR_MESSAGING_MESSAGE_CONVERSATION_ID = "messaging.message.conversation_id";
|
|
298
|
+
var ATTR_MESSAGING_CONSUMER_GROUP_NAME = "messaging.consumer.group.name";
|
|
299
|
+
var ATTR_MESSAGING_HEADER_PREFIX = "messaging.header.";
|
|
300
|
+
var ATTR_MESSAGING_NATS_STREAM_NAME = "messaging.nats.stream.name";
|
|
301
|
+
var ATTR_MESSAGING_NATS_STREAM_SEQUENCE = "messaging.nats.message.stream_sequence";
|
|
302
|
+
var ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE = "messaging.nats.message.consumer_sequence";
|
|
303
|
+
var ATTR_MESSAGING_NATS_DELIVERY_COUNT = "messaging.nats.message.delivery_count";
|
|
304
|
+
var ATTR_MESSAGING_NATS_BODY = "messaging.nats.message.body";
|
|
305
|
+
var ATTR_MESSAGING_NATS_BODY_TRUNCATED = "messaging.nats.message.body.truncated";
|
|
306
|
+
var ATTR_SERVER_ADDRESS = "server.address";
|
|
307
|
+
var ATTR_SERVER_PORT = "server.port";
|
|
308
|
+
var ATTR_JETSTREAM_SERVICE_NAME = "jetstream.service.name";
|
|
309
|
+
var ATTR_JETSTREAM_KIND = "jetstream.kind";
|
|
310
|
+
var ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR = "jetstream.rpc.reply.has_error";
|
|
311
|
+
var ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE = "jetstream.rpc.reply.error.code";
|
|
312
|
+
var ATTR_JETSTREAM_PROVISIONING_ENTITY = "jetstream.provisioning.entity";
|
|
313
|
+
var ATTR_JETSTREAM_PROVISIONING_ACTION = "jetstream.provisioning.action";
|
|
314
|
+
var ATTR_JETSTREAM_PROVISIONING_NAME = "jetstream.provisioning.name";
|
|
315
|
+
var ATTR_JETSTREAM_SELF_HEALING_REASON = "jetstream.self_healing.reason";
|
|
316
|
+
var ATTR_JETSTREAM_MIGRATION_REASON = "jetstream.migration.reason";
|
|
317
|
+
var ATTR_JETSTREAM_DEAD_LETTER_REASON = "jetstream.dead_letter.reason";
|
|
318
|
+
var ATTR_JETSTREAM_SCHEDULE_TARGET = "jetstream.schedule.target";
|
|
319
|
+
var ATTR_NATS_CONNECTION_SERVER = "nats.connection.server";
|
|
320
|
+
var NATS_MSG_ID_HEADER = "Nats-Msg-Id";
|
|
321
|
+
var HOOK_PUBLISH = "publishHook";
|
|
322
|
+
var HOOK_CONSUME = "consumeHook";
|
|
323
|
+
var HOOK_RESPONSE = "responseHook";
|
|
324
|
+
var SPAN_NAME_PUBLISH = "publish";
|
|
325
|
+
var SPAN_NAME_PROCESS = "process";
|
|
326
|
+
var SPAN_NAME_SEND = "send";
|
|
327
|
+
var SPAN_NAME_DEAD_LETTER = "dead_letter";
|
|
328
|
+
var SPAN_NAME_NATS_CONNECTION = "nats.connection";
|
|
329
|
+
var SPAN_NAME_JETSTREAM_SHUTDOWN = "jetstream.shutdown";
|
|
330
|
+
var SPAN_NAME_JETSTREAM_SELF_HEALING = "jetstream.self_healing";
|
|
331
|
+
var SPAN_NAME_JETSTREAM_MIGRATION = "jetstream.migration";
|
|
332
|
+
var SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX = "jetstream.provisioning.";
|
|
333
|
+
var EVENT_CONNECTION_DISCONNECTED = "connection.disconnected";
|
|
334
|
+
var EVENT_CONNECTION_RECONNECTED = "connection.reconnected";
|
|
335
|
+
var messagingHeaderAttr = (headerName) => `${ATTR_MESSAGING_HEADER_PREFIX}${headerName.toLowerCase()}`;
|
|
336
|
+
|
|
337
|
+
// src/otel/trace-kinds.ts
|
|
338
|
+
var JetstreamTrace = /* @__PURE__ */ ((JetstreamTrace2) => {
|
|
339
|
+
JetstreamTrace2["Publish"] = "publish";
|
|
340
|
+
JetstreamTrace2["Consume"] = "consume";
|
|
341
|
+
JetstreamTrace2["RpcClientSend"] = "rpc.client.send";
|
|
342
|
+
JetstreamTrace2["DeadLetter"] = "dead_letter";
|
|
343
|
+
JetstreamTrace2["ConnectionLifecycle"] = "connection.lifecycle";
|
|
344
|
+
JetstreamTrace2["SelfHealing"] = "self_healing";
|
|
345
|
+
JetstreamTrace2["Provisioning"] = "provisioning";
|
|
346
|
+
JetstreamTrace2["Migration"] = "migration";
|
|
347
|
+
JetstreamTrace2["Shutdown"] = "shutdown";
|
|
348
|
+
return JetstreamTrace2;
|
|
349
|
+
})(JetstreamTrace || {});
|
|
350
|
+
var DEFAULT_TRACES = [
|
|
351
|
+
"publish" /* Publish */,
|
|
352
|
+
"consume" /* Consume */,
|
|
353
|
+
"rpc.client.send" /* RpcClientSend */,
|
|
354
|
+
"dead_letter" /* DeadLetter */
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
// src/otel/capture.ts
|
|
358
|
+
var NEGATION_PREFIX = "!";
|
|
359
|
+
var REGEX_SPECIAL_RE = /[.+?^${}()|[\]\\*]/gu;
|
|
360
|
+
var escapeForRegex = (input) => input.replace(REGEX_SPECIAL_RE, "\\$&");
|
|
361
|
+
var globToRegex = (glob) => {
|
|
362
|
+
const escaped = escapeForRegex(glob.toLowerCase()).replace(/\\\*/gu, ".*");
|
|
363
|
+
return new RegExp(`^${escaped}$`, "u");
|
|
364
|
+
};
|
|
365
|
+
var compileHeaderAllowlist = (allowlist) => {
|
|
366
|
+
if (allowlist === true) return () => true;
|
|
367
|
+
if (allowlist === false) return () => false;
|
|
368
|
+
if (allowlist.length === 0) return () => false;
|
|
369
|
+
const includes = [];
|
|
370
|
+
const excludes = [];
|
|
371
|
+
for (const pattern of allowlist) {
|
|
372
|
+
const isExclude = pattern.startsWith(NEGATION_PREFIX);
|
|
373
|
+
const body = isExclude ? pattern.slice(NEGATION_PREFIX.length) : pattern;
|
|
374
|
+
const regex = globToRegex(body);
|
|
375
|
+
if (isExclude) excludes.push(regex);
|
|
376
|
+
else includes.push(regex);
|
|
377
|
+
}
|
|
378
|
+
return (name) => {
|
|
379
|
+
const lower = name.toLowerCase();
|
|
380
|
+
if (!includes.some((re) => re.test(lower))) return false;
|
|
381
|
+
if (excludes.some((re) => re.test(lower))) return false;
|
|
382
|
+
return true;
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
var subjectMatcherCache = /* @__PURE__ */ new WeakMap();
|
|
386
|
+
var compileSubjectAllowlist = (allowlist) => {
|
|
387
|
+
if (!allowlist || allowlist.length === 0) return () => true;
|
|
388
|
+
const cached = subjectMatcherCache.get(allowlist);
|
|
389
|
+
if (cached) return cached;
|
|
390
|
+
const regexes = allowlist.map(globToRegex);
|
|
391
|
+
const matcher = (subject) => {
|
|
392
|
+
const lower = subject.toLowerCase();
|
|
393
|
+
return regexes.some((re) => re.test(lower));
|
|
394
|
+
};
|
|
395
|
+
subjectMatcherCache.set(allowlist, matcher);
|
|
396
|
+
return matcher;
|
|
397
|
+
};
|
|
398
|
+
var subjectMatchesAllowlist = (subject, allowlist) => compileSubjectAllowlist(allowlist)(subject);
|
|
399
|
+
var HEADER_DENYLIST = /* @__PURE__ */ new Set([
|
|
400
|
+
"traceparent",
|
|
401
|
+
"tracestate",
|
|
402
|
+
"baggage",
|
|
403
|
+
"sentry-trace",
|
|
404
|
+
"b3",
|
|
405
|
+
"x-b3-traceid",
|
|
406
|
+
"x-b3-spanid",
|
|
407
|
+
"x-b3-parentspanid",
|
|
408
|
+
"x-b3-sampled",
|
|
409
|
+
"x-b3-flags",
|
|
410
|
+
"uber-trace-id",
|
|
411
|
+
"x-correlation-id",
|
|
412
|
+
"x-reply-to",
|
|
413
|
+
"x-error",
|
|
414
|
+
"x-subject",
|
|
415
|
+
"x-caller-name"
|
|
416
|
+
]);
|
|
417
|
+
var isNatsServerHeader = (lower) => lower.startsWith("nats-");
|
|
418
|
+
var captureMatchingHeaders = (headers2, matcher) => {
|
|
419
|
+
if (!headers2) return {};
|
|
420
|
+
const out = {};
|
|
421
|
+
for (const key of headers2.keys()) {
|
|
422
|
+
const lower = key.toLowerCase();
|
|
423
|
+
if (HEADER_DENYLIST.has(lower)) continue;
|
|
424
|
+
if (isNatsServerHeader(lower)) continue;
|
|
425
|
+
if (!matcher(key)) continue;
|
|
426
|
+
const value = headers2.get(key);
|
|
427
|
+
if (value === "") continue;
|
|
428
|
+
out[messagingHeaderAttr(lower)] = value;
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
};
|
|
432
|
+
var tryUtf8Decode = (bytes) => {
|
|
433
|
+
try {
|
|
434
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
435
|
+
} catch {
|
|
436
|
+
return Buffer.from(bytes).toString("base64");
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
var captureBodyAttribute = (subject, payload, capture) => {
|
|
440
|
+
if (capture === false) return {};
|
|
441
|
+
if (payload.byteLength === 0) return {};
|
|
442
|
+
if (!subjectMatchesAllowlist(subject, capture.subjectAllowlist)) return {};
|
|
443
|
+
const { maxBytes } = capture;
|
|
444
|
+
const truncated = payload.byteLength > maxBytes;
|
|
445
|
+
const slice = truncated ? payload.subarray(0, maxBytes) : payload;
|
|
446
|
+
const decoded = tryUtf8Decode(slice);
|
|
447
|
+
const out = { [ATTR_MESSAGING_NATS_BODY]: decoded };
|
|
448
|
+
if (truncated) out[ATTR_MESSAGING_NATS_BODY_TRUNCATED] = true;
|
|
449
|
+
return out;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/otel/config.ts
|
|
453
|
+
var PublishKind = /* @__PURE__ */ ((PublishKind2) => {
|
|
454
|
+
PublishKind2["Event"] = "event";
|
|
455
|
+
PublishKind2["RpcRequest"] = "rpc.request";
|
|
456
|
+
PublishKind2["Broadcast"] = "broadcast";
|
|
457
|
+
PublishKind2["Ordered"] = "ordered";
|
|
458
|
+
return PublishKind2;
|
|
459
|
+
})(PublishKind || {});
|
|
460
|
+
var ConsumeKind = /* @__PURE__ */ ((ConsumeKind2) => {
|
|
461
|
+
ConsumeKind2["Event"] = "event";
|
|
462
|
+
ConsumeKind2["Rpc"] = "rpc";
|
|
463
|
+
ConsumeKind2["Broadcast"] = "broadcast";
|
|
464
|
+
ConsumeKind2["Ordered"] = "ordered";
|
|
465
|
+
return ConsumeKind2;
|
|
466
|
+
})(ConsumeKind || {});
|
|
467
|
+
var DEFAULT_CAPTURE_HEADERS = ["x-request-id"];
|
|
468
|
+
var DEFAULT_CAPTURE_BODY_MAX_BYTES = 4096;
|
|
469
|
+
var NESTJS_BARE_ERROR_MESSAGE = "Internal server error";
|
|
470
|
+
var isNestjsBareErrorSentinel = (obj) => obj.status === "error" && obj.message === NESTJS_BARE_ERROR_MESSAGE;
|
|
471
|
+
var defaultErrorClassifier = (err) => {
|
|
472
|
+
if (err === null || typeof err !== "object") return "unexpected";
|
|
473
|
+
if (!(err instanceof Error)) {
|
|
474
|
+
if (isNestjsBareErrorSentinel(err)) return "unexpected";
|
|
475
|
+
return "expected";
|
|
476
|
+
}
|
|
477
|
+
let proto = err;
|
|
478
|
+
while (proto) {
|
|
479
|
+
const name = proto.constructor?.name;
|
|
480
|
+
if (name === "RpcException" || name === "HttpException") return "expected";
|
|
481
|
+
proto = Object.getPrototypeOf(proto);
|
|
482
|
+
}
|
|
483
|
+
return "unexpected";
|
|
484
|
+
};
|
|
485
|
+
var expandTracesOption = (option) => {
|
|
486
|
+
if (option === void 0 || option === "default") return new Set(DEFAULT_TRACES);
|
|
487
|
+
if (option === "all") return new Set(Object.values(JetstreamTrace));
|
|
488
|
+
if (option === "none") return /* @__PURE__ */ new Set();
|
|
489
|
+
return new Set(option);
|
|
490
|
+
};
|
|
491
|
+
var compileHeaderMatcher = (option) => compileHeaderAllowlist(option ?? DEFAULT_CAPTURE_HEADERS);
|
|
492
|
+
var resolveCaptureBody = (option) => {
|
|
493
|
+
if (option === void 0 || option === false) return false;
|
|
494
|
+
if (option === true) return { maxBytes: DEFAULT_CAPTURE_BODY_MAX_BYTES };
|
|
495
|
+
return {
|
|
496
|
+
maxBytes: option.maxBytes ?? DEFAULT_CAPTURE_BODY_MAX_BYTES,
|
|
497
|
+
subjectAllowlist: option.subjectAllowlist
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
var resolveOtelOptions = (options = {}) => {
|
|
501
|
+
return {
|
|
502
|
+
enabled: options.enabled ?? true,
|
|
503
|
+
traces: expandTracesOption(options.traces),
|
|
504
|
+
captureHeaders: compileHeaderMatcher(options.captureHeaders),
|
|
505
|
+
captureBody: resolveCaptureBody(options.captureBody),
|
|
506
|
+
publishHook: options.publishHook,
|
|
507
|
+
consumeHook: options.consumeHook,
|
|
508
|
+
responseHook: options.responseHook,
|
|
509
|
+
shouldTracePublish: options.shouldTracePublish,
|
|
510
|
+
shouldTraceConsume: options.shouldTraceConsume,
|
|
511
|
+
errorClassifier: options.errorClassifier ?? defaultErrorClassifier
|
|
512
|
+
};
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/otel/internal-utils.ts
|
|
516
|
+
var import_common = require("@nestjs/common");
|
|
517
|
+
var logger = new import_common.Logger("Jetstream:Otel");
|
|
518
|
+
var safelyInvokeHook = (hookName, hook, ...args) => {
|
|
519
|
+
if (!hook) return;
|
|
520
|
+
const logHookFailure = (err) => {
|
|
521
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
522
|
+
logger.debug(`OTel ${hookName} threw: ${message}`);
|
|
523
|
+
};
|
|
524
|
+
try {
|
|
525
|
+
const result = hook(...args);
|
|
526
|
+
if (result !== null && typeof result === "object" && "then" in result && typeof result.then === "function") {
|
|
527
|
+
result.then(void 0, logHookFailure);
|
|
528
|
+
}
|
|
529
|
+
} catch (err) {
|
|
530
|
+
logHookFailure(err);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
var stripIpv6Brackets = (host) => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
534
|
+
var parsePort = (portRaw) => {
|
|
535
|
+
if (!portRaw) return void 0;
|
|
536
|
+
const port = Number.parseInt(portRaw, 10);
|
|
537
|
+
return Number.isInteger(port) ? port : void 0;
|
|
538
|
+
};
|
|
539
|
+
var parseServerAddress = (servers) => {
|
|
540
|
+
const raw = servers[0];
|
|
541
|
+
if (!raw) return null;
|
|
542
|
+
if (raw.includes("://")) {
|
|
543
|
+
try {
|
|
544
|
+
const url = new URL(raw);
|
|
545
|
+
if (url.hostname.length === 0) return null;
|
|
546
|
+
const host2 = stripIpv6Brackets(url.hostname);
|
|
547
|
+
const port2 = parsePort(url.port || void 0);
|
|
548
|
+
return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (raw.startsWith("[")) {
|
|
554
|
+
const closeIdx = raw.indexOf("]");
|
|
555
|
+
if (closeIdx <= 0) return null;
|
|
556
|
+
const host2 = raw.slice(1, closeIdx);
|
|
557
|
+
const port2 = parsePort(raw.slice(closeIdx + 1).replace(/^:/u, ""));
|
|
558
|
+
return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
|
|
559
|
+
}
|
|
560
|
+
const [host, portRaw] = raw.split(":");
|
|
561
|
+
if (!host) return null;
|
|
562
|
+
const port = parsePort(portRaw);
|
|
563
|
+
return port === void 0 ? { host } : { host, port };
|
|
564
|
+
};
|
|
565
|
+
var deriveOtelAttrs = (options) => ({
|
|
566
|
+
otel: resolveOtelOptions(options.otel),
|
|
567
|
+
serviceName: internalName(options.name),
|
|
568
|
+
serverEndpoint: parseServerAddress(options.servers)
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// src/otel/propagator.ts
|
|
572
|
+
var import_api = require("@opentelemetry/api");
|
|
573
|
+
var injectContext = (ctx, carrier, setter) => {
|
|
574
|
+
import_api.propagation.inject(ctx, carrier, setter);
|
|
575
|
+
};
|
|
576
|
+
var extractContext = (ctx, carrier, getter) => import_api.propagation.extract(ctx, carrier, getter);
|
|
577
|
+
|
|
578
|
+
// src/otel/tracer.ts
|
|
579
|
+
var import_api2 = require("@opentelemetry/api");
|
|
580
|
+
var PACKAGE_VERSION = true ? "2.10.0" : "0.0.0";
|
|
581
|
+
var getTracer = () => import_api2.trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
|
|
582
|
+
|
|
583
|
+
// src/otel/carrier.ts
|
|
584
|
+
var hdrsSetter = {
|
|
585
|
+
set: (headers2, key, value) => {
|
|
586
|
+
headers2.set(key, value);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var hdrsGetter = {
|
|
590
|
+
keys: (headers2) => headers2 ? headers2.keys() : [],
|
|
591
|
+
get: (headers2, key) => {
|
|
592
|
+
if (!headers2) return void 0;
|
|
593
|
+
const all = typeof headers2.values === "function" ? headers2.values(key) : void 0;
|
|
594
|
+
if (Array.isArray(all)) {
|
|
595
|
+
const nonEmpty = all.filter((value) => value !== "");
|
|
596
|
+
if (nonEmpty.length === 0) return void 0;
|
|
597
|
+
return nonEmpty.join(",");
|
|
598
|
+
}
|
|
599
|
+
const single = headers2.get(key);
|
|
600
|
+
return single === "" ? void 0 : single;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/otel/attributes.ts
|
|
605
|
+
var MESSAGING_SYSTEM = "nats";
|
|
606
|
+
var baseMessagingAttributes = (ctx) => {
|
|
607
|
+
const attrs = {
|
|
608
|
+
[ATTR_MESSAGING_SYSTEM]: MESSAGING_SYSTEM,
|
|
609
|
+
[ATTR_MESSAGING_DESTINATION_NAME]: ctx.subject,
|
|
610
|
+
[ATTR_MESSAGING_CLIENT_ID]: ctx.serviceName
|
|
611
|
+
};
|
|
612
|
+
if (ctx.serverAddress) attrs[ATTR_SERVER_ADDRESS] = ctx.serverAddress;
|
|
613
|
+
if (ctx.serverPort !== void 0) attrs[ATTR_SERVER_PORT] = ctx.serverPort;
|
|
614
|
+
if (ctx.pattern && ctx.pattern !== ctx.subject) {
|
|
615
|
+
attrs[ATTR_MESSAGING_DESTINATION_TEMPLATE] = ctx.pattern;
|
|
616
|
+
}
|
|
617
|
+
return attrs;
|
|
618
|
+
};
|
|
619
|
+
var jetstreamKindForPublish = (kind) => kind === "rpc.request" /* RpcRequest */ ? "rpc" /* Rpc */ : kind;
|
|
620
|
+
var buildPublishAttributes = (ctx) => {
|
|
621
|
+
const attrs = {
|
|
622
|
+
...baseMessagingAttributes(ctx),
|
|
623
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PUBLISH,
|
|
624
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
|
|
625
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
|
|
626
|
+
[ATTR_JETSTREAM_KIND]: jetstreamKindForPublish(ctx.kind)
|
|
627
|
+
};
|
|
628
|
+
if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
|
|
629
|
+
if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
|
|
630
|
+
return attrs;
|
|
631
|
+
};
|
|
632
|
+
var buildConsumeAttributes = (ctx) => {
|
|
633
|
+
const { msg, info, kind, payloadBytes } = ctx;
|
|
634
|
+
const attrs = {
|
|
635
|
+
...baseMessagingAttributes(ctx),
|
|
636
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PROCESS,
|
|
637
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
|
|
638
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: payloadBytes,
|
|
639
|
+
[ATTR_JETSTREAM_KIND]: kind
|
|
640
|
+
};
|
|
641
|
+
if (info) {
|
|
642
|
+
attrs[ATTR_MESSAGING_NATS_STREAM_NAME] = info.stream;
|
|
643
|
+
attrs[ATTR_MESSAGING_CONSUMER_GROUP_NAME] = info.consumer;
|
|
644
|
+
attrs[ATTR_MESSAGING_NATS_STREAM_SEQUENCE] = info.streamSequence;
|
|
645
|
+
attrs[ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE] = info.deliverySequence;
|
|
646
|
+
attrs[ATTR_MESSAGING_NATS_DELIVERY_COUNT] = info.deliveryCount;
|
|
647
|
+
}
|
|
648
|
+
const messageId = msg.headers?.get(NATS_MSG_ID_HEADER);
|
|
649
|
+
if (messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = messageId;
|
|
650
|
+
return attrs;
|
|
651
|
+
};
|
|
652
|
+
var buildRpcClientAttributes = (ctx) => {
|
|
653
|
+
const attrs = {
|
|
654
|
+
...baseMessagingAttributes(ctx),
|
|
655
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_SEND,
|
|
656
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
|
|
657
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
|
|
658
|
+
[ATTR_JETSTREAM_KIND]: "rpc" /* Rpc */
|
|
659
|
+
};
|
|
660
|
+
if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
|
|
661
|
+
if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
|
|
662
|
+
return attrs;
|
|
663
|
+
};
|
|
664
|
+
var buildDeadLetterAttributes = (ctx) => {
|
|
665
|
+
const attrs = {
|
|
666
|
+
...baseMessagingAttributes(ctx),
|
|
667
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_DEAD_LETTER,
|
|
668
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
|
|
669
|
+
[ATTR_MESSAGING_NATS_DELIVERY_COUNT]: ctx.finalDeliveryCount
|
|
670
|
+
};
|
|
671
|
+
if (ctx.reason) attrs[ATTR_JETSTREAM_DEAD_LETTER_REASON] = ctx.reason;
|
|
672
|
+
return attrs;
|
|
673
|
+
};
|
|
674
|
+
var extractFromRpcException = (record) => {
|
|
675
|
+
const getError = record.getError;
|
|
676
|
+
if (typeof getError !== "function") return void 0;
|
|
677
|
+
return codeFromPayload(getError.call(record));
|
|
678
|
+
};
|
|
679
|
+
var extractFromHttpException = (record) => {
|
|
680
|
+
const getStatus = record.getStatus;
|
|
681
|
+
if (typeof getStatus !== "function") return void 0;
|
|
682
|
+
const status = getStatus.call(record);
|
|
683
|
+
return typeof status === "number" && Number.isFinite(status) ? `HTTP_${status}` : void 0;
|
|
684
|
+
};
|
|
685
|
+
var extractFromOwnCode = (record) => {
|
|
686
|
+
const code = record.code ?? record.errorCode;
|
|
687
|
+
return typeof code === "string" && isStableErrorCode(code) ? code : void 0;
|
|
688
|
+
};
|
|
689
|
+
var extractExpectedErrorCode = (err) => {
|
|
690
|
+
if (err === null || typeof err !== "object") return void 0;
|
|
691
|
+
const record = err;
|
|
692
|
+
if (hasAncestorNamed(err, "RpcException")) {
|
|
693
|
+
const fromPayload = extractFromRpcException(record);
|
|
694
|
+
if (fromPayload !== void 0) return fromPayload;
|
|
695
|
+
}
|
|
696
|
+
if (hasAncestorNamed(err, "HttpException")) {
|
|
697
|
+
const fromStatus = extractFromHttpException(record);
|
|
698
|
+
if (fromStatus !== void 0) return fromStatus;
|
|
699
|
+
}
|
|
700
|
+
return extractFromOwnCode(record);
|
|
701
|
+
};
|
|
702
|
+
var hasAncestorNamed = (err, name) => {
|
|
703
|
+
let proto = Object.getPrototypeOf(err);
|
|
704
|
+
while (proto) {
|
|
705
|
+
const ctorName = proto.constructor?.name;
|
|
706
|
+
if (ctorName === name) return true;
|
|
707
|
+
proto = Object.getPrototypeOf(proto);
|
|
708
|
+
}
|
|
709
|
+
return false;
|
|
710
|
+
};
|
|
711
|
+
var STABLE_ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/u;
|
|
712
|
+
var isStableErrorCode = (value) => STABLE_ERROR_CODE_RE.test(value);
|
|
713
|
+
var codeFromPayload = (payload) => {
|
|
714
|
+
if (payload === null || payload === void 0) return void 0;
|
|
715
|
+
if (typeof payload === "string") return isStableErrorCode(payload) ? payload : void 0;
|
|
716
|
+
if (typeof payload === "object") {
|
|
717
|
+
const code = payload.code;
|
|
718
|
+
if (typeof code === "string" && isStableErrorCode(code)) return code;
|
|
719
|
+
}
|
|
720
|
+
return void 0;
|
|
721
|
+
};
|
|
722
|
+
var buildExpectedErrorAttributes = (err) => {
|
|
723
|
+
const attrs = {
|
|
724
|
+
[ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR]: true
|
|
725
|
+
};
|
|
726
|
+
const code = extractExpectedErrorCode(err);
|
|
727
|
+
if (code !== void 0) attrs[ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE] = code;
|
|
728
|
+
return attrs;
|
|
729
|
+
};
|
|
730
|
+
var applyExpectedErrorAttributes = (span, err) => {
|
|
731
|
+
span.setAttributes(buildExpectedErrorAttributes(err));
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// src/otel/spans/publish.ts
|
|
735
|
+
var import_common2 = require("@nestjs/common");
|
|
736
|
+
var import_api3 = require("@opentelemetry/api");
|
|
737
|
+
var logger2 = new import_common2.Logger("Jetstream:Otel");
|
|
738
|
+
var shouldTracePublishSafe = (predicate, subject, record) => {
|
|
739
|
+
if (!predicate) return true;
|
|
740
|
+
try {
|
|
741
|
+
return predicate(subject, record);
|
|
742
|
+
} catch (err) {
|
|
743
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
744
|
+
logger2.debug(`OTel shouldTracePublish threw: ${message}`);
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
var withPublishSpan = async (ctx, config, fn) => {
|
|
749
|
+
if (!config.enabled) return fn();
|
|
750
|
+
const shouldCreateSpan = config.traces.has("publish" /* Publish */) && shouldTracePublishSafe(config.shouldTracePublish, ctx.subject, ctx.record);
|
|
751
|
+
if (!shouldCreateSpan) {
|
|
752
|
+
injectContext(import_api3.context.active(), ctx.headers, hdrsSetter);
|
|
753
|
+
return fn();
|
|
754
|
+
}
|
|
755
|
+
const tracer = getTracer();
|
|
756
|
+
const span = tracer.startSpan(`${SPAN_NAME_PUBLISH} ${ctx.subject}`, {
|
|
757
|
+
kind: import_api3.SpanKind.PRODUCER,
|
|
758
|
+
attributes: {
|
|
759
|
+
...buildPublishAttributes({
|
|
760
|
+
subject: ctx.subject,
|
|
761
|
+
pattern: ctx.pattern,
|
|
762
|
+
serviceName: ctx.serviceName,
|
|
763
|
+
serverAddress: ctx.endpoint?.host,
|
|
764
|
+
serverPort: ctx.endpoint?.port,
|
|
765
|
+
kind: ctx.kind,
|
|
766
|
+
payloadBytes: ctx.payloadBytes,
|
|
767
|
+
messageId: ctx.messageId,
|
|
768
|
+
correlationId: ctx.correlationId
|
|
769
|
+
}),
|
|
770
|
+
...ctx.scheduleTarget ? { [ATTR_JETSTREAM_SCHEDULE_TARGET]: ctx.scheduleTarget } : {},
|
|
771
|
+
...captureMatchingHeaders(ctx.headers, config.captureHeaders),
|
|
772
|
+
...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
const ctxWithSpan = import_api3.trace.setSpan(import_api3.context.active(), span);
|
|
776
|
+
injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
|
|
777
|
+
const start = Date.now();
|
|
778
|
+
const invokeResponseHook = (error) => {
|
|
779
|
+
import_api3.context.with(ctxWithSpan, () => {
|
|
780
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
781
|
+
subject: ctx.subject,
|
|
782
|
+
durationMs: Date.now() - start,
|
|
783
|
+
error
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
try {
|
|
788
|
+
const result = await import_api3.context.with(ctxWithSpan, async () => {
|
|
789
|
+
safelyInvokeHook(HOOK_PUBLISH, config.publishHook, span, {
|
|
790
|
+
subject: ctx.subject,
|
|
791
|
+
record: ctx.record,
|
|
792
|
+
kind: ctx.kind
|
|
793
|
+
});
|
|
794
|
+
return fn();
|
|
795
|
+
});
|
|
796
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
797
|
+
invokeResponseHook();
|
|
798
|
+
return result;
|
|
799
|
+
} catch (err) {
|
|
800
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
801
|
+
span.recordException(error);
|
|
802
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: error.message });
|
|
803
|
+
invokeResponseHook(error);
|
|
804
|
+
throw err;
|
|
805
|
+
} finally {
|
|
806
|
+
span.end();
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/otel/spans/consume.ts
|
|
811
|
+
var import_api4 = require("@opentelemetry/api");
|
|
812
|
+
var isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
|
|
813
|
+
var applyExpectedError = (span, err) => {
|
|
814
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
815
|
+
applyExpectedErrorAttributes(span, err);
|
|
816
|
+
};
|
|
817
|
+
var applyUnexpectedError = (span, err) => {
|
|
818
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
819
|
+
span.recordException(error);
|
|
820
|
+
span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: error.message });
|
|
821
|
+
};
|
|
822
|
+
var withConsumeSpan = (ctx, config, fn, options = {}) => {
|
|
823
|
+
if (!config.enabled) return fn();
|
|
824
|
+
const parentCtx = extractContext(import_api4.ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
|
|
825
|
+
const shouldCreateSpan = config.traces.has("consume" /* Consume */) && (config.shouldTraceConsume?.(ctx.subject, ctx.msg) ?? true);
|
|
826
|
+
if (!shouldCreateSpan) {
|
|
827
|
+
return import_api4.context.with(parentCtx, fn);
|
|
828
|
+
}
|
|
829
|
+
const tracer = getTracer();
|
|
830
|
+
const span = tracer.startSpan(
|
|
831
|
+
`${SPAN_NAME_PROCESS} ${ctx.subject}`,
|
|
832
|
+
{
|
|
833
|
+
kind: import_api4.SpanKind.CONSUMER,
|
|
834
|
+
attributes: {
|
|
835
|
+
...buildConsumeAttributes({
|
|
836
|
+
subject: ctx.subject,
|
|
837
|
+
pattern: ctx.pattern,
|
|
838
|
+
msg: ctx.msg,
|
|
839
|
+
info: ctx.info,
|
|
840
|
+
kind: ctx.kind,
|
|
841
|
+
payloadBytes: ctx.payloadBytes,
|
|
842
|
+
serviceName: ctx.serviceName,
|
|
843
|
+
serverAddress: ctx.endpoint?.host,
|
|
844
|
+
serverPort: ctx.endpoint?.port
|
|
845
|
+
}),
|
|
846
|
+
...captureMatchingHeaders(ctx.msg.headers, config.captureHeaders),
|
|
847
|
+
...captureBodyAttribute(ctx.subject, ctx.msg.data, config.captureBody)
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
parentCtx
|
|
851
|
+
);
|
|
852
|
+
const ctxWithSpan = import_api4.trace.setSpan(parentCtx, span);
|
|
853
|
+
const start = Date.now();
|
|
854
|
+
let finalized = false;
|
|
855
|
+
const { signal, timeoutLabel = "handler.timeout" } = options;
|
|
856
|
+
let detachAbort = null;
|
|
857
|
+
const invokeResponseHook = (durationMs, error) => {
|
|
858
|
+
import_api4.context.with(ctxWithSpan, () => {
|
|
859
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
860
|
+
subject: ctx.subject,
|
|
861
|
+
durationMs,
|
|
862
|
+
error
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
};
|
|
866
|
+
const finishOk2 = () => {
|
|
867
|
+
if (finalized) return;
|
|
868
|
+
finalized = true;
|
|
869
|
+
detachAbort?.();
|
|
870
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
871
|
+
invokeResponseHook(Date.now() - start);
|
|
872
|
+
span.end();
|
|
873
|
+
};
|
|
874
|
+
const finishError2 = (err) => {
|
|
875
|
+
if (finalized) return;
|
|
876
|
+
finalized = true;
|
|
877
|
+
detachAbort?.();
|
|
878
|
+
let classification = "unexpected";
|
|
879
|
+
try {
|
|
880
|
+
classification = config.errorClassifier(err);
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
if (classification === "expected") {
|
|
884
|
+
applyExpectedError(span, err);
|
|
885
|
+
} else {
|
|
886
|
+
applyUnexpectedError(span, err);
|
|
887
|
+
}
|
|
888
|
+
invokeResponseHook(Date.now() - start, err instanceof Error ? err : new Error(String(err)));
|
|
889
|
+
span.end();
|
|
890
|
+
};
|
|
891
|
+
const onAbort = () => {
|
|
892
|
+
if (finalized) return;
|
|
893
|
+
finalized = true;
|
|
894
|
+
const error = new Error(timeoutLabel);
|
|
895
|
+
span.addEvent(timeoutLabel);
|
|
896
|
+
span.recordException(error);
|
|
897
|
+
span.setStatus({ code: import_api4.SpanStatusCode.ERROR, message: timeoutLabel });
|
|
898
|
+
invokeResponseHook(Date.now() - start, error);
|
|
899
|
+
span.end();
|
|
900
|
+
};
|
|
901
|
+
if (signal) {
|
|
902
|
+
if (signal.aborted) {
|
|
903
|
+
onAbort();
|
|
904
|
+
} else {
|
|
905
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
906
|
+
detachAbort = () => {
|
|
907
|
+
signal.removeEventListener("abort", onAbort);
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
let result;
|
|
912
|
+
try {
|
|
913
|
+
result = import_api4.context.with(ctxWithSpan, () => {
|
|
914
|
+
safelyInvokeHook(HOOK_CONSUME, config.consumeHook, span, {
|
|
915
|
+
subject: ctx.subject,
|
|
916
|
+
msg: ctx.msg,
|
|
917
|
+
handlerMetadata: ctx.handlerMetadata,
|
|
918
|
+
kind: ctx.kind
|
|
919
|
+
});
|
|
920
|
+
return fn();
|
|
921
|
+
});
|
|
922
|
+
} catch (err) {
|
|
923
|
+
finishError2(err);
|
|
924
|
+
throw err;
|
|
925
|
+
}
|
|
926
|
+
if (isPromiseLike(result)) {
|
|
927
|
+
return Promise.resolve(result).then(
|
|
928
|
+
(value) => {
|
|
929
|
+
finishOk2();
|
|
930
|
+
return value;
|
|
931
|
+
},
|
|
932
|
+
(err) => {
|
|
933
|
+
finishError2(err);
|
|
934
|
+
throw err;
|
|
935
|
+
}
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
finishOk2();
|
|
939
|
+
return result;
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
// src/otel/spans/rpc-client.ts
|
|
943
|
+
var import_common3 = require("@nestjs/common");
|
|
944
|
+
var import_api5 = require("@opentelemetry/api");
|
|
945
|
+
var logger3 = new import_common3.Logger("Jetstream:Otel");
|
|
946
|
+
var RPC_TIMEOUT_MESSAGE = "rpc.timeout";
|
|
947
|
+
var beginRpcClientSpan = (ctx, config) => {
|
|
948
|
+
if (!config.enabled) {
|
|
949
|
+
return {
|
|
950
|
+
activeContext: import_api5.context.active(),
|
|
951
|
+
finish: () => void 0
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
if (!config.traces.has("rpc.client.send" /* RpcClientSend */)) {
|
|
955
|
+
injectContext(import_api5.context.active(), ctx.headers, hdrsSetter);
|
|
956
|
+
return {
|
|
957
|
+
activeContext: import_api5.context.active(),
|
|
958
|
+
finish: () => void 0
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const tracer = getTracer();
|
|
962
|
+
const span = tracer.startSpan(`${SPAN_NAME_SEND} ${ctx.subject}`, {
|
|
963
|
+
kind: import_api5.SpanKind.CLIENT,
|
|
964
|
+
attributes: {
|
|
965
|
+
...buildRpcClientAttributes({
|
|
966
|
+
subject: ctx.subject,
|
|
967
|
+
pattern: ctx.pattern,
|
|
968
|
+
correlationId: ctx.correlationId,
|
|
969
|
+
payloadBytes: ctx.payloadBytes,
|
|
970
|
+
messageId: ctx.messageId,
|
|
971
|
+
serviceName: ctx.serviceName,
|
|
972
|
+
serverAddress: ctx.endpoint?.host,
|
|
973
|
+
serverPort: ctx.endpoint?.port
|
|
974
|
+
}),
|
|
975
|
+
...captureMatchingHeaders(ctx.headers, config.captureHeaders),
|
|
976
|
+
...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
const ctxWithSpan = import_api5.trace.setSpan(import_api5.context.active(), span);
|
|
980
|
+
injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
|
|
981
|
+
const start = Date.now();
|
|
982
|
+
let finalized = false;
|
|
983
|
+
const finish = (outcome) => {
|
|
984
|
+
if (finalized) return;
|
|
985
|
+
finalized = true;
|
|
986
|
+
let reply;
|
|
987
|
+
let error;
|
|
988
|
+
switch (outcome.kind) {
|
|
989
|
+
case "ok" /* Ok */:
|
|
990
|
+
reply = outcome.reply;
|
|
991
|
+
span.setStatus({ code: import_api5.SpanStatusCode.OK });
|
|
992
|
+
break;
|
|
993
|
+
case "reply-error" /* ReplyError */:
|
|
994
|
+
reply = outcome.replyPayload;
|
|
995
|
+
applyExpectedErrorAttributes(span, outcome.replyPayload);
|
|
996
|
+
span.setStatus({ code: import_api5.SpanStatusCode.OK });
|
|
997
|
+
break;
|
|
998
|
+
case "timeout" /* Timeout */:
|
|
999
|
+
error = new Error(RPC_TIMEOUT_MESSAGE);
|
|
1000
|
+
span.addEvent(RPC_TIMEOUT_MESSAGE);
|
|
1001
|
+
span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: RPC_TIMEOUT_MESSAGE });
|
|
1002
|
+
break;
|
|
1003
|
+
case "error" /* Error */:
|
|
1004
|
+
error = outcome.error;
|
|
1005
|
+
span.recordException(outcome.error);
|
|
1006
|
+
span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: outcome.error.message });
|
|
1007
|
+
break;
|
|
1008
|
+
default: {
|
|
1009
|
+
const unknownOutcome = outcome;
|
|
1010
|
+
logger3.error(`Unhandled RPC outcome: ${String(unknownOutcome.kind)}`);
|
|
1011
|
+
span.setStatus({ code: import_api5.SpanStatusCode.ERROR, message: "unknown outcome" });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
import_api5.context.with(ctxWithSpan, () => {
|
|
1015
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
1016
|
+
subject: ctx.subject,
|
|
1017
|
+
durationMs: Date.now() - start,
|
|
1018
|
+
reply,
|
|
1019
|
+
error
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
span.end();
|
|
1023
|
+
};
|
|
1024
|
+
return { activeContext: ctxWithSpan, finish };
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// src/otel/spans/dead-letter.ts
|
|
1028
|
+
var import_api6 = require("@opentelemetry/api");
|
|
1029
|
+
var withDeadLetterSpan = async (ctx, config, fn) => {
|
|
1030
|
+
if (!config.enabled || !config.traces.has("dead_letter" /* DeadLetter */)) {
|
|
1031
|
+
return fn();
|
|
1032
|
+
}
|
|
1033
|
+
const parentCtx = extractContext(import_api6.ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
|
|
1034
|
+
const tracer = getTracer();
|
|
1035
|
+
const span = tracer.startSpan(
|
|
1036
|
+
`${SPAN_NAME_DEAD_LETTER} ${ctx.msg.subject}`,
|
|
1037
|
+
{
|
|
1038
|
+
kind: import_api6.SpanKind.INTERNAL,
|
|
1039
|
+
attributes: buildDeadLetterAttributes({
|
|
1040
|
+
subject: ctx.msg.subject,
|
|
1041
|
+
pattern: ctx.pattern,
|
|
1042
|
+
serviceName: ctx.serviceName,
|
|
1043
|
+
serverAddress: ctx.endpoint?.host,
|
|
1044
|
+
serverPort: ctx.endpoint?.port,
|
|
1045
|
+
finalDeliveryCount: ctx.finalDeliveryCount,
|
|
1046
|
+
reason: ctx.reason
|
|
1047
|
+
})
|
|
1048
|
+
},
|
|
1049
|
+
parentCtx
|
|
1050
|
+
);
|
|
1051
|
+
const ctxWithSpan = import_api6.trace.setSpan(parentCtx, span);
|
|
1052
|
+
const start = Date.now();
|
|
1053
|
+
const invokeResponseHook = (error) => {
|
|
1054
|
+
import_api6.context.with(ctxWithSpan, () => {
|
|
1055
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
1056
|
+
subject: ctx.msg.subject,
|
|
1057
|
+
durationMs: Date.now() - start,
|
|
1058
|
+
error
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
};
|
|
1062
|
+
try {
|
|
1063
|
+
const result = await import_api6.context.with(ctxWithSpan, fn);
|
|
1064
|
+
span.setStatus({ code: import_api6.SpanStatusCode.OK });
|
|
1065
|
+
invokeResponseHook();
|
|
1066
|
+
return result;
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1069
|
+
span.recordException(error);
|
|
1070
|
+
span.setStatus({ code: import_api6.SpanStatusCode.ERROR, message: error.message });
|
|
1071
|
+
invokeResponseHook(error);
|
|
1072
|
+
throw err;
|
|
1073
|
+
} finally {
|
|
1074
|
+
span.end();
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
// src/otel/spans/infrastructure.ts
|
|
1079
|
+
var import_common4 = require("@nestjs/common");
|
|
1080
|
+
var import_api7 = require("@opentelemetry/api");
|
|
1081
|
+
var logger4 = new import_common4.Logger("Jetstream:Otel");
|
|
1082
|
+
var startInfraSpan = (config, traceKind, name, ctx, extraAttributes = {}) => {
|
|
1083
|
+
if (!config.enabled || !config.traces.has(traceKind)) return null;
|
|
1084
|
+
const tracer = getTracer();
|
|
1085
|
+
const attributes = {
|
|
1086
|
+
[ATTR_JETSTREAM_SERVICE_NAME]: ctx.serviceName
|
|
1087
|
+
};
|
|
1088
|
+
if (ctx.endpoint?.host) attributes[ATTR_SERVER_ADDRESS] = ctx.endpoint.host;
|
|
1089
|
+
if (ctx.endpoint?.port !== void 0) attributes[ATTR_SERVER_PORT] = ctx.endpoint.port;
|
|
1090
|
+
for (const [key, value] of Object.entries(extraAttributes)) {
|
|
1091
|
+
if (value !== void 0) attributes[key] = value;
|
|
1092
|
+
}
|
|
1093
|
+
return tracer.startSpan(name, { kind: import_api7.SpanKind.INTERNAL, attributes });
|
|
1094
|
+
};
|
|
1095
|
+
var finishOk = (span) => {
|
|
1096
|
+
span.setStatus({ code: import_api7.SpanStatusCode.OK });
|
|
1097
|
+
span.end();
|
|
1098
|
+
};
|
|
1099
|
+
var finishError = (span, err) => {
|
|
1100
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1101
|
+
span.recordException(error);
|
|
1102
|
+
span.setStatus({ code: import_api7.SpanStatusCode.ERROR, message: error.message });
|
|
1103
|
+
span.end();
|
|
1104
|
+
};
|
|
1105
|
+
var wrapInfra = async (config, traceKind, name, ctx, attributes, op) => {
|
|
1106
|
+
const span = startInfraSpan(config, traceKind, name, ctx, attributes);
|
|
1107
|
+
if (!span) return op();
|
|
1108
|
+
const ctxWithSpan = import_api7.trace.setSpan(import_api7.context.active(), span);
|
|
1109
|
+
try {
|
|
1110
|
+
const result = await import_api7.context.with(ctxWithSpan, op);
|
|
1111
|
+
finishOk(span);
|
|
1112
|
+
return result;
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
finishError(span, err);
|
|
1115
|
+
throw err;
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
var beginConnectionLifecycleSpan = (config, ctx) => {
|
|
1119
|
+
const span = startInfraSpan(
|
|
1120
|
+
config,
|
|
1121
|
+
"connection.lifecycle" /* ConnectionLifecycle */,
|
|
1122
|
+
SPAN_NAME_NATS_CONNECTION,
|
|
1123
|
+
ctx,
|
|
1124
|
+
{ [ATTR_NATS_CONNECTION_SERVER]: ctx.server }
|
|
1125
|
+
);
|
|
1126
|
+
if (!span) {
|
|
1127
|
+
return {
|
|
1128
|
+
recordEvent: () => void 0,
|
|
1129
|
+
finish: () => void 0
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
let finalized = false;
|
|
1133
|
+
return {
|
|
1134
|
+
recordEvent: (name, attributes) => {
|
|
1135
|
+
if (finalized) {
|
|
1136
|
+
logger4.debug(`recordEvent('${name}') called after connection span finished`);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
span.addEvent(name, attributes);
|
|
1140
|
+
},
|
|
1141
|
+
finish: (err) => {
|
|
1142
|
+
if (finalized) return;
|
|
1143
|
+
finalized = true;
|
|
1144
|
+
if (err === void 0) finishOk(span);
|
|
1145
|
+
else finishError(span, err);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
};
|
|
1149
|
+
var withSelfHealingSpan = (config, ctx, op) => wrapInfra(
|
|
1150
|
+
config,
|
|
1151
|
+
"self_healing" /* SelfHealing */,
|
|
1152
|
+
SPAN_NAME_JETSTREAM_SELF_HEALING,
|
|
1153
|
+
ctx,
|
|
1154
|
+
{
|
|
1155
|
+
[ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
|
|
1156
|
+
[ATTR_MESSAGING_CONSUMER_GROUP_NAME]: ctx.consumer,
|
|
1157
|
+
[ATTR_JETSTREAM_SELF_HEALING_REASON]: ctx.reason
|
|
1158
|
+
},
|
|
1159
|
+
op
|
|
1160
|
+
);
|
|
1161
|
+
var withProvisioningSpan = (config, ctx, op) => wrapInfra(
|
|
1162
|
+
config,
|
|
1163
|
+
"provisioning" /* Provisioning */,
|
|
1164
|
+
`${SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX}${ctx.entity}`,
|
|
1165
|
+
ctx,
|
|
1166
|
+
{
|
|
1167
|
+
[ATTR_JETSTREAM_PROVISIONING_ENTITY]: ctx.entity,
|
|
1168
|
+
[ATTR_JETSTREAM_PROVISIONING_ACTION]: ctx.action,
|
|
1169
|
+
[ATTR_JETSTREAM_PROVISIONING_NAME]: ctx.name
|
|
1170
|
+
},
|
|
1171
|
+
op
|
|
1172
|
+
);
|
|
1173
|
+
var withMigrationSpan = (config, ctx, op) => wrapInfra(
|
|
1174
|
+
config,
|
|
1175
|
+
"migration" /* Migration */,
|
|
1176
|
+
SPAN_NAME_JETSTREAM_MIGRATION,
|
|
1177
|
+
ctx,
|
|
1178
|
+
{
|
|
1179
|
+
[ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
|
|
1180
|
+
[ATTR_JETSTREAM_MIGRATION_REASON]: ctx.reason
|
|
1181
|
+
},
|
|
1182
|
+
op
|
|
1183
|
+
);
|
|
1184
|
+
var withShutdownSpan = (config, ctx, op) => wrapInfra(config, "shutdown" /* Shutdown */, SPAN_NAME_JETSTREAM_SHUTDOWN, ctx, {}, op);
|
|
1185
|
+
|
|
267
1186
|
// src/client/jetstream.record.ts
|
|
268
1187
|
var JetstreamRecord = class {
|
|
269
1188
|
constructor(data, headers2, timeout, messageId, schedule, ttl) {
|
|
@@ -416,9 +1335,14 @@ var JetstreamRecordBuilder = class {
|
|
|
416
1335
|
this.ttlDuration
|
|
417
1336
|
);
|
|
418
1337
|
}
|
|
419
|
-
/**
|
|
1338
|
+
/**
|
|
1339
|
+
* Validate that a header key is not reserved. NATS treats header names
|
|
1340
|
+
* case-insensitively, so the check is against the lowercase form to keep
|
|
1341
|
+
* `'X-Correlation-ID'`, `'x-correlation-id'`, and any other casing in
|
|
1342
|
+
* lockstep. `RESERVED_HEADERS` is defined as an all-lowercase set.
|
|
1343
|
+
*/
|
|
420
1344
|
validateHeaderKey(key) {
|
|
421
|
-
if (RESERVED_HEADERS.has(key)) {
|
|
1345
|
+
if (RESERVED_HEADERS.has(key.toLowerCase())) {
|
|
422
1346
|
throw new Error(
|
|
423
1347
|
`Header "${key}" is reserved by the JetStream transport and cannot be set manually. Reserved headers: ${[...RESERVED_HEADERS].join(", ")}`
|
|
424
1348
|
);
|
|
@@ -438,6 +1362,12 @@ var nanosToGoDuration = (nanos) => {
|
|
|
438
1362
|
};
|
|
439
1363
|
|
|
440
1364
|
// src/client/jetstream.client.ts
|
|
1365
|
+
var BROADCAST_SUBJECT_PREFIX = "broadcast.";
|
|
1366
|
+
var detectEventKind = (pattern) => {
|
|
1367
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */)) return "broadcast" /* Broadcast */;
|
|
1368
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
|
|
1369
|
+
return "event" /* Event */;
|
|
1370
|
+
};
|
|
441
1371
|
var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
442
1372
|
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
443
1373
|
super();
|
|
@@ -447,12 +1377,40 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
447
1377
|
this.eventBus = eventBus;
|
|
448
1378
|
this.targetName = targetServiceName;
|
|
449
1379
|
this.callerName = internalName(this.rootOptions.name);
|
|
450
|
-
|
|
451
|
-
|
|
1380
|
+
const targetInternal = internalName(targetServiceName);
|
|
1381
|
+
this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
|
|
1382
|
+
this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
|
|
1383
|
+
this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
|
|
1384
|
+
this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
|
|
1385
|
+
this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
|
|
1386
|
+
const derived = deriveOtelAttrs(this.rootOptions);
|
|
1387
|
+
this.otel = derived.otel;
|
|
1388
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
1389
|
+
}
|
|
1390
|
+
logger = new import_common5.Logger("Jetstream:Client");
|
|
452
1391
|
/** Target service name this client sends messages to. */
|
|
453
1392
|
targetName;
|
|
454
1393
|
/** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
|
|
455
1394
|
callerName;
|
|
1395
|
+
/**
|
|
1396
|
+
* Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
|
|
1397
|
+
* per stream kind this client may publish to. Built once in the constructor
|
|
1398
|
+
* so producing a full subject is a single string concat with the user pattern.
|
|
1399
|
+
*/
|
|
1400
|
+
eventSubjectPrefix;
|
|
1401
|
+
commandSubjectPrefix;
|
|
1402
|
+
orderedSubjectPrefix;
|
|
1403
|
+
/**
|
|
1404
|
+
* RPC configuration snapshots. The values are derived from rootOptions at
|
|
1405
|
+
* construction time so the publish hot path never has to re-run
|
|
1406
|
+
* isCoreRpcMode / getRpcTimeout on every call.
|
|
1407
|
+
*/
|
|
1408
|
+
isCoreMode;
|
|
1409
|
+
defaultRpcTimeout;
|
|
1410
|
+
/** Resolved OpenTelemetry configuration, computed once in the constructor. */
|
|
1411
|
+
otel;
|
|
1412
|
+
/** Server endpoint parts used for `server.address` / `server.port` span attributes. */
|
|
1413
|
+
serverEndpoint;
|
|
456
1414
|
/** Shared inbox for JetStream-mode RPC responses. */
|
|
457
1415
|
inbox = null;
|
|
458
1416
|
inboxSubscription = null;
|
|
@@ -462,6 +1420,12 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
462
1420
|
pendingTimeouts = /* @__PURE__ */ new Map();
|
|
463
1421
|
/** Subscription to connection status events for disconnect handling. */
|
|
464
1422
|
statusSubscription = null;
|
|
1423
|
+
/**
|
|
1424
|
+
* Cached readiness flag. Once `connect()` has wired the inbox and status
|
|
1425
|
+
* subscription, subsequent publishes skip the `await connect()` microtask
|
|
1426
|
+
* and reach for the underlying connection synchronously instead.
|
|
1427
|
+
*/
|
|
1428
|
+
readyForPublish = false;
|
|
465
1429
|
/**
|
|
466
1430
|
* Establish connection. Called automatically by NestJS on first use.
|
|
467
1431
|
*
|
|
@@ -472,7 +1436,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
472
1436
|
*/
|
|
473
1437
|
async connect() {
|
|
474
1438
|
const nc = await this.connection.getConnection();
|
|
475
|
-
if (
|
|
1439
|
+
if (!this.isCoreMode && !this.inboxSubscription) {
|
|
476
1440
|
this.setupInbox(nc);
|
|
477
1441
|
}
|
|
478
1442
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
@@ -480,12 +1444,14 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
480
1444
|
this.handleDisconnect();
|
|
481
1445
|
}
|
|
482
1446
|
});
|
|
1447
|
+
this.readyForPublish = true;
|
|
483
1448
|
return nc;
|
|
484
1449
|
}
|
|
485
1450
|
/** Clean up resources: reject pending RPCs, unsubscribe from status events. */
|
|
486
1451
|
async close() {
|
|
487
1452
|
this.statusSubscription?.unsubscribe();
|
|
488
1453
|
this.statusSubscription = null;
|
|
1454
|
+
this.readyForPublish = false;
|
|
489
1455
|
this.rejectPendingRpcs(new Error("Client closed"));
|
|
490
1456
|
}
|
|
491
1457
|
/**
|
|
@@ -509,36 +1475,58 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
509
1475
|
* set to the original event subject.
|
|
510
1476
|
*/
|
|
511
1477
|
async dispatchEvent(packet) {
|
|
512
|
-
await this.connect();
|
|
1478
|
+
if (!this.readyForPublish) await this.connect();
|
|
513
1479
|
const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
514
1480
|
const eventSubject = this.buildEventSubject(packet.pattern);
|
|
1481
|
+
const publishSubject = schedule ? this.buildScheduleSubject(eventSubject) : eventSubject;
|
|
515
1482
|
const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1483
|
+
const encoded = this.codec.encode(data);
|
|
1484
|
+
const effectiveMsgId = messageId ?? import_nuid.nuid.next();
|
|
1485
|
+
const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
|
|
1486
|
+
await withPublishSpan(
|
|
1487
|
+
{
|
|
1488
|
+
subject: publishSubject,
|
|
1489
|
+
pattern: packet.pattern,
|
|
1490
|
+
record,
|
|
1491
|
+
kind: detectEventKind(packet.pattern),
|
|
1492
|
+
payloadBytes: encoded.length,
|
|
1493
|
+
payload: encoded,
|
|
1494
|
+
messageId: effectiveMsgId,
|
|
519
1495
|
headers: msgHeaders,
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
schedule:
|
|
523
|
-
|
|
524
|
-
|
|
1496
|
+
serviceName: this.callerName,
|
|
1497
|
+
endpoint: this.serverEndpoint,
|
|
1498
|
+
scheduleTarget: schedule ? eventSubject : void 0
|
|
1499
|
+
},
|
|
1500
|
+
this.otel,
|
|
1501
|
+
async () => {
|
|
1502
|
+
const warnIfDuplicate = (kindLabel, ack2) => {
|
|
1503
|
+
if (ack2.duplicate) {
|
|
1504
|
+
this.logger.warn(
|
|
1505
|
+
`Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
if (schedule) {
|
|
1510
|
+
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1511
|
+
headers: msgHeaders,
|
|
1512
|
+
msgID: effectiveMsgId,
|
|
1513
|
+
ttl,
|
|
1514
|
+
schedule: {
|
|
1515
|
+
specification: schedule.at,
|
|
1516
|
+
target: eventSubject
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
warnIfDuplicate("scheduled", ack2);
|
|
1520
|
+
return;
|
|
525
1521
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
} else {
|
|
533
|
-
const ack = await this.connection.getJetStreamClient().publish(eventSubject, this.codec.encode(data), {
|
|
534
|
-
headers: msgHeaders,
|
|
535
|
-
msgID: messageId ?? import_nuid.nuid.next(),
|
|
536
|
-
ttl
|
|
537
|
-
});
|
|
538
|
-
if (ack.duplicate) {
|
|
539
|
-
this.logger.warn(`Duplicate event publish detected: ${eventSubject} (seq: ${ack.seq})`);
|
|
1522
|
+
const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1523
|
+
headers: msgHeaders,
|
|
1524
|
+
msgID: effectiveMsgId,
|
|
1525
|
+
ttl
|
|
1526
|
+
});
|
|
1527
|
+
warnIfDuplicate("event", ack);
|
|
540
1528
|
}
|
|
541
|
-
|
|
1529
|
+
);
|
|
542
1530
|
return void 0;
|
|
543
1531
|
}
|
|
544
1532
|
/**
|
|
@@ -548,7 +1536,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
548
1536
|
* JetStream mode: publishes to stream + waits for inbox response.
|
|
549
1537
|
*/
|
|
550
1538
|
publish(packet, callback) {
|
|
551
|
-
const subject =
|
|
1539
|
+
const subject = this.commandSubjectPrefix + packet.pattern;
|
|
552
1540
|
const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
553
1541
|
if (schedule) {
|
|
554
1542
|
this.logger.warn(
|
|
@@ -565,7 +1553,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
565
1553
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
566
1554
|
};
|
|
567
1555
|
let jetStreamCorrelationId = null;
|
|
568
|
-
if (
|
|
1556
|
+
if (this.isCoreMode) {
|
|
569
1557
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
570
1558
|
} else {
|
|
571
1559
|
jetStreamCorrelationId = import_nuid.nuid.next();
|
|
@@ -589,37 +1577,102 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
589
1577
|
}
|
|
590
1578
|
/** Core mode: nc.request() with timeout. */
|
|
591
1579
|
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
1580
|
+
const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
|
|
1581
|
+
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
1582
|
+
const encoded = this.codec.encode(data);
|
|
1583
|
+
const spanHandle = beginRpcClientSpan(
|
|
1584
|
+
{
|
|
1585
|
+
subject,
|
|
1586
|
+
payloadBytes: encoded.length,
|
|
1587
|
+
payload: encoded,
|
|
1588
|
+
headers: hdrs,
|
|
1589
|
+
serviceName: this.callerName,
|
|
1590
|
+
endpoint: this.serverEndpoint
|
|
1591
|
+
},
|
|
1592
|
+
this.otel
|
|
1593
|
+
);
|
|
592
1594
|
try {
|
|
593
|
-
const nc = await this.connect();
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
1595
|
+
const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
|
|
1596
|
+
const response = await import_api8.context.with(
|
|
1597
|
+
spanHandle.activeContext,
|
|
1598
|
+
() => nc.request(subject, encoded, {
|
|
1599
|
+
timeout: effectiveTimeout,
|
|
1600
|
+
headers: hdrs
|
|
1601
|
+
})
|
|
1602
|
+
);
|
|
600
1603
|
const decoded = this.codec.decode(response.data);
|
|
601
1604
|
if (response.headers?.get("x-error" /* Error */)) {
|
|
1605
|
+
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
|
|
602
1606
|
callback({ err: decoded, response: null, isDisposed: true });
|
|
603
1607
|
} else {
|
|
1608
|
+
spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
|
|
604
1609
|
callback({ err: null, response: decoded, isDisposed: true });
|
|
605
1610
|
}
|
|
606
1611
|
} catch (err) {
|
|
607
1612
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
608
|
-
|
|
609
|
-
|
|
1613
|
+
if (error instanceof import_transport_node.TimeoutError) {
|
|
1614
|
+
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1615
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
|
|
1616
|
+
} else {
|
|
1617
|
+
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1618
|
+
this.eventBus.emit("error" /* Error */, error, "client-rpc");
|
|
1619
|
+
}
|
|
610
1620
|
callback({ err: error, response: null, isDisposed: true });
|
|
611
1621
|
}
|
|
612
1622
|
}
|
|
613
1623
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
614
1624
|
async publishJetStreamRpc(subject, data, callback, options) {
|
|
615
1625
|
const { headers: customHeaders, correlationId, messageId } = options;
|
|
616
|
-
const effectiveTimeout = options.timeout ?? this.
|
|
617
|
-
this.
|
|
1626
|
+
const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
|
|
1627
|
+
const hdrs = this.buildHeaders(customHeaders, {
|
|
1628
|
+
subject,
|
|
1629
|
+
correlationId,
|
|
1630
|
+
replyTo: this.inbox ?? ""
|
|
1631
|
+
});
|
|
1632
|
+
const encoded = this.codec.encode(data);
|
|
1633
|
+
const spanHandle = beginRpcClientSpan(
|
|
1634
|
+
{
|
|
1635
|
+
subject,
|
|
1636
|
+
correlationId,
|
|
1637
|
+
payloadBytes: encoded.length,
|
|
1638
|
+
payload: encoded,
|
|
1639
|
+
messageId,
|
|
1640
|
+
headers: hdrs,
|
|
1641
|
+
serviceName: this.callerName,
|
|
1642
|
+
endpoint: this.serverEndpoint
|
|
1643
|
+
},
|
|
1644
|
+
this.otel
|
|
1645
|
+
);
|
|
1646
|
+
this.pendingMessages.set(correlationId, (packet) => {
|
|
1647
|
+
if (packet.err) {
|
|
1648
|
+
if (packet.err instanceof Error) {
|
|
1649
|
+
spanHandle.finish({ kind: "error" /* Error */, error: packet.err });
|
|
1650
|
+
} else {
|
|
1651
|
+
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
|
|
1652
|
+
}
|
|
1653
|
+
} else {
|
|
1654
|
+
spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
|
|
1655
|
+
}
|
|
1656
|
+
callback(packet);
|
|
1657
|
+
});
|
|
1658
|
+
const timeoutId = setTimeout(() => {
|
|
1659
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
1660
|
+
this.pendingTimeouts.delete(correlationId);
|
|
1661
|
+
this.pendingMessages.delete(correlationId);
|
|
1662
|
+
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1663
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
1664
|
+
callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
|
|
1665
|
+
}, effectiveTimeout);
|
|
1666
|
+
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
618
1667
|
try {
|
|
619
|
-
await this.connect();
|
|
1668
|
+
if (!this.readyForPublish) await this.connect();
|
|
620
1669
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
621
1670
|
if (!this.inbox) {
|
|
1671
|
+
clearTimeout(timeoutId);
|
|
1672
|
+
this.pendingTimeouts.delete(correlationId);
|
|
622
1673
|
this.pendingMessages.delete(correlationId);
|
|
1674
|
+
const inboxError = new Error("Inbox not initialized");
|
|
1675
|
+
spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
|
|
623
1676
|
callback({
|
|
624
1677
|
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
625
1678
|
response: null,
|
|
@@ -627,24 +1680,13 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
627
1680
|
});
|
|
628
1681
|
return;
|
|
629
1682
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
this.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}, effectiveTimeout);
|
|
638
|
-
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
639
|
-
const hdrs = this.buildHeaders(customHeaders, {
|
|
640
|
-
subject,
|
|
641
|
-
correlationId,
|
|
642
|
-
replyTo: this.inbox
|
|
643
|
-
});
|
|
644
|
-
await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
645
|
-
headers: hdrs,
|
|
646
|
-
msgID: messageId ?? import_nuid.nuid.next()
|
|
647
|
-
});
|
|
1683
|
+
await import_api8.context.with(
|
|
1684
|
+
spanHandle.activeContext,
|
|
1685
|
+
() => this.connection.getJetStreamClient().publish(subject, encoded, {
|
|
1686
|
+
headers: hdrs,
|
|
1687
|
+
msgID: messageId ?? import_nuid.nuid.next()
|
|
1688
|
+
})
|
|
1689
|
+
);
|
|
648
1690
|
} catch (err) {
|
|
649
1691
|
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
650
1692
|
if (existingTimeout) {
|
|
@@ -654,7 +1696,8 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
654
1696
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
655
1697
|
this.pendingMessages.delete(correlationId);
|
|
656
1698
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
657
|
-
|
|
1699
|
+
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1700
|
+
this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
|
|
658
1701
|
callback({ err: error, response: null, isDisposed: true });
|
|
659
1702
|
}
|
|
660
1703
|
}
|
|
@@ -662,6 +1705,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
662
1705
|
handleDisconnect() {
|
|
663
1706
|
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
664
1707
|
this.inbox = null;
|
|
1708
|
+
this.readyForPublish = false;
|
|
665
1709
|
}
|
|
666
1710
|
/** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
|
|
667
1711
|
rejectPendingRpcs(error) {
|
|
@@ -725,19 +1769,22 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
725
1769
|
this.pendingMessages.delete(correlationId);
|
|
726
1770
|
}
|
|
727
1771
|
}
|
|
728
|
-
/**
|
|
1772
|
+
/**
|
|
1773
|
+
* Resolve a user pattern to a fully-qualified NATS subject, dispatching
|
|
1774
|
+
* between the event, broadcast, and ordered prefixes.
|
|
1775
|
+
*
|
|
1776
|
+
* The leading-char check short-circuits the `startsWith` comparisons for
|
|
1777
|
+
* patterns that cannot possibly carry a broadcast/ordered marker, which is
|
|
1778
|
+
* the overwhelmingly common case.
|
|
1779
|
+
*/
|
|
729
1780
|
buildEventSubject(pattern) {
|
|
730
|
-
if (pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
731
|
-
return
|
|
732
|
-
}
|
|
733
|
-
if (pattern.startsWith("ordered:" /* Ordered */)) {
|
|
734
|
-
return buildSubject(
|
|
735
|
-
this.targetName,
|
|
736
|
-
"ordered" /* Ordered */,
|
|
737
|
-
pattern.slice("ordered:" /* Ordered */.length)
|
|
738
|
-
);
|
|
1781
|
+
if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
1782
|
+
return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
|
|
739
1783
|
}
|
|
740
|
-
|
|
1784
|
+
if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
|
|
1785
|
+
return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
|
|
1786
|
+
}
|
|
1787
|
+
return this.eventSubjectPrefix + pattern;
|
|
741
1788
|
}
|
|
742
1789
|
/** Build NATS headers merging custom headers with transport headers. */
|
|
743
1790
|
buildHeaders(customHeaders, transport) {
|
|
@@ -762,7 +1809,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
762
1809
|
if (rawData instanceof JetstreamRecord) {
|
|
763
1810
|
return {
|
|
764
1811
|
data: rawData.data,
|
|
765
|
-
hdrs: rawData.headers.size > 0 ?
|
|
1812
|
+
hdrs: rawData.headers.size > 0 ? rawData.headers : null,
|
|
766
1813
|
timeout: rawData.timeout,
|
|
767
1814
|
messageId: rawData.messageId,
|
|
768
1815
|
schedule: rawData.schedule,
|
|
@@ -805,11 +1852,6 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
805
1852
|
const pattern = withoutPrefix.slice(dotIndex + 1);
|
|
806
1853
|
return `${targetPrefix}_sch.${pattern}`;
|
|
807
1854
|
}
|
|
808
|
-
getRpcTimeout() {
|
|
809
|
-
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
810
|
-
const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
811
|
-
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
812
|
-
}
|
|
813
1855
|
};
|
|
814
1856
|
|
|
815
1857
|
// src/codec/json.codec.ts
|
|
@@ -824,10 +1866,23 @@ var JsonCodec = class {
|
|
|
824
1866
|
}
|
|
825
1867
|
};
|
|
826
1868
|
|
|
1869
|
+
// src/codec/msgpack.codec.ts
|
|
1870
|
+
var MsgpackCodec = class {
|
|
1871
|
+
constructor(packr) {
|
|
1872
|
+
this.packr = packr;
|
|
1873
|
+
}
|
|
1874
|
+
encode(data) {
|
|
1875
|
+
return this.packr.pack(data);
|
|
1876
|
+
}
|
|
1877
|
+
decode(data) {
|
|
1878
|
+
return this.packr.unpack(data);
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
|
|
827
1882
|
// src/connection/connection.provider.ts
|
|
828
|
-
var
|
|
1883
|
+
var import_common6 = require("@nestjs/common");
|
|
829
1884
|
var import_transport_node2 = require("@nats-io/transport-node");
|
|
830
|
-
var
|
|
1885
|
+
var import_jetstream8 = require("@nats-io/jetstream");
|
|
831
1886
|
var import_rxjs = require("rxjs");
|
|
832
1887
|
var DEFAULT_OPTIONS = {
|
|
833
1888
|
maxReconnectAttempts: -1,
|
|
@@ -837,6 +1892,10 @@ var ConnectionProvider = class {
|
|
|
837
1892
|
constructor(options, eventBus) {
|
|
838
1893
|
this.options = options;
|
|
839
1894
|
this.eventBus = eventBus;
|
|
1895
|
+
const derived = deriveOtelAttrs(options);
|
|
1896
|
+
this.otel = derived.otel;
|
|
1897
|
+
this.otelServiceName = derived.serviceName;
|
|
1898
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
840
1899
|
this.nc$ = (0, import_rxjs.defer)(() => this.getConnection()).pipe(
|
|
841
1900
|
(0, import_rxjs.shareReplay)({ bufferSize: 1, refCount: false })
|
|
842
1901
|
);
|
|
@@ -849,12 +1908,16 @@ var ConnectionProvider = class {
|
|
|
849
1908
|
nc$;
|
|
850
1909
|
/** Live stream of connection status events (no replay). */
|
|
851
1910
|
status$;
|
|
852
|
-
logger = new
|
|
1911
|
+
logger = new import_common6.Logger("Jetstream:Connection");
|
|
853
1912
|
connection = null;
|
|
854
1913
|
connectionPromise = null;
|
|
855
1914
|
jsClient = null;
|
|
856
1915
|
jsmInstance = null;
|
|
857
1916
|
jsmPromise = null;
|
|
1917
|
+
otel;
|
|
1918
|
+
otelServiceName;
|
|
1919
|
+
otelEndpoint;
|
|
1920
|
+
lifecycleSpan = null;
|
|
858
1921
|
/**
|
|
859
1922
|
* Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
|
|
860
1923
|
*
|
|
@@ -897,7 +1960,7 @@ var ConnectionProvider = class {
|
|
|
897
1960
|
if (!this.connection || this.connection.isClosed()) {
|
|
898
1961
|
throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
|
|
899
1962
|
}
|
|
900
|
-
this.jsClient ??= (0,
|
|
1963
|
+
this.jsClient ??= (0, import_jetstream8.jetstream)(this.connection);
|
|
901
1964
|
return this.jsClient;
|
|
902
1965
|
}
|
|
903
1966
|
/** Direct access to the raw NATS connection, or `null` if not yet connected. */
|
|
@@ -918,14 +1981,24 @@ var ConnectionProvider = class {
|
|
|
918
1981
|
}
|
|
919
1982
|
if (!this.connection || this.connection.isClosed()) return;
|
|
920
1983
|
try {
|
|
921
|
-
await
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1984
|
+
await withShutdownSpan(
|
|
1985
|
+
this.otel,
|
|
1986
|
+
{ serviceName: this.otelServiceName, endpoint: this.otelEndpoint },
|
|
1987
|
+
async () => {
|
|
1988
|
+
try {
|
|
1989
|
+
await this.connection?.drain();
|
|
1990
|
+
await this.connection?.closed();
|
|
1991
|
+
} catch {
|
|
1992
|
+
try {
|
|
1993
|
+
await this.connection?.close();
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
);
|
|
928
1999
|
} finally {
|
|
2000
|
+
this.lifecycleSpan?.finish();
|
|
2001
|
+
this.lifecycleSpan = null;
|
|
929
2002
|
this.connection = null;
|
|
930
2003
|
this.connectionPromise = null;
|
|
931
2004
|
this.jsClient = null;
|
|
@@ -936,7 +2009,7 @@ var ConnectionProvider = class {
|
|
|
936
2009
|
async initJetStreamManager() {
|
|
937
2010
|
try {
|
|
938
2011
|
const nc = await this.getConnection();
|
|
939
|
-
this.jsmInstance = await (0,
|
|
2012
|
+
this.jsmInstance = await (0, import_jetstream8.jetstreamManager)(nc);
|
|
940
2013
|
this.logger.log("JetStream manager initialized");
|
|
941
2014
|
return this.jsmInstance;
|
|
942
2015
|
} finally {
|
|
@@ -945,17 +2018,25 @@ var ConnectionProvider = class {
|
|
|
945
2018
|
}
|
|
946
2019
|
/** Internal: establish the physical connection with reconnect monitoring. */
|
|
947
2020
|
async establish() {
|
|
948
|
-
const name = internalName(this.options.name);
|
|
949
2021
|
try {
|
|
950
2022
|
const nc = await (0, import_transport_node2.connect)({
|
|
951
2023
|
...DEFAULT_OPTIONS,
|
|
2024
|
+
// Default the NATS connection name to the OTel-derived service name so
|
|
2025
|
+
// `nats server info` lines up with span attributes, but let user-supplied
|
|
2026
|
+
// `connectionOptions.name` win when set.
|
|
2027
|
+
name: this.otelServiceName,
|
|
952
2028
|
...this.options.connectionOptions,
|
|
953
|
-
servers: this.options.servers
|
|
954
|
-
name
|
|
2029
|
+
servers: this.options.servers
|
|
955
2030
|
});
|
|
956
2031
|
this.connection = nc;
|
|
957
2032
|
this.logger.log(`NATS connection established: ${nc.getServer()}`);
|
|
958
2033
|
this.eventBus.emit("connect" /* Connect */, nc.getServer());
|
|
2034
|
+
this.lifecycleSpan?.finish();
|
|
2035
|
+
this.lifecycleSpan = beginConnectionLifecycleSpan(this.otel, {
|
|
2036
|
+
serviceName: this.otelServiceName,
|
|
2037
|
+
endpoint: this.otelEndpoint,
|
|
2038
|
+
server: nc.getServer()
|
|
2039
|
+
});
|
|
959
2040
|
this.monitorStatus(nc);
|
|
960
2041
|
return nc;
|
|
961
2042
|
} catch (err) {
|
|
@@ -965,37 +2046,55 @@ var ConnectionProvider = class {
|
|
|
965
2046
|
throw err;
|
|
966
2047
|
}
|
|
967
2048
|
}
|
|
2049
|
+
/** Handle a single `nc.status()` event, emitting hooks and span events. */
|
|
2050
|
+
handleStatusEvent(status, nc) {
|
|
2051
|
+
switch (status.type) {
|
|
2052
|
+
case "disconnect":
|
|
2053
|
+
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
2054
|
+
this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_DISCONNECTED);
|
|
2055
|
+
break;
|
|
2056
|
+
case "reconnect":
|
|
2057
|
+
this.jsClient = null;
|
|
2058
|
+
this.jsmInstance = null;
|
|
2059
|
+
this.jsmPromise = null;
|
|
2060
|
+
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
2061
|
+
this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_RECONNECTED, {
|
|
2062
|
+
[ATTR_NATS_CONNECTION_SERVER]: nc.getServer()
|
|
2063
|
+
});
|
|
2064
|
+
break;
|
|
2065
|
+
case "error":
|
|
2066
|
+
this.eventBus.emit(
|
|
2067
|
+
"error" /* Error */,
|
|
2068
|
+
status.error,
|
|
2069
|
+
"connection"
|
|
2070
|
+
);
|
|
2071
|
+
break;
|
|
2072
|
+
case "update":
|
|
2073
|
+
case "ldm":
|
|
2074
|
+
case "reconnecting":
|
|
2075
|
+
case "ping":
|
|
2076
|
+
case "staleConnection":
|
|
2077
|
+
case "forceReconnect":
|
|
2078
|
+
case "slowConsumer":
|
|
2079
|
+
case "close":
|
|
2080
|
+
break;
|
|
2081
|
+
default: {
|
|
2082
|
+
const _exhaustive = status;
|
|
2083
|
+
const unknown = _exhaustive.type ?? "unknown";
|
|
2084
|
+
this.logger.warn(`Unhandled NATS status event: ${unknown}`);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
968
2088
|
/** Subscribe to connection status events and emit hooks. */
|
|
969
2089
|
monitorStatus(nc) {
|
|
970
2090
|
void (async () => {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
975
|
-
break;
|
|
976
|
-
case "reconnect":
|
|
977
|
-
this.jsClient = null;
|
|
978
|
-
this.jsmInstance = null;
|
|
979
|
-
this.jsmPromise = null;
|
|
980
|
-
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
981
|
-
break;
|
|
982
|
-
case "error":
|
|
983
|
-
this.eventBus.emit(
|
|
984
|
-
"error" /* Error */,
|
|
985
|
-
status.error,
|
|
986
|
-
"connection"
|
|
987
|
-
);
|
|
988
|
-
break;
|
|
989
|
-
case "update":
|
|
990
|
-
case "ldm":
|
|
991
|
-
case "reconnecting":
|
|
992
|
-
case "ping":
|
|
993
|
-
case "staleConnection":
|
|
994
|
-
case "forceReconnect":
|
|
995
|
-
case "slowConsumer":
|
|
996
|
-
case "close":
|
|
997
|
-
break;
|
|
2091
|
+
try {
|
|
2092
|
+
for await (const status of nc.status()) {
|
|
2093
|
+
this.handleStatusEvent(status, nc);
|
|
998
2094
|
}
|
|
2095
|
+
} finally {
|
|
2096
|
+
this.lifecycleSpan?.finish();
|
|
2097
|
+
this.lifecycleSpan = null;
|
|
999
2098
|
}
|
|
1000
2099
|
})().catch((err) => {
|
|
1001
2100
|
this.logger.error("Status monitor error", err);
|
|
@@ -1007,8 +2106,8 @@ var ConnectionProvider = class {
|
|
|
1007
2106
|
var EventBus = class {
|
|
1008
2107
|
hooks;
|
|
1009
2108
|
logger;
|
|
1010
|
-
constructor(
|
|
1011
|
-
this.logger =
|
|
2109
|
+
constructor(logger5, hooks) {
|
|
2110
|
+
this.logger = logger5;
|
|
1012
2111
|
this.hooks = hooks ?? {};
|
|
1013
2112
|
}
|
|
1014
2113
|
/**
|
|
@@ -1036,6 +2135,15 @@ var EventBus = class {
|
|
|
1036
2135
|
kind
|
|
1037
2136
|
);
|
|
1038
2137
|
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Check whether a hook is registered for the given event.
|
|
2140
|
+
*
|
|
2141
|
+
* Used by the routing hot path to elide the emit call entirely when the
|
|
2142
|
+
* transport owner did not register a listener.
|
|
2143
|
+
*/
|
|
2144
|
+
hasHook(event) {
|
|
2145
|
+
return this.hooks[event] !== void 0;
|
|
2146
|
+
}
|
|
1039
2147
|
callHook(event, hook, ...args) {
|
|
1040
2148
|
try {
|
|
1041
2149
|
const result = hook(...args);
|
|
@@ -1055,12 +2163,12 @@ var EventBus = class {
|
|
|
1055
2163
|
};
|
|
1056
2164
|
|
|
1057
2165
|
// src/health/jetstream.health-indicator.ts
|
|
1058
|
-
var
|
|
2166
|
+
var import_common7 = require("@nestjs/common");
|
|
1059
2167
|
var JetstreamHealthIndicator = class {
|
|
1060
2168
|
constructor(connection) {
|
|
1061
2169
|
this.connection = connection;
|
|
1062
2170
|
}
|
|
1063
|
-
logger = new
|
|
2171
|
+
logger = new import_common7.Logger("Jetstream:Health");
|
|
1064
2172
|
/**
|
|
1065
2173
|
* Plain health status check.
|
|
1066
2174
|
*
|
|
@@ -1117,7 +2225,7 @@ var JetstreamHealthIndicator = class {
|
|
|
1117
2225
|
}
|
|
1118
2226
|
};
|
|
1119
2227
|
JetstreamHealthIndicator = __decorateClass([
|
|
1120
|
-
(0,
|
|
2228
|
+
(0, import_common7.Injectable)()
|
|
1121
2229
|
], JetstreamHealthIndicator);
|
|
1122
2230
|
|
|
1123
2231
|
// src/server/strategy.ts
|
|
@@ -1266,7 +2374,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
1266
2374
|
};
|
|
1267
2375
|
|
|
1268
2376
|
// src/server/core-rpc.server.ts
|
|
1269
|
-
var
|
|
2377
|
+
var import_common8 = require("@nestjs/common");
|
|
1270
2378
|
var import_transport_node3 = require("@nats-io/transport-node");
|
|
1271
2379
|
|
|
1272
2380
|
// src/context/rpc.context.ts
|
|
@@ -1276,9 +2384,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
|
|
|
1276
2384
|
_retryDelay;
|
|
1277
2385
|
_shouldTerminate = false;
|
|
1278
2386
|
_terminateReason;
|
|
1279
|
-
// ---------------------------------------------------------------------------
|
|
1280
|
-
// Message accessors
|
|
1281
|
-
// ---------------------------------------------------------------------------
|
|
1282
2387
|
/**
|
|
1283
2388
|
* Get the underlying NATS message.
|
|
1284
2389
|
*
|
|
@@ -1313,9 +2418,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
|
|
|
1313
2418
|
isJetStream() {
|
|
1314
2419
|
return "ack" in this.args[0];
|
|
1315
2420
|
}
|
|
1316
|
-
// ---------------------------------------------------------------------------
|
|
1317
|
-
// JetStream metadata (return undefined for Core NATS messages)
|
|
1318
|
-
// ---------------------------------------------------------------------------
|
|
1319
2421
|
/** How many times this message has been delivered. */
|
|
1320
2422
|
getDeliveryCount() {
|
|
1321
2423
|
return this.asJetStream()?.info.deliveryCount;
|
|
@@ -1337,9 +2439,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
|
|
|
1337
2439
|
getCallerName() {
|
|
1338
2440
|
return this.getHeader("x-caller-name" /* CallerName */);
|
|
1339
2441
|
}
|
|
1340
|
-
// ---------------------------------------------------------------------------
|
|
1341
|
-
// Handler-controlled settlement
|
|
1342
|
-
// ---------------------------------------------------------------------------
|
|
1343
2442
|
/**
|
|
1344
2443
|
* Signal the transport to retry (nak) this message instead of acknowledging it.
|
|
1345
2444
|
*
|
|
@@ -1384,9 +2483,6 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
|
|
|
1384
2483
|
throw new Error(`${method}() is only available for JetStream messages`);
|
|
1385
2484
|
}
|
|
1386
2485
|
}
|
|
1387
|
-
// ---------------------------------------------------------------------------
|
|
1388
|
-
// Transport-facing state (read by EventRouter)
|
|
1389
|
-
// ---------------------------------------------------------------------------
|
|
1390
2486
|
/** @internal */
|
|
1391
2487
|
get shouldRetry() {
|
|
1392
2488
|
return this._shouldRetry;
|
|
@@ -1418,16 +2514,71 @@ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
|
|
|
1418
2514
|
const interval = Math.floor(ackWaitNanos / 1e6 / 2);
|
|
1419
2515
|
return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
|
|
1420
2516
|
};
|
|
2517
|
+
var AckExtensionPool = class {
|
|
2518
|
+
entries = /* @__PURE__ */ new Set();
|
|
2519
|
+
handle = null;
|
|
2520
|
+
handleFireAt = 0;
|
|
2521
|
+
schedule(msg, interval) {
|
|
2522
|
+
const entry = {
|
|
2523
|
+
msg,
|
|
2524
|
+
interval,
|
|
2525
|
+
nextFireAt: Date.now() + interval,
|
|
2526
|
+
active: true
|
|
2527
|
+
};
|
|
2528
|
+
this.entries.add(entry);
|
|
2529
|
+
this.ensureWake(entry.nextFireAt);
|
|
2530
|
+
return entry;
|
|
2531
|
+
}
|
|
2532
|
+
cancel(entry) {
|
|
2533
|
+
if (!entry.active) return;
|
|
2534
|
+
entry.active = false;
|
|
2535
|
+
this.entries.delete(entry);
|
|
2536
|
+
if (this.entries.size === 0 && this.handle !== null) {
|
|
2537
|
+
clearTimeout(this.handle);
|
|
2538
|
+
this.handle = null;
|
|
2539
|
+
this.handleFireAt = 0;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* Ensure the shared timer will fire no later than `dueAt`. If an earlier
|
|
2544
|
+
* wake is already scheduled, leave it; otherwise replace it with a tighter one.
|
|
2545
|
+
*/
|
|
2546
|
+
ensureWake(dueAt) {
|
|
2547
|
+
if (this.handle !== null && this.handleFireAt <= dueAt) return;
|
|
2548
|
+
if (this.handle !== null) clearTimeout(this.handle);
|
|
2549
|
+
const delay = Math.max(0, dueAt - Date.now());
|
|
2550
|
+
const handle = setTimeout(() => {
|
|
2551
|
+
this.tick();
|
|
2552
|
+
}, delay);
|
|
2553
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
2554
|
+
this.handle = handle;
|
|
2555
|
+
this.handleFireAt = dueAt;
|
|
2556
|
+
}
|
|
2557
|
+
tick() {
|
|
2558
|
+
this.handle = null;
|
|
2559
|
+
this.handleFireAt = 0;
|
|
2560
|
+
const now = Date.now();
|
|
2561
|
+
let earliest = Infinity;
|
|
2562
|
+
for (const entry of this.entries) {
|
|
2563
|
+
if (!entry.active) continue;
|
|
2564
|
+
if (entry.nextFireAt <= now) {
|
|
2565
|
+
try {
|
|
2566
|
+
entry.msg.working();
|
|
2567
|
+
} catch {
|
|
2568
|
+
}
|
|
2569
|
+
entry.nextFireAt = now + entry.interval;
|
|
2570
|
+
}
|
|
2571
|
+
if (entry.nextFireAt < earliest) earliest = entry.nextFireAt;
|
|
2572
|
+
}
|
|
2573
|
+
if (earliest !== Infinity) this.ensureWake(earliest);
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
var pool = new AckExtensionPool();
|
|
1421
2577
|
var startAckExtensionTimer = (msg, interval) => {
|
|
1422
2578
|
if (interval === null || interval <= 0) return null;
|
|
1423
|
-
const
|
|
1424
|
-
try {
|
|
1425
|
-
msg.working();
|
|
1426
|
-
} catch {
|
|
1427
|
-
}
|
|
1428
|
-
}, interval);
|
|
2579
|
+
const entry = pool.schedule(msg, interval);
|
|
1429
2580
|
return () => {
|
|
1430
|
-
|
|
2581
|
+
pool.cancel(entry);
|
|
1431
2582
|
};
|
|
1432
2583
|
};
|
|
1433
2584
|
|
|
@@ -1441,11 +2592,8 @@ var serializeError = (err) => {
|
|
|
1441
2592
|
|
|
1442
2593
|
// src/utils/unwrap-result.ts
|
|
1443
2594
|
var import_rxjs2 = require("rxjs");
|
|
1444
|
-
var RESOLVED_VOID = Promise.resolve(void 0);
|
|
1445
|
-
var RESOLVED_NULL = Promise.resolve(null);
|
|
1446
2595
|
var unwrapResult = (result) => {
|
|
1447
|
-
if (result === void 0) return
|
|
1448
|
-
if (result === null) return RESOLVED_NULL;
|
|
2596
|
+
if (result === void 0 || result === null) return result;
|
|
1449
2597
|
if ((0, import_rxjs2.isObservable)(result)) {
|
|
1450
2598
|
return subscribeToFirst(result);
|
|
1451
2599
|
}
|
|
@@ -1454,8 +2602,9 @@ var unwrapResult = (result) => {
|
|
|
1454
2602
|
(resolved) => (0, import_rxjs2.isObservable)(resolved) ? subscribeToFirst(resolved) : resolved
|
|
1455
2603
|
);
|
|
1456
2604
|
}
|
|
1457
|
-
return
|
|
2605
|
+
return result;
|
|
1458
2606
|
};
|
|
2607
|
+
var isPromiseLike2 = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
|
|
1459
2608
|
var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
1460
2609
|
let done = false;
|
|
1461
2610
|
let subscription = null;
|
|
@@ -1485,20 +2634,25 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
|
1485
2634
|
// src/server/core-rpc.server.ts
|
|
1486
2635
|
var CoreRpcServer = class {
|
|
1487
2636
|
constructor(options, connection, patternRegistry, codec, eventBus) {
|
|
1488
|
-
this.options = options;
|
|
1489
2637
|
this.connection = connection;
|
|
1490
2638
|
this.patternRegistry = patternRegistry;
|
|
1491
2639
|
this.codec = codec;
|
|
1492
2640
|
this.eventBus = eventBus;
|
|
2641
|
+
const derived = deriveOtelAttrs(options);
|
|
2642
|
+
this.otel = derived.otel;
|
|
2643
|
+
this.serviceName = derived.serviceName;
|
|
2644
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
1493
2645
|
}
|
|
1494
|
-
logger = new
|
|
2646
|
+
logger = new import_common8.Logger("Jetstream:CoreRpc");
|
|
1495
2647
|
subscription = null;
|
|
2648
|
+
otel;
|
|
2649
|
+
serviceName;
|
|
2650
|
+
serverEndpoint;
|
|
1496
2651
|
/** Start listening for RPC requests on the command subject. */
|
|
1497
2652
|
async start() {
|
|
1498
2653
|
const nc = await this.connection.getConnection();
|
|
1499
|
-
const
|
|
1500
|
-
const
|
|
1501
|
-
const queue = `${serviceName}_cmd_queue`;
|
|
2654
|
+
const subject = `${this.serviceName}.cmd.>`;
|
|
2655
|
+
const queue = `${this.serviceName}_cmd_queue`;
|
|
1502
2656
|
this.subscription = nc.subscribe(subject, {
|
|
1503
2657
|
queue,
|
|
1504
2658
|
callback: (err, msg) => {
|
|
@@ -1543,10 +2697,29 @@ var CoreRpcServer = class {
|
|
|
1543
2697
|
}
|
|
1544
2698
|
const ctx = new RpcContext([msg]);
|
|
1545
2699
|
try {
|
|
1546
|
-
const
|
|
1547
|
-
|
|
2700
|
+
const raw = await withConsumeSpan(
|
|
2701
|
+
{
|
|
2702
|
+
subject: msg.subject,
|
|
2703
|
+
msg,
|
|
2704
|
+
kind: "rpc" /* Rpc */,
|
|
2705
|
+
payloadBytes: msg.data.length,
|
|
2706
|
+
handlerMetadata: { pattern: msg.subject },
|
|
2707
|
+
serviceName: this.serviceName,
|
|
2708
|
+
endpoint: this.serverEndpoint
|
|
2709
|
+
},
|
|
2710
|
+
this.otel,
|
|
2711
|
+
() => {
|
|
2712
|
+
const out = unwrapResult(handler(data, ctx));
|
|
2713
|
+
return isPromiseLike2(out) ? out : out;
|
|
2714
|
+
}
|
|
2715
|
+
);
|
|
2716
|
+
msg.respond(this.codec.encode(raw));
|
|
1548
2717
|
} catch (err) {
|
|
1549
|
-
this.
|
|
2718
|
+
this.eventBus.emit(
|
|
2719
|
+
"error" /* Error */,
|
|
2720
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
2721
|
+
`core-rpc-handler:${msg.subject}`
|
|
2722
|
+
);
|
|
1550
2723
|
this.respondWithError(msg, err);
|
|
1551
2724
|
}
|
|
1552
2725
|
}
|
|
@@ -1563,9 +2736,17 @@ var CoreRpcServer = class {
|
|
|
1563
2736
|
};
|
|
1564
2737
|
|
|
1565
2738
|
// src/server/infrastructure/stream.provider.ts
|
|
1566
|
-
var
|
|
2739
|
+
var import_common10 = require("@nestjs/common");
|
|
1567
2740
|
var import_jetstream14 = require("@nats-io/jetstream");
|
|
1568
2741
|
|
|
2742
|
+
// src/server/infrastructure/nats-error-codes.ts
|
|
2743
|
+
var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
|
|
2744
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerNotFound"] = 10014] = "ConsumerNotFound";
|
|
2745
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerAlreadyExists"] = 10148] = "ConsumerAlreadyExists";
|
|
2746
|
+
NatsErrorCode2[NatsErrorCode2["StreamNotFound"] = 10059] = "StreamNotFound";
|
|
2747
|
+
return NatsErrorCode2;
|
|
2748
|
+
})(NatsErrorCode || {});
|
|
2749
|
+
|
|
1569
2750
|
// src/server/infrastructure/stream-config-diff.ts
|
|
1570
2751
|
var TRANSPORT_CONTROLLED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1571
2752
|
"retention"
|
|
@@ -1622,7 +2803,7 @@ var isEqual = (a, b) => {
|
|
|
1622
2803
|
};
|
|
1623
2804
|
|
|
1624
2805
|
// src/server/infrastructure/stream-migration.ts
|
|
1625
|
-
var
|
|
2806
|
+
var import_common9 = require("@nestjs/common");
|
|
1626
2807
|
var import_jetstream13 = require("@nats-io/jetstream");
|
|
1627
2808
|
var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
|
|
1628
2809
|
var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
|
|
@@ -1631,7 +2812,7 @@ var StreamMigration = class {
|
|
|
1631
2812
|
constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
|
|
1632
2813
|
this.sourcingTimeoutMs = sourcingTimeoutMs;
|
|
1633
2814
|
}
|
|
1634
|
-
logger = new
|
|
2815
|
+
logger = new import_common9.Logger("Jetstream:Stream");
|
|
1635
2816
|
async migrate(jsm, streamName2, newConfig) {
|
|
1636
2817
|
const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
|
|
1637
2818
|
const startTime = Date.now();
|
|
@@ -1713,9 +2894,16 @@ var StreamProvider = class {
|
|
|
1713
2894
|
constructor(options, connection) {
|
|
1714
2895
|
this.options = options;
|
|
1715
2896
|
this.connection = connection;
|
|
2897
|
+
const derived = deriveOtelAttrs(options);
|
|
2898
|
+
this.otel = derived.otel;
|
|
2899
|
+
this.otelServiceName = derived.serviceName;
|
|
2900
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
1716
2901
|
}
|
|
1717
|
-
logger = new
|
|
2902
|
+
logger = new import_common10.Logger("Jetstream:Stream");
|
|
1718
2903
|
migration = new StreamMigration();
|
|
2904
|
+
otel;
|
|
2905
|
+
otelServiceName;
|
|
2906
|
+
otelEndpoint;
|
|
1719
2907
|
/**
|
|
1720
2908
|
* Ensure all required streams exist with correct configuration.
|
|
1721
2909
|
*
|
|
@@ -1761,32 +2949,56 @@ var StreamProvider = class {
|
|
|
1761
2949
|
/** Ensure a single stream exists, creating or updating as needed. */
|
|
1762
2950
|
async ensureStream(jsm, kind) {
|
|
1763
2951
|
const config = this.buildConfig(kind);
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2952
|
+
return withProvisioningSpan(
|
|
2953
|
+
this.otel,
|
|
2954
|
+
{
|
|
2955
|
+
serviceName: this.otelServiceName,
|
|
2956
|
+
endpoint: this.otelEndpoint,
|
|
2957
|
+
entity: "stream",
|
|
2958
|
+
name: config.name,
|
|
2959
|
+
action: "ensure"
|
|
2960
|
+
},
|
|
2961
|
+
async () => {
|
|
2962
|
+
this.logger.log(`Ensuring stream: ${config.name}`);
|
|
2963
|
+
try {
|
|
2964
|
+
const currentInfo = await jsm.streams.info(config.name);
|
|
2965
|
+
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
if (err instanceof import_jetstream14.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2968
|
+
this.logger.log(`Creating stream: ${config.name}`);
|
|
2969
|
+
return await jsm.streams.add(config);
|
|
2970
|
+
}
|
|
2971
|
+
throw err;
|
|
2972
|
+
}
|
|
1772
2973
|
}
|
|
1773
|
-
|
|
1774
|
-
}
|
|
2974
|
+
);
|
|
1775
2975
|
}
|
|
1776
2976
|
/** Ensure a dead-letter queue stream exists, creating or updating as needed. */
|
|
1777
2977
|
async ensureDlqStream(jsm) {
|
|
1778
2978
|
const config = this.buildDlqConfig();
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2979
|
+
return withProvisioningSpan(
|
|
2980
|
+
this.otel,
|
|
2981
|
+
{
|
|
2982
|
+
serviceName: this.otelServiceName,
|
|
2983
|
+
endpoint: this.otelEndpoint,
|
|
2984
|
+
entity: "stream",
|
|
2985
|
+
name: config.name,
|
|
2986
|
+
action: "ensure"
|
|
2987
|
+
},
|
|
2988
|
+
async () => {
|
|
2989
|
+
this.logger.log(`Ensuring DLQ stream: ${config.name}`);
|
|
2990
|
+
try {
|
|
2991
|
+
const currentInfo = await jsm.streams.info(config.name);
|
|
2992
|
+
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2993
|
+
} catch (err) {
|
|
2994
|
+
if (err instanceof import_jetstream14.JetStreamApiError && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2995
|
+
this.logger.log(`Creating DLQ stream: ${config.name}`);
|
|
2996
|
+
return await jsm.streams.add(config);
|
|
2997
|
+
}
|
|
2998
|
+
throw err;
|
|
2999
|
+
}
|
|
1787
3000
|
}
|
|
1788
|
-
|
|
1789
|
-
}
|
|
3001
|
+
);
|
|
1790
3002
|
}
|
|
1791
3003
|
async handleExistingStream(jsm, currentInfo, config) {
|
|
1792
3004
|
const diff = compareStreamConfig(currentInfo.config, config);
|
|
@@ -1815,7 +3027,18 @@ var StreamProvider = class {
|
|
|
1815
3027
|
}
|
|
1816
3028
|
return currentInfo;
|
|
1817
3029
|
}
|
|
1818
|
-
await
|
|
3030
|
+
await withMigrationSpan(
|
|
3031
|
+
this.otel,
|
|
3032
|
+
{
|
|
3033
|
+
serviceName: this.otelServiceName,
|
|
3034
|
+
endpoint: this.otelEndpoint,
|
|
3035
|
+
stream: config.name,
|
|
3036
|
+
reason: diff.changes.filter((c) => c.mutability === "immutable").map((c) => c.property).join(", ")
|
|
3037
|
+
},
|
|
3038
|
+
async () => {
|
|
3039
|
+
await this.migration.migrate(jsm, config.name, config);
|
|
3040
|
+
}
|
|
3041
|
+
);
|
|
1819
3042
|
return await jsm.streams.info(config.name);
|
|
1820
3043
|
}
|
|
1821
3044
|
buildMutableOnlyConfig(config, currentConfig, diff) {
|
|
@@ -1931,7 +3154,7 @@ var StreamProvider = class {
|
|
|
1931
3154
|
};
|
|
1932
3155
|
|
|
1933
3156
|
// src/server/infrastructure/consumer.provider.ts
|
|
1934
|
-
var
|
|
3157
|
+
var import_common11 = require("@nestjs/common");
|
|
1935
3158
|
var import_jetstream16 = require("@nats-io/jetstream");
|
|
1936
3159
|
var ConsumerProvider = class {
|
|
1937
3160
|
constructor(options, connection, streamProvider, patternRegistry) {
|
|
@@ -1939,8 +3162,15 @@ var ConsumerProvider = class {
|
|
|
1939
3162
|
this.connection = connection;
|
|
1940
3163
|
this.streamProvider = streamProvider;
|
|
1941
3164
|
this.patternRegistry = patternRegistry;
|
|
1942
|
-
|
|
1943
|
-
|
|
3165
|
+
const derived = deriveOtelAttrs(options);
|
|
3166
|
+
this.otel = derived.otel;
|
|
3167
|
+
this.otelServiceName = derived.serviceName;
|
|
3168
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
3169
|
+
}
|
|
3170
|
+
logger = new import_common11.Logger("Jetstream:Consumer");
|
|
3171
|
+
otel;
|
|
3172
|
+
otelServiceName;
|
|
3173
|
+
otelEndpoint;
|
|
1944
3174
|
/**
|
|
1945
3175
|
* Ensure consumers exist for the specified kinds.
|
|
1946
3176
|
*
|
|
@@ -1970,17 +3200,29 @@ var ConsumerProvider = class {
|
|
|
1970
3200
|
const stream = this.streamProvider.getStreamName(kind);
|
|
1971
3201
|
const config = this.buildConfig(kind);
|
|
1972
3202
|
const name = config.durable_name;
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
3203
|
+
return withProvisioningSpan(
|
|
3204
|
+
this.otel,
|
|
3205
|
+
{
|
|
3206
|
+
serviceName: this.otelServiceName,
|
|
3207
|
+
endpoint: this.otelEndpoint,
|
|
3208
|
+
entity: "consumer",
|
|
3209
|
+
name,
|
|
3210
|
+
action: "ensure"
|
|
3211
|
+
},
|
|
3212
|
+
async () => {
|
|
3213
|
+
this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
|
|
3214
|
+
try {
|
|
3215
|
+
await jsm.consumers.info(stream, name);
|
|
3216
|
+
this.logger.debug(`Consumer exists, updating: ${name}`);
|
|
3217
|
+
return await jsm.consumers.update(stream, name, config);
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
if (!(err instanceof import_jetstream16.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3220
|
+
throw err;
|
|
3221
|
+
}
|
|
3222
|
+
return await this.createConsumer(jsm, stream, name, config);
|
|
3223
|
+
}
|
|
1981
3224
|
}
|
|
1982
|
-
|
|
1983
|
-
}
|
|
3225
|
+
);
|
|
1984
3226
|
}
|
|
1985
3227
|
/**
|
|
1986
3228
|
* Recover a consumer that disappeared during runtime.
|
|
@@ -1999,16 +3241,28 @@ var ConsumerProvider = class {
|
|
|
1999
3241
|
const stream = this.streamProvider.getStreamName(kind);
|
|
2000
3242
|
const config = this.buildConfig(kind);
|
|
2001
3243
|
const name = config.durable_name;
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
3244
|
+
return withProvisioningSpan(
|
|
3245
|
+
this.otel,
|
|
3246
|
+
{
|
|
3247
|
+
serviceName: this.otelServiceName,
|
|
3248
|
+
endpoint: this.otelEndpoint,
|
|
3249
|
+
entity: "consumer",
|
|
3250
|
+
name,
|
|
3251
|
+
action: "recover"
|
|
3252
|
+
},
|
|
3253
|
+
async () => {
|
|
3254
|
+
this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
|
|
3255
|
+
await this.assertNoMigrationInProgress(jsm, stream);
|
|
3256
|
+
try {
|
|
3257
|
+
return await jsm.consumers.info(stream, name);
|
|
3258
|
+
} catch (err) {
|
|
3259
|
+
if (!(err instanceof import_jetstream16.JetStreamApiError) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3260
|
+
throw err;
|
|
3261
|
+
}
|
|
3262
|
+
return await this.createConsumer(jsm, stream, name, config);
|
|
3263
|
+
}
|
|
2009
3264
|
}
|
|
2010
|
-
|
|
2011
|
-
}
|
|
3265
|
+
);
|
|
2012
3266
|
}
|
|
2013
3267
|
/**
|
|
2014
3268
|
* Throw if a migration backup stream exists for this stream.
|
|
@@ -2124,7 +3378,7 @@ var ConsumerProvider = class {
|
|
|
2124
3378
|
};
|
|
2125
3379
|
|
|
2126
3380
|
// src/server/infrastructure/message.provider.ts
|
|
2127
|
-
var
|
|
3381
|
+
var import_common12 = require("@nestjs/common");
|
|
2128
3382
|
var import_jetstream18 = require("@nats-io/jetstream");
|
|
2129
3383
|
var import_rxjs3 = require("rxjs");
|
|
2130
3384
|
var MessageProvider = class {
|
|
@@ -2134,7 +3388,7 @@ var MessageProvider = class {
|
|
|
2134
3388
|
this.consumeOptionsMap = consumeOptionsMap;
|
|
2135
3389
|
this.consumerRecoveryFn = consumerRecoveryFn;
|
|
2136
3390
|
}
|
|
2137
|
-
logger = new
|
|
3391
|
+
logger = new import_common12.Logger("Jetstream:Message");
|
|
2138
3392
|
activeIterators = /* @__PURE__ */ new Set();
|
|
2139
3393
|
orderedReadyResolve = null;
|
|
2140
3394
|
orderedReadyReject = null;
|
|
@@ -2254,13 +3508,17 @@ var MessageProvider = class {
|
|
|
2254
3508
|
}
|
|
2255
3509
|
const defaults = { idle_heartbeat: 5e3 };
|
|
2256
3510
|
const userOptions = this.consumeOptionsMap.get(kind) ?? {};
|
|
2257
|
-
const messages = await consumer.consume({
|
|
3511
|
+
const messages = await consumer.consume({
|
|
3512
|
+
...defaults,
|
|
3513
|
+
...userOptions,
|
|
3514
|
+
callback: (msg) => {
|
|
3515
|
+
target$.next(msg);
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
2258
3518
|
this.activeIterators.add(messages);
|
|
2259
3519
|
this.monitorConsumerHealth(messages, consumerName2);
|
|
2260
3520
|
try {
|
|
2261
|
-
|
|
2262
|
-
target$.next(msg);
|
|
2263
|
-
}
|
|
3521
|
+
await messages.closed();
|
|
2264
3522
|
} finally {
|
|
2265
3523
|
this.activeIterators.delete(messages);
|
|
2266
3524
|
}
|
|
@@ -2352,11 +3610,16 @@ var MessageProvider = class {
|
|
|
2352
3610
|
})
|
|
2353
3611
|
);
|
|
2354
3612
|
}
|
|
2355
|
-
/** Single iteration: create ordered consumer ->
|
|
3613
|
+
/** Single iteration: create ordered consumer -> push messages into the subject. */
|
|
2356
3614
|
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
2357
3615
|
const js = this.connection.getJetStreamClient();
|
|
2358
3616
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
2359
|
-
const
|
|
3617
|
+
const orderedMessages$ = this.orderedMessages$;
|
|
3618
|
+
const messages = await consumer.consume({
|
|
3619
|
+
callback: (msg) => {
|
|
3620
|
+
orderedMessages$.next(msg);
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
2360
3623
|
if (this.orderedReadyResolve) {
|
|
2361
3624
|
this.orderedReadyResolve();
|
|
2362
3625
|
this.orderedReadyResolve = null;
|
|
@@ -2364,9 +3627,7 @@ var MessageProvider = class {
|
|
|
2364
3627
|
}
|
|
2365
3628
|
this.activeIterators.add(messages);
|
|
2366
3629
|
try {
|
|
2367
|
-
|
|
2368
|
-
this.orderedMessages$.next(msg);
|
|
2369
|
-
}
|
|
3630
|
+
await messages.closed();
|
|
2370
3631
|
} finally {
|
|
2371
3632
|
this.activeIterators.delete(messages);
|
|
2372
3633
|
}
|
|
@@ -2374,7 +3635,7 @@ var MessageProvider = class {
|
|
|
2374
3635
|
};
|
|
2375
3636
|
|
|
2376
3637
|
// src/server/infrastructure/metadata.provider.ts
|
|
2377
|
-
var
|
|
3638
|
+
var import_common13 = require("@nestjs/common");
|
|
2378
3639
|
var import_kv = require("@nats-io/kv");
|
|
2379
3640
|
var MetadataProvider = class {
|
|
2380
3641
|
constructor(options, connection) {
|
|
@@ -2383,7 +3644,7 @@ var MetadataProvider = class {
|
|
|
2383
3644
|
this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
|
|
2384
3645
|
this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
|
|
2385
3646
|
}
|
|
2386
|
-
logger = new
|
|
3647
|
+
logger = new import_common13.Logger("Jetstream:Metadata");
|
|
2387
3648
|
bucketName;
|
|
2388
3649
|
replicas;
|
|
2389
3650
|
ttl;
|
|
@@ -2476,7 +3737,7 @@ var MetadataProvider = class {
|
|
|
2476
3737
|
};
|
|
2477
3738
|
|
|
2478
3739
|
// src/server/routing/pattern-registry.ts
|
|
2479
|
-
var
|
|
3740
|
+
var import_common14 = require("@nestjs/common");
|
|
2480
3741
|
var HANDLER_LABELS = {
|
|
2481
3742
|
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
2482
3743
|
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
@@ -2487,7 +3748,7 @@ var PatternRegistry = class {
|
|
|
2487
3748
|
constructor(options) {
|
|
2488
3749
|
this.options = options;
|
|
2489
3750
|
}
|
|
2490
|
-
logger = new
|
|
3751
|
+
logger = new import_common14.Logger("Jetstream:PatternRegistry");
|
|
2491
3752
|
registry = /* @__PURE__ */ new Map();
|
|
2492
3753
|
// Cached after registerHandlers() — the registry is immutable from that point
|
|
2493
3754
|
cachedPatterns = null;
|
|
@@ -2644,9 +3905,13 @@ var PatternRegistry = class {
|
|
|
2644
3905
|
};
|
|
2645
3906
|
|
|
2646
3907
|
// src/server/routing/event.router.ts
|
|
2647
|
-
var
|
|
2648
|
-
var import_rxjs4 = require("rxjs");
|
|
3908
|
+
var import_common15 = require("@nestjs/common");
|
|
2649
3909
|
var import_transport_node4 = require("@nats-io/transport-node");
|
|
3910
|
+
var eventConsumeKindFor = (kind) => {
|
|
3911
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
3912
|
+
if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
|
|
3913
|
+
return "event" /* Event */;
|
|
3914
|
+
};
|
|
2650
3915
|
var EventRouter = class {
|
|
2651
3916
|
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
|
|
2652
3917
|
this.messageProvider = messageProvider;
|
|
@@ -2658,9 +3923,22 @@ var EventRouter = class {
|
|
|
2658
3923
|
this.ackWaitMap = ackWaitMap;
|
|
2659
3924
|
this.connection = connection;
|
|
2660
3925
|
this.options = options;
|
|
3926
|
+
if (options) {
|
|
3927
|
+
const derived = deriveOtelAttrs(options);
|
|
3928
|
+
this.otel = derived.otel;
|
|
3929
|
+
this.serviceName = derived.serviceName;
|
|
3930
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
3931
|
+
} else {
|
|
3932
|
+
this.otel = resolveOtelOptions({ enabled: false });
|
|
3933
|
+
this.serviceName = "";
|
|
3934
|
+
this.serverEndpoint = null;
|
|
3935
|
+
}
|
|
2661
3936
|
}
|
|
2662
|
-
logger = new
|
|
3937
|
+
logger = new import_common15.Logger("Jetstream:EventRouter");
|
|
2663
3938
|
subscriptions = [];
|
|
3939
|
+
otel;
|
|
3940
|
+
serviceName;
|
|
3941
|
+
serverEndpoint;
|
|
2664
3942
|
/**
|
|
2665
3943
|
* Update the max_deliver thresholds from actual NATS consumer configs.
|
|
2666
3944
|
* Called after consumers are ensured so the DLQ map reflects reality.
|
|
@@ -2684,15 +3962,233 @@ var EventRouter = class {
|
|
|
2684
3962
|
}
|
|
2685
3963
|
this.subscriptions.length = 0;
|
|
2686
3964
|
}
|
|
2687
|
-
/** Subscribe to a message stream and route each message. */
|
|
3965
|
+
/** Subscribe to a message stream and route each message to its handler. */
|
|
2688
3966
|
subscribeToStream(stream$, kind) {
|
|
2689
3967
|
const isOrdered = kind === "ordered" /* Ordered */;
|
|
3968
|
+
const patternRegistry = this.patternRegistry;
|
|
3969
|
+
const codec = this.codec;
|
|
3970
|
+
const eventBus = this.eventBus;
|
|
3971
|
+
const logger5 = this.logger;
|
|
3972
|
+
const deadLetterConfig = this.deadLetterConfig;
|
|
3973
|
+
const otel = this.otel;
|
|
3974
|
+
const serviceName = this.serviceName;
|
|
3975
|
+
const serverEndpoint = this.serverEndpoint;
|
|
3976
|
+
const spanKind = eventConsumeKindFor(kind);
|
|
2690
3977
|
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
3978
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
2691
3979
|
const concurrency = this.getConcurrency(kind);
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
)
|
|
2695
|
-
|
|
3980
|
+
const hasDlqCheck = deadLetterConfig !== void 0;
|
|
3981
|
+
const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
|
|
3982
|
+
const isDeadLetter = (msg) => {
|
|
3983
|
+
if (!hasDlqCheck) return false;
|
|
3984
|
+
const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
|
|
3985
|
+
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
3986
|
+
return msg.info.deliveryCount >= maxDeliver;
|
|
3987
|
+
};
|
|
3988
|
+
const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
|
|
3989
|
+
const settleSuccess = (msg, ctx) => {
|
|
3990
|
+
if (ctx.shouldTerminate) msg.term(ctx.terminateReason);
|
|
3991
|
+
else if (ctx.shouldRetry) msg.nak(ctx.retryDelay);
|
|
3992
|
+
else msg.ack();
|
|
3993
|
+
};
|
|
3994
|
+
const settleFailure = async (msg, data, err) => {
|
|
3995
|
+
if (handleDeadLetter !== null && isDeadLetter(msg)) {
|
|
3996
|
+
await handleDeadLetter(msg, data, err);
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
msg.nak();
|
|
4000
|
+
};
|
|
4001
|
+
const resolveEvent = (msg) => {
|
|
4002
|
+
const subject = msg.subject;
|
|
4003
|
+
try {
|
|
4004
|
+
const handler = patternRegistry.getHandler(subject);
|
|
4005
|
+
if (!handler) {
|
|
4006
|
+
msg.term(`No handler for event: ${subject}`);
|
|
4007
|
+
logger5.error(`No handler for subject: ${subject}`);
|
|
4008
|
+
return null;
|
|
4009
|
+
}
|
|
4010
|
+
let data;
|
|
4011
|
+
try {
|
|
4012
|
+
data = codec.decode(msg.data);
|
|
4013
|
+
} catch (err) {
|
|
4014
|
+
msg.term("Decode error");
|
|
4015
|
+
logger5.error(`Decode error for ${subject}:`, err);
|
|
4016
|
+
return null;
|
|
4017
|
+
}
|
|
4018
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4019
|
+
return { handler, data };
|
|
4020
|
+
} catch (err) {
|
|
4021
|
+
logger5.error(`Unexpected error in ${kind} event router`, err);
|
|
4022
|
+
try {
|
|
4023
|
+
msg.term("Unexpected router error");
|
|
4024
|
+
} catch (termErr) {
|
|
4025
|
+
logger5.error(`Failed to terminate message ${subject}:`, termErr);
|
|
4026
|
+
}
|
|
4027
|
+
return null;
|
|
4028
|
+
}
|
|
4029
|
+
};
|
|
4030
|
+
const handleSafe = (msg) => {
|
|
4031
|
+
const resolved = resolveEvent(msg);
|
|
4032
|
+
if (resolved === null) return void 0;
|
|
4033
|
+
const { handler, data } = resolved;
|
|
4034
|
+
const ctx = new RpcContext([msg]);
|
|
4035
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4036
|
+
let pending;
|
|
4037
|
+
try {
|
|
4038
|
+
pending = withConsumeSpan(
|
|
4039
|
+
{
|
|
4040
|
+
subject: msg.subject,
|
|
4041
|
+
msg,
|
|
4042
|
+
info: msg.info,
|
|
4043
|
+
kind: spanKind,
|
|
4044
|
+
payloadBytes: msg.data.length,
|
|
4045
|
+
handlerMetadata: { pattern: msg.subject },
|
|
4046
|
+
serviceName,
|
|
4047
|
+
endpoint: serverEndpoint
|
|
4048
|
+
},
|
|
4049
|
+
otel,
|
|
4050
|
+
() => unwrapResult(handler(data, ctx))
|
|
4051
|
+
);
|
|
4052
|
+
} catch (err) {
|
|
4053
|
+
eventBus.emit(
|
|
4054
|
+
"error" /* Error */,
|
|
4055
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4056
|
+
`${kind}-handler:${msg.subject}`
|
|
4057
|
+
);
|
|
4058
|
+
return settleFailure(msg, data, err).finally(() => {
|
|
4059
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4060
|
+
});
|
|
4061
|
+
}
|
|
4062
|
+
if (!isPromiseLike2(pending)) {
|
|
4063
|
+
settleSuccess(msg, ctx);
|
|
4064
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4065
|
+
return void 0;
|
|
4066
|
+
}
|
|
4067
|
+
return pending.then(
|
|
4068
|
+
() => {
|
|
4069
|
+
settleSuccess(msg, ctx);
|
|
4070
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4071
|
+
},
|
|
4072
|
+
async (err) => {
|
|
4073
|
+
eventBus.emit(
|
|
4074
|
+
"error" /* Error */,
|
|
4075
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4076
|
+
`${kind}-handler:${msg.subject}`
|
|
4077
|
+
);
|
|
4078
|
+
try {
|
|
4079
|
+
await settleFailure(msg, data, err);
|
|
4080
|
+
} finally {
|
|
4081
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
);
|
|
4085
|
+
};
|
|
4086
|
+
const handleOrderedSafe = (msg) => {
|
|
4087
|
+
const subject = msg.subject;
|
|
4088
|
+
let handler;
|
|
4089
|
+
let data;
|
|
4090
|
+
try {
|
|
4091
|
+
handler = patternRegistry.getHandler(subject);
|
|
4092
|
+
if (!handler) {
|
|
4093
|
+
logger5.error(`No handler for subject: ${subject}`);
|
|
4094
|
+
return void 0;
|
|
4095
|
+
}
|
|
4096
|
+
try {
|
|
4097
|
+
data = codec.decode(msg.data);
|
|
4098
|
+
} catch (err) {
|
|
4099
|
+
logger5.error(`Decode error for ${subject}:`, err);
|
|
4100
|
+
return void 0;
|
|
4101
|
+
}
|
|
4102
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4103
|
+
} catch (err) {
|
|
4104
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4105
|
+
return void 0;
|
|
4106
|
+
}
|
|
4107
|
+
const ctx = new RpcContext([msg]);
|
|
4108
|
+
const warnIfSettlementAttempted = () => {
|
|
4109
|
+
if (ctx.shouldRetry || ctx.shouldTerminate) {
|
|
4110
|
+
logger5.warn(
|
|
4111
|
+
`retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
|
|
4112
|
+
);
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
4115
|
+
let pending;
|
|
4116
|
+
try {
|
|
4117
|
+
pending = withConsumeSpan(
|
|
4118
|
+
{
|
|
4119
|
+
subject: msg.subject,
|
|
4120
|
+
msg,
|
|
4121
|
+
info: msg.info,
|
|
4122
|
+
kind: spanKind,
|
|
4123
|
+
payloadBytes: msg.data.length,
|
|
4124
|
+
handlerMetadata: { pattern: msg.subject },
|
|
4125
|
+
serviceName,
|
|
4126
|
+
endpoint: serverEndpoint
|
|
4127
|
+
},
|
|
4128
|
+
otel,
|
|
4129
|
+
() => unwrapResult(handler(data, ctx))
|
|
4130
|
+
);
|
|
4131
|
+
} catch (err) {
|
|
4132
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4133
|
+
return void 0;
|
|
4134
|
+
}
|
|
4135
|
+
if (!isPromiseLike2(pending)) {
|
|
4136
|
+
warnIfSettlementAttempted();
|
|
4137
|
+
return void 0;
|
|
4138
|
+
}
|
|
4139
|
+
return pending.then(warnIfSettlementAttempted, (err) => {
|
|
4140
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4141
|
+
});
|
|
4142
|
+
};
|
|
4143
|
+
const route = isOrdered ? handleOrderedSafe : handleSafe;
|
|
4144
|
+
const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
|
|
4145
|
+
const backlogWarnThreshold = 1e3;
|
|
4146
|
+
let active = 0;
|
|
4147
|
+
let backlogWarned = false;
|
|
4148
|
+
const backlog = [];
|
|
4149
|
+
const onAsyncDone = () => {
|
|
4150
|
+
active--;
|
|
4151
|
+
drainBacklog();
|
|
4152
|
+
};
|
|
4153
|
+
const drainBacklog = () => {
|
|
4154
|
+
while (active < maxActive) {
|
|
4155
|
+
const next = backlog.shift();
|
|
4156
|
+
if (next === void 0) return;
|
|
4157
|
+
active++;
|
|
4158
|
+
const result = route(next);
|
|
4159
|
+
if (result !== void 0) {
|
|
4160
|
+
void result.finally(onAsyncDone);
|
|
4161
|
+
} else {
|
|
4162
|
+
active--;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
4166
|
+
};
|
|
4167
|
+
const subscription = stream$.subscribe({
|
|
4168
|
+
next: (msg) => {
|
|
4169
|
+
if (active >= maxActive) {
|
|
4170
|
+
backlog.push(msg);
|
|
4171
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
4172
|
+
backlogWarned = true;
|
|
4173
|
+
logger5.warn(
|
|
4174
|
+
`${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
active++;
|
|
4180
|
+
const result = route(msg);
|
|
4181
|
+
if (result !== void 0) {
|
|
4182
|
+
void result.finally(onAsyncDone);
|
|
4183
|
+
} else {
|
|
4184
|
+
active--;
|
|
4185
|
+
if (backlog.length > 0) drainBacklog();
|
|
4186
|
+
}
|
|
4187
|
+
},
|
|
4188
|
+
error: (err) => {
|
|
4189
|
+
logger5.error(`Stream error in ${kind} router`, err);
|
|
4190
|
+
}
|
|
4191
|
+
});
|
|
2696
4192
|
this.subscriptions.push(subscription);
|
|
2697
4193
|
}
|
|
2698
4194
|
getConcurrency(kind) {
|
|
@@ -2705,94 +4201,11 @@ var EventRouter = class {
|
|
|
2705
4201
|
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
2706
4202
|
return void 0;
|
|
2707
4203
|
}
|
|
2708
|
-
/** Handle a single event message with error isolation. */
|
|
2709
|
-
async handleSafe(msg, ackExtensionInterval, kind) {
|
|
2710
|
-
try {
|
|
2711
|
-
const resolved = this.decodeMessage(msg);
|
|
2712
|
-
if (!resolved) return;
|
|
2713
|
-
await this.executeHandler(
|
|
2714
|
-
resolved.handler,
|
|
2715
|
-
resolved.data,
|
|
2716
|
-
resolved.ctx,
|
|
2717
|
-
msg,
|
|
2718
|
-
ackExtensionInterval
|
|
2719
|
-
);
|
|
2720
|
-
} catch (err) {
|
|
2721
|
-
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
/** Handle an ordered message with error isolation. */
|
|
2725
|
-
async handleOrderedSafe(msg) {
|
|
2726
|
-
try {
|
|
2727
|
-
const resolved = this.decodeMessage(msg, true);
|
|
2728
|
-
if (!resolved) return;
|
|
2729
|
-
await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
|
|
2730
|
-
if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
|
|
2731
|
-
this.logger.warn(
|
|
2732
|
-
`retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
|
|
2733
|
-
);
|
|
2734
|
-
}
|
|
2735
|
-
} catch (err) {
|
|
2736
|
-
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
2740
|
-
decodeMessage(msg, isOrdered = false) {
|
|
2741
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
2742
|
-
if (!handler) {
|
|
2743
|
-
if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
|
|
2744
|
-
this.logger.error(`No handler for subject: ${msg.subject}`);
|
|
2745
|
-
return null;
|
|
2746
|
-
}
|
|
2747
|
-
let data;
|
|
2748
|
-
try {
|
|
2749
|
-
data = this.codec.decode(msg.data);
|
|
2750
|
-
} catch (err) {
|
|
2751
|
-
if (!isOrdered) msg.term("Decode error");
|
|
2752
|
-
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
2753
|
-
return null;
|
|
2754
|
-
}
|
|
2755
|
-
this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
|
|
2756
|
-
return { handler, data, ctx: new RpcContext([msg]) };
|
|
2757
|
-
}
|
|
2758
|
-
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
2759
|
-
async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
|
|
2760
|
-
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
2761
|
-
try {
|
|
2762
|
-
await unwrapResult(handler(data, ctx));
|
|
2763
|
-
if (ctx.shouldTerminate) {
|
|
2764
|
-
msg.term(ctx.terminateReason);
|
|
2765
|
-
} else if (ctx.shouldRetry) {
|
|
2766
|
-
msg.nak(ctx.retryDelay);
|
|
2767
|
-
} else {
|
|
2768
|
-
msg.ack();
|
|
2769
|
-
}
|
|
2770
|
-
} catch (err) {
|
|
2771
|
-
this.logger.error(`Event handler error (${msg.subject}):`, err);
|
|
2772
|
-
if (this.isDeadLetter(msg)) {
|
|
2773
|
-
await this.handleDeadLetter(msg, data, err);
|
|
2774
|
-
} else {
|
|
2775
|
-
msg.nak();
|
|
2776
|
-
}
|
|
2777
|
-
} finally {
|
|
2778
|
-
stopAckExtension?.();
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
/** Check if the message has exhausted all delivery attempts. */
|
|
2782
|
-
isDeadLetter(msg) {
|
|
2783
|
-
if (!this.deadLetterConfig) return false;
|
|
2784
|
-
const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
|
|
2785
|
-
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
2786
|
-
return msg.info.deliveryCount >= maxDeliver;
|
|
2787
|
-
}
|
|
2788
|
-
/** Handle a dead letter: invoke callback, then term or nak based on result. */
|
|
2789
4204
|
/**
|
|
2790
|
-
*
|
|
2791
|
-
*
|
|
2792
|
-
*
|
|
2793
|
-
*
|
|
2794
|
-
* On success, terminates the message. On error, leaves it unacknowledged (nak)
|
|
2795
|
-
* so NATS can retry the delivery on the next cycle.
|
|
4205
|
+
* Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
|
|
4206
|
+
* success or `nak` on hook failure so NATS retries on the next delivery
|
|
4207
|
+
* cycle. Used when DLQ stream isn't configured, or when publishing to it
|
|
4208
|
+
* failed and we still have to surface the message somewhere observable.
|
|
2796
4209
|
*/
|
|
2797
4210
|
async fallbackToOnDeadLetterCallback(info, msg) {
|
|
2798
4211
|
if (!this.deadLetterConfig) {
|
|
@@ -2884,21 +4297,37 @@ var EventRouter = class {
|
|
|
2884
4297
|
streamSequence: msg.info.streamSequence,
|
|
2885
4298
|
timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
|
|
2886
4299
|
};
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
4300
|
+
await withDeadLetterSpan(
|
|
4301
|
+
{
|
|
4302
|
+
msg,
|
|
4303
|
+
// Pattern resolution mirrors event-routing: when a registered
|
|
4304
|
+
// pattern matches, surface it on the DLQ span so APM can filter
|
|
4305
|
+
// dead letters by handler without parsing the subject. Falls back
|
|
4306
|
+
// to the subject itself when no glob handler is in play.
|
|
4307
|
+
pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
|
|
4308
|
+
finalDeliveryCount: msg.info.deliveryCount,
|
|
4309
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
4310
|
+
serviceName: this.serviceName,
|
|
4311
|
+
endpoint: this.serverEndpoint
|
|
4312
|
+
},
|
|
4313
|
+
this.otel,
|
|
4314
|
+
async () => {
|
|
4315
|
+
this.eventBus.emit("deadLetter" /* DeadLetter */, info);
|
|
4316
|
+
if (!this.options?.dlq) {
|
|
4317
|
+
await this.fallbackToOnDeadLetterCallback(info, msg);
|
|
4318
|
+
} else {
|
|
4319
|
+
await this.publishToDlq(msg, info, error);
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
);
|
|
2893
4323
|
}
|
|
2894
4324
|
};
|
|
2895
4325
|
|
|
2896
4326
|
// src/server/routing/rpc.router.ts
|
|
2897
|
-
var
|
|
4327
|
+
var import_common16 = require("@nestjs/common");
|
|
2898
4328
|
var import_transport_node5 = require("@nats-io/transport-node");
|
|
2899
|
-
var import_rxjs5 = require("rxjs");
|
|
2900
4329
|
var RpcRouter = class {
|
|
2901
|
-
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
4330
|
+
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
|
|
2902
4331
|
this.messageProvider = messageProvider;
|
|
2903
4332
|
this.patternRegistry = patternRegistry;
|
|
2904
4333
|
this.connection = connection;
|
|
@@ -2908,13 +4337,26 @@ var RpcRouter = class {
|
|
|
2908
4337
|
this.ackWaitMap = ackWaitMap;
|
|
2909
4338
|
this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
|
|
2910
4339
|
this.concurrency = rpcOptions?.concurrency;
|
|
4340
|
+
if (options) {
|
|
4341
|
+
const derived = deriveOtelAttrs(options);
|
|
4342
|
+
this.otel = derived.otel;
|
|
4343
|
+
this.serviceName = derived.serviceName;
|
|
4344
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
4345
|
+
} else {
|
|
4346
|
+
this.otel = resolveOtelOptions({ enabled: false });
|
|
4347
|
+
this.serviceName = "";
|
|
4348
|
+
this.serverEndpoint = null;
|
|
4349
|
+
}
|
|
2911
4350
|
}
|
|
2912
|
-
logger = new
|
|
4351
|
+
logger = new import_common16.Logger("Jetstream:RpcRouter");
|
|
2913
4352
|
timeout;
|
|
2914
4353
|
concurrency;
|
|
2915
4354
|
resolvedAckExtensionInterval;
|
|
2916
4355
|
subscription = null;
|
|
2917
4356
|
cachedNc = null;
|
|
4357
|
+
otel;
|
|
4358
|
+
serviceName;
|
|
4359
|
+
serverEndpoint;
|
|
2918
4360
|
/** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
|
|
2919
4361
|
get ackExtensionInterval() {
|
|
2920
4362
|
if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
|
|
@@ -2927,99 +4369,213 @@ var RpcRouter = class {
|
|
|
2927
4369
|
/** Start routing command messages to handlers. */
|
|
2928
4370
|
async start() {
|
|
2929
4371
|
this.cachedNc = await this.connection.getConnection();
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
this.
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
const { headers: msgHeaders } = msg;
|
|
2947
|
-
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
2948
|
-
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
2949
|
-
if (!replyTo || !correlationId) {
|
|
2950
|
-
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
2951
|
-
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
2952
|
-
return;
|
|
2953
|
-
}
|
|
2954
|
-
let data;
|
|
2955
|
-
try {
|
|
2956
|
-
data = this.codec.decode(msg.data);
|
|
2957
|
-
} catch (err) {
|
|
2958
|
-
msg.term("Decode error");
|
|
2959
|
-
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
2960
|
-
return;
|
|
2961
|
-
}
|
|
2962
|
-
this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
|
|
2963
|
-
await this.executeHandler(handler, data, msg, replyTo, correlationId);
|
|
2964
|
-
} catch (err) {
|
|
2965
|
-
this.logger.error("Unexpected error in RPC router", err);
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
/** Execute handler, publish response, settle message. */
|
|
2969
|
-
async executeHandler(handler, data, msg, replyTo, correlationId) {
|
|
2970
|
-
const nc = this.cachedNc ?? await this.connection.getConnection();
|
|
2971
|
-
const ctx = new RpcContext([msg]);
|
|
2972
|
-
let settled = false;
|
|
2973
|
-
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
2974
|
-
const timeoutId = setTimeout(() => {
|
|
2975
|
-
if (settled) return;
|
|
2976
|
-
settled = true;
|
|
2977
|
-
stopAckExtension?.();
|
|
2978
|
-
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
2979
|
-
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
2980
|
-
msg.term("Handler timeout");
|
|
2981
|
-
}, this.timeout);
|
|
2982
|
-
try {
|
|
2983
|
-
const result = await unwrapResult(handler(data, ctx));
|
|
2984
|
-
if (settled) return;
|
|
2985
|
-
settled = true;
|
|
2986
|
-
clearTimeout(timeoutId);
|
|
2987
|
-
stopAckExtension?.();
|
|
2988
|
-
msg.ack();
|
|
4372
|
+
const nc = this.cachedNc;
|
|
4373
|
+
const patternRegistry = this.patternRegistry;
|
|
4374
|
+
const codec = this.codec;
|
|
4375
|
+
const eventBus = this.eventBus;
|
|
4376
|
+
const logger5 = this.logger;
|
|
4377
|
+
const timeout = this.timeout;
|
|
4378
|
+
const ackExtensionInterval = this.ackExtensionInterval;
|
|
4379
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
4380
|
+
const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
|
|
4381
|
+
const otel = this.otel;
|
|
4382
|
+
const serviceName = this.serviceName;
|
|
4383
|
+
const serverEndpoint = this.serverEndpoint;
|
|
4384
|
+
const emitRpcTimeout = (subject, correlationId) => {
|
|
4385
|
+
eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
4386
|
+
};
|
|
4387
|
+
const publishReply = (replyTo, correlationId, payload) => {
|
|
2989
4388
|
try {
|
|
2990
4389
|
const hdrs = (0, import_transport_node5.headers)();
|
|
2991
4390
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2992
|
-
nc.publish(replyTo,
|
|
4391
|
+
nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
|
|
2993
4392
|
} catch (publishErr) {
|
|
2994
|
-
|
|
4393
|
+
logger5.error(`Failed to publish RPC response`, publishErr);
|
|
2995
4394
|
}
|
|
2996
|
-
}
|
|
2997
|
-
|
|
2998
|
-
settled = true;
|
|
2999
|
-
clearTimeout(timeoutId);
|
|
3000
|
-
stopAckExtension?.();
|
|
4395
|
+
};
|
|
4396
|
+
const publishErrorReply = (replyTo, correlationId, subject, err) => {
|
|
3001
4397
|
try {
|
|
3002
4398
|
const hdrs = (0, import_transport_node5.headers)();
|
|
3003
4399
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
3004
4400
|
hdrs.set("x-error" /* Error */, "true");
|
|
3005
|
-
nc.publish(replyTo,
|
|
4401
|
+
nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
|
|
3006
4402
|
} catch (encodeErr) {
|
|
3007
|
-
|
|
4403
|
+
logger5.error(`Failed to encode RPC error for ${subject}`, encodeErr);
|
|
3008
4404
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
4405
|
+
};
|
|
4406
|
+
const resolveCommand = (msg) => {
|
|
4407
|
+
const subject = msg.subject;
|
|
4408
|
+
try {
|
|
4409
|
+
const handler = patternRegistry.getHandler(subject);
|
|
4410
|
+
if (!handler) {
|
|
4411
|
+
msg.term(`No handler for RPC: ${subject}`);
|
|
4412
|
+
logger5.error(`No handler for RPC subject: ${subject}`);
|
|
4413
|
+
return null;
|
|
4414
|
+
}
|
|
4415
|
+
const msgHeaders = msg.headers;
|
|
4416
|
+
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
4417
|
+
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
4418
|
+
if (!replyTo || !correlationId) {
|
|
4419
|
+
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
4420
|
+
logger5.error(`Missing headers for RPC: ${subject}`);
|
|
4421
|
+
return null;
|
|
4422
|
+
}
|
|
4423
|
+
let data;
|
|
4424
|
+
try {
|
|
4425
|
+
data = codec.decode(msg.data);
|
|
4426
|
+
} catch (err) {
|
|
4427
|
+
msg.term("Decode error");
|
|
4428
|
+
logger5.error(`Decode error for RPC ${subject}:`, err);
|
|
4429
|
+
return null;
|
|
4430
|
+
}
|
|
4431
|
+
eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
|
|
4432
|
+
return { handler, data, replyTo, correlationId };
|
|
4433
|
+
} catch (err) {
|
|
4434
|
+
logger5.error("Unexpected error in RPC router", err);
|
|
4435
|
+
try {
|
|
4436
|
+
msg.term("Unexpected router error");
|
|
4437
|
+
} catch (termErr) {
|
|
4438
|
+
logger5.error(`Failed to terminate RPC message ${subject}:`, termErr);
|
|
4439
|
+
}
|
|
4440
|
+
return null;
|
|
4441
|
+
}
|
|
4442
|
+
};
|
|
4443
|
+
const handleSafe = (msg) => {
|
|
4444
|
+
const resolved = resolveCommand(msg);
|
|
4445
|
+
if (resolved === null) return void 0;
|
|
4446
|
+
const { handler, data, replyTo, correlationId } = resolved;
|
|
4447
|
+
const subject = msg.subject;
|
|
4448
|
+
const ctx = new RpcContext([msg]);
|
|
4449
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4450
|
+
const reportHandlerError = (err) => {
|
|
4451
|
+
eventBus.emit(
|
|
4452
|
+
"error" /* Error */,
|
|
4453
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4454
|
+
`rpc-handler:${subject}`
|
|
4455
|
+
);
|
|
4456
|
+
publishErrorReply(replyTo, correlationId, subject, err);
|
|
4457
|
+
msg.term(`Handler error: ${subject}`);
|
|
4458
|
+
};
|
|
4459
|
+
const abortController = new AbortController();
|
|
4460
|
+
let pending;
|
|
4461
|
+
try {
|
|
4462
|
+
pending = withConsumeSpan(
|
|
4463
|
+
{
|
|
4464
|
+
subject,
|
|
4465
|
+
msg,
|
|
4466
|
+
info: msg.info,
|
|
4467
|
+
kind: "rpc" /* Rpc */,
|
|
4468
|
+
payloadBytes: msg.data.length,
|
|
4469
|
+
handlerMetadata: { pattern: subject },
|
|
4470
|
+
serviceName,
|
|
4471
|
+
endpoint: serverEndpoint
|
|
4472
|
+
},
|
|
4473
|
+
otel,
|
|
4474
|
+
() => unwrapResult(handler(data, ctx)),
|
|
4475
|
+
{ signal: abortController.signal, timeoutLabel: "rpc.handler.timeout" }
|
|
4476
|
+
);
|
|
4477
|
+
} catch (err) {
|
|
4478
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4479
|
+
reportHandlerError(err);
|
|
4480
|
+
return void 0;
|
|
4481
|
+
}
|
|
4482
|
+
if (!isPromiseLike2(pending)) {
|
|
4483
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4484
|
+
msg.ack();
|
|
4485
|
+
publishReply(replyTo, correlationId, pending);
|
|
4486
|
+
return void 0;
|
|
4487
|
+
}
|
|
4488
|
+
let settled = false;
|
|
4489
|
+
const timeoutId = setTimeout(() => {
|
|
4490
|
+
if (settled) return;
|
|
4491
|
+
settled = true;
|
|
4492
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4493
|
+
abortController.abort();
|
|
4494
|
+
emitRpcTimeout(subject, correlationId);
|
|
4495
|
+
msg.term("Handler timeout");
|
|
4496
|
+
}, timeout);
|
|
4497
|
+
return pending.then(
|
|
4498
|
+
(result) => {
|
|
4499
|
+
if (settled) return;
|
|
4500
|
+
settled = true;
|
|
4501
|
+
clearTimeout(timeoutId);
|
|
4502
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4503
|
+
msg.ack();
|
|
4504
|
+
publishReply(replyTo, correlationId, result);
|
|
4505
|
+
},
|
|
4506
|
+
(err) => {
|
|
4507
|
+
if (settled) return;
|
|
4508
|
+
settled = true;
|
|
4509
|
+
clearTimeout(timeoutId);
|
|
4510
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4511
|
+
reportHandlerError(err);
|
|
4512
|
+
}
|
|
4513
|
+
);
|
|
4514
|
+
};
|
|
4515
|
+
const backlogWarnThreshold = 1e3;
|
|
4516
|
+
let active = 0;
|
|
4517
|
+
let backlogWarned = false;
|
|
4518
|
+
const backlog = [];
|
|
4519
|
+
const onAsyncDone = () => {
|
|
4520
|
+
active--;
|
|
4521
|
+
drainBacklog();
|
|
4522
|
+
};
|
|
4523
|
+
const drainBacklog = () => {
|
|
4524
|
+
while (active < maxActive) {
|
|
4525
|
+
const next = backlog.shift();
|
|
4526
|
+
if (next === void 0) return;
|
|
4527
|
+
active++;
|
|
4528
|
+
const result = handleSafe(next);
|
|
4529
|
+
if (result !== void 0) {
|
|
4530
|
+
void result.finally(onAsyncDone);
|
|
4531
|
+
} else {
|
|
4532
|
+
active--;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
4536
|
+
};
|
|
4537
|
+
this.subscription = this.messageProvider.commands$.subscribe({
|
|
4538
|
+
next: (msg) => {
|
|
4539
|
+
if (active >= maxActive) {
|
|
4540
|
+
backlog.push(msg);
|
|
4541
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
4542
|
+
backlogWarned = true;
|
|
4543
|
+
logger5.warn(
|
|
4544
|
+
`RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
4545
|
+
);
|
|
4546
|
+
}
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
active++;
|
|
4550
|
+
const result = handleSafe(msg);
|
|
4551
|
+
if (result !== void 0) {
|
|
4552
|
+
void result.finally(onAsyncDone);
|
|
4553
|
+
} else {
|
|
4554
|
+
active--;
|
|
4555
|
+
if (backlog.length > 0) drainBacklog();
|
|
4556
|
+
}
|
|
4557
|
+
},
|
|
4558
|
+
error: (err) => {
|
|
4559
|
+
logger5.error("Stream error in RPC router", err);
|
|
4560
|
+
}
|
|
4561
|
+
});
|
|
4562
|
+
}
|
|
4563
|
+
/** Stop routing and unsubscribe. */
|
|
4564
|
+
destroy() {
|
|
4565
|
+
this.subscription?.unsubscribe();
|
|
4566
|
+
this.subscription = null;
|
|
3011
4567
|
}
|
|
3012
4568
|
};
|
|
3013
4569
|
|
|
3014
4570
|
// src/shutdown/shutdown.manager.ts
|
|
3015
|
-
var
|
|
4571
|
+
var import_common17 = require("@nestjs/common");
|
|
3016
4572
|
var ShutdownManager = class {
|
|
3017
4573
|
constructor(connection, eventBus, timeout) {
|
|
3018
4574
|
this.connection = connection;
|
|
3019
4575
|
this.eventBus = eventBus;
|
|
3020
4576
|
this.timeout = timeout;
|
|
3021
4577
|
}
|
|
3022
|
-
logger = new
|
|
4578
|
+
logger = new import_common17.Logger("Jetstream:Shutdown");
|
|
3023
4579
|
shutdownPromise;
|
|
3024
4580
|
/**
|
|
3025
4581
|
* Execute the full shutdown sequence.
|
|
@@ -3059,9 +4615,6 @@ var JetstreamModule = class {
|
|
|
3059
4615
|
this.shutdownManager = shutdownManager;
|
|
3060
4616
|
this.strategy = strategy;
|
|
3061
4617
|
}
|
|
3062
|
-
// -------------------------------------------------------------------
|
|
3063
|
-
// forRoot — global module registration
|
|
3064
|
-
// -------------------------------------------------------------------
|
|
3065
4618
|
/**
|
|
3066
4619
|
* Register the JetStream transport globally.
|
|
3067
4620
|
*
|
|
@@ -3088,9 +4641,6 @@ var JetstreamModule = class {
|
|
|
3088
4641
|
]
|
|
3089
4642
|
};
|
|
3090
4643
|
}
|
|
3091
|
-
// -------------------------------------------------------------------
|
|
3092
|
-
// forRootAsync — async global module registration
|
|
3093
|
-
// -------------------------------------------------------------------
|
|
3094
4644
|
/**
|
|
3095
4645
|
* Register the JetStream transport globally with async configuration.
|
|
3096
4646
|
*
|
|
@@ -3119,9 +4669,6 @@ var JetstreamModule = class {
|
|
|
3119
4669
|
]
|
|
3120
4670
|
};
|
|
3121
4671
|
}
|
|
3122
|
-
// -------------------------------------------------------------------
|
|
3123
|
-
// forFeature — per-module client registration
|
|
3124
|
-
// -------------------------------------------------------------------
|
|
3125
4672
|
/**
|
|
3126
4673
|
* Register a lightweight client proxy for a target service.
|
|
3127
4674
|
*
|
|
@@ -3147,9 +4694,6 @@ var JetstreamModule = class {
|
|
|
3147
4694
|
exports: [clientToken]
|
|
3148
4695
|
};
|
|
3149
4696
|
}
|
|
3150
|
-
// -------------------------------------------------------------------
|
|
3151
|
-
// Provider factories
|
|
3152
|
-
// -------------------------------------------------------------------
|
|
3153
4697
|
static createCoreProviders(options) {
|
|
3154
4698
|
return [
|
|
3155
4699
|
{
|
|
@@ -3167,8 +4711,8 @@ var JetstreamModule = class {
|
|
|
3167
4711
|
provide: JETSTREAM_EVENT_BUS,
|
|
3168
4712
|
inject: [JETSTREAM_OPTIONS],
|
|
3169
4713
|
useFactory: (options) => {
|
|
3170
|
-
const
|
|
3171
|
-
return new EventBus(
|
|
4714
|
+
const logger5 = new import_common18.Logger("Jetstream:Module");
|
|
4715
|
+
return new EventBus(logger5, options.hooks);
|
|
3172
4716
|
}
|
|
3173
4717
|
},
|
|
3174
4718
|
// Codec — global encode/decode
|
|
@@ -3207,10 +4751,8 @@ var JetstreamModule = class {
|
|
|
3207
4751
|
);
|
|
3208
4752
|
}
|
|
3209
4753
|
},
|
|
3210
|
-
// ---------------------------------------------------------------
|
|
3211
4754
|
// Consumer infrastructure — only created when consumer !== false.
|
|
3212
4755
|
// Providers return null when consumer is disabled (publisher-only mode).
|
|
3213
|
-
// ---------------------------------------------------------------
|
|
3214
4756
|
// PatternRegistry — subject-to-handler mapping
|
|
3215
4757
|
{
|
|
3216
4758
|
provide: PatternRegistry,
|
|
@@ -3246,8 +4788,14 @@ var JetstreamModule = class {
|
|
|
3246
4788
|
// MessageProvider — pull-based message consumption
|
|
3247
4789
|
{
|
|
3248
4790
|
provide: MessageProvider,
|
|
3249
|
-
inject: [
|
|
3250
|
-
|
|
4791
|
+
inject: [
|
|
4792
|
+
JETSTREAM_OPTIONS,
|
|
4793
|
+
JETSTREAM_CONNECTION,
|
|
4794
|
+
JETSTREAM_EVENT_BUS,
|
|
4795
|
+
ConsumerProvider,
|
|
4796
|
+
StreamProvider
|
|
4797
|
+
],
|
|
4798
|
+
useFactory: (options, connection, eventBus, consumerProvider, streamProvider) => {
|
|
3251
4799
|
if (options.consumer === false) return null;
|
|
3252
4800
|
const consumeOptionsMap = /* @__PURE__ */ new Map();
|
|
3253
4801
|
if (options.events?.consume)
|
|
@@ -3257,10 +4805,22 @@ var JetstreamModule = class {
|
|
|
3257
4805
|
if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
|
|
3258
4806
|
consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
|
|
3259
4807
|
}
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
4808
|
+
const derived = deriveOtelAttrs(options);
|
|
4809
|
+
const { otel, serverEndpoint: otelEndpoint, serviceName: otelServiceName } = derived;
|
|
4810
|
+
const consumerRecoveryFn = consumerProvider && streamProvider ? async (kind) => withSelfHealingSpan(
|
|
4811
|
+
otel,
|
|
4812
|
+
{
|
|
4813
|
+
serviceName: otelServiceName,
|
|
4814
|
+
endpoint: otelEndpoint,
|
|
4815
|
+
consumer: consumerProvider.getConsumerName(kind),
|
|
4816
|
+
stream: streamProvider.getStreamName(kind),
|
|
4817
|
+
reason: "consumer not found"
|
|
4818
|
+
},
|
|
4819
|
+
async () => {
|
|
4820
|
+
const jsm = await connection.getJetStreamManager();
|
|
4821
|
+
return consumerProvider.recoverConsumer(jsm, kind);
|
|
4822
|
+
}
|
|
4823
|
+
) : void 0;
|
|
3264
4824
|
return new MessageProvider(connection, eventBus, consumeOptionsMap, consumerRecoveryFn);
|
|
3265
4825
|
}
|
|
3266
4826
|
},
|
|
@@ -3331,7 +4891,8 @@ var JetstreamModule = class {
|
|
|
3331
4891
|
codec,
|
|
3332
4892
|
eventBus,
|
|
3333
4893
|
rpcOptions,
|
|
3334
|
-
ackWaitMap
|
|
4894
|
+
ackWaitMap,
|
|
4895
|
+
options
|
|
3335
4896
|
);
|
|
3336
4897
|
}
|
|
3337
4898
|
},
|
|
@@ -3434,9 +4995,6 @@ var JetstreamModule = class {
|
|
|
3434
4995
|
}
|
|
3435
4996
|
];
|
|
3436
4997
|
}
|
|
3437
|
-
// -------------------------------------------------------------------
|
|
3438
|
-
// Lifecycle hooks
|
|
3439
|
-
// -------------------------------------------------------------------
|
|
3440
4998
|
/**
|
|
3441
4999
|
* Gracefully shut down the transport on application termination.
|
|
3442
5000
|
*/
|
|
@@ -3447,23 +5005,34 @@ var JetstreamModule = class {
|
|
|
3447
5005
|
}
|
|
3448
5006
|
};
|
|
3449
5007
|
JetstreamModule = __decorateClass([
|
|
3450
|
-
(0,
|
|
3451
|
-
(0,
|
|
3452
|
-
__decorateParam(0, (0,
|
|
3453
|
-
__decorateParam(0, (0,
|
|
3454
|
-
__decorateParam(1, (0,
|
|
3455
|
-
__decorateParam(1, (0,
|
|
5008
|
+
(0, import_common18.Global)(),
|
|
5009
|
+
(0, import_common18.Module)({}),
|
|
5010
|
+
__decorateParam(0, (0, import_common18.Optional)()),
|
|
5011
|
+
__decorateParam(0, (0, import_common18.Inject)(ShutdownManager)),
|
|
5012
|
+
__decorateParam(1, (0, import_common18.Optional)()),
|
|
5013
|
+
__decorateParam(1, (0, import_common18.Inject)(JetstreamStrategy))
|
|
3456
5014
|
], JetstreamModule);
|
|
3457
5015
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3458
5016
|
0 && (module.exports = {
|
|
5017
|
+
ConsumeKind,
|
|
5018
|
+
DEFAULT_BROADCAST_CONSUMER_CONFIG,
|
|
5019
|
+
DEFAULT_BROADCAST_STREAM_CONFIG,
|
|
5020
|
+
DEFAULT_COMMAND_CONSUMER_CONFIG,
|
|
5021
|
+
DEFAULT_COMMAND_STREAM_CONFIG,
|
|
5022
|
+
DEFAULT_DLQ_STREAM_CONFIG,
|
|
5023
|
+
DEFAULT_EVENT_CONSUMER_CONFIG,
|
|
5024
|
+
DEFAULT_EVENT_STREAM_CONFIG,
|
|
5025
|
+
DEFAULT_JETSTREAM_RPC_TIMEOUT,
|
|
3459
5026
|
DEFAULT_METADATA_BUCKET,
|
|
3460
5027
|
DEFAULT_METADATA_HISTORY,
|
|
3461
5028
|
DEFAULT_METADATA_REPLICAS,
|
|
3462
5029
|
DEFAULT_METADATA_TTL,
|
|
3463
|
-
|
|
5030
|
+
DEFAULT_ORDERED_STREAM_CONFIG,
|
|
5031
|
+
DEFAULT_RPC_TIMEOUT,
|
|
5032
|
+
DEFAULT_SHUTDOWN_TIMEOUT,
|
|
5033
|
+
DEFAULT_TRACES,
|
|
3464
5034
|
JETSTREAM_CODEC,
|
|
3465
5035
|
JETSTREAM_CONNECTION,
|
|
3466
|
-
JETSTREAM_EVENT_BUS,
|
|
3467
5036
|
JETSTREAM_OPTIONS,
|
|
3468
5037
|
JetstreamClient,
|
|
3469
5038
|
JetstreamDlqHeader,
|
|
@@ -3473,12 +5042,18 @@ JetstreamModule = __decorateClass([
|
|
|
3473
5042
|
JetstreamRecord,
|
|
3474
5043
|
JetstreamRecordBuilder,
|
|
3475
5044
|
JetstreamStrategy,
|
|
5045
|
+
JetstreamTrace,
|
|
3476
5046
|
JsonCodec,
|
|
3477
5047
|
MIN_METADATA_TTL,
|
|
3478
5048
|
MessageKind,
|
|
5049
|
+
MsgpackCodec,
|
|
5050
|
+
NatsErrorCode,
|
|
3479
5051
|
PatternPrefix,
|
|
5052
|
+
PublishKind,
|
|
5053
|
+
RESERVED_HEADERS,
|
|
3480
5054
|
RpcContext,
|
|
3481
5055
|
StreamKind,
|
|
5056
|
+
TRACER_NAME,
|
|
3482
5057
|
TransportEvent,
|
|
3483
5058
|
buildBroadcastSubject,
|
|
3484
5059
|
buildSubject,
|