@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.js
CHANGED
|
@@ -14,19 +14,21 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
14
14
|
import {
|
|
15
15
|
Global,
|
|
16
16
|
Inject,
|
|
17
|
-
Logger as
|
|
17
|
+
Logger as Logger18,
|
|
18
18
|
Module,
|
|
19
19
|
Optional
|
|
20
20
|
} from "@nestjs/common";
|
|
21
21
|
|
|
22
22
|
// src/client/jetstream.client.ts
|
|
23
|
-
import { Logger } from "@nestjs/common";
|
|
23
|
+
import { Logger as Logger5 } from "@nestjs/common";
|
|
24
24
|
import { ClientProxy } from "@nestjs/microservices";
|
|
25
|
+
import { context as context6 } from "@opentelemetry/api";
|
|
26
|
+
import { nuid } from "@nats-io/nuid";
|
|
25
27
|
import {
|
|
26
28
|
createInbox,
|
|
27
|
-
headers as natsHeaders
|
|
29
|
+
headers as natsHeaders,
|
|
30
|
+
TimeoutError
|
|
28
31
|
} from "@nats-io/transport-node";
|
|
29
|
-
import { nuid } from "@nats-io/nuid";
|
|
30
32
|
|
|
31
33
|
// src/interfaces/hooks.interface.ts
|
|
32
34
|
var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
|
|
@@ -224,6 +226,920 @@ var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
|
|
|
224
226
|
var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
|
|
225
227
|
var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
|
|
226
228
|
|
|
229
|
+
// src/otel/constants.ts
|
|
230
|
+
var TRACER_NAME = "@horizon-republic/nestjs-jetstream";
|
|
231
|
+
|
|
232
|
+
// src/otel/attribute-keys.ts
|
|
233
|
+
var ATTR_MESSAGING_SYSTEM = "messaging.system";
|
|
234
|
+
var ATTR_MESSAGING_DESTINATION_NAME = "messaging.destination.name";
|
|
235
|
+
var ATTR_MESSAGING_DESTINATION_TEMPLATE = "messaging.destination.template";
|
|
236
|
+
var ATTR_MESSAGING_CLIENT_ID = "messaging.client.id";
|
|
237
|
+
var ATTR_MESSAGING_OPERATION_NAME = "messaging.operation.name";
|
|
238
|
+
var ATTR_MESSAGING_OPERATION_TYPE = "messaging.operation.type";
|
|
239
|
+
var ATTR_MESSAGING_MESSAGE_BODY_SIZE = "messaging.message.body.size";
|
|
240
|
+
var ATTR_MESSAGING_MESSAGE_ID = "messaging.message.id";
|
|
241
|
+
var ATTR_MESSAGING_MESSAGE_CONVERSATION_ID = "messaging.message.conversation_id";
|
|
242
|
+
var ATTR_MESSAGING_CONSUMER_GROUP_NAME = "messaging.consumer.group.name";
|
|
243
|
+
var ATTR_MESSAGING_HEADER_PREFIX = "messaging.header.";
|
|
244
|
+
var ATTR_MESSAGING_NATS_STREAM_NAME = "messaging.nats.stream.name";
|
|
245
|
+
var ATTR_MESSAGING_NATS_STREAM_SEQUENCE = "messaging.nats.message.stream_sequence";
|
|
246
|
+
var ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE = "messaging.nats.message.consumer_sequence";
|
|
247
|
+
var ATTR_MESSAGING_NATS_DELIVERY_COUNT = "messaging.nats.message.delivery_count";
|
|
248
|
+
var ATTR_MESSAGING_NATS_BODY = "messaging.nats.message.body";
|
|
249
|
+
var ATTR_MESSAGING_NATS_BODY_TRUNCATED = "messaging.nats.message.body.truncated";
|
|
250
|
+
var ATTR_SERVER_ADDRESS = "server.address";
|
|
251
|
+
var ATTR_SERVER_PORT = "server.port";
|
|
252
|
+
var ATTR_JETSTREAM_SERVICE_NAME = "jetstream.service.name";
|
|
253
|
+
var ATTR_JETSTREAM_KIND = "jetstream.kind";
|
|
254
|
+
var ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR = "jetstream.rpc.reply.has_error";
|
|
255
|
+
var ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE = "jetstream.rpc.reply.error.code";
|
|
256
|
+
var ATTR_JETSTREAM_PROVISIONING_ENTITY = "jetstream.provisioning.entity";
|
|
257
|
+
var ATTR_JETSTREAM_PROVISIONING_ACTION = "jetstream.provisioning.action";
|
|
258
|
+
var ATTR_JETSTREAM_PROVISIONING_NAME = "jetstream.provisioning.name";
|
|
259
|
+
var ATTR_JETSTREAM_SELF_HEALING_REASON = "jetstream.self_healing.reason";
|
|
260
|
+
var ATTR_JETSTREAM_MIGRATION_REASON = "jetstream.migration.reason";
|
|
261
|
+
var ATTR_JETSTREAM_DEAD_LETTER_REASON = "jetstream.dead_letter.reason";
|
|
262
|
+
var ATTR_JETSTREAM_SCHEDULE_TARGET = "jetstream.schedule.target";
|
|
263
|
+
var ATTR_NATS_CONNECTION_SERVER = "nats.connection.server";
|
|
264
|
+
var NATS_MSG_ID_HEADER = "Nats-Msg-Id";
|
|
265
|
+
var HOOK_PUBLISH = "publishHook";
|
|
266
|
+
var HOOK_CONSUME = "consumeHook";
|
|
267
|
+
var HOOK_RESPONSE = "responseHook";
|
|
268
|
+
var SPAN_NAME_PUBLISH = "publish";
|
|
269
|
+
var SPAN_NAME_PROCESS = "process";
|
|
270
|
+
var SPAN_NAME_SEND = "send";
|
|
271
|
+
var SPAN_NAME_DEAD_LETTER = "dead_letter";
|
|
272
|
+
var SPAN_NAME_NATS_CONNECTION = "nats.connection";
|
|
273
|
+
var SPAN_NAME_JETSTREAM_SHUTDOWN = "jetstream.shutdown";
|
|
274
|
+
var SPAN_NAME_JETSTREAM_SELF_HEALING = "jetstream.self_healing";
|
|
275
|
+
var SPAN_NAME_JETSTREAM_MIGRATION = "jetstream.migration";
|
|
276
|
+
var SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX = "jetstream.provisioning.";
|
|
277
|
+
var EVENT_CONNECTION_DISCONNECTED = "connection.disconnected";
|
|
278
|
+
var EVENT_CONNECTION_RECONNECTED = "connection.reconnected";
|
|
279
|
+
var messagingHeaderAttr = (headerName) => `${ATTR_MESSAGING_HEADER_PREFIX}${headerName.toLowerCase()}`;
|
|
280
|
+
|
|
281
|
+
// src/otel/trace-kinds.ts
|
|
282
|
+
var JetstreamTrace = /* @__PURE__ */ ((JetstreamTrace2) => {
|
|
283
|
+
JetstreamTrace2["Publish"] = "publish";
|
|
284
|
+
JetstreamTrace2["Consume"] = "consume";
|
|
285
|
+
JetstreamTrace2["RpcClientSend"] = "rpc.client.send";
|
|
286
|
+
JetstreamTrace2["DeadLetter"] = "dead_letter";
|
|
287
|
+
JetstreamTrace2["ConnectionLifecycle"] = "connection.lifecycle";
|
|
288
|
+
JetstreamTrace2["SelfHealing"] = "self_healing";
|
|
289
|
+
JetstreamTrace2["Provisioning"] = "provisioning";
|
|
290
|
+
JetstreamTrace2["Migration"] = "migration";
|
|
291
|
+
JetstreamTrace2["Shutdown"] = "shutdown";
|
|
292
|
+
return JetstreamTrace2;
|
|
293
|
+
})(JetstreamTrace || {});
|
|
294
|
+
var DEFAULT_TRACES = [
|
|
295
|
+
"publish" /* Publish */,
|
|
296
|
+
"consume" /* Consume */,
|
|
297
|
+
"rpc.client.send" /* RpcClientSend */,
|
|
298
|
+
"dead_letter" /* DeadLetter */
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
// src/otel/capture.ts
|
|
302
|
+
var NEGATION_PREFIX = "!";
|
|
303
|
+
var REGEX_SPECIAL_RE = /[.+?^${}()|[\]\\*]/gu;
|
|
304
|
+
var escapeForRegex = (input) => input.replace(REGEX_SPECIAL_RE, "\\$&");
|
|
305
|
+
var globToRegex = (glob) => {
|
|
306
|
+
const escaped = escapeForRegex(glob.toLowerCase()).replace(/\\\*/gu, ".*");
|
|
307
|
+
return new RegExp(`^${escaped}$`, "u");
|
|
308
|
+
};
|
|
309
|
+
var compileHeaderAllowlist = (allowlist) => {
|
|
310
|
+
if (allowlist === true) return () => true;
|
|
311
|
+
if (allowlist === false) return () => false;
|
|
312
|
+
if (allowlist.length === 0) return () => false;
|
|
313
|
+
const includes = [];
|
|
314
|
+
const excludes = [];
|
|
315
|
+
for (const pattern of allowlist) {
|
|
316
|
+
const isExclude = pattern.startsWith(NEGATION_PREFIX);
|
|
317
|
+
const body = isExclude ? pattern.slice(NEGATION_PREFIX.length) : pattern;
|
|
318
|
+
const regex = globToRegex(body);
|
|
319
|
+
if (isExclude) excludes.push(regex);
|
|
320
|
+
else includes.push(regex);
|
|
321
|
+
}
|
|
322
|
+
return (name) => {
|
|
323
|
+
const lower = name.toLowerCase();
|
|
324
|
+
if (!includes.some((re) => re.test(lower))) return false;
|
|
325
|
+
if (excludes.some((re) => re.test(lower))) return false;
|
|
326
|
+
return true;
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
var subjectMatcherCache = /* @__PURE__ */ new WeakMap();
|
|
330
|
+
var compileSubjectAllowlist = (allowlist) => {
|
|
331
|
+
if (!allowlist || allowlist.length === 0) return () => true;
|
|
332
|
+
const cached = subjectMatcherCache.get(allowlist);
|
|
333
|
+
if (cached) return cached;
|
|
334
|
+
const regexes = allowlist.map(globToRegex);
|
|
335
|
+
const matcher = (subject) => {
|
|
336
|
+
const lower = subject.toLowerCase();
|
|
337
|
+
return regexes.some((re) => re.test(lower));
|
|
338
|
+
};
|
|
339
|
+
subjectMatcherCache.set(allowlist, matcher);
|
|
340
|
+
return matcher;
|
|
341
|
+
};
|
|
342
|
+
var subjectMatchesAllowlist = (subject, allowlist) => compileSubjectAllowlist(allowlist)(subject);
|
|
343
|
+
var HEADER_DENYLIST = /* @__PURE__ */ new Set([
|
|
344
|
+
"traceparent",
|
|
345
|
+
"tracestate",
|
|
346
|
+
"baggage",
|
|
347
|
+
"sentry-trace",
|
|
348
|
+
"b3",
|
|
349
|
+
"x-b3-traceid",
|
|
350
|
+
"x-b3-spanid",
|
|
351
|
+
"x-b3-parentspanid",
|
|
352
|
+
"x-b3-sampled",
|
|
353
|
+
"x-b3-flags",
|
|
354
|
+
"uber-trace-id",
|
|
355
|
+
"x-correlation-id",
|
|
356
|
+
"x-reply-to",
|
|
357
|
+
"x-error",
|
|
358
|
+
"x-subject",
|
|
359
|
+
"x-caller-name"
|
|
360
|
+
]);
|
|
361
|
+
var isNatsServerHeader = (lower) => lower.startsWith("nats-");
|
|
362
|
+
var captureMatchingHeaders = (headers2, matcher) => {
|
|
363
|
+
if (!headers2) return {};
|
|
364
|
+
const out = {};
|
|
365
|
+
for (const key of headers2.keys()) {
|
|
366
|
+
const lower = key.toLowerCase();
|
|
367
|
+
if (HEADER_DENYLIST.has(lower)) continue;
|
|
368
|
+
if (isNatsServerHeader(lower)) continue;
|
|
369
|
+
if (!matcher(key)) continue;
|
|
370
|
+
const value = headers2.get(key);
|
|
371
|
+
if (value === "") continue;
|
|
372
|
+
out[messagingHeaderAttr(lower)] = value;
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
};
|
|
376
|
+
var tryUtf8Decode = (bytes) => {
|
|
377
|
+
try {
|
|
378
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
379
|
+
} catch {
|
|
380
|
+
return Buffer.from(bytes).toString("base64");
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
var captureBodyAttribute = (subject, payload, capture) => {
|
|
384
|
+
if (capture === false) return {};
|
|
385
|
+
if (payload.byteLength === 0) return {};
|
|
386
|
+
if (!subjectMatchesAllowlist(subject, capture.subjectAllowlist)) return {};
|
|
387
|
+
const { maxBytes } = capture;
|
|
388
|
+
const truncated = payload.byteLength > maxBytes;
|
|
389
|
+
const slice = truncated ? payload.subarray(0, maxBytes) : payload;
|
|
390
|
+
const decoded = tryUtf8Decode(slice);
|
|
391
|
+
const out = { [ATTR_MESSAGING_NATS_BODY]: decoded };
|
|
392
|
+
if (truncated) out[ATTR_MESSAGING_NATS_BODY_TRUNCATED] = true;
|
|
393
|
+
return out;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/otel/config.ts
|
|
397
|
+
var PublishKind = /* @__PURE__ */ ((PublishKind2) => {
|
|
398
|
+
PublishKind2["Event"] = "event";
|
|
399
|
+
PublishKind2["RpcRequest"] = "rpc.request";
|
|
400
|
+
PublishKind2["Broadcast"] = "broadcast";
|
|
401
|
+
PublishKind2["Ordered"] = "ordered";
|
|
402
|
+
return PublishKind2;
|
|
403
|
+
})(PublishKind || {});
|
|
404
|
+
var ConsumeKind = /* @__PURE__ */ ((ConsumeKind2) => {
|
|
405
|
+
ConsumeKind2["Event"] = "event";
|
|
406
|
+
ConsumeKind2["Rpc"] = "rpc";
|
|
407
|
+
ConsumeKind2["Broadcast"] = "broadcast";
|
|
408
|
+
ConsumeKind2["Ordered"] = "ordered";
|
|
409
|
+
return ConsumeKind2;
|
|
410
|
+
})(ConsumeKind || {});
|
|
411
|
+
var DEFAULT_CAPTURE_HEADERS = ["x-request-id"];
|
|
412
|
+
var DEFAULT_CAPTURE_BODY_MAX_BYTES = 4096;
|
|
413
|
+
var NESTJS_BARE_ERROR_MESSAGE = "Internal server error";
|
|
414
|
+
var isNestjsBareErrorSentinel = (obj) => obj.status === "error" && obj.message === NESTJS_BARE_ERROR_MESSAGE;
|
|
415
|
+
var defaultErrorClassifier = (err) => {
|
|
416
|
+
if (err === null || typeof err !== "object") return "unexpected";
|
|
417
|
+
if (!(err instanceof Error)) {
|
|
418
|
+
if (isNestjsBareErrorSentinel(err)) return "unexpected";
|
|
419
|
+
return "expected";
|
|
420
|
+
}
|
|
421
|
+
let proto = err;
|
|
422
|
+
while (proto) {
|
|
423
|
+
const name = proto.constructor?.name;
|
|
424
|
+
if (name === "RpcException" || name === "HttpException") return "expected";
|
|
425
|
+
proto = Object.getPrototypeOf(proto);
|
|
426
|
+
}
|
|
427
|
+
return "unexpected";
|
|
428
|
+
};
|
|
429
|
+
var expandTracesOption = (option) => {
|
|
430
|
+
if (option === void 0 || option === "default") return new Set(DEFAULT_TRACES);
|
|
431
|
+
if (option === "all") return new Set(Object.values(JetstreamTrace));
|
|
432
|
+
if (option === "none") return /* @__PURE__ */ new Set();
|
|
433
|
+
return new Set(option);
|
|
434
|
+
};
|
|
435
|
+
var compileHeaderMatcher = (option) => compileHeaderAllowlist(option ?? DEFAULT_CAPTURE_HEADERS);
|
|
436
|
+
var resolveCaptureBody = (option) => {
|
|
437
|
+
if (option === void 0 || option === false) return false;
|
|
438
|
+
if (option === true) return { maxBytes: DEFAULT_CAPTURE_BODY_MAX_BYTES };
|
|
439
|
+
return {
|
|
440
|
+
maxBytes: option.maxBytes ?? DEFAULT_CAPTURE_BODY_MAX_BYTES,
|
|
441
|
+
subjectAllowlist: option.subjectAllowlist
|
|
442
|
+
};
|
|
443
|
+
};
|
|
444
|
+
var resolveOtelOptions = (options = {}) => {
|
|
445
|
+
return {
|
|
446
|
+
enabled: options.enabled ?? true,
|
|
447
|
+
traces: expandTracesOption(options.traces),
|
|
448
|
+
captureHeaders: compileHeaderMatcher(options.captureHeaders),
|
|
449
|
+
captureBody: resolveCaptureBody(options.captureBody),
|
|
450
|
+
publishHook: options.publishHook,
|
|
451
|
+
consumeHook: options.consumeHook,
|
|
452
|
+
responseHook: options.responseHook,
|
|
453
|
+
shouldTracePublish: options.shouldTracePublish,
|
|
454
|
+
shouldTraceConsume: options.shouldTraceConsume,
|
|
455
|
+
errorClassifier: options.errorClassifier ?? defaultErrorClassifier
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// src/otel/internal-utils.ts
|
|
460
|
+
import { Logger } from "@nestjs/common";
|
|
461
|
+
var logger = new Logger("Jetstream:Otel");
|
|
462
|
+
var safelyInvokeHook = (hookName, hook, ...args) => {
|
|
463
|
+
if (!hook) return;
|
|
464
|
+
const logHookFailure = (err) => {
|
|
465
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
466
|
+
logger.debug(`OTel ${hookName} threw: ${message}`);
|
|
467
|
+
};
|
|
468
|
+
try {
|
|
469
|
+
const result = hook(...args);
|
|
470
|
+
if (result !== null && typeof result === "object" && "then" in result && typeof result.then === "function") {
|
|
471
|
+
result.then(void 0, logHookFailure);
|
|
472
|
+
}
|
|
473
|
+
} catch (err) {
|
|
474
|
+
logHookFailure(err);
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var stripIpv6Brackets = (host) => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
478
|
+
var parsePort = (portRaw) => {
|
|
479
|
+
if (!portRaw) return void 0;
|
|
480
|
+
const port = Number.parseInt(portRaw, 10);
|
|
481
|
+
return Number.isInteger(port) ? port : void 0;
|
|
482
|
+
};
|
|
483
|
+
var parseServerAddress = (servers) => {
|
|
484
|
+
const raw = servers[0];
|
|
485
|
+
if (!raw) return null;
|
|
486
|
+
if (raw.includes("://")) {
|
|
487
|
+
try {
|
|
488
|
+
const url = new URL(raw);
|
|
489
|
+
if (url.hostname.length === 0) return null;
|
|
490
|
+
const host2 = stripIpv6Brackets(url.hostname);
|
|
491
|
+
const port2 = parsePort(url.port || void 0);
|
|
492
|
+
return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (raw.startsWith("[")) {
|
|
498
|
+
const closeIdx = raw.indexOf("]");
|
|
499
|
+
if (closeIdx <= 0) return null;
|
|
500
|
+
const host2 = raw.slice(1, closeIdx);
|
|
501
|
+
const port2 = parsePort(raw.slice(closeIdx + 1).replace(/^:/u, ""));
|
|
502
|
+
return port2 === void 0 ? { host: host2 } : { host: host2, port: port2 };
|
|
503
|
+
}
|
|
504
|
+
const [host, portRaw] = raw.split(":");
|
|
505
|
+
if (!host) return null;
|
|
506
|
+
const port = parsePort(portRaw);
|
|
507
|
+
return port === void 0 ? { host } : { host, port };
|
|
508
|
+
};
|
|
509
|
+
var deriveOtelAttrs = (options) => ({
|
|
510
|
+
otel: resolveOtelOptions(options.otel),
|
|
511
|
+
serviceName: internalName(options.name),
|
|
512
|
+
serverEndpoint: parseServerAddress(options.servers)
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// src/otel/propagator.ts
|
|
516
|
+
import {
|
|
517
|
+
propagation
|
|
518
|
+
} from "@opentelemetry/api";
|
|
519
|
+
var injectContext = (ctx, carrier, setter) => {
|
|
520
|
+
propagation.inject(ctx, carrier, setter);
|
|
521
|
+
};
|
|
522
|
+
var extractContext = (ctx, carrier, getter) => propagation.extract(ctx, carrier, getter);
|
|
523
|
+
|
|
524
|
+
// src/otel/tracer.ts
|
|
525
|
+
import { trace } from "@opentelemetry/api";
|
|
526
|
+
var PACKAGE_VERSION = true ? "2.10.0" : "0.0.0";
|
|
527
|
+
var getTracer = () => trace.getTracer(TRACER_NAME, PACKAGE_VERSION);
|
|
528
|
+
|
|
529
|
+
// src/otel/carrier.ts
|
|
530
|
+
var hdrsSetter = {
|
|
531
|
+
set: (headers2, key, value) => {
|
|
532
|
+
headers2.set(key, value);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var hdrsGetter = {
|
|
536
|
+
keys: (headers2) => headers2 ? headers2.keys() : [],
|
|
537
|
+
get: (headers2, key) => {
|
|
538
|
+
if (!headers2) return void 0;
|
|
539
|
+
const all = typeof headers2.values === "function" ? headers2.values(key) : void 0;
|
|
540
|
+
if (Array.isArray(all)) {
|
|
541
|
+
const nonEmpty = all.filter((value) => value !== "");
|
|
542
|
+
if (nonEmpty.length === 0) return void 0;
|
|
543
|
+
return nonEmpty.join(",");
|
|
544
|
+
}
|
|
545
|
+
const single = headers2.get(key);
|
|
546
|
+
return single === "" ? void 0 : single;
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// src/otel/attributes.ts
|
|
551
|
+
var MESSAGING_SYSTEM = "nats";
|
|
552
|
+
var baseMessagingAttributes = (ctx) => {
|
|
553
|
+
const attrs = {
|
|
554
|
+
[ATTR_MESSAGING_SYSTEM]: MESSAGING_SYSTEM,
|
|
555
|
+
[ATTR_MESSAGING_DESTINATION_NAME]: ctx.subject,
|
|
556
|
+
[ATTR_MESSAGING_CLIENT_ID]: ctx.serviceName
|
|
557
|
+
};
|
|
558
|
+
if (ctx.serverAddress) attrs[ATTR_SERVER_ADDRESS] = ctx.serverAddress;
|
|
559
|
+
if (ctx.serverPort !== void 0) attrs[ATTR_SERVER_PORT] = ctx.serverPort;
|
|
560
|
+
if (ctx.pattern && ctx.pattern !== ctx.subject) {
|
|
561
|
+
attrs[ATTR_MESSAGING_DESTINATION_TEMPLATE] = ctx.pattern;
|
|
562
|
+
}
|
|
563
|
+
return attrs;
|
|
564
|
+
};
|
|
565
|
+
var jetstreamKindForPublish = (kind) => kind === "rpc.request" /* RpcRequest */ ? "rpc" /* Rpc */ : kind;
|
|
566
|
+
var buildPublishAttributes = (ctx) => {
|
|
567
|
+
const attrs = {
|
|
568
|
+
...baseMessagingAttributes(ctx),
|
|
569
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PUBLISH,
|
|
570
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
|
|
571
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
|
|
572
|
+
[ATTR_JETSTREAM_KIND]: jetstreamKindForPublish(ctx.kind)
|
|
573
|
+
};
|
|
574
|
+
if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
|
|
575
|
+
if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
|
|
576
|
+
return attrs;
|
|
577
|
+
};
|
|
578
|
+
var buildConsumeAttributes = (ctx) => {
|
|
579
|
+
const { msg, info, kind, payloadBytes } = ctx;
|
|
580
|
+
const attrs = {
|
|
581
|
+
...baseMessagingAttributes(ctx),
|
|
582
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_PROCESS,
|
|
583
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
|
|
584
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: payloadBytes,
|
|
585
|
+
[ATTR_JETSTREAM_KIND]: kind
|
|
586
|
+
};
|
|
587
|
+
if (info) {
|
|
588
|
+
attrs[ATTR_MESSAGING_NATS_STREAM_NAME] = info.stream;
|
|
589
|
+
attrs[ATTR_MESSAGING_CONSUMER_GROUP_NAME] = info.consumer;
|
|
590
|
+
attrs[ATTR_MESSAGING_NATS_STREAM_SEQUENCE] = info.streamSequence;
|
|
591
|
+
attrs[ATTR_MESSAGING_NATS_CONSUMER_SEQUENCE] = info.deliverySequence;
|
|
592
|
+
attrs[ATTR_MESSAGING_NATS_DELIVERY_COUNT] = info.deliveryCount;
|
|
593
|
+
}
|
|
594
|
+
const messageId = msg.headers?.get(NATS_MSG_ID_HEADER);
|
|
595
|
+
if (messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = messageId;
|
|
596
|
+
return attrs;
|
|
597
|
+
};
|
|
598
|
+
var buildRpcClientAttributes = (ctx) => {
|
|
599
|
+
const attrs = {
|
|
600
|
+
...baseMessagingAttributes(ctx),
|
|
601
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_SEND,
|
|
602
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_SEND,
|
|
603
|
+
[ATTR_MESSAGING_MESSAGE_BODY_SIZE]: ctx.payloadBytes,
|
|
604
|
+
[ATTR_JETSTREAM_KIND]: "rpc" /* Rpc */
|
|
605
|
+
};
|
|
606
|
+
if (ctx.correlationId) attrs[ATTR_MESSAGING_MESSAGE_CONVERSATION_ID] = ctx.correlationId;
|
|
607
|
+
if (ctx.messageId) attrs[ATTR_MESSAGING_MESSAGE_ID] = ctx.messageId;
|
|
608
|
+
return attrs;
|
|
609
|
+
};
|
|
610
|
+
var buildDeadLetterAttributes = (ctx) => {
|
|
611
|
+
const attrs = {
|
|
612
|
+
...baseMessagingAttributes(ctx),
|
|
613
|
+
[ATTR_MESSAGING_OPERATION_NAME]: SPAN_NAME_DEAD_LETTER,
|
|
614
|
+
[ATTR_MESSAGING_OPERATION_TYPE]: SPAN_NAME_PROCESS,
|
|
615
|
+
[ATTR_MESSAGING_NATS_DELIVERY_COUNT]: ctx.finalDeliveryCount
|
|
616
|
+
};
|
|
617
|
+
if (ctx.reason) attrs[ATTR_JETSTREAM_DEAD_LETTER_REASON] = ctx.reason;
|
|
618
|
+
return attrs;
|
|
619
|
+
};
|
|
620
|
+
var extractFromRpcException = (record) => {
|
|
621
|
+
const getError = record.getError;
|
|
622
|
+
if (typeof getError !== "function") return void 0;
|
|
623
|
+
return codeFromPayload(getError.call(record));
|
|
624
|
+
};
|
|
625
|
+
var extractFromHttpException = (record) => {
|
|
626
|
+
const getStatus = record.getStatus;
|
|
627
|
+
if (typeof getStatus !== "function") return void 0;
|
|
628
|
+
const status = getStatus.call(record);
|
|
629
|
+
return typeof status === "number" && Number.isFinite(status) ? `HTTP_${status}` : void 0;
|
|
630
|
+
};
|
|
631
|
+
var extractFromOwnCode = (record) => {
|
|
632
|
+
const code = record.code ?? record.errorCode;
|
|
633
|
+
return typeof code === "string" && isStableErrorCode(code) ? code : void 0;
|
|
634
|
+
};
|
|
635
|
+
var extractExpectedErrorCode = (err) => {
|
|
636
|
+
if (err === null || typeof err !== "object") return void 0;
|
|
637
|
+
const record = err;
|
|
638
|
+
if (hasAncestorNamed(err, "RpcException")) {
|
|
639
|
+
const fromPayload = extractFromRpcException(record);
|
|
640
|
+
if (fromPayload !== void 0) return fromPayload;
|
|
641
|
+
}
|
|
642
|
+
if (hasAncestorNamed(err, "HttpException")) {
|
|
643
|
+
const fromStatus = extractFromHttpException(record);
|
|
644
|
+
if (fromStatus !== void 0) return fromStatus;
|
|
645
|
+
}
|
|
646
|
+
return extractFromOwnCode(record);
|
|
647
|
+
};
|
|
648
|
+
var hasAncestorNamed = (err, name) => {
|
|
649
|
+
let proto = Object.getPrototypeOf(err);
|
|
650
|
+
while (proto) {
|
|
651
|
+
const ctorName = proto.constructor?.name;
|
|
652
|
+
if (ctorName === name) return true;
|
|
653
|
+
proto = Object.getPrototypeOf(proto);
|
|
654
|
+
}
|
|
655
|
+
return false;
|
|
656
|
+
};
|
|
657
|
+
var STABLE_ERROR_CODE_RE = /^[A-Z][A-Z0-9_]*$/u;
|
|
658
|
+
var isStableErrorCode = (value) => STABLE_ERROR_CODE_RE.test(value);
|
|
659
|
+
var codeFromPayload = (payload) => {
|
|
660
|
+
if (payload === null || payload === void 0) return void 0;
|
|
661
|
+
if (typeof payload === "string") return isStableErrorCode(payload) ? payload : void 0;
|
|
662
|
+
if (typeof payload === "object") {
|
|
663
|
+
const code = payload.code;
|
|
664
|
+
if (typeof code === "string" && isStableErrorCode(code)) return code;
|
|
665
|
+
}
|
|
666
|
+
return void 0;
|
|
667
|
+
};
|
|
668
|
+
var buildExpectedErrorAttributes = (err) => {
|
|
669
|
+
const attrs = {
|
|
670
|
+
[ATTR_JETSTREAM_RPC_REPLY_HAS_ERROR]: true
|
|
671
|
+
};
|
|
672
|
+
const code = extractExpectedErrorCode(err);
|
|
673
|
+
if (code !== void 0) attrs[ATTR_JETSTREAM_RPC_REPLY_ERROR_CODE] = code;
|
|
674
|
+
return attrs;
|
|
675
|
+
};
|
|
676
|
+
var applyExpectedErrorAttributes = (span, err) => {
|
|
677
|
+
span.setAttributes(buildExpectedErrorAttributes(err));
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/otel/spans/publish.ts
|
|
681
|
+
import { Logger as Logger2 } from "@nestjs/common";
|
|
682
|
+
import { SpanKind, SpanStatusCode, context, trace as trace2 } from "@opentelemetry/api";
|
|
683
|
+
var logger2 = new Logger2("Jetstream:Otel");
|
|
684
|
+
var shouldTracePublishSafe = (predicate, subject, record) => {
|
|
685
|
+
if (!predicate) return true;
|
|
686
|
+
try {
|
|
687
|
+
return predicate(subject, record);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
690
|
+
logger2.debug(`OTel shouldTracePublish threw: ${message}`);
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
var withPublishSpan = async (ctx, config, fn) => {
|
|
695
|
+
if (!config.enabled) return fn();
|
|
696
|
+
const shouldCreateSpan = config.traces.has("publish" /* Publish */) && shouldTracePublishSafe(config.shouldTracePublish, ctx.subject, ctx.record);
|
|
697
|
+
if (!shouldCreateSpan) {
|
|
698
|
+
injectContext(context.active(), ctx.headers, hdrsSetter);
|
|
699
|
+
return fn();
|
|
700
|
+
}
|
|
701
|
+
const tracer = getTracer();
|
|
702
|
+
const span = tracer.startSpan(`${SPAN_NAME_PUBLISH} ${ctx.subject}`, {
|
|
703
|
+
kind: SpanKind.PRODUCER,
|
|
704
|
+
attributes: {
|
|
705
|
+
...buildPublishAttributes({
|
|
706
|
+
subject: ctx.subject,
|
|
707
|
+
pattern: ctx.pattern,
|
|
708
|
+
serviceName: ctx.serviceName,
|
|
709
|
+
serverAddress: ctx.endpoint?.host,
|
|
710
|
+
serverPort: ctx.endpoint?.port,
|
|
711
|
+
kind: ctx.kind,
|
|
712
|
+
payloadBytes: ctx.payloadBytes,
|
|
713
|
+
messageId: ctx.messageId,
|
|
714
|
+
correlationId: ctx.correlationId
|
|
715
|
+
}),
|
|
716
|
+
...ctx.scheduleTarget ? { [ATTR_JETSTREAM_SCHEDULE_TARGET]: ctx.scheduleTarget } : {},
|
|
717
|
+
...captureMatchingHeaders(ctx.headers, config.captureHeaders),
|
|
718
|
+
...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
const ctxWithSpan = trace2.setSpan(context.active(), span);
|
|
722
|
+
injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
|
|
723
|
+
const start = Date.now();
|
|
724
|
+
const invokeResponseHook = (error) => {
|
|
725
|
+
context.with(ctxWithSpan, () => {
|
|
726
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
727
|
+
subject: ctx.subject,
|
|
728
|
+
durationMs: Date.now() - start,
|
|
729
|
+
error
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
};
|
|
733
|
+
try {
|
|
734
|
+
const result = await context.with(ctxWithSpan, async () => {
|
|
735
|
+
safelyInvokeHook(HOOK_PUBLISH, config.publishHook, span, {
|
|
736
|
+
subject: ctx.subject,
|
|
737
|
+
record: ctx.record,
|
|
738
|
+
kind: ctx.kind
|
|
739
|
+
});
|
|
740
|
+
return fn();
|
|
741
|
+
});
|
|
742
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
743
|
+
invokeResponseHook();
|
|
744
|
+
return result;
|
|
745
|
+
} catch (err) {
|
|
746
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
747
|
+
span.recordException(error);
|
|
748
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
749
|
+
invokeResponseHook(error);
|
|
750
|
+
throw err;
|
|
751
|
+
} finally {
|
|
752
|
+
span.end();
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// src/otel/spans/consume.ts
|
|
757
|
+
import {
|
|
758
|
+
ROOT_CONTEXT,
|
|
759
|
+
SpanKind as SpanKind2,
|
|
760
|
+
SpanStatusCode as SpanStatusCode2,
|
|
761
|
+
context as context2,
|
|
762
|
+
trace as trace3
|
|
763
|
+
} from "@opentelemetry/api";
|
|
764
|
+
var isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
|
|
765
|
+
var applyExpectedError = (span, err) => {
|
|
766
|
+
span.setStatus({ code: SpanStatusCode2.OK });
|
|
767
|
+
applyExpectedErrorAttributes(span, err);
|
|
768
|
+
};
|
|
769
|
+
var applyUnexpectedError = (span, err) => {
|
|
770
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
771
|
+
span.recordException(error);
|
|
772
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: error.message });
|
|
773
|
+
};
|
|
774
|
+
var withConsumeSpan = (ctx, config, fn, options = {}) => {
|
|
775
|
+
if (!config.enabled) return fn();
|
|
776
|
+
const parentCtx = extractContext(ROOT_CONTEXT, ctx.msg.headers, hdrsGetter);
|
|
777
|
+
const shouldCreateSpan = config.traces.has("consume" /* Consume */) && (config.shouldTraceConsume?.(ctx.subject, ctx.msg) ?? true);
|
|
778
|
+
if (!shouldCreateSpan) {
|
|
779
|
+
return context2.with(parentCtx, fn);
|
|
780
|
+
}
|
|
781
|
+
const tracer = getTracer();
|
|
782
|
+
const span = tracer.startSpan(
|
|
783
|
+
`${SPAN_NAME_PROCESS} ${ctx.subject}`,
|
|
784
|
+
{
|
|
785
|
+
kind: SpanKind2.CONSUMER,
|
|
786
|
+
attributes: {
|
|
787
|
+
...buildConsumeAttributes({
|
|
788
|
+
subject: ctx.subject,
|
|
789
|
+
pattern: ctx.pattern,
|
|
790
|
+
msg: ctx.msg,
|
|
791
|
+
info: ctx.info,
|
|
792
|
+
kind: ctx.kind,
|
|
793
|
+
payloadBytes: ctx.payloadBytes,
|
|
794
|
+
serviceName: ctx.serviceName,
|
|
795
|
+
serverAddress: ctx.endpoint?.host,
|
|
796
|
+
serverPort: ctx.endpoint?.port
|
|
797
|
+
}),
|
|
798
|
+
...captureMatchingHeaders(ctx.msg.headers, config.captureHeaders),
|
|
799
|
+
...captureBodyAttribute(ctx.subject, ctx.msg.data, config.captureBody)
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
parentCtx
|
|
803
|
+
);
|
|
804
|
+
const ctxWithSpan = trace3.setSpan(parentCtx, span);
|
|
805
|
+
const start = Date.now();
|
|
806
|
+
let finalized = false;
|
|
807
|
+
const { signal, timeoutLabel = "handler.timeout" } = options;
|
|
808
|
+
let detachAbort = null;
|
|
809
|
+
const invokeResponseHook = (durationMs, error) => {
|
|
810
|
+
context2.with(ctxWithSpan, () => {
|
|
811
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
812
|
+
subject: ctx.subject,
|
|
813
|
+
durationMs,
|
|
814
|
+
error
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
const finishOk2 = () => {
|
|
819
|
+
if (finalized) return;
|
|
820
|
+
finalized = true;
|
|
821
|
+
detachAbort?.();
|
|
822
|
+
span.setStatus({ code: SpanStatusCode2.OK });
|
|
823
|
+
invokeResponseHook(Date.now() - start);
|
|
824
|
+
span.end();
|
|
825
|
+
};
|
|
826
|
+
const finishError2 = (err) => {
|
|
827
|
+
if (finalized) return;
|
|
828
|
+
finalized = true;
|
|
829
|
+
detachAbort?.();
|
|
830
|
+
let classification = "unexpected";
|
|
831
|
+
try {
|
|
832
|
+
classification = config.errorClassifier(err);
|
|
833
|
+
} catch {
|
|
834
|
+
}
|
|
835
|
+
if (classification === "expected") {
|
|
836
|
+
applyExpectedError(span, err);
|
|
837
|
+
} else {
|
|
838
|
+
applyUnexpectedError(span, err);
|
|
839
|
+
}
|
|
840
|
+
invokeResponseHook(Date.now() - start, err instanceof Error ? err : new Error(String(err)));
|
|
841
|
+
span.end();
|
|
842
|
+
};
|
|
843
|
+
const onAbort = () => {
|
|
844
|
+
if (finalized) return;
|
|
845
|
+
finalized = true;
|
|
846
|
+
const error = new Error(timeoutLabel);
|
|
847
|
+
span.addEvent(timeoutLabel);
|
|
848
|
+
span.recordException(error);
|
|
849
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: timeoutLabel });
|
|
850
|
+
invokeResponseHook(Date.now() - start, error);
|
|
851
|
+
span.end();
|
|
852
|
+
};
|
|
853
|
+
if (signal) {
|
|
854
|
+
if (signal.aborted) {
|
|
855
|
+
onAbort();
|
|
856
|
+
} else {
|
|
857
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
858
|
+
detachAbort = () => {
|
|
859
|
+
signal.removeEventListener("abort", onAbort);
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
let result;
|
|
864
|
+
try {
|
|
865
|
+
result = context2.with(ctxWithSpan, () => {
|
|
866
|
+
safelyInvokeHook(HOOK_CONSUME, config.consumeHook, span, {
|
|
867
|
+
subject: ctx.subject,
|
|
868
|
+
msg: ctx.msg,
|
|
869
|
+
handlerMetadata: ctx.handlerMetadata,
|
|
870
|
+
kind: ctx.kind
|
|
871
|
+
});
|
|
872
|
+
return fn();
|
|
873
|
+
});
|
|
874
|
+
} catch (err) {
|
|
875
|
+
finishError2(err);
|
|
876
|
+
throw err;
|
|
877
|
+
}
|
|
878
|
+
if (isPromiseLike(result)) {
|
|
879
|
+
return Promise.resolve(result).then(
|
|
880
|
+
(value) => {
|
|
881
|
+
finishOk2();
|
|
882
|
+
return value;
|
|
883
|
+
},
|
|
884
|
+
(err) => {
|
|
885
|
+
finishError2(err);
|
|
886
|
+
throw err;
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
finishOk2();
|
|
891
|
+
return result;
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// src/otel/spans/rpc-client.ts
|
|
895
|
+
import { Logger as Logger3 } from "@nestjs/common";
|
|
896
|
+
import { SpanKind as SpanKind3, SpanStatusCode as SpanStatusCode3, context as context3, trace as trace4 } from "@opentelemetry/api";
|
|
897
|
+
var logger3 = new Logger3("Jetstream:Otel");
|
|
898
|
+
var RPC_TIMEOUT_MESSAGE = "rpc.timeout";
|
|
899
|
+
var beginRpcClientSpan = (ctx, config) => {
|
|
900
|
+
if (!config.enabled) {
|
|
901
|
+
return {
|
|
902
|
+
activeContext: context3.active(),
|
|
903
|
+
finish: () => void 0
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (!config.traces.has("rpc.client.send" /* RpcClientSend */)) {
|
|
907
|
+
injectContext(context3.active(), ctx.headers, hdrsSetter);
|
|
908
|
+
return {
|
|
909
|
+
activeContext: context3.active(),
|
|
910
|
+
finish: () => void 0
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const tracer = getTracer();
|
|
914
|
+
const span = tracer.startSpan(`${SPAN_NAME_SEND} ${ctx.subject}`, {
|
|
915
|
+
kind: SpanKind3.CLIENT,
|
|
916
|
+
attributes: {
|
|
917
|
+
...buildRpcClientAttributes({
|
|
918
|
+
subject: ctx.subject,
|
|
919
|
+
pattern: ctx.pattern,
|
|
920
|
+
correlationId: ctx.correlationId,
|
|
921
|
+
payloadBytes: ctx.payloadBytes,
|
|
922
|
+
messageId: ctx.messageId,
|
|
923
|
+
serviceName: ctx.serviceName,
|
|
924
|
+
serverAddress: ctx.endpoint?.host,
|
|
925
|
+
serverPort: ctx.endpoint?.port
|
|
926
|
+
}),
|
|
927
|
+
...captureMatchingHeaders(ctx.headers, config.captureHeaders),
|
|
928
|
+
...captureBodyAttribute(ctx.subject, ctx.payload, config.captureBody)
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
const ctxWithSpan = trace4.setSpan(context3.active(), span);
|
|
932
|
+
injectContext(ctxWithSpan, ctx.headers, hdrsSetter);
|
|
933
|
+
const start = Date.now();
|
|
934
|
+
let finalized = false;
|
|
935
|
+
const finish = (outcome) => {
|
|
936
|
+
if (finalized) return;
|
|
937
|
+
finalized = true;
|
|
938
|
+
let reply;
|
|
939
|
+
let error;
|
|
940
|
+
switch (outcome.kind) {
|
|
941
|
+
case "ok" /* Ok */:
|
|
942
|
+
reply = outcome.reply;
|
|
943
|
+
span.setStatus({ code: SpanStatusCode3.OK });
|
|
944
|
+
break;
|
|
945
|
+
case "reply-error" /* ReplyError */:
|
|
946
|
+
reply = outcome.replyPayload;
|
|
947
|
+
applyExpectedErrorAttributes(span, outcome.replyPayload);
|
|
948
|
+
span.setStatus({ code: SpanStatusCode3.OK });
|
|
949
|
+
break;
|
|
950
|
+
case "timeout" /* Timeout */:
|
|
951
|
+
error = new Error(RPC_TIMEOUT_MESSAGE);
|
|
952
|
+
span.addEvent(RPC_TIMEOUT_MESSAGE);
|
|
953
|
+
span.setStatus({ code: SpanStatusCode3.ERROR, message: RPC_TIMEOUT_MESSAGE });
|
|
954
|
+
break;
|
|
955
|
+
case "error" /* Error */:
|
|
956
|
+
error = outcome.error;
|
|
957
|
+
span.recordException(outcome.error);
|
|
958
|
+
span.setStatus({ code: SpanStatusCode3.ERROR, message: outcome.error.message });
|
|
959
|
+
break;
|
|
960
|
+
default: {
|
|
961
|
+
const unknownOutcome = outcome;
|
|
962
|
+
logger3.error(`Unhandled RPC outcome: ${String(unknownOutcome.kind)}`);
|
|
963
|
+
span.setStatus({ code: SpanStatusCode3.ERROR, message: "unknown outcome" });
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
context3.with(ctxWithSpan, () => {
|
|
967
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
968
|
+
subject: ctx.subject,
|
|
969
|
+
durationMs: Date.now() - start,
|
|
970
|
+
reply,
|
|
971
|
+
error
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
span.end();
|
|
975
|
+
};
|
|
976
|
+
return { activeContext: ctxWithSpan, finish };
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// src/otel/spans/dead-letter.ts
|
|
980
|
+
import { ROOT_CONTEXT as ROOT_CONTEXT2, SpanKind as SpanKind4, SpanStatusCode as SpanStatusCode4, context as context4, trace as trace5 } from "@opentelemetry/api";
|
|
981
|
+
var withDeadLetterSpan = async (ctx, config, fn) => {
|
|
982
|
+
if (!config.enabled || !config.traces.has("dead_letter" /* DeadLetter */)) {
|
|
983
|
+
return fn();
|
|
984
|
+
}
|
|
985
|
+
const parentCtx = extractContext(ROOT_CONTEXT2, ctx.msg.headers, hdrsGetter);
|
|
986
|
+
const tracer = getTracer();
|
|
987
|
+
const span = tracer.startSpan(
|
|
988
|
+
`${SPAN_NAME_DEAD_LETTER} ${ctx.msg.subject}`,
|
|
989
|
+
{
|
|
990
|
+
kind: SpanKind4.INTERNAL,
|
|
991
|
+
attributes: buildDeadLetterAttributes({
|
|
992
|
+
subject: ctx.msg.subject,
|
|
993
|
+
pattern: ctx.pattern,
|
|
994
|
+
serviceName: ctx.serviceName,
|
|
995
|
+
serverAddress: ctx.endpoint?.host,
|
|
996
|
+
serverPort: ctx.endpoint?.port,
|
|
997
|
+
finalDeliveryCount: ctx.finalDeliveryCount,
|
|
998
|
+
reason: ctx.reason
|
|
999
|
+
})
|
|
1000
|
+
},
|
|
1001
|
+
parentCtx
|
|
1002
|
+
);
|
|
1003
|
+
const ctxWithSpan = trace5.setSpan(parentCtx, span);
|
|
1004
|
+
const start = Date.now();
|
|
1005
|
+
const invokeResponseHook = (error) => {
|
|
1006
|
+
context4.with(ctxWithSpan, () => {
|
|
1007
|
+
safelyInvokeHook(HOOK_RESPONSE, config.responseHook, span, {
|
|
1008
|
+
subject: ctx.msg.subject,
|
|
1009
|
+
durationMs: Date.now() - start,
|
|
1010
|
+
error
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
};
|
|
1014
|
+
try {
|
|
1015
|
+
const result = await context4.with(ctxWithSpan, fn);
|
|
1016
|
+
span.setStatus({ code: SpanStatusCode4.OK });
|
|
1017
|
+
invokeResponseHook();
|
|
1018
|
+
return result;
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1021
|
+
span.recordException(error);
|
|
1022
|
+
span.setStatus({ code: SpanStatusCode4.ERROR, message: error.message });
|
|
1023
|
+
invokeResponseHook(error);
|
|
1024
|
+
throw err;
|
|
1025
|
+
} finally {
|
|
1026
|
+
span.end();
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
// src/otel/spans/infrastructure.ts
|
|
1031
|
+
import { Logger as Logger4 } from "@nestjs/common";
|
|
1032
|
+
import {
|
|
1033
|
+
SpanKind as SpanKind5,
|
|
1034
|
+
SpanStatusCode as SpanStatusCode5,
|
|
1035
|
+
context as context5,
|
|
1036
|
+
trace as trace6
|
|
1037
|
+
} from "@opentelemetry/api";
|
|
1038
|
+
var logger4 = new Logger4("Jetstream:Otel");
|
|
1039
|
+
var startInfraSpan = (config, traceKind, name, ctx, extraAttributes = {}) => {
|
|
1040
|
+
if (!config.enabled || !config.traces.has(traceKind)) return null;
|
|
1041
|
+
const tracer = getTracer();
|
|
1042
|
+
const attributes = {
|
|
1043
|
+
[ATTR_JETSTREAM_SERVICE_NAME]: ctx.serviceName
|
|
1044
|
+
};
|
|
1045
|
+
if (ctx.endpoint?.host) attributes[ATTR_SERVER_ADDRESS] = ctx.endpoint.host;
|
|
1046
|
+
if (ctx.endpoint?.port !== void 0) attributes[ATTR_SERVER_PORT] = ctx.endpoint.port;
|
|
1047
|
+
for (const [key, value] of Object.entries(extraAttributes)) {
|
|
1048
|
+
if (value !== void 0) attributes[key] = value;
|
|
1049
|
+
}
|
|
1050
|
+
return tracer.startSpan(name, { kind: SpanKind5.INTERNAL, attributes });
|
|
1051
|
+
};
|
|
1052
|
+
var finishOk = (span) => {
|
|
1053
|
+
span.setStatus({ code: SpanStatusCode5.OK });
|
|
1054
|
+
span.end();
|
|
1055
|
+
};
|
|
1056
|
+
var finishError = (span, err) => {
|
|
1057
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1058
|
+
span.recordException(error);
|
|
1059
|
+
span.setStatus({ code: SpanStatusCode5.ERROR, message: error.message });
|
|
1060
|
+
span.end();
|
|
1061
|
+
};
|
|
1062
|
+
var wrapInfra = async (config, traceKind, name, ctx, attributes, op) => {
|
|
1063
|
+
const span = startInfraSpan(config, traceKind, name, ctx, attributes);
|
|
1064
|
+
if (!span) return op();
|
|
1065
|
+
const ctxWithSpan = trace6.setSpan(context5.active(), span);
|
|
1066
|
+
try {
|
|
1067
|
+
const result = await context5.with(ctxWithSpan, op);
|
|
1068
|
+
finishOk(span);
|
|
1069
|
+
return result;
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
finishError(span, err);
|
|
1072
|
+
throw err;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
var beginConnectionLifecycleSpan = (config, ctx) => {
|
|
1076
|
+
const span = startInfraSpan(
|
|
1077
|
+
config,
|
|
1078
|
+
"connection.lifecycle" /* ConnectionLifecycle */,
|
|
1079
|
+
SPAN_NAME_NATS_CONNECTION,
|
|
1080
|
+
ctx,
|
|
1081
|
+
{ [ATTR_NATS_CONNECTION_SERVER]: ctx.server }
|
|
1082
|
+
);
|
|
1083
|
+
if (!span) {
|
|
1084
|
+
return {
|
|
1085
|
+
recordEvent: () => void 0,
|
|
1086
|
+
finish: () => void 0
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
let finalized = false;
|
|
1090
|
+
return {
|
|
1091
|
+
recordEvent: (name, attributes) => {
|
|
1092
|
+
if (finalized) {
|
|
1093
|
+
logger4.debug(`recordEvent('${name}') called after connection span finished`);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
span.addEvent(name, attributes);
|
|
1097
|
+
},
|
|
1098
|
+
finish: (err) => {
|
|
1099
|
+
if (finalized) return;
|
|
1100
|
+
finalized = true;
|
|
1101
|
+
if (err === void 0) finishOk(span);
|
|
1102
|
+
else finishError(span, err);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
};
|
|
1106
|
+
var withSelfHealingSpan = (config, ctx, op) => wrapInfra(
|
|
1107
|
+
config,
|
|
1108
|
+
"self_healing" /* SelfHealing */,
|
|
1109
|
+
SPAN_NAME_JETSTREAM_SELF_HEALING,
|
|
1110
|
+
ctx,
|
|
1111
|
+
{
|
|
1112
|
+
[ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
|
|
1113
|
+
[ATTR_MESSAGING_CONSUMER_GROUP_NAME]: ctx.consumer,
|
|
1114
|
+
[ATTR_JETSTREAM_SELF_HEALING_REASON]: ctx.reason
|
|
1115
|
+
},
|
|
1116
|
+
op
|
|
1117
|
+
);
|
|
1118
|
+
var withProvisioningSpan = (config, ctx, op) => wrapInfra(
|
|
1119
|
+
config,
|
|
1120
|
+
"provisioning" /* Provisioning */,
|
|
1121
|
+
`${SPAN_NAME_JETSTREAM_PROVISIONING_PREFIX}${ctx.entity}`,
|
|
1122
|
+
ctx,
|
|
1123
|
+
{
|
|
1124
|
+
[ATTR_JETSTREAM_PROVISIONING_ENTITY]: ctx.entity,
|
|
1125
|
+
[ATTR_JETSTREAM_PROVISIONING_ACTION]: ctx.action,
|
|
1126
|
+
[ATTR_JETSTREAM_PROVISIONING_NAME]: ctx.name
|
|
1127
|
+
},
|
|
1128
|
+
op
|
|
1129
|
+
);
|
|
1130
|
+
var withMigrationSpan = (config, ctx, op) => wrapInfra(
|
|
1131
|
+
config,
|
|
1132
|
+
"migration" /* Migration */,
|
|
1133
|
+
SPAN_NAME_JETSTREAM_MIGRATION,
|
|
1134
|
+
ctx,
|
|
1135
|
+
{
|
|
1136
|
+
[ATTR_MESSAGING_NATS_STREAM_NAME]: ctx.stream,
|
|
1137
|
+
[ATTR_JETSTREAM_MIGRATION_REASON]: ctx.reason
|
|
1138
|
+
},
|
|
1139
|
+
op
|
|
1140
|
+
);
|
|
1141
|
+
var withShutdownSpan = (config, ctx, op) => wrapInfra(config, "shutdown" /* Shutdown */, SPAN_NAME_JETSTREAM_SHUTDOWN, ctx, {}, op);
|
|
1142
|
+
|
|
227
1143
|
// src/client/jetstream.record.ts
|
|
228
1144
|
var JetstreamRecord = class {
|
|
229
1145
|
constructor(data, headers2, timeout, messageId, schedule, ttl) {
|
|
@@ -376,9 +1292,14 @@ var JetstreamRecordBuilder = class {
|
|
|
376
1292
|
this.ttlDuration
|
|
377
1293
|
);
|
|
378
1294
|
}
|
|
379
|
-
/**
|
|
1295
|
+
/**
|
|
1296
|
+
* Validate that a header key is not reserved. NATS treats header names
|
|
1297
|
+
* case-insensitively, so the check is against the lowercase form to keep
|
|
1298
|
+
* `'X-Correlation-ID'`, `'x-correlation-id'`, and any other casing in
|
|
1299
|
+
* lockstep. `RESERVED_HEADERS` is defined as an all-lowercase set.
|
|
1300
|
+
*/
|
|
380
1301
|
validateHeaderKey(key) {
|
|
381
|
-
if (RESERVED_HEADERS.has(key)) {
|
|
1302
|
+
if (RESERVED_HEADERS.has(key.toLowerCase())) {
|
|
382
1303
|
throw new Error(
|
|
383
1304
|
`Header "${key}" is reserved by the JetStream transport and cannot be set manually. Reserved headers: ${[...RESERVED_HEADERS].join(", ")}`
|
|
384
1305
|
);
|
|
@@ -398,6 +1319,12 @@ var nanosToGoDuration = (nanos) => {
|
|
|
398
1319
|
};
|
|
399
1320
|
|
|
400
1321
|
// src/client/jetstream.client.ts
|
|
1322
|
+
var BROADCAST_SUBJECT_PREFIX = "broadcast.";
|
|
1323
|
+
var detectEventKind = (pattern) => {
|
|
1324
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */)) return "broadcast" /* Broadcast */;
|
|
1325
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) return "ordered" /* Ordered */;
|
|
1326
|
+
return "event" /* Event */;
|
|
1327
|
+
};
|
|
401
1328
|
var JetstreamClient = class extends ClientProxy {
|
|
402
1329
|
constructor(rootOptions, targetServiceName, connection, codec, eventBus) {
|
|
403
1330
|
super();
|
|
@@ -407,12 +1334,40 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
407
1334
|
this.eventBus = eventBus;
|
|
408
1335
|
this.targetName = targetServiceName;
|
|
409
1336
|
this.callerName = internalName(this.rootOptions.name);
|
|
410
|
-
|
|
411
|
-
|
|
1337
|
+
const targetInternal = internalName(targetServiceName);
|
|
1338
|
+
this.eventSubjectPrefix = `${targetInternal}.${"ev" /* Event */}.`;
|
|
1339
|
+
this.commandSubjectPrefix = `${targetInternal}.${"cmd" /* Command */}.`;
|
|
1340
|
+
this.orderedSubjectPrefix = `${targetInternal}.${"ordered" /* Ordered */}.`;
|
|
1341
|
+
this.isCoreMode = isCoreRpcMode(this.rootOptions.rpc);
|
|
1342
|
+
this.defaultRpcTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? this.rootOptions.rpc?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT : this.rootOptions.rpc?.timeout ?? DEFAULT_RPC_TIMEOUT;
|
|
1343
|
+
const derived = deriveOtelAttrs(this.rootOptions);
|
|
1344
|
+
this.otel = derived.otel;
|
|
1345
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
1346
|
+
}
|
|
1347
|
+
logger = new Logger5("Jetstream:Client");
|
|
412
1348
|
/** Target service name this client sends messages to. */
|
|
413
1349
|
targetName;
|
|
414
1350
|
/** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
|
|
415
1351
|
callerName;
|
|
1352
|
+
/**
|
|
1353
|
+
* Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
|
|
1354
|
+
* per stream kind this client may publish to. Built once in the constructor
|
|
1355
|
+
* so producing a full subject is a single string concat with the user pattern.
|
|
1356
|
+
*/
|
|
1357
|
+
eventSubjectPrefix;
|
|
1358
|
+
commandSubjectPrefix;
|
|
1359
|
+
orderedSubjectPrefix;
|
|
1360
|
+
/**
|
|
1361
|
+
* RPC configuration snapshots. The values are derived from rootOptions at
|
|
1362
|
+
* construction time so the publish hot path never has to re-run
|
|
1363
|
+
* isCoreRpcMode / getRpcTimeout on every call.
|
|
1364
|
+
*/
|
|
1365
|
+
isCoreMode;
|
|
1366
|
+
defaultRpcTimeout;
|
|
1367
|
+
/** Resolved OpenTelemetry configuration, computed once in the constructor. */
|
|
1368
|
+
otel;
|
|
1369
|
+
/** Server endpoint parts used for `server.address` / `server.port` span attributes. */
|
|
1370
|
+
serverEndpoint;
|
|
416
1371
|
/** Shared inbox for JetStream-mode RPC responses. */
|
|
417
1372
|
inbox = null;
|
|
418
1373
|
inboxSubscription = null;
|
|
@@ -422,6 +1377,12 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
422
1377
|
pendingTimeouts = /* @__PURE__ */ new Map();
|
|
423
1378
|
/** Subscription to connection status events for disconnect handling. */
|
|
424
1379
|
statusSubscription = null;
|
|
1380
|
+
/**
|
|
1381
|
+
* Cached readiness flag. Once `connect()` has wired the inbox and status
|
|
1382
|
+
* subscription, subsequent publishes skip the `await connect()` microtask
|
|
1383
|
+
* and reach for the underlying connection synchronously instead.
|
|
1384
|
+
*/
|
|
1385
|
+
readyForPublish = false;
|
|
425
1386
|
/**
|
|
426
1387
|
* Establish connection. Called automatically by NestJS on first use.
|
|
427
1388
|
*
|
|
@@ -432,7 +1393,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
432
1393
|
*/
|
|
433
1394
|
async connect() {
|
|
434
1395
|
const nc = await this.connection.getConnection();
|
|
435
|
-
if (
|
|
1396
|
+
if (!this.isCoreMode && !this.inboxSubscription) {
|
|
436
1397
|
this.setupInbox(nc);
|
|
437
1398
|
}
|
|
438
1399
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
@@ -440,12 +1401,14 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
440
1401
|
this.handleDisconnect();
|
|
441
1402
|
}
|
|
442
1403
|
});
|
|
1404
|
+
this.readyForPublish = true;
|
|
443
1405
|
return nc;
|
|
444
1406
|
}
|
|
445
1407
|
/** Clean up resources: reject pending RPCs, unsubscribe from status events. */
|
|
446
1408
|
async close() {
|
|
447
1409
|
this.statusSubscription?.unsubscribe();
|
|
448
1410
|
this.statusSubscription = null;
|
|
1411
|
+
this.readyForPublish = false;
|
|
449
1412
|
this.rejectPendingRpcs(new Error("Client closed"));
|
|
450
1413
|
}
|
|
451
1414
|
/**
|
|
@@ -469,36 +1432,58 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
469
1432
|
* set to the original event subject.
|
|
470
1433
|
*/
|
|
471
1434
|
async dispatchEvent(packet) {
|
|
472
|
-
await this.connect();
|
|
1435
|
+
if (!this.readyForPublish) await this.connect();
|
|
473
1436
|
const { data, hdrs, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
474
1437
|
const eventSubject = this.buildEventSubject(packet.pattern);
|
|
1438
|
+
const publishSubject = schedule ? this.buildScheduleSubject(eventSubject) : eventSubject;
|
|
475
1439
|
const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1440
|
+
const encoded = this.codec.encode(data);
|
|
1441
|
+
const effectiveMsgId = messageId ?? nuid.next();
|
|
1442
|
+
const record = packet.data instanceof JetstreamRecord ? packet.data : new JetstreamRecord(data, /* @__PURE__ */ new Map());
|
|
1443
|
+
await withPublishSpan(
|
|
1444
|
+
{
|
|
1445
|
+
subject: publishSubject,
|
|
1446
|
+
pattern: packet.pattern,
|
|
1447
|
+
record,
|
|
1448
|
+
kind: detectEventKind(packet.pattern),
|
|
1449
|
+
payloadBytes: encoded.length,
|
|
1450
|
+
payload: encoded,
|
|
1451
|
+
messageId: effectiveMsgId,
|
|
479
1452
|
headers: msgHeaders,
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
schedule:
|
|
483
|
-
|
|
484
|
-
|
|
1453
|
+
serviceName: this.callerName,
|
|
1454
|
+
endpoint: this.serverEndpoint,
|
|
1455
|
+
scheduleTarget: schedule ? eventSubject : void 0
|
|
1456
|
+
},
|
|
1457
|
+
this.otel,
|
|
1458
|
+
async () => {
|
|
1459
|
+
const warnIfDuplicate = (kindLabel, ack2) => {
|
|
1460
|
+
if (ack2.duplicate) {
|
|
1461
|
+
this.logger.warn(
|
|
1462
|
+
`Duplicate ${kindLabel} publish detected: ${publishSubject} (seq: ${ack2.seq})`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
if (schedule) {
|
|
1467
|
+
const ack2 = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1468
|
+
headers: msgHeaders,
|
|
1469
|
+
msgID: effectiveMsgId,
|
|
1470
|
+
ttl,
|
|
1471
|
+
schedule: {
|
|
1472
|
+
specification: schedule.at,
|
|
1473
|
+
target: eventSubject
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
warnIfDuplicate("scheduled", ack2);
|
|
1477
|
+
return;
|
|
485
1478
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
} else {
|
|
493
|
-
const ack = await this.connection.getJetStreamClient().publish(eventSubject, this.codec.encode(data), {
|
|
494
|
-
headers: msgHeaders,
|
|
495
|
-
msgID: messageId ?? nuid.next(),
|
|
496
|
-
ttl
|
|
497
|
-
});
|
|
498
|
-
if (ack.duplicate) {
|
|
499
|
-
this.logger.warn(`Duplicate event publish detected: ${eventSubject} (seq: ${ack.seq})`);
|
|
1479
|
+
const ack = await this.connection.getJetStreamClient().publish(publishSubject, encoded, {
|
|
1480
|
+
headers: msgHeaders,
|
|
1481
|
+
msgID: effectiveMsgId,
|
|
1482
|
+
ttl
|
|
1483
|
+
});
|
|
1484
|
+
warnIfDuplicate("event", ack);
|
|
500
1485
|
}
|
|
501
|
-
|
|
1486
|
+
);
|
|
502
1487
|
return void 0;
|
|
503
1488
|
}
|
|
504
1489
|
/**
|
|
@@ -508,7 +1493,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
508
1493
|
* JetStream mode: publishes to stream + waits for inbox response.
|
|
509
1494
|
*/
|
|
510
1495
|
publish(packet, callback) {
|
|
511
|
-
const subject =
|
|
1496
|
+
const subject = this.commandSubjectPrefix + packet.pattern;
|
|
512
1497
|
const { data, hdrs, timeout, messageId, schedule, ttl } = this.extractRecordData(packet.data);
|
|
513
1498
|
if (schedule) {
|
|
514
1499
|
this.logger.warn(
|
|
@@ -525,7 +1510,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
525
1510
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
526
1511
|
};
|
|
527
1512
|
let jetStreamCorrelationId = null;
|
|
528
|
-
if (
|
|
1513
|
+
if (this.isCoreMode) {
|
|
529
1514
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
530
1515
|
} else {
|
|
531
1516
|
jetStreamCorrelationId = nuid.next();
|
|
@@ -549,37 +1534,102 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
549
1534
|
}
|
|
550
1535
|
/** Core mode: nc.request() with timeout. */
|
|
551
1536
|
async publishCoreRpc(subject, data, customHeaders, timeout, callback) {
|
|
1537
|
+
const effectiveTimeout = timeout ?? this.defaultRpcTimeout;
|
|
1538
|
+
const hdrs = this.buildHeaders(customHeaders, { subject });
|
|
1539
|
+
const encoded = this.codec.encode(data);
|
|
1540
|
+
const spanHandle = beginRpcClientSpan(
|
|
1541
|
+
{
|
|
1542
|
+
subject,
|
|
1543
|
+
payloadBytes: encoded.length,
|
|
1544
|
+
payload: encoded,
|
|
1545
|
+
headers: hdrs,
|
|
1546
|
+
serviceName: this.callerName,
|
|
1547
|
+
endpoint: this.serverEndpoint
|
|
1548
|
+
},
|
|
1549
|
+
this.otel
|
|
1550
|
+
);
|
|
552
1551
|
try {
|
|
553
|
-
const nc = await this.connect();
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1552
|
+
const nc = this.readyForPublish ? this.connection.unwrap : await this.connect();
|
|
1553
|
+
const response = await context6.with(
|
|
1554
|
+
spanHandle.activeContext,
|
|
1555
|
+
() => nc.request(subject, encoded, {
|
|
1556
|
+
timeout: effectiveTimeout,
|
|
1557
|
+
headers: hdrs
|
|
1558
|
+
})
|
|
1559
|
+
);
|
|
560
1560
|
const decoded = this.codec.decode(response.data);
|
|
561
1561
|
if (response.headers?.get("x-error" /* Error */)) {
|
|
1562
|
+
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: decoded });
|
|
562
1563
|
callback({ err: decoded, response: null, isDisposed: true });
|
|
563
1564
|
} else {
|
|
1565
|
+
spanHandle.finish({ kind: "ok" /* Ok */, reply: decoded });
|
|
564
1566
|
callback({ err: null, response: decoded, isDisposed: true });
|
|
565
1567
|
}
|
|
566
1568
|
} catch (err) {
|
|
567
1569
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
568
|
-
|
|
569
|
-
|
|
1570
|
+
if (error instanceof TimeoutError) {
|
|
1571
|
+
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1572
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, "");
|
|
1573
|
+
} else {
|
|
1574
|
+
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1575
|
+
this.eventBus.emit("error" /* Error */, error, "client-rpc");
|
|
1576
|
+
}
|
|
570
1577
|
callback({ err: error, response: null, isDisposed: true });
|
|
571
1578
|
}
|
|
572
1579
|
}
|
|
573
1580
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
574
1581
|
async publishJetStreamRpc(subject, data, callback, options) {
|
|
575
1582
|
const { headers: customHeaders, correlationId, messageId } = options;
|
|
576
|
-
const effectiveTimeout = options.timeout ?? this.
|
|
577
|
-
this.
|
|
1583
|
+
const effectiveTimeout = options.timeout ?? this.defaultRpcTimeout;
|
|
1584
|
+
const hdrs = this.buildHeaders(customHeaders, {
|
|
1585
|
+
subject,
|
|
1586
|
+
correlationId,
|
|
1587
|
+
replyTo: this.inbox ?? ""
|
|
1588
|
+
});
|
|
1589
|
+
const encoded = this.codec.encode(data);
|
|
1590
|
+
const spanHandle = beginRpcClientSpan(
|
|
1591
|
+
{
|
|
1592
|
+
subject,
|
|
1593
|
+
correlationId,
|
|
1594
|
+
payloadBytes: encoded.length,
|
|
1595
|
+
payload: encoded,
|
|
1596
|
+
messageId,
|
|
1597
|
+
headers: hdrs,
|
|
1598
|
+
serviceName: this.callerName,
|
|
1599
|
+
endpoint: this.serverEndpoint
|
|
1600
|
+
},
|
|
1601
|
+
this.otel
|
|
1602
|
+
);
|
|
1603
|
+
this.pendingMessages.set(correlationId, (packet) => {
|
|
1604
|
+
if (packet.err) {
|
|
1605
|
+
if (packet.err instanceof Error) {
|
|
1606
|
+
spanHandle.finish({ kind: "error" /* Error */, error: packet.err });
|
|
1607
|
+
} else {
|
|
1608
|
+
spanHandle.finish({ kind: "reply-error" /* ReplyError */, replyPayload: packet.err });
|
|
1609
|
+
}
|
|
1610
|
+
} else {
|
|
1611
|
+
spanHandle.finish({ kind: "ok" /* Ok */, reply: packet.response });
|
|
1612
|
+
}
|
|
1613
|
+
callback(packet);
|
|
1614
|
+
});
|
|
1615
|
+
const timeoutId = setTimeout(() => {
|
|
1616
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
1617
|
+
this.pendingTimeouts.delete(correlationId);
|
|
1618
|
+
this.pendingMessages.delete(correlationId);
|
|
1619
|
+
spanHandle.finish({ kind: "timeout" /* Timeout */ });
|
|
1620
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
1621
|
+
callback({ err: new Error(RPC_TIMEOUT_MESSAGE), response: null, isDisposed: true });
|
|
1622
|
+
}, effectiveTimeout);
|
|
1623
|
+
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
578
1624
|
try {
|
|
579
|
-
await this.connect();
|
|
1625
|
+
if (!this.readyForPublish) await this.connect();
|
|
580
1626
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
581
1627
|
if (!this.inbox) {
|
|
1628
|
+
clearTimeout(timeoutId);
|
|
1629
|
+
this.pendingTimeouts.delete(correlationId);
|
|
582
1630
|
this.pendingMessages.delete(correlationId);
|
|
1631
|
+
const inboxError = new Error("Inbox not initialized");
|
|
1632
|
+
spanHandle.finish({ kind: "error" /* Error */, error: inboxError });
|
|
583
1633
|
callback({
|
|
584
1634
|
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
585
1635
|
response: null,
|
|
@@ -587,24 +1637,13 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
587
1637
|
});
|
|
588
1638
|
return;
|
|
589
1639
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
this.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}, effectiveTimeout);
|
|
598
|
-
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
599
|
-
const hdrs = this.buildHeaders(customHeaders, {
|
|
600
|
-
subject,
|
|
601
|
-
correlationId,
|
|
602
|
-
replyTo: this.inbox
|
|
603
|
-
});
|
|
604
|
-
await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
605
|
-
headers: hdrs,
|
|
606
|
-
msgID: messageId ?? nuid.next()
|
|
607
|
-
});
|
|
1640
|
+
await context6.with(
|
|
1641
|
+
spanHandle.activeContext,
|
|
1642
|
+
() => this.connection.getJetStreamClient().publish(subject, encoded, {
|
|
1643
|
+
headers: hdrs,
|
|
1644
|
+
msgID: messageId ?? nuid.next()
|
|
1645
|
+
})
|
|
1646
|
+
);
|
|
608
1647
|
} catch (err) {
|
|
609
1648
|
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
610
1649
|
if (existingTimeout) {
|
|
@@ -614,7 +1653,8 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
614
1653
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
615
1654
|
this.pendingMessages.delete(correlationId);
|
|
616
1655
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
617
|
-
|
|
1656
|
+
spanHandle.finish({ kind: "error" /* Error */, error });
|
|
1657
|
+
this.eventBus.emit("error" /* Error */, error, `jetstream-rpc-publish:${subject}`);
|
|
618
1658
|
callback({ err: error, response: null, isDisposed: true });
|
|
619
1659
|
}
|
|
620
1660
|
}
|
|
@@ -622,6 +1662,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
622
1662
|
handleDisconnect() {
|
|
623
1663
|
this.rejectPendingRpcs(new Error("Connection lost"));
|
|
624
1664
|
this.inbox = null;
|
|
1665
|
+
this.readyForPublish = false;
|
|
625
1666
|
}
|
|
626
1667
|
/** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
|
|
627
1668
|
rejectPendingRpcs(error) {
|
|
@@ -685,19 +1726,22 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
685
1726
|
this.pendingMessages.delete(correlationId);
|
|
686
1727
|
}
|
|
687
1728
|
}
|
|
688
|
-
/**
|
|
1729
|
+
/**
|
|
1730
|
+
* Resolve a user pattern to a fully-qualified NATS subject, dispatching
|
|
1731
|
+
* between the event, broadcast, and ordered prefixes.
|
|
1732
|
+
*
|
|
1733
|
+
* The leading-char check short-circuits the `startsWith` comparisons for
|
|
1734
|
+
* patterns that cannot possibly carry a broadcast/ordered marker, which is
|
|
1735
|
+
* the overwhelmingly common case.
|
|
1736
|
+
*/
|
|
689
1737
|
buildEventSubject(pattern) {
|
|
690
|
-
if (pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
691
|
-
return
|
|
692
|
-
}
|
|
693
|
-
if (pattern.startsWith("ordered:" /* Ordered */)) {
|
|
694
|
-
return
|
|
695
|
-
this.targetName,
|
|
696
|
-
"ordered" /* Ordered */,
|
|
697
|
-
pattern.slice("ordered:" /* Ordered */.length)
|
|
698
|
-
);
|
|
1738
|
+
if (pattern.charCodeAt(0) === 98 && pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
1739
|
+
return BROADCAST_SUBJECT_PREFIX + pattern.slice("broadcast:" /* Broadcast */.length);
|
|
1740
|
+
}
|
|
1741
|
+
if (pattern.charCodeAt(0) === 111 && pattern.startsWith("ordered:" /* Ordered */)) {
|
|
1742
|
+
return this.orderedSubjectPrefix + pattern.slice("ordered:" /* Ordered */.length);
|
|
699
1743
|
}
|
|
700
|
-
return
|
|
1744
|
+
return this.eventSubjectPrefix + pattern;
|
|
701
1745
|
}
|
|
702
1746
|
/** Build NATS headers merging custom headers with transport headers. */
|
|
703
1747
|
buildHeaders(customHeaders, transport) {
|
|
@@ -722,7 +1766,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
722
1766
|
if (rawData instanceof JetstreamRecord) {
|
|
723
1767
|
return {
|
|
724
1768
|
data: rawData.data,
|
|
725
|
-
hdrs: rawData.headers.size > 0 ?
|
|
1769
|
+
hdrs: rawData.headers.size > 0 ? rawData.headers : null,
|
|
726
1770
|
timeout: rawData.timeout,
|
|
727
1771
|
messageId: rawData.messageId,
|
|
728
1772
|
schedule: rawData.schedule,
|
|
@@ -765,11 +1809,6 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
765
1809
|
const pattern = withoutPrefix.slice(dotIndex + 1);
|
|
766
1810
|
return `${targetPrefix}_sch.${pattern}`;
|
|
767
1811
|
}
|
|
768
|
-
getRpcTimeout() {
|
|
769
|
-
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
770
|
-
const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
771
|
-
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
772
|
-
}
|
|
773
1812
|
};
|
|
774
1813
|
|
|
775
1814
|
// src/codec/json.codec.ts
|
|
@@ -784,8 +1823,21 @@ var JsonCodec = class {
|
|
|
784
1823
|
}
|
|
785
1824
|
};
|
|
786
1825
|
|
|
1826
|
+
// src/codec/msgpack.codec.ts
|
|
1827
|
+
var MsgpackCodec = class {
|
|
1828
|
+
constructor(packr) {
|
|
1829
|
+
this.packr = packr;
|
|
1830
|
+
}
|
|
1831
|
+
encode(data) {
|
|
1832
|
+
return this.packr.pack(data);
|
|
1833
|
+
}
|
|
1834
|
+
decode(data) {
|
|
1835
|
+
return this.packr.unpack(data);
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
|
|
787
1839
|
// src/connection/connection.provider.ts
|
|
788
|
-
import { Logger as
|
|
1840
|
+
import { Logger as Logger6 } from "@nestjs/common";
|
|
789
1841
|
import {
|
|
790
1842
|
connect
|
|
791
1843
|
} from "@nats-io/transport-node";
|
|
@@ -802,6 +1854,10 @@ var ConnectionProvider = class {
|
|
|
802
1854
|
constructor(options, eventBus) {
|
|
803
1855
|
this.options = options;
|
|
804
1856
|
this.eventBus = eventBus;
|
|
1857
|
+
const derived = deriveOtelAttrs(options);
|
|
1858
|
+
this.otel = derived.otel;
|
|
1859
|
+
this.otelServiceName = derived.serviceName;
|
|
1860
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
805
1861
|
this.nc$ = defer(() => this.getConnection()).pipe(
|
|
806
1862
|
shareReplay({ bufferSize: 1, refCount: false })
|
|
807
1863
|
);
|
|
@@ -814,12 +1870,16 @@ var ConnectionProvider = class {
|
|
|
814
1870
|
nc$;
|
|
815
1871
|
/** Live stream of connection status events (no replay). */
|
|
816
1872
|
status$;
|
|
817
|
-
logger = new
|
|
1873
|
+
logger = new Logger6("Jetstream:Connection");
|
|
818
1874
|
connection = null;
|
|
819
1875
|
connectionPromise = null;
|
|
820
1876
|
jsClient = null;
|
|
821
1877
|
jsmInstance = null;
|
|
822
1878
|
jsmPromise = null;
|
|
1879
|
+
otel;
|
|
1880
|
+
otelServiceName;
|
|
1881
|
+
otelEndpoint;
|
|
1882
|
+
lifecycleSpan = null;
|
|
823
1883
|
/**
|
|
824
1884
|
* Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
|
|
825
1885
|
*
|
|
@@ -883,14 +1943,24 @@ var ConnectionProvider = class {
|
|
|
883
1943
|
}
|
|
884
1944
|
if (!this.connection || this.connection.isClosed()) return;
|
|
885
1945
|
try {
|
|
886
|
-
await
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1946
|
+
await withShutdownSpan(
|
|
1947
|
+
this.otel,
|
|
1948
|
+
{ serviceName: this.otelServiceName, endpoint: this.otelEndpoint },
|
|
1949
|
+
async () => {
|
|
1950
|
+
try {
|
|
1951
|
+
await this.connection?.drain();
|
|
1952
|
+
await this.connection?.closed();
|
|
1953
|
+
} catch {
|
|
1954
|
+
try {
|
|
1955
|
+
await this.connection?.close();
|
|
1956
|
+
} catch {
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
);
|
|
893
1961
|
} finally {
|
|
1962
|
+
this.lifecycleSpan?.finish();
|
|
1963
|
+
this.lifecycleSpan = null;
|
|
894
1964
|
this.connection = null;
|
|
895
1965
|
this.connectionPromise = null;
|
|
896
1966
|
this.jsClient = null;
|
|
@@ -910,17 +1980,25 @@ var ConnectionProvider = class {
|
|
|
910
1980
|
}
|
|
911
1981
|
/** Internal: establish the physical connection with reconnect monitoring. */
|
|
912
1982
|
async establish() {
|
|
913
|
-
const name = internalName(this.options.name);
|
|
914
1983
|
try {
|
|
915
1984
|
const nc = await connect({
|
|
916
1985
|
...DEFAULT_OPTIONS,
|
|
1986
|
+
// Default the NATS connection name to the OTel-derived service name so
|
|
1987
|
+
// `nats server info` lines up with span attributes, but let user-supplied
|
|
1988
|
+
// `connectionOptions.name` win when set.
|
|
1989
|
+
name: this.otelServiceName,
|
|
917
1990
|
...this.options.connectionOptions,
|
|
918
|
-
servers: this.options.servers
|
|
919
|
-
name
|
|
1991
|
+
servers: this.options.servers
|
|
920
1992
|
});
|
|
921
1993
|
this.connection = nc;
|
|
922
1994
|
this.logger.log(`NATS connection established: ${nc.getServer()}`);
|
|
923
1995
|
this.eventBus.emit("connect" /* Connect */, nc.getServer());
|
|
1996
|
+
this.lifecycleSpan?.finish();
|
|
1997
|
+
this.lifecycleSpan = beginConnectionLifecycleSpan(this.otel, {
|
|
1998
|
+
serviceName: this.otelServiceName,
|
|
1999
|
+
endpoint: this.otelEndpoint,
|
|
2000
|
+
server: nc.getServer()
|
|
2001
|
+
});
|
|
924
2002
|
this.monitorStatus(nc);
|
|
925
2003
|
return nc;
|
|
926
2004
|
} catch (err) {
|
|
@@ -930,37 +2008,55 @@ var ConnectionProvider = class {
|
|
|
930
2008
|
throw err;
|
|
931
2009
|
}
|
|
932
2010
|
}
|
|
2011
|
+
/** Handle a single `nc.status()` event, emitting hooks and span events. */
|
|
2012
|
+
handleStatusEvent(status, nc) {
|
|
2013
|
+
switch (status.type) {
|
|
2014
|
+
case "disconnect":
|
|
2015
|
+
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
2016
|
+
this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_DISCONNECTED);
|
|
2017
|
+
break;
|
|
2018
|
+
case "reconnect":
|
|
2019
|
+
this.jsClient = null;
|
|
2020
|
+
this.jsmInstance = null;
|
|
2021
|
+
this.jsmPromise = null;
|
|
2022
|
+
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
2023
|
+
this.lifecycleSpan?.recordEvent(EVENT_CONNECTION_RECONNECTED, {
|
|
2024
|
+
[ATTR_NATS_CONNECTION_SERVER]: nc.getServer()
|
|
2025
|
+
});
|
|
2026
|
+
break;
|
|
2027
|
+
case "error":
|
|
2028
|
+
this.eventBus.emit(
|
|
2029
|
+
"error" /* Error */,
|
|
2030
|
+
status.error,
|
|
2031
|
+
"connection"
|
|
2032
|
+
);
|
|
2033
|
+
break;
|
|
2034
|
+
case "update":
|
|
2035
|
+
case "ldm":
|
|
2036
|
+
case "reconnecting":
|
|
2037
|
+
case "ping":
|
|
2038
|
+
case "staleConnection":
|
|
2039
|
+
case "forceReconnect":
|
|
2040
|
+
case "slowConsumer":
|
|
2041
|
+
case "close":
|
|
2042
|
+
break;
|
|
2043
|
+
default: {
|
|
2044
|
+
const _exhaustive = status;
|
|
2045
|
+
const unknown = _exhaustive.type ?? "unknown";
|
|
2046
|
+
this.logger.warn(`Unhandled NATS status event: ${unknown}`);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
933
2050
|
/** Subscribe to connection status events and emit hooks. */
|
|
934
2051
|
monitorStatus(nc) {
|
|
935
2052
|
void (async () => {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
940
|
-
break;
|
|
941
|
-
case "reconnect":
|
|
942
|
-
this.jsClient = null;
|
|
943
|
-
this.jsmInstance = null;
|
|
944
|
-
this.jsmPromise = null;
|
|
945
|
-
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
946
|
-
break;
|
|
947
|
-
case "error":
|
|
948
|
-
this.eventBus.emit(
|
|
949
|
-
"error" /* Error */,
|
|
950
|
-
status.error,
|
|
951
|
-
"connection"
|
|
952
|
-
);
|
|
953
|
-
break;
|
|
954
|
-
case "update":
|
|
955
|
-
case "ldm":
|
|
956
|
-
case "reconnecting":
|
|
957
|
-
case "ping":
|
|
958
|
-
case "staleConnection":
|
|
959
|
-
case "forceReconnect":
|
|
960
|
-
case "slowConsumer":
|
|
961
|
-
case "close":
|
|
962
|
-
break;
|
|
2053
|
+
try {
|
|
2054
|
+
for await (const status of nc.status()) {
|
|
2055
|
+
this.handleStatusEvent(status, nc);
|
|
963
2056
|
}
|
|
2057
|
+
} finally {
|
|
2058
|
+
this.lifecycleSpan?.finish();
|
|
2059
|
+
this.lifecycleSpan = null;
|
|
964
2060
|
}
|
|
965
2061
|
})().catch((err) => {
|
|
966
2062
|
this.logger.error("Status monitor error", err);
|
|
@@ -972,8 +2068,8 @@ var ConnectionProvider = class {
|
|
|
972
2068
|
var EventBus = class {
|
|
973
2069
|
hooks;
|
|
974
2070
|
logger;
|
|
975
|
-
constructor(
|
|
976
|
-
this.logger =
|
|
2071
|
+
constructor(logger5, hooks) {
|
|
2072
|
+
this.logger = logger5;
|
|
977
2073
|
this.hooks = hooks ?? {};
|
|
978
2074
|
}
|
|
979
2075
|
/**
|
|
@@ -1001,6 +2097,15 @@ var EventBus = class {
|
|
|
1001
2097
|
kind
|
|
1002
2098
|
);
|
|
1003
2099
|
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Check whether a hook is registered for the given event.
|
|
2102
|
+
*
|
|
2103
|
+
* Used by the routing hot path to elide the emit call entirely when the
|
|
2104
|
+
* transport owner did not register a listener.
|
|
2105
|
+
*/
|
|
2106
|
+
hasHook(event) {
|
|
2107
|
+
return this.hooks[event] !== void 0;
|
|
2108
|
+
}
|
|
1004
2109
|
callHook(event, hook, ...args) {
|
|
1005
2110
|
try {
|
|
1006
2111
|
const result = hook(...args);
|
|
@@ -1020,12 +2125,12 @@ var EventBus = class {
|
|
|
1020
2125
|
};
|
|
1021
2126
|
|
|
1022
2127
|
// src/health/jetstream.health-indicator.ts
|
|
1023
|
-
import { Injectable, Logger as
|
|
2128
|
+
import { Injectable, Logger as Logger7 } from "@nestjs/common";
|
|
1024
2129
|
var JetstreamHealthIndicator = class {
|
|
1025
2130
|
constructor(connection) {
|
|
1026
2131
|
this.connection = connection;
|
|
1027
2132
|
}
|
|
1028
|
-
logger = new
|
|
2133
|
+
logger = new Logger7("Jetstream:Health");
|
|
1029
2134
|
/**
|
|
1030
2135
|
* Plain health status check.
|
|
1031
2136
|
*
|
|
@@ -1231,7 +2336,7 @@ var JetstreamStrategy = class extends Server {
|
|
|
1231
2336
|
};
|
|
1232
2337
|
|
|
1233
2338
|
// src/server/core-rpc.server.ts
|
|
1234
|
-
import { Logger as
|
|
2339
|
+
import { Logger as Logger8 } from "@nestjs/common";
|
|
1235
2340
|
import { headers as natsHeaders2 } from "@nats-io/transport-node";
|
|
1236
2341
|
|
|
1237
2342
|
// src/context/rpc.context.ts
|
|
@@ -1241,9 +2346,6 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1241
2346
|
_retryDelay;
|
|
1242
2347
|
_shouldTerminate = false;
|
|
1243
2348
|
_terminateReason;
|
|
1244
|
-
// ---------------------------------------------------------------------------
|
|
1245
|
-
// Message accessors
|
|
1246
|
-
// ---------------------------------------------------------------------------
|
|
1247
2349
|
/**
|
|
1248
2350
|
* Get the underlying NATS message.
|
|
1249
2351
|
*
|
|
@@ -1278,9 +2380,6 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1278
2380
|
isJetStream() {
|
|
1279
2381
|
return "ack" in this.args[0];
|
|
1280
2382
|
}
|
|
1281
|
-
// ---------------------------------------------------------------------------
|
|
1282
|
-
// JetStream metadata (return undefined for Core NATS messages)
|
|
1283
|
-
// ---------------------------------------------------------------------------
|
|
1284
2383
|
/** How many times this message has been delivered. */
|
|
1285
2384
|
getDeliveryCount() {
|
|
1286
2385
|
return this.asJetStream()?.info.deliveryCount;
|
|
@@ -1302,9 +2401,6 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1302
2401
|
getCallerName() {
|
|
1303
2402
|
return this.getHeader("x-caller-name" /* CallerName */);
|
|
1304
2403
|
}
|
|
1305
|
-
// ---------------------------------------------------------------------------
|
|
1306
|
-
// Handler-controlled settlement
|
|
1307
|
-
// ---------------------------------------------------------------------------
|
|
1308
2404
|
/**
|
|
1309
2405
|
* Signal the transport to retry (nak) this message instead of acknowledging it.
|
|
1310
2406
|
*
|
|
@@ -1349,9 +2445,6 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1349
2445
|
throw new Error(`${method}() is only available for JetStream messages`);
|
|
1350
2446
|
}
|
|
1351
2447
|
}
|
|
1352
|
-
// ---------------------------------------------------------------------------
|
|
1353
|
-
// Transport-facing state (read by EventRouter)
|
|
1354
|
-
// ---------------------------------------------------------------------------
|
|
1355
2448
|
/** @internal */
|
|
1356
2449
|
get shouldRetry() {
|
|
1357
2450
|
return this._shouldRetry;
|
|
@@ -1383,16 +2476,71 @@ var resolveAckExtensionInterval = (config, ackWaitNanos) => {
|
|
|
1383
2476
|
const interval = Math.floor(ackWaitNanos / 1e6 / 2);
|
|
1384
2477
|
return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
|
|
1385
2478
|
};
|
|
2479
|
+
var AckExtensionPool = class {
|
|
2480
|
+
entries = /* @__PURE__ */ new Set();
|
|
2481
|
+
handle = null;
|
|
2482
|
+
handleFireAt = 0;
|
|
2483
|
+
schedule(msg, interval) {
|
|
2484
|
+
const entry = {
|
|
2485
|
+
msg,
|
|
2486
|
+
interval,
|
|
2487
|
+
nextFireAt: Date.now() + interval,
|
|
2488
|
+
active: true
|
|
2489
|
+
};
|
|
2490
|
+
this.entries.add(entry);
|
|
2491
|
+
this.ensureWake(entry.nextFireAt);
|
|
2492
|
+
return entry;
|
|
2493
|
+
}
|
|
2494
|
+
cancel(entry) {
|
|
2495
|
+
if (!entry.active) return;
|
|
2496
|
+
entry.active = false;
|
|
2497
|
+
this.entries.delete(entry);
|
|
2498
|
+
if (this.entries.size === 0 && this.handle !== null) {
|
|
2499
|
+
clearTimeout(this.handle);
|
|
2500
|
+
this.handle = null;
|
|
2501
|
+
this.handleFireAt = 0;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Ensure the shared timer will fire no later than `dueAt`. If an earlier
|
|
2506
|
+
* wake is already scheduled, leave it; otherwise replace it with a tighter one.
|
|
2507
|
+
*/
|
|
2508
|
+
ensureWake(dueAt) {
|
|
2509
|
+
if (this.handle !== null && this.handleFireAt <= dueAt) return;
|
|
2510
|
+
if (this.handle !== null) clearTimeout(this.handle);
|
|
2511
|
+
const delay = Math.max(0, dueAt - Date.now());
|
|
2512
|
+
const handle = setTimeout(() => {
|
|
2513
|
+
this.tick();
|
|
2514
|
+
}, delay);
|
|
2515
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
2516
|
+
this.handle = handle;
|
|
2517
|
+
this.handleFireAt = dueAt;
|
|
2518
|
+
}
|
|
2519
|
+
tick() {
|
|
2520
|
+
this.handle = null;
|
|
2521
|
+
this.handleFireAt = 0;
|
|
2522
|
+
const now = Date.now();
|
|
2523
|
+
let earliest = Infinity;
|
|
2524
|
+
for (const entry of this.entries) {
|
|
2525
|
+
if (!entry.active) continue;
|
|
2526
|
+
if (entry.nextFireAt <= now) {
|
|
2527
|
+
try {
|
|
2528
|
+
entry.msg.working();
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
2531
|
+
entry.nextFireAt = now + entry.interval;
|
|
2532
|
+
}
|
|
2533
|
+
if (entry.nextFireAt < earliest) earliest = entry.nextFireAt;
|
|
2534
|
+
}
|
|
2535
|
+
if (earliest !== Infinity) this.ensureWake(earliest);
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
var pool = new AckExtensionPool();
|
|
1386
2539
|
var startAckExtensionTimer = (msg, interval) => {
|
|
1387
2540
|
if (interval === null || interval <= 0) return null;
|
|
1388
|
-
const
|
|
1389
|
-
try {
|
|
1390
|
-
msg.working();
|
|
1391
|
-
} catch {
|
|
1392
|
-
}
|
|
1393
|
-
}, interval);
|
|
2541
|
+
const entry = pool.schedule(msg, interval);
|
|
1394
2542
|
return () => {
|
|
1395
|
-
|
|
2543
|
+
pool.cancel(entry);
|
|
1396
2544
|
};
|
|
1397
2545
|
};
|
|
1398
2546
|
|
|
@@ -1406,11 +2554,8 @@ var serializeError = (err) => {
|
|
|
1406
2554
|
|
|
1407
2555
|
// src/utils/unwrap-result.ts
|
|
1408
2556
|
import { isObservable } from "rxjs";
|
|
1409
|
-
var RESOLVED_VOID = Promise.resolve(void 0);
|
|
1410
|
-
var RESOLVED_NULL = Promise.resolve(null);
|
|
1411
2557
|
var unwrapResult = (result) => {
|
|
1412
|
-
if (result === void 0) return
|
|
1413
|
-
if (result === null) return RESOLVED_NULL;
|
|
2558
|
+
if (result === void 0 || result === null) return result;
|
|
1414
2559
|
if (isObservable(result)) {
|
|
1415
2560
|
return subscribeToFirst(result);
|
|
1416
2561
|
}
|
|
@@ -1419,8 +2564,9 @@ var unwrapResult = (result) => {
|
|
|
1419
2564
|
(resolved) => isObservable(resolved) ? subscribeToFirst(resolved) : resolved
|
|
1420
2565
|
);
|
|
1421
2566
|
}
|
|
1422
|
-
return
|
|
2567
|
+
return result;
|
|
1423
2568
|
};
|
|
2569
|
+
var isPromiseLike2 = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
|
|
1424
2570
|
var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
1425
2571
|
let done = false;
|
|
1426
2572
|
let subscription = null;
|
|
@@ -1450,20 +2596,25 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
|
1450
2596
|
// src/server/core-rpc.server.ts
|
|
1451
2597
|
var CoreRpcServer = class {
|
|
1452
2598
|
constructor(options, connection, patternRegistry, codec, eventBus) {
|
|
1453
|
-
this.options = options;
|
|
1454
2599
|
this.connection = connection;
|
|
1455
2600
|
this.patternRegistry = patternRegistry;
|
|
1456
2601
|
this.codec = codec;
|
|
1457
2602
|
this.eventBus = eventBus;
|
|
2603
|
+
const derived = deriveOtelAttrs(options);
|
|
2604
|
+
this.otel = derived.otel;
|
|
2605
|
+
this.serviceName = derived.serviceName;
|
|
2606
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
1458
2607
|
}
|
|
1459
|
-
logger = new
|
|
2608
|
+
logger = new Logger8("Jetstream:CoreRpc");
|
|
1460
2609
|
subscription = null;
|
|
2610
|
+
otel;
|
|
2611
|
+
serviceName;
|
|
2612
|
+
serverEndpoint;
|
|
1461
2613
|
/** Start listening for RPC requests on the command subject. */
|
|
1462
2614
|
async start() {
|
|
1463
2615
|
const nc = await this.connection.getConnection();
|
|
1464
|
-
const
|
|
1465
|
-
const
|
|
1466
|
-
const queue = `${serviceName}_cmd_queue`;
|
|
2616
|
+
const subject = `${this.serviceName}.cmd.>`;
|
|
2617
|
+
const queue = `${this.serviceName}_cmd_queue`;
|
|
1467
2618
|
this.subscription = nc.subscribe(subject, {
|
|
1468
2619
|
queue,
|
|
1469
2620
|
callback: (err, msg) => {
|
|
@@ -1508,10 +2659,29 @@ var CoreRpcServer = class {
|
|
|
1508
2659
|
}
|
|
1509
2660
|
const ctx = new RpcContext([msg]);
|
|
1510
2661
|
try {
|
|
1511
|
-
const
|
|
1512
|
-
|
|
2662
|
+
const raw = await withConsumeSpan(
|
|
2663
|
+
{
|
|
2664
|
+
subject: msg.subject,
|
|
2665
|
+
msg,
|
|
2666
|
+
kind: "rpc" /* Rpc */,
|
|
2667
|
+
payloadBytes: msg.data.length,
|
|
2668
|
+
handlerMetadata: { pattern: msg.subject },
|
|
2669
|
+
serviceName: this.serviceName,
|
|
2670
|
+
endpoint: this.serverEndpoint
|
|
2671
|
+
},
|
|
2672
|
+
this.otel,
|
|
2673
|
+
() => {
|
|
2674
|
+
const out = unwrapResult(handler(data, ctx));
|
|
2675
|
+
return isPromiseLike2(out) ? out : out;
|
|
2676
|
+
}
|
|
2677
|
+
);
|
|
2678
|
+
msg.respond(this.codec.encode(raw));
|
|
1513
2679
|
} catch (err) {
|
|
1514
|
-
this.
|
|
2680
|
+
this.eventBus.emit(
|
|
2681
|
+
"error" /* Error */,
|
|
2682
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
2683
|
+
`core-rpc-handler:${msg.subject}`
|
|
2684
|
+
);
|
|
1515
2685
|
this.respondWithError(msg, err);
|
|
1516
2686
|
}
|
|
1517
2687
|
}
|
|
@@ -1528,9 +2698,17 @@ var CoreRpcServer = class {
|
|
|
1528
2698
|
};
|
|
1529
2699
|
|
|
1530
2700
|
// src/server/infrastructure/stream.provider.ts
|
|
1531
|
-
import { Logger as
|
|
2701
|
+
import { Logger as Logger10 } from "@nestjs/common";
|
|
1532
2702
|
import { JetStreamApiError as JetStreamApiError2 } from "@nats-io/jetstream";
|
|
1533
2703
|
|
|
2704
|
+
// src/server/infrastructure/nats-error-codes.ts
|
|
2705
|
+
var NatsErrorCode = /* @__PURE__ */ ((NatsErrorCode2) => {
|
|
2706
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerNotFound"] = 10014] = "ConsumerNotFound";
|
|
2707
|
+
NatsErrorCode2[NatsErrorCode2["ConsumerAlreadyExists"] = 10148] = "ConsumerAlreadyExists";
|
|
2708
|
+
NatsErrorCode2[NatsErrorCode2["StreamNotFound"] = 10059] = "StreamNotFound";
|
|
2709
|
+
return NatsErrorCode2;
|
|
2710
|
+
})(NatsErrorCode || {});
|
|
2711
|
+
|
|
1534
2712
|
// src/server/infrastructure/stream-config-diff.ts
|
|
1535
2713
|
var TRANSPORT_CONTROLLED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1536
2714
|
"retention"
|
|
@@ -1587,7 +2765,7 @@ var isEqual = (a, b) => {
|
|
|
1587
2765
|
};
|
|
1588
2766
|
|
|
1589
2767
|
// src/server/infrastructure/stream-migration.ts
|
|
1590
|
-
import { Logger as
|
|
2768
|
+
import { Logger as Logger9 } from "@nestjs/common";
|
|
1591
2769
|
import { JetStreamApiError } from "@nats-io/jetstream";
|
|
1592
2770
|
var MIGRATION_BACKUP_SUFFIX = "__migration_backup";
|
|
1593
2771
|
var DEFAULT_SOURCING_TIMEOUT_MS = 3e4;
|
|
@@ -1596,7 +2774,7 @@ var StreamMigration = class {
|
|
|
1596
2774
|
constructor(sourcingTimeoutMs = DEFAULT_SOURCING_TIMEOUT_MS) {
|
|
1597
2775
|
this.sourcingTimeoutMs = sourcingTimeoutMs;
|
|
1598
2776
|
}
|
|
1599
|
-
logger = new
|
|
2777
|
+
logger = new Logger9("Jetstream:Stream");
|
|
1600
2778
|
async migrate(jsm, streamName2, newConfig) {
|
|
1601
2779
|
const backupName = `${streamName2}${MIGRATION_BACKUP_SUFFIX}`;
|
|
1602
2780
|
const startTime = Date.now();
|
|
@@ -1678,9 +2856,16 @@ var StreamProvider = class {
|
|
|
1678
2856
|
constructor(options, connection) {
|
|
1679
2857
|
this.options = options;
|
|
1680
2858
|
this.connection = connection;
|
|
2859
|
+
const derived = deriveOtelAttrs(options);
|
|
2860
|
+
this.otel = derived.otel;
|
|
2861
|
+
this.otelServiceName = derived.serviceName;
|
|
2862
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
1681
2863
|
}
|
|
1682
|
-
logger = new
|
|
2864
|
+
logger = new Logger10("Jetstream:Stream");
|
|
1683
2865
|
migration = new StreamMigration();
|
|
2866
|
+
otel;
|
|
2867
|
+
otelServiceName;
|
|
2868
|
+
otelEndpoint;
|
|
1684
2869
|
/**
|
|
1685
2870
|
* Ensure all required streams exist with correct configuration.
|
|
1686
2871
|
*
|
|
@@ -1726,32 +2911,56 @@ var StreamProvider = class {
|
|
|
1726
2911
|
/** Ensure a single stream exists, creating or updating as needed. */
|
|
1727
2912
|
async ensureStream(jsm, kind) {
|
|
1728
2913
|
const config = this.buildConfig(kind);
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2914
|
+
return withProvisioningSpan(
|
|
2915
|
+
this.otel,
|
|
2916
|
+
{
|
|
2917
|
+
serviceName: this.otelServiceName,
|
|
2918
|
+
endpoint: this.otelEndpoint,
|
|
2919
|
+
entity: "stream",
|
|
2920
|
+
name: config.name,
|
|
2921
|
+
action: "ensure"
|
|
2922
|
+
},
|
|
2923
|
+
async () => {
|
|
2924
|
+
this.logger.log(`Ensuring stream: ${config.name}`);
|
|
2925
|
+
try {
|
|
2926
|
+
const currentInfo = await jsm.streams.info(config.name);
|
|
2927
|
+
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2928
|
+
} catch (err) {
|
|
2929
|
+
if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2930
|
+
this.logger.log(`Creating stream: ${config.name}`);
|
|
2931
|
+
return await jsm.streams.add(config);
|
|
2932
|
+
}
|
|
2933
|
+
throw err;
|
|
2934
|
+
}
|
|
1737
2935
|
}
|
|
1738
|
-
|
|
1739
|
-
}
|
|
2936
|
+
);
|
|
1740
2937
|
}
|
|
1741
2938
|
/** Ensure a dead-letter queue stream exists, creating or updating as needed. */
|
|
1742
2939
|
async ensureDlqStream(jsm) {
|
|
1743
2940
|
const config = this.buildDlqConfig();
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2941
|
+
return withProvisioningSpan(
|
|
2942
|
+
this.otel,
|
|
2943
|
+
{
|
|
2944
|
+
serviceName: this.otelServiceName,
|
|
2945
|
+
endpoint: this.otelEndpoint,
|
|
2946
|
+
entity: "stream",
|
|
2947
|
+
name: config.name,
|
|
2948
|
+
action: "ensure"
|
|
2949
|
+
},
|
|
2950
|
+
async () => {
|
|
2951
|
+
this.logger.log(`Ensuring DLQ stream: ${config.name}`);
|
|
2952
|
+
try {
|
|
2953
|
+
const currentInfo = await jsm.streams.info(config.name);
|
|
2954
|
+
return await this.handleExistingStream(jsm, currentInfo, config);
|
|
2955
|
+
} catch (err) {
|
|
2956
|
+
if (err instanceof JetStreamApiError2 && err.apiError().err_code === 10059 /* StreamNotFound */) {
|
|
2957
|
+
this.logger.log(`Creating DLQ stream: ${config.name}`);
|
|
2958
|
+
return await jsm.streams.add(config);
|
|
2959
|
+
}
|
|
2960
|
+
throw err;
|
|
2961
|
+
}
|
|
1752
2962
|
}
|
|
1753
|
-
|
|
1754
|
-
}
|
|
2963
|
+
);
|
|
1755
2964
|
}
|
|
1756
2965
|
async handleExistingStream(jsm, currentInfo, config) {
|
|
1757
2966
|
const diff = compareStreamConfig(currentInfo.config, config);
|
|
@@ -1780,7 +2989,18 @@ var StreamProvider = class {
|
|
|
1780
2989
|
}
|
|
1781
2990
|
return currentInfo;
|
|
1782
2991
|
}
|
|
1783
|
-
await
|
|
2992
|
+
await withMigrationSpan(
|
|
2993
|
+
this.otel,
|
|
2994
|
+
{
|
|
2995
|
+
serviceName: this.otelServiceName,
|
|
2996
|
+
endpoint: this.otelEndpoint,
|
|
2997
|
+
stream: config.name,
|
|
2998
|
+
reason: diff.changes.filter((c) => c.mutability === "immutable").map((c) => c.property).join(", ")
|
|
2999
|
+
},
|
|
3000
|
+
async () => {
|
|
3001
|
+
await this.migration.migrate(jsm, config.name, config);
|
|
3002
|
+
}
|
|
3003
|
+
);
|
|
1784
3004
|
return await jsm.streams.info(config.name);
|
|
1785
3005
|
}
|
|
1786
3006
|
buildMutableOnlyConfig(config, currentConfig, diff) {
|
|
@@ -1896,7 +3116,7 @@ var StreamProvider = class {
|
|
|
1896
3116
|
};
|
|
1897
3117
|
|
|
1898
3118
|
// src/server/infrastructure/consumer.provider.ts
|
|
1899
|
-
import { Logger as
|
|
3119
|
+
import { Logger as Logger11 } from "@nestjs/common";
|
|
1900
3120
|
import { JetStreamApiError as JetStreamApiError3 } from "@nats-io/jetstream";
|
|
1901
3121
|
var ConsumerProvider = class {
|
|
1902
3122
|
constructor(options, connection, streamProvider, patternRegistry) {
|
|
@@ -1904,8 +3124,15 @@ var ConsumerProvider = class {
|
|
|
1904
3124
|
this.connection = connection;
|
|
1905
3125
|
this.streamProvider = streamProvider;
|
|
1906
3126
|
this.patternRegistry = patternRegistry;
|
|
1907
|
-
|
|
1908
|
-
|
|
3127
|
+
const derived = deriveOtelAttrs(options);
|
|
3128
|
+
this.otel = derived.otel;
|
|
3129
|
+
this.otelServiceName = derived.serviceName;
|
|
3130
|
+
this.otelEndpoint = derived.serverEndpoint;
|
|
3131
|
+
}
|
|
3132
|
+
logger = new Logger11("Jetstream:Consumer");
|
|
3133
|
+
otel;
|
|
3134
|
+
otelServiceName;
|
|
3135
|
+
otelEndpoint;
|
|
1909
3136
|
/**
|
|
1910
3137
|
* Ensure consumers exist for the specified kinds.
|
|
1911
3138
|
*
|
|
@@ -1935,17 +3162,29 @@ var ConsumerProvider = class {
|
|
|
1935
3162
|
const stream = this.streamProvider.getStreamName(kind);
|
|
1936
3163
|
const config = this.buildConfig(kind);
|
|
1937
3164
|
const name = config.durable_name;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
3165
|
+
return withProvisioningSpan(
|
|
3166
|
+
this.otel,
|
|
3167
|
+
{
|
|
3168
|
+
serviceName: this.otelServiceName,
|
|
3169
|
+
endpoint: this.otelEndpoint,
|
|
3170
|
+
entity: "consumer",
|
|
3171
|
+
name,
|
|
3172
|
+
action: "ensure"
|
|
3173
|
+
},
|
|
3174
|
+
async () => {
|
|
3175
|
+
this.logger.log(`Ensuring consumer: ${name} on stream: ${stream}`);
|
|
3176
|
+
try {
|
|
3177
|
+
await jsm.consumers.info(stream, name);
|
|
3178
|
+
this.logger.debug(`Consumer exists, updating: ${name}`);
|
|
3179
|
+
return await jsm.consumers.update(stream, name, config);
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3182
|
+
throw err;
|
|
3183
|
+
}
|
|
3184
|
+
return await this.createConsumer(jsm, stream, name, config);
|
|
3185
|
+
}
|
|
1946
3186
|
}
|
|
1947
|
-
|
|
1948
|
-
}
|
|
3187
|
+
);
|
|
1949
3188
|
}
|
|
1950
3189
|
/**
|
|
1951
3190
|
* Recover a consumer that disappeared during runtime.
|
|
@@ -1964,16 +3203,28 @@ var ConsumerProvider = class {
|
|
|
1964
3203
|
const stream = this.streamProvider.getStreamName(kind);
|
|
1965
3204
|
const config = this.buildConfig(kind);
|
|
1966
3205
|
const name = config.durable_name;
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
3206
|
+
return withProvisioningSpan(
|
|
3207
|
+
this.otel,
|
|
3208
|
+
{
|
|
3209
|
+
serviceName: this.otelServiceName,
|
|
3210
|
+
endpoint: this.otelEndpoint,
|
|
3211
|
+
entity: "consumer",
|
|
3212
|
+
name,
|
|
3213
|
+
action: "recover"
|
|
3214
|
+
},
|
|
3215
|
+
async () => {
|
|
3216
|
+
this.logger.log(`Recovering consumer: ${name} on stream: ${stream}`);
|
|
3217
|
+
await this.assertNoMigrationInProgress(jsm, stream);
|
|
3218
|
+
try {
|
|
3219
|
+
return await jsm.consumers.info(stream, name);
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
if (!(err instanceof JetStreamApiError3) || err.apiError().err_code !== 10014 /* ConsumerNotFound */) {
|
|
3222
|
+
throw err;
|
|
3223
|
+
}
|
|
3224
|
+
return await this.createConsumer(jsm, stream, name, config);
|
|
3225
|
+
}
|
|
1974
3226
|
}
|
|
1975
|
-
|
|
1976
|
-
}
|
|
3227
|
+
);
|
|
1977
3228
|
}
|
|
1978
3229
|
/**
|
|
1979
3230
|
* Throw if a migration backup stream exists for this stream.
|
|
@@ -2089,7 +3340,7 @@ var ConsumerProvider = class {
|
|
|
2089
3340
|
};
|
|
2090
3341
|
|
|
2091
3342
|
// src/server/infrastructure/message.provider.ts
|
|
2092
|
-
import { Logger as
|
|
3343
|
+
import { Logger as Logger12 } from "@nestjs/common";
|
|
2093
3344
|
import { DeliverPolicy as DeliverPolicy2 } from "@nats-io/jetstream";
|
|
2094
3345
|
import {
|
|
2095
3346
|
catchError,
|
|
@@ -2109,7 +3360,7 @@ var MessageProvider = class {
|
|
|
2109
3360
|
this.consumeOptionsMap = consumeOptionsMap;
|
|
2110
3361
|
this.consumerRecoveryFn = consumerRecoveryFn;
|
|
2111
3362
|
}
|
|
2112
|
-
logger = new
|
|
3363
|
+
logger = new Logger12("Jetstream:Message");
|
|
2113
3364
|
activeIterators = /* @__PURE__ */ new Set();
|
|
2114
3365
|
orderedReadyResolve = null;
|
|
2115
3366
|
orderedReadyReject = null;
|
|
@@ -2229,13 +3480,17 @@ var MessageProvider = class {
|
|
|
2229
3480
|
}
|
|
2230
3481
|
const defaults = { idle_heartbeat: 5e3 };
|
|
2231
3482
|
const userOptions = this.consumeOptionsMap.get(kind) ?? {};
|
|
2232
|
-
const messages = await consumer.consume({
|
|
3483
|
+
const messages = await consumer.consume({
|
|
3484
|
+
...defaults,
|
|
3485
|
+
...userOptions,
|
|
3486
|
+
callback: (msg) => {
|
|
3487
|
+
target$.next(msg);
|
|
3488
|
+
}
|
|
3489
|
+
});
|
|
2233
3490
|
this.activeIterators.add(messages);
|
|
2234
3491
|
this.monitorConsumerHealth(messages, consumerName2);
|
|
2235
3492
|
try {
|
|
2236
|
-
|
|
2237
|
-
target$.next(msg);
|
|
2238
|
-
}
|
|
3493
|
+
await messages.closed();
|
|
2239
3494
|
} finally {
|
|
2240
3495
|
this.activeIterators.delete(messages);
|
|
2241
3496
|
}
|
|
@@ -2327,11 +3582,16 @@ var MessageProvider = class {
|
|
|
2327
3582
|
})
|
|
2328
3583
|
);
|
|
2329
3584
|
}
|
|
2330
|
-
/** Single iteration: create ordered consumer ->
|
|
3585
|
+
/** Single iteration: create ordered consumer -> push messages into the subject. */
|
|
2331
3586
|
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
2332
3587
|
const js = this.connection.getJetStreamClient();
|
|
2333
3588
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
2334
|
-
const
|
|
3589
|
+
const orderedMessages$ = this.orderedMessages$;
|
|
3590
|
+
const messages = await consumer.consume({
|
|
3591
|
+
callback: (msg) => {
|
|
3592
|
+
orderedMessages$.next(msg);
|
|
3593
|
+
}
|
|
3594
|
+
});
|
|
2335
3595
|
if (this.orderedReadyResolve) {
|
|
2336
3596
|
this.orderedReadyResolve();
|
|
2337
3597
|
this.orderedReadyResolve = null;
|
|
@@ -2339,9 +3599,7 @@ var MessageProvider = class {
|
|
|
2339
3599
|
}
|
|
2340
3600
|
this.activeIterators.add(messages);
|
|
2341
3601
|
try {
|
|
2342
|
-
|
|
2343
|
-
this.orderedMessages$.next(msg);
|
|
2344
|
-
}
|
|
3602
|
+
await messages.closed();
|
|
2345
3603
|
} finally {
|
|
2346
3604
|
this.activeIterators.delete(messages);
|
|
2347
3605
|
}
|
|
@@ -2349,7 +3607,7 @@ var MessageProvider = class {
|
|
|
2349
3607
|
};
|
|
2350
3608
|
|
|
2351
3609
|
// src/server/infrastructure/metadata.provider.ts
|
|
2352
|
-
import { Logger as
|
|
3610
|
+
import { Logger as Logger13 } from "@nestjs/common";
|
|
2353
3611
|
import { Kvm } from "@nats-io/kv";
|
|
2354
3612
|
var MetadataProvider = class {
|
|
2355
3613
|
constructor(options, connection) {
|
|
@@ -2358,7 +3616,7 @@ var MetadataProvider = class {
|
|
|
2358
3616
|
this.replicas = options.metadata?.replicas ?? DEFAULT_METADATA_REPLICAS;
|
|
2359
3617
|
this.ttl = Math.max(options.metadata?.ttl ?? DEFAULT_METADATA_TTL, MIN_METADATA_TTL);
|
|
2360
3618
|
}
|
|
2361
|
-
logger = new
|
|
3619
|
+
logger = new Logger13("Jetstream:Metadata");
|
|
2362
3620
|
bucketName;
|
|
2363
3621
|
replicas;
|
|
2364
3622
|
ttl;
|
|
@@ -2451,7 +3709,7 @@ var MetadataProvider = class {
|
|
|
2451
3709
|
};
|
|
2452
3710
|
|
|
2453
3711
|
// src/server/routing/pattern-registry.ts
|
|
2454
|
-
import { Logger as
|
|
3712
|
+
import { Logger as Logger14 } from "@nestjs/common";
|
|
2455
3713
|
var HANDLER_LABELS = {
|
|
2456
3714
|
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
2457
3715
|
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
@@ -2462,7 +3720,7 @@ var PatternRegistry = class {
|
|
|
2462
3720
|
constructor(options) {
|
|
2463
3721
|
this.options = options;
|
|
2464
3722
|
}
|
|
2465
|
-
logger = new
|
|
3723
|
+
logger = new Logger14("Jetstream:PatternRegistry");
|
|
2466
3724
|
registry = /* @__PURE__ */ new Map();
|
|
2467
3725
|
// Cached after registerHandlers() — the registry is immutable from that point
|
|
2468
3726
|
cachedPatterns = null;
|
|
@@ -2619,9 +3877,13 @@ var PatternRegistry = class {
|
|
|
2619
3877
|
};
|
|
2620
3878
|
|
|
2621
3879
|
// src/server/routing/event.router.ts
|
|
2622
|
-
import { Logger as
|
|
2623
|
-
import { concatMap, from as from2, mergeMap } from "rxjs";
|
|
3880
|
+
import { Logger as Logger15 } from "@nestjs/common";
|
|
2624
3881
|
import { headers as natsHeaders3 } from "@nats-io/transport-node";
|
|
3882
|
+
var eventConsumeKindFor = (kind) => {
|
|
3883
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast" /* Broadcast */;
|
|
3884
|
+
if (kind === "ordered" /* Ordered */) return "ordered" /* Ordered */;
|
|
3885
|
+
return "event" /* Event */;
|
|
3886
|
+
};
|
|
2625
3887
|
var EventRouter = class {
|
|
2626
3888
|
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap, connection, options) {
|
|
2627
3889
|
this.messageProvider = messageProvider;
|
|
@@ -2633,9 +3895,22 @@ var EventRouter = class {
|
|
|
2633
3895
|
this.ackWaitMap = ackWaitMap;
|
|
2634
3896
|
this.connection = connection;
|
|
2635
3897
|
this.options = options;
|
|
3898
|
+
if (options) {
|
|
3899
|
+
const derived = deriveOtelAttrs(options);
|
|
3900
|
+
this.otel = derived.otel;
|
|
3901
|
+
this.serviceName = derived.serviceName;
|
|
3902
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
3903
|
+
} else {
|
|
3904
|
+
this.otel = resolveOtelOptions({ enabled: false });
|
|
3905
|
+
this.serviceName = "";
|
|
3906
|
+
this.serverEndpoint = null;
|
|
3907
|
+
}
|
|
2636
3908
|
}
|
|
2637
|
-
logger = new
|
|
3909
|
+
logger = new Logger15("Jetstream:EventRouter");
|
|
2638
3910
|
subscriptions = [];
|
|
3911
|
+
otel;
|
|
3912
|
+
serviceName;
|
|
3913
|
+
serverEndpoint;
|
|
2639
3914
|
/**
|
|
2640
3915
|
* Update the max_deliver thresholds from actual NATS consumer configs.
|
|
2641
3916
|
* Called after consumers are ensured so the DLQ map reflects reality.
|
|
@@ -2659,15 +3934,233 @@ var EventRouter = class {
|
|
|
2659
3934
|
}
|
|
2660
3935
|
this.subscriptions.length = 0;
|
|
2661
3936
|
}
|
|
2662
|
-
/** Subscribe to a message stream and route each message. */
|
|
3937
|
+
/** Subscribe to a message stream and route each message to its handler. */
|
|
2663
3938
|
subscribeToStream(stream$, kind) {
|
|
2664
3939
|
const isOrdered = kind === "ordered" /* Ordered */;
|
|
3940
|
+
const patternRegistry = this.patternRegistry;
|
|
3941
|
+
const codec = this.codec;
|
|
3942
|
+
const eventBus = this.eventBus;
|
|
3943
|
+
const logger5 = this.logger;
|
|
3944
|
+
const deadLetterConfig = this.deadLetterConfig;
|
|
3945
|
+
const otel = this.otel;
|
|
3946
|
+
const serviceName = this.serviceName;
|
|
3947
|
+
const serverEndpoint = this.serverEndpoint;
|
|
3948
|
+
const spanKind = eventConsumeKindFor(kind);
|
|
2665
3949
|
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
3950
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
2666
3951
|
const concurrency = this.getConcurrency(kind);
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
)
|
|
2670
|
-
|
|
3952
|
+
const hasDlqCheck = deadLetterConfig !== void 0;
|
|
3953
|
+
const emitRouted = eventBus.hasHook("messageRouted" /* MessageRouted */);
|
|
3954
|
+
const isDeadLetter = (msg) => {
|
|
3955
|
+
if (!hasDlqCheck) return false;
|
|
3956
|
+
const maxDeliver = deadLetterConfig.maxDeliverByStream?.get(msg.info.stream);
|
|
3957
|
+
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
3958
|
+
return msg.info.deliveryCount >= maxDeliver;
|
|
3959
|
+
};
|
|
3960
|
+
const handleDeadLetter = hasDlqCheck ? (msg, data, err) => this.handleDeadLetter(msg, data, err) : null;
|
|
3961
|
+
const settleSuccess = (msg, ctx) => {
|
|
3962
|
+
if (ctx.shouldTerminate) msg.term(ctx.terminateReason);
|
|
3963
|
+
else if (ctx.shouldRetry) msg.nak(ctx.retryDelay);
|
|
3964
|
+
else msg.ack();
|
|
3965
|
+
};
|
|
3966
|
+
const settleFailure = async (msg, data, err) => {
|
|
3967
|
+
if (handleDeadLetter !== null && isDeadLetter(msg)) {
|
|
3968
|
+
await handleDeadLetter(msg, data, err);
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
msg.nak();
|
|
3972
|
+
};
|
|
3973
|
+
const resolveEvent = (msg) => {
|
|
3974
|
+
const subject = msg.subject;
|
|
3975
|
+
try {
|
|
3976
|
+
const handler = patternRegistry.getHandler(subject);
|
|
3977
|
+
if (!handler) {
|
|
3978
|
+
msg.term(`No handler for event: ${subject}`);
|
|
3979
|
+
logger5.error(`No handler for subject: ${subject}`);
|
|
3980
|
+
return null;
|
|
3981
|
+
}
|
|
3982
|
+
let data;
|
|
3983
|
+
try {
|
|
3984
|
+
data = codec.decode(msg.data);
|
|
3985
|
+
} catch (err) {
|
|
3986
|
+
msg.term("Decode error");
|
|
3987
|
+
logger5.error(`Decode error for ${subject}:`, err);
|
|
3988
|
+
return null;
|
|
3989
|
+
}
|
|
3990
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
3991
|
+
return { handler, data };
|
|
3992
|
+
} catch (err) {
|
|
3993
|
+
logger5.error(`Unexpected error in ${kind} event router`, err);
|
|
3994
|
+
try {
|
|
3995
|
+
msg.term("Unexpected router error");
|
|
3996
|
+
} catch (termErr) {
|
|
3997
|
+
logger5.error(`Failed to terminate message ${subject}:`, termErr);
|
|
3998
|
+
}
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
};
|
|
4002
|
+
const handleSafe = (msg) => {
|
|
4003
|
+
const resolved = resolveEvent(msg);
|
|
4004
|
+
if (resolved === null) return void 0;
|
|
4005
|
+
const { handler, data } = resolved;
|
|
4006
|
+
const ctx = new RpcContext([msg]);
|
|
4007
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4008
|
+
let pending;
|
|
4009
|
+
try {
|
|
4010
|
+
pending = withConsumeSpan(
|
|
4011
|
+
{
|
|
4012
|
+
subject: msg.subject,
|
|
4013
|
+
msg,
|
|
4014
|
+
info: msg.info,
|
|
4015
|
+
kind: spanKind,
|
|
4016
|
+
payloadBytes: msg.data.length,
|
|
4017
|
+
handlerMetadata: { pattern: msg.subject },
|
|
4018
|
+
serviceName,
|
|
4019
|
+
endpoint: serverEndpoint
|
|
4020
|
+
},
|
|
4021
|
+
otel,
|
|
4022
|
+
() => unwrapResult(handler(data, ctx))
|
|
4023
|
+
);
|
|
4024
|
+
} catch (err) {
|
|
4025
|
+
eventBus.emit(
|
|
4026
|
+
"error" /* Error */,
|
|
4027
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4028
|
+
`${kind}-handler:${msg.subject}`
|
|
4029
|
+
);
|
|
4030
|
+
return settleFailure(msg, data, err).finally(() => {
|
|
4031
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
if (!isPromiseLike2(pending)) {
|
|
4035
|
+
settleSuccess(msg, ctx);
|
|
4036
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4037
|
+
return void 0;
|
|
4038
|
+
}
|
|
4039
|
+
return pending.then(
|
|
4040
|
+
() => {
|
|
4041
|
+
settleSuccess(msg, ctx);
|
|
4042
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4043
|
+
},
|
|
4044
|
+
async (err) => {
|
|
4045
|
+
eventBus.emit(
|
|
4046
|
+
"error" /* Error */,
|
|
4047
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4048
|
+
`${kind}-handler:${msg.subject}`
|
|
4049
|
+
);
|
|
4050
|
+
try {
|
|
4051
|
+
await settleFailure(msg, data, err);
|
|
4052
|
+
} finally {
|
|
4053
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
);
|
|
4057
|
+
};
|
|
4058
|
+
const handleOrderedSafe = (msg) => {
|
|
4059
|
+
const subject = msg.subject;
|
|
4060
|
+
let handler;
|
|
4061
|
+
let data;
|
|
4062
|
+
try {
|
|
4063
|
+
handler = patternRegistry.getHandler(subject);
|
|
4064
|
+
if (!handler) {
|
|
4065
|
+
logger5.error(`No handler for subject: ${subject}`);
|
|
4066
|
+
return void 0;
|
|
4067
|
+
}
|
|
4068
|
+
try {
|
|
4069
|
+
data = codec.decode(msg.data);
|
|
4070
|
+
} catch (err) {
|
|
4071
|
+
logger5.error(`Decode error for ${subject}:`, err);
|
|
4072
|
+
return void 0;
|
|
4073
|
+
}
|
|
4074
|
+
if (emitRouted) eventBus.emitMessageRouted(subject, "event" /* Event */);
|
|
4075
|
+
} catch (err) {
|
|
4076
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4077
|
+
return void 0;
|
|
4078
|
+
}
|
|
4079
|
+
const ctx = new RpcContext([msg]);
|
|
4080
|
+
const warnIfSettlementAttempted = () => {
|
|
4081
|
+
if (ctx.shouldRetry || ctx.shouldTerminate) {
|
|
4082
|
+
logger5.warn(
|
|
4083
|
+
`retry()/terminate() ignored for ordered message ${subject} \u2014 ordered consumers auto-acknowledge`
|
|
4084
|
+
);
|
|
4085
|
+
}
|
|
4086
|
+
};
|
|
4087
|
+
let pending;
|
|
4088
|
+
try {
|
|
4089
|
+
pending = withConsumeSpan(
|
|
4090
|
+
{
|
|
4091
|
+
subject: msg.subject,
|
|
4092
|
+
msg,
|
|
4093
|
+
info: msg.info,
|
|
4094
|
+
kind: spanKind,
|
|
4095
|
+
payloadBytes: msg.data.length,
|
|
4096
|
+
handlerMetadata: { pattern: msg.subject },
|
|
4097
|
+
serviceName,
|
|
4098
|
+
endpoint: serverEndpoint
|
|
4099
|
+
},
|
|
4100
|
+
otel,
|
|
4101
|
+
() => unwrapResult(handler(data, ctx))
|
|
4102
|
+
);
|
|
4103
|
+
} catch (err) {
|
|
4104
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4105
|
+
return void 0;
|
|
4106
|
+
}
|
|
4107
|
+
if (!isPromiseLike2(pending)) {
|
|
4108
|
+
warnIfSettlementAttempted();
|
|
4109
|
+
return void 0;
|
|
4110
|
+
}
|
|
4111
|
+
return pending.then(warnIfSettlementAttempted, (err) => {
|
|
4112
|
+
logger5.error(`Ordered handler error (${subject}):`, err);
|
|
4113
|
+
});
|
|
4114
|
+
};
|
|
4115
|
+
const route = isOrdered ? handleOrderedSafe : handleSafe;
|
|
4116
|
+
const maxActive = isOrdered ? 1 : concurrency ?? Number.POSITIVE_INFINITY;
|
|
4117
|
+
const backlogWarnThreshold = 1e3;
|
|
4118
|
+
let active = 0;
|
|
4119
|
+
let backlogWarned = false;
|
|
4120
|
+
const backlog = [];
|
|
4121
|
+
const onAsyncDone = () => {
|
|
4122
|
+
active--;
|
|
4123
|
+
drainBacklog();
|
|
4124
|
+
};
|
|
4125
|
+
const drainBacklog = () => {
|
|
4126
|
+
while (active < maxActive) {
|
|
4127
|
+
const next = backlog.shift();
|
|
4128
|
+
if (next === void 0) return;
|
|
4129
|
+
active++;
|
|
4130
|
+
const result = route(next);
|
|
4131
|
+
if (result !== void 0) {
|
|
4132
|
+
void result.finally(onAsyncDone);
|
|
4133
|
+
} else {
|
|
4134
|
+
active--;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
4138
|
+
};
|
|
4139
|
+
const subscription = stream$.subscribe({
|
|
4140
|
+
next: (msg) => {
|
|
4141
|
+
if (active >= maxActive) {
|
|
4142
|
+
backlog.push(msg);
|
|
4143
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
4144
|
+
backlogWarned = true;
|
|
4145
|
+
logger5.warn(
|
|
4146
|
+
`${kind} backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
4147
|
+
);
|
|
4148
|
+
}
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
active++;
|
|
4152
|
+
const result = route(msg);
|
|
4153
|
+
if (result !== void 0) {
|
|
4154
|
+
void result.finally(onAsyncDone);
|
|
4155
|
+
} else {
|
|
4156
|
+
active--;
|
|
4157
|
+
if (backlog.length > 0) drainBacklog();
|
|
4158
|
+
}
|
|
4159
|
+
},
|
|
4160
|
+
error: (err) => {
|
|
4161
|
+
logger5.error(`Stream error in ${kind} router`, err);
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
2671
4164
|
this.subscriptions.push(subscription);
|
|
2672
4165
|
}
|
|
2673
4166
|
getConcurrency(kind) {
|
|
@@ -2680,94 +4173,11 @@ var EventRouter = class {
|
|
|
2680
4173
|
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
2681
4174
|
return void 0;
|
|
2682
4175
|
}
|
|
2683
|
-
/** Handle a single event message with error isolation. */
|
|
2684
|
-
async handleSafe(msg, ackExtensionInterval, kind) {
|
|
2685
|
-
try {
|
|
2686
|
-
const resolved = this.decodeMessage(msg);
|
|
2687
|
-
if (!resolved) return;
|
|
2688
|
-
await this.executeHandler(
|
|
2689
|
-
resolved.handler,
|
|
2690
|
-
resolved.data,
|
|
2691
|
-
resolved.ctx,
|
|
2692
|
-
msg,
|
|
2693
|
-
ackExtensionInterval
|
|
2694
|
-
);
|
|
2695
|
-
} catch (err) {
|
|
2696
|
-
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
/** Handle an ordered message with error isolation. */
|
|
2700
|
-
async handleOrderedSafe(msg) {
|
|
2701
|
-
try {
|
|
2702
|
-
const resolved = this.decodeMessage(msg, true);
|
|
2703
|
-
if (!resolved) return;
|
|
2704
|
-
await unwrapResult(resolved.handler(resolved.data, resolved.ctx));
|
|
2705
|
-
if (resolved.ctx.shouldRetry || resolved.ctx.shouldTerminate) {
|
|
2706
|
-
this.logger.warn(
|
|
2707
|
-
`retry()/terminate() ignored for ordered message ${msg.subject} \u2014 ordered consumers auto-acknowledge`
|
|
2708
|
-
);
|
|
2709
|
-
}
|
|
2710
|
-
} catch (err) {
|
|
2711
|
-
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
2715
|
-
decodeMessage(msg, isOrdered = false) {
|
|
2716
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
2717
|
-
if (!handler) {
|
|
2718
|
-
if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
|
|
2719
|
-
this.logger.error(`No handler for subject: ${msg.subject}`);
|
|
2720
|
-
return null;
|
|
2721
|
-
}
|
|
2722
|
-
let data;
|
|
2723
|
-
try {
|
|
2724
|
-
data = this.codec.decode(msg.data);
|
|
2725
|
-
} catch (err) {
|
|
2726
|
-
if (!isOrdered) msg.term("Decode error");
|
|
2727
|
-
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
2728
|
-
return null;
|
|
2729
|
-
}
|
|
2730
|
-
this.eventBus.emitMessageRouted(msg.subject, "event" /* Event */);
|
|
2731
|
-
return { handler, data, ctx: new RpcContext([msg]) };
|
|
2732
|
-
}
|
|
2733
|
-
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
2734
|
-
async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
|
|
2735
|
-
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
2736
|
-
try {
|
|
2737
|
-
await unwrapResult(handler(data, ctx));
|
|
2738
|
-
if (ctx.shouldTerminate) {
|
|
2739
|
-
msg.term(ctx.terminateReason);
|
|
2740
|
-
} else if (ctx.shouldRetry) {
|
|
2741
|
-
msg.nak(ctx.retryDelay);
|
|
2742
|
-
} else {
|
|
2743
|
-
msg.ack();
|
|
2744
|
-
}
|
|
2745
|
-
} catch (err) {
|
|
2746
|
-
this.logger.error(`Event handler error (${msg.subject}):`, err);
|
|
2747
|
-
if (this.isDeadLetter(msg)) {
|
|
2748
|
-
await this.handleDeadLetter(msg, data, err);
|
|
2749
|
-
} else {
|
|
2750
|
-
msg.nak();
|
|
2751
|
-
}
|
|
2752
|
-
} finally {
|
|
2753
|
-
stopAckExtension?.();
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
|
-
/** Check if the message has exhausted all delivery attempts. */
|
|
2757
|
-
isDeadLetter(msg) {
|
|
2758
|
-
if (!this.deadLetterConfig) return false;
|
|
2759
|
-
const maxDeliver = this.deadLetterConfig.maxDeliverByStream.get(msg.info.stream);
|
|
2760
|
-
if (maxDeliver === void 0 || maxDeliver <= 0) return false;
|
|
2761
|
-
return msg.info.deliveryCount >= maxDeliver;
|
|
2762
|
-
}
|
|
2763
|
-
/** Handle a dead letter: invoke callback, then term or nak based on result. */
|
|
2764
4176
|
/**
|
|
2765
|
-
*
|
|
2766
|
-
*
|
|
2767
|
-
*
|
|
2768
|
-
*
|
|
2769
|
-
* On success, terminates the message. On error, leaves it unacknowledged (nak)
|
|
2770
|
-
* so NATS can retry the delivery on the next cycle.
|
|
4177
|
+
* Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
|
|
4178
|
+
* success or `nak` on hook failure so NATS retries on the next delivery
|
|
4179
|
+
* cycle. Used when DLQ stream isn't configured, or when publishing to it
|
|
4180
|
+
* failed and we still have to surface the message somewhere observable.
|
|
2771
4181
|
*/
|
|
2772
4182
|
async fallbackToOnDeadLetterCallback(info, msg) {
|
|
2773
4183
|
if (!this.deadLetterConfig) {
|
|
@@ -2859,21 +4269,37 @@ var EventRouter = class {
|
|
|
2859
4269
|
streamSequence: msg.info.streamSequence,
|
|
2860
4270
|
timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
|
|
2861
4271
|
};
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
4272
|
+
await withDeadLetterSpan(
|
|
4273
|
+
{
|
|
4274
|
+
msg,
|
|
4275
|
+
// Pattern resolution mirrors event-routing: when a registered
|
|
4276
|
+
// pattern matches, surface it on the DLQ span so APM can filter
|
|
4277
|
+
// dead letters by handler without parsing the subject. Falls back
|
|
4278
|
+
// to the subject itself when no glob handler is in play.
|
|
4279
|
+
pattern: this.patternRegistry.getHandler(msg.subject) ? msg.subject : void 0,
|
|
4280
|
+
finalDeliveryCount: msg.info.deliveryCount,
|
|
4281
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
4282
|
+
serviceName: this.serviceName,
|
|
4283
|
+
endpoint: this.serverEndpoint
|
|
4284
|
+
},
|
|
4285
|
+
this.otel,
|
|
4286
|
+
async () => {
|
|
4287
|
+
this.eventBus.emit("deadLetter" /* DeadLetter */, info);
|
|
4288
|
+
if (!this.options?.dlq) {
|
|
4289
|
+
await this.fallbackToOnDeadLetterCallback(info, msg);
|
|
4290
|
+
} else {
|
|
4291
|
+
await this.publishToDlq(msg, info, error);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
);
|
|
2868
4295
|
}
|
|
2869
4296
|
};
|
|
2870
4297
|
|
|
2871
4298
|
// src/server/routing/rpc.router.ts
|
|
2872
|
-
import { Logger as
|
|
4299
|
+
import { Logger as Logger16 } from "@nestjs/common";
|
|
2873
4300
|
import { headers } from "@nats-io/transport-node";
|
|
2874
|
-
import { from as from3, mergeMap as mergeMap2 } from "rxjs";
|
|
2875
4301
|
var RpcRouter = class {
|
|
2876
|
-
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
4302
|
+
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap, options) {
|
|
2877
4303
|
this.messageProvider = messageProvider;
|
|
2878
4304
|
this.patternRegistry = patternRegistry;
|
|
2879
4305
|
this.connection = connection;
|
|
@@ -2883,13 +4309,26 @@ var RpcRouter = class {
|
|
|
2883
4309
|
this.ackWaitMap = ackWaitMap;
|
|
2884
4310
|
this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
|
|
2885
4311
|
this.concurrency = rpcOptions?.concurrency;
|
|
4312
|
+
if (options) {
|
|
4313
|
+
const derived = deriveOtelAttrs(options);
|
|
4314
|
+
this.otel = derived.otel;
|
|
4315
|
+
this.serviceName = derived.serviceName;
|
|
4316
|
+
this.serverEndpoint = derived.serverEndpoint;
|
|
4317
|
+
} else {
|
|
4318
|
+
this.otel = resolveOtelOptions({ enabled: false });
|
|
4319
|
+
this.serviceName = "";
|
|
4320
|
+
this.serverEndpoint = null;
|
|
4321
|
+
}
|
|
2886
4322
|
}
|
|
2887
|
-
logger = new
|
|
4323
|
+
logger = new Logger16("Jetstream:RpcRouter");
|
|
2888
4324
|
timeout;
|
|
2889
4325
|
concurrency;
|
|
2890
4326
|
resolvedAckExtensionInterval;
|
|
2891
4327
|
subscription = null;
|
|
2892
4328
|
cachedNc = null;
|
|
4329
|
+
otel;
|
|
4330
|
+
serviceName;
|
|
4331
|
+
serverEndpoint;
|
|
2893
4332
|
/** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
|
|
2894
4333
|
get ackExtensionInterval() {
|
|
2895
4334
|
if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
|
|
@@ -2902,99 +4341,213 @@ var RpcRouter = class {
|
|
|
2902
4341
|
/** Start routing command messages to handlers. */
|
|
2903
4342
|
async start() {
|
|
2904
4343
|
this.cachedNc = await this.connection.getConnection();
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
this.
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
const { headers: msgHeaders } = msg;
|
|
2922
|
-
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
2923
|
-
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
2924
|
-
if (!replyTo || !correlationId) {
|
|
2925
|
-
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
2926
|
-
this.logger.error(`Missing headers for RPC: ${msg.subject}`);
|
|
2927
|
-
return;
|
|
2928
|
-
}
|
|
2929
|
-
let data;
|
|
2930
|
-
try {
|
|
2931
|
-
data = this.codec.decode(msg.data);
|
|
2932
|
-
} catch (err) {
|
|
2933
|
-
msg.term("Decode error");
|
|
2934
|
-
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
2935
|
-
return;
|
|
2936
|
-
}
|
|
2937
|
-
this.eventBus.emitMessageRouted(msg.subject, "rpc" /* Rpc */);
|
|
2938
|
-
await this.executeHandler(handler, data, msg, replyTo, correlationId);
|
|
2939
|
-
} catch (err) {
|
|
2940
|
-
this.logger.error("Unexpected error in RPC router", err);
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
/** Execute handler, publish response, settle message. */
|
|
2944
|
-
async executeHandler(handler, data, msg, replyTo, correlationId) {
|
|
2945
|
-
const nc = this.cachedNc ?? await this.connection.getConnection();
|
|
2946
|
-
const ctx = new RpcContext([msg]);
|
|
2947
|
-
let settled = false;
|
|
2948
|
-
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
2949
|
-
const timeoutId = setTimeout(() => {
|
|
2950
|
-
if (settled) return;
|
|
2951
|
-
settled = true;
|
|
2952
|
-
stopAckExtension?.();
|
|
2953
|
-
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
2954
|
-
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
2955
|
-
msg.term("Handler timeout");
|
|
2956
|
-
}, this.timeout);
|
|
2957
|
-
try {
|
|
2958
|
-
const result = await unwrapResult(handler(data, ctx));
|
|
2959
|
-
if (settled) return;
|
|
2960
|
-
settled = true;
|
|
2961
|
-
clearTimeout(timeoutId);
|
|
2962
|
-
stopAckExtension?.();
|
|
2963
|
-
msg.ack();
|
|
4344
|
+
const nc = this.cachedNc;
|
|
4345
|
+
const patternRegistry = this.patternRegistry;
|
|
4346
|
+
const codec = this.codec;
|
|
4347
|
+
const eventBus = this.eventBus;
|
|
4348
|
+
const logger5 = this.logger;
|
|
4349
|
+
const timeout = this.timeout;
|
|
4350
|
+
const ackExtensionInterval = this.ackExtensionInterval;
|
|
4351
|
+
const hasAckExtension = ackExtensionInterval !== null && ackExtensionInterval > 0;
|
|
4352
|
+
const maxActive = this.concurrency ?? Number.POSITIVE_INFINITY;
|
|
4353
|
+
const otel = this.otel;
|
|
4354
|
+
const serviceName = this.serviceName;
|
|
4355
|
+
const serverEndpoint = this.serverEndpoint;
|
|
4356
|
+
const emitRpcTimeout = (subject, correlationId) => {
|
|
4357
|
+
eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
4358
|
+
};
|
|
4359
|
+
const publishReply = (replyTo, correlationId, payload) => {
|
|
2964
4360
|
try {
|
|
2965
4361
|
const hdrs = headers();
|
|
2966
4362
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2967
|
-
nc.publish(replyTo,
|
|
4363
|
+
nc.publish(replyTo, codec.encode(payload), { headers: hdrs });
|
|
2968
4364
|
} catch (publishErr) {
|
|
2969
|
-
|
|
4365
|
+
logger5.error(`Failed to publish RPC response`, publishErr);
|
|
2970
4366
|
}
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
settled = true;
|
|
2974
|
-
clearTimeout(timeoutId);
|
|
2975
|
-
stopAckExtension?.();
|
|
4367
|
+
};
|
|
4368
|
+
const publishErrorReply = (replyTo, correlationId, subject, err) => {
|
|
2976
4369
|
try {
|
|
2977
4370
|
const hdrs = headers();
|
|
2978
4371
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
2979
4372
|
hdrs.set("x-error" /* Error */, "true");
|
|
2980
|
-
nc.publish(replyTo,
|
|
4373
|
+
nc.publish(replyTo, codec.encode(serializeError(err)), { headers: hdrs });
|
|
2981
4374
|
} catch (encodeErr) {
|
|
2982
|
-
|
|
4375
|
+
logger5.error(`Failed to encode RPC error for ${subject}`, encodeErr);
|
|
2983
4376
|
}
|
|
2984
|
-
|
|
2985
|
-
|
|
4377
|
+
};
|
|
4378
|
+
const resolveCommand = (msg) => {
|
|
4379
|
+
const subject = msg.subject;
|
|
4380
|
+
try {
|
|
4381
|
+
const handler = patternRegistry.getHandler(subject);
|
|
4382
|
+
if (!handler) {
|
|
4383
|
+
msg.term(`No handler for RPC: ${subject}`);
|
|
4384
|
+
logger5.error(`No handler for RPC subject: ${subject}`);
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
const msgHeaders = msg.headers;
|
|
4388
|
+
const replyTo = msgHeaders?.get("x-reply-to" /* ReplyTo */);
|
|
4389
|
+
const correlationId = msgHeaders?.get("x-correlation-id" /* CorrelationId */);
|
|
4390
|
+
if (!replyTo || !correlationId) {
|
|
4391
|
+
msg.term("Missing required headers (reply-to or correlation-id)");
|
|
4392
|
+
logger5.error(`Missing headers for RPC: ${subject}`);
|
|
4393
|
+
return null;
|
|
4394
|
+
}
|
|
4395
|
+
let data;
|
|
4396
|
+
try {
|
|
4397
|
+
data = codec.decode(msg.data);
|
|
4398
|
+
} catch (err) {
|
|
4399
|
+
msg.term("Decode error");
|
|
4400
|
+
logger5.error(`Decode error for RPC ${subject}:`, err);
|
|
4401
|
+
return null;
|
|
4402
|
+
}
|
|
4403
|
+
eventBus.emitMessageRouted(subject, "rpc" /* Rpc */);
|
|
4404
|
+
return { handler, data, replyTo, correlationId };
|
|
4405
|
+
} catch (err) {
|
|
4406
|
+
logger5.error("Unexpected error in RPC router", err);
|
|
4407
|
+
try {
|
|
4408
|
+
msg.term("Unexpected router error");
|
|
4409
|
+
} catch (termErr) {
|
|
4410
|
+
logger5.error(`Failed to terminate RPC message ${subject}:`, termErr);
|
|
4411
|
+
}
|
|
4412
|
+
return null;
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4415
|
+
const handleSafe = (msg) => {
|
|
4416
|
+
const resolved = resolveCommand(msg);
|
|
4417
|
+
if (resolved === null) return void 0;
|
|
4418
|
+
const { handler, data, replyTo, correlationId } = resolved;
|
|
4419
|
+
const subject = msg.subject;
|
|
4420
|
+
const ctx = new RpcContext([msg]);
|
|
4421
|
+
const stopAckExtension = hasAckExtension ? startAckExtensionTimer(msg, ackExtensionInterval) : null;
|
|
4422
|
+
const reportHandlerError = (err) => {
|
|
4423
|
+
eventBus.emit(
|
|
4424
|
+
"error" /* Error */,
|
|
4425
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
4426
|
+
`rpc-handler:${subject}`
|
|
4427
|
+
);
|
|
4428
|
+
publishErrorReply(replyTo, correlationId, subject, err);
|
|
4429
|
+
msg.term(`Handler error: ${subject}`);
|
|
4430
|
+
};
|
|
4431
|
+
const abortController = new AbortController();
|
|
4432
|
+
let pending;
|
|
4433
|
+
try {
|
|
4434
|
+
pending = withConsumeSpan(
|
|
4435
|
+
{
|
|
4436
|
+
subject,
|
|
4437
|
+
msg,
|
|
4438
|
+
info: msg.info,
|
|
4439
|
+
kind: "rpc" /* Rpc */,
|
|
4440
|
+
payloadBytes: msg.data.length,
|
|
4441
|
+
handlerMetadata: { pattern: subject },
|
|
4442
|
+
serviceName,
|
|
4443
|
+
endpoint: serverEndpoint
|
|
4444
|
+
},
|
|
4445
|
+
otel,
|
|
4446
|
+
() => unwrapResult(handler(data, ctx)),
|
|
4447
|
+
{ signal: abortController.signal, timeoutLabel: "rpc.handler.timeout" }
|
|
4448
|
+
);
|
|
4449
|
+
} catch (err) {
|
|
4450
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4451
|
+
reportHandlerError(err);
|
|
4452
|
+
return void 0;
|
|
4453
|
+
}
|
|
4454
|
+
if (!isPromiseLike2(pending)) {
|
|
4455
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4456
|
+
msg.ack();
|
|
4457
|
+
publishReply(replyTo, correlationId, pending);
|
|
4458
|
+
return void 0;
|
|
4459
|
+
}
|
|
4460
|
+
let settled = false;
|
|
4461
|
+
const timeoutId = setTimeout(() => {
|
|
4462
|
+
if (settled) return;
|
|
4463
|
+
settled = true;
|
|
4464
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4465
|
+
abortController.abort();
|
|
4466
|
+
emitRpcTimeout(subject, correlationId);
|
|
4467
|
+
msg.term("Handler timeout");
|
|
4468
|
+
}, timeout);
|
|
4469
|
+
return pending.then(
|
|
4470
|
+
(result) => {
|
|
4471
|
+
if (settled) return;
|
|
4472
|
+
settled = true;
|
|
4473
|
+
clearTimeout(timeoutId);
|
|
4474
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4475
|
+
msg.ack();
|
|
4476
|
+
publishReply(replyTo, correlationId, result);
|
|
4477
|
+
},
|
|
4478
|
+
(err) => {
|
|
4479
|
+
if (settled) return;
|
|
4480
|
+
settled = true;
|
|
4481
|
+
clearTimeout(timeoutId);
|
|
4482
|
+
if (stopAckExtension !== null) stopAckExtension();
|
|
4483
|
+
reportHandlerError(err);
|
|
4484
|
+
}
|
|
4485
|
+
);
|
|
4486
|
+
};
|
|
4487
|
+
const backlogWarnThreshold = 1e3;
|
|
4488
|
+
let active = 0;
|
|
4489
|
+
let backlogWarned = false;
|
|
4490
|
+
const backlog = [];
|
|
4491
|
+
const onAsyncDone = () => {
|
|
4492
|
+
active--;
|
|
4493
|
+
drainBacklog();
|
|
4494
|
+
};
|
|
4495
|
+
const drainBacklog = () => {
|
|
4496
|
+
while (active < maxActive) {
|
|
4497
|
+
const next = backlog.shift();
|
|
4498
|
+
if (next === void 0) return;
|
|
4499
|
+
active++;
|
|
4500
|
+
const result = handleSafe(next);
|
|
4501
|
+
if (result !== void 0) {
|
|
4502
|
+
void result.finally(onAsyncDone);
|
|
4503
|
+
} else {
|
|
4504
|
+
active--;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
if (backlog.length < backlogWarnThreshold) backlogWarned = false;
|
|
4508
|
+
};
|
|
4509
|
+
this.subscription = this.messageProvider.commands$.subscribe({
|
|
4510
|
+
next: (msg) => {
|
|
4511
|
+
if (active >= maxActive) {
|
|
4512
|
+
backlog.push(msg);
|
|
4513
|
+
if (!backlogWarned && backlog.length >= backlogWarnThreshold) {
|
|
4514
|
+
backlogWarned = true;
|
|
4515
|
+
logger5.warn(
|
|
4516
|
+
`RPC backlog reached ${backlog.length} messages \u2014 consumer may be falling behind`
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
active++;
|
|
4522
|
+
const result = handleSafe(msg);
|
|
4523
|
+
if (result !== void 0) {
|
|
4524
|
+
void result.finally(onAsyncDone);
|
|
4525
|
+
} else {
|
|
4526
|
+
active--;
|
|
4527
|
+
if (backlog.length > 0) drainBacklog();
|
|
4528
|
+
}
|
|
4529
|
+
},
|
|
4530
|
+
error: (err) => {
|
|
4531
|
+
logger5.error("Stream error in RPC router", err);
|
|
4532
|
+
}
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
/** Stop routing and unsubscribe. */
|
|
4536
|
+
destroy() {
|
|
4537
|
+
this.subscription?.unsubscribe();
|
|
4538
|
+
this.subscription = null;
|
|
2986
4539
|
}
|
|
2987
4540
|
};
|
|
2988
4541
|
|
|
2989
4542
|
// src/shutdown/shutdown.manager.ts
|
|
2990
|
-
import { Logger as
|
|
4543
|
+
import { Logger as Logger17 } from "@nestjs/common";
|
|
2991
4544
|
var ShutdownManager = class {
|
|
2992
4545
|
constructor(connection, eventBus, timeout) {
|
|
2993
4546
|
this.connection = connection;
|
|
2994
4547
|
this.eventBus = eventBus;
|
|
2995
4548
|
this.timeout = timeout;
|
|
2996
4549
|
}
|
|
2997
|
-
logger = new
|
|
4550
|
+
logger = new Logger17("Jetstream:Shutdown");
|
|
2998
4551
|
shutdownPromise;
|
|
2999
4552
|
/**
|
|
3000
4553
|
* Execute the full shutdown sequence.
|
|
@@ -3034,9 +4587,6 @@ var JetstreamModule = class {
|
|
|
3034
4587
|
this.shutdownManager = shutdownManager;
|
|
3035
4588
|
this.strategy = strategy;
|
|
3036
4589
|
}
|
|
3037
|
-
// -------------------------------------------------------------------
|
|
3038
|
-
// forRoot — global module registration
|
|
3039
|
-
// -------------------------------------------------------------------
|
|
3040
4590
|
/**
|
|
3041
4591
|
* Register the JetStream transport globally.
|
|
3042
4592
|
*
|
|
@@ -3063,9 +4613,6 @@ var JetstreamModule = class {
|
|
|
3063
4613
|
]
|
|
3064
4614
|
};
|
|
3065
4615
|
}
|
|
3066
|
-
// -------------------------------------------------------------------
|
|
3067
|
-
// forRootAsync — async global module registration
|
|
3068
|
-
// -------------------------------------------------------------------
|
|
3069
4616
|
/**
|
|
3070
4617
|
* Register the JetStream transport globally with async configuration.
|
|
3071
4618
|
*
|
|
@@ -3094,9 +4641,6 @@ var JetstreamModule = class {
|
|
|
3094
4641
|
]
|
|
3095
4642
|
};
|
|
3096
4643
|
}
|
|
3097
|
-
// -------------------------------------------------------------------
|
|
3098
|
-
// forFeature — per-module client registration
|
|
3099
|
-
// -------------------------------------------------------------------
|
|
3100
4644
|
/**
|
|
3101
4645
|
* Register a lightweight client proxy for a target service.
|
|
3102
4646
|
*
|
|
@@ -3122,9 +4666,6 @@ var JetstreamModule = class {
|
|
|
3122
4666
|
exports: [clientToken]
|
|
3123
4667
|
};
|
|
3124
4668
|
}
|
|
3125
|
-
// -------------------------------------------------------------------
|
|
3126
|
-
// Provider factories
|
|
3127
|
-
// -------------------------------------------------------------------
|
|
3128
4669
|
static createCoreProviders(options) {
|
|
3129
4670
|
return [
|
|
3130
4671
|
{
|
|
@@ -3142,8 +4683,8 @@ var JetstreamModule = class {
|
|
|
3142
4683
|
provide: JETSTREAM_EVENT_BUS,
|
|
3143
4684
|
inject: [JETSTREAM_OPTIONS],
|
|
3144
4685
|
useFactory: (options) => {
|
|
3145
|
-
const
|
|
3146
|
-
return new EventBus(
|
|
4686
|
+
const logger5 = new Logger18("Jetstream:Module");
|
|
4687
|
+
return new EventBus(logger5, options.hooks);
|
|
3147
4688
|
}
|
|
3148
4689
|
},
|
|
3149
4690
|
// Codec — global encode/decode
|
|
@@ -3182,10 +4723,8 @@ var JetstreamModule = class {
|
|
|
3182
4723
|
);
|
|
3183
4724
|
}
|
|
3184
4725
|
},
|
|
3185
|
-
// ---------------------------------------------------------------
|
|
3186
4726
|
// Consumer infrastructure — only created when consumer !== false.
|
|
3187
4727
|
// Providers return null when consumer is disabled (publisher-only mode).
|
|
3188
|
-
// ---------------------------------------------------------------
|
|
3189
4728
|
// PatternRegistry — subject-to-handler mapping
|
|
3190
4729
|
{
|
|
3191
4730
|
provide: PatternRegistry,
|
|
@@ -3221,8 +4760,14 @@ var JetstreamModule = class {
|
|
|
3221
4760
|
// MessageProvider — pull-based message consumption
|
|
3222
4761
|
{
|
|
3223
4762
|
provide: MessageProvider,
|
|
3224
|
-
inject: [
|
|
3225
|
-
|
|
4763
|
+
inject: [
|
|
4764
|
+
JETSTREAM_OPTIONS,
|
|
4765
|
+
JETSTREAM_CONNECTION,
|
|
4766
|
+
JETSTREAM_EVENT_BUS,
|
|
4767
|
+
ConsumerProvider,
|
|
4768
|
+
StreamProvider
|
|
4769
|
+
],
|
|
4770
|
+
useFactory: (options, connection, eventBus, consumerProvider, streamProvider) => {
|
|
3226
4771
|
if (options.consumer === false) return null;
|
|
3227
4772
|
const consumeOptionsMap = /* @__PURE__ */ new Map();
|
|
3228
4773
|
if (options.events?.consume)
|
|
@@ -3232,10 +4777,22 @@ var JetstreamModule = class {
|
|
|
3232
4777
|
if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
|
|
3233
4778
|
consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
|
|
3234
4779
|
}
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
4780
|
+
const derived = deriveOtelAttrs(options);
|
|
4781
|
+
const { otel, serverEndpoint: otelEndpoint, serviceName: otelServiceName } = derived;
|
|
4782
|
+
const consumerRecoveryFn = consumerProvider && streamProvider ? async (kind) => withSelfHealingSpan(
|
|
4783
|
+
otel,
|
|
4784
|
+
{
|
|
4785
|
+
serviceName: otelServiceName,
|
|
4786
|
+
endpoint: otelEndpoint,
|
|
4787
|
+
consumer: consumerProvider.getConsumerName(kind),
|
|
4788
|
+
stream: streamProvider.getStreamName(kind),
|
|
4789
|
+
reason: "consumer not found"
|
|
4790
|
+
},
|
|
4791
|
+
async () => {
|
|
4792
|
+
const jsm = await connection.getJetStreamManager();
|
|
4793
|
+
return consumerProvider.recoverConsumer(jsm, kind);
|
|
4794
|
+
}
|
|
4795
|
+
) : void 0;
|
|
3239
4796
|
return new MessageProvider(connection, eventBus, consumeOptionsMap, consumerRecoveryFn);
|
|
3240
4797
|
}
|
|
3241
4798
|
},
|
|
@@ -3306,7 +4863,8 @@ var JetstreamModule = class {
|
|
|
3306
4863
|
codec,
|
|
3307
4864
|
eventBus,
|
|
3308
4865
|
rpcOptions,
|
|
3309
|
-
ackWaitMap
|
|
4866
|
+
ackWaitMap,
|
|
4867
|
+
options
|
|
3310
4868
|
);
|
|
3311
4869
|
}
|
|
3312
4870
|
},
|
|
@@ -3409,9 +4967,6 @@ var JetstreamModule = class {
|
|
|
3409
4967
|
}
|
|
3410
4968
|
];
|
|
3411
4969
|
}
|
|
3412
|
-
// -------------------------------------------------------------------
|
|
3413
|
-
// Lifecycle hooks
|
|
3414
|
-
// -------------------------------------------------------------------
|
|
3415
4970
|
/**
|
|
3416
4971
|
* Gracefully shut down the transport on application termination.
|
|
3417
4972
|
*/
|
|
@@ -3430,14 +4985,25 @@ JetstreamModule = __decorateClass([
|
|
|
3430
4985
|
__decorateParam(1, Inject(JetstreamStrategy))
|
|
3431
4986
|
], JetstreamModule);
|
|
3432
4987
|
export {
|
|
4988
|
+
ConsumeKind,
|
|
4989
|
+
DEFAULT_BROADCAST_CONSUMER_CONFIG,
|
|
4990
|
+
DEFAULT_BROADCAST_STREAM_CONFIG,
|
|
4991
|
+
DEFAULT_COMMAND_CONSUMER_CONFIG,
|
|
4992
|
+
DEFAULT_COMMAND_STREAM_CONFIG,
|
|
4993
|
+
DEFAULT_DLQ_STREAM_CONFIG,
|
|
4994
|
+
DEFAULT_EVENT_CONSUMER_CONFIG,
|
|
4995
|
+
DEFAULT_EVENT_STREAM_CONFIG,
|
|
4996
|
+
DEFAULT_JETSTREAM_RPC_TIMEOUT,
|
|
3433
4997
|
DEFAULT_METADATA_BUCKET,
|
|
3434
4998
|
DEFAULT_METADATA_HISTORY,
|
|
3435
4999
|
DEFAULT_METADATA_REPLICAS,
|
|
3436
5000
|
DEFAULT_METADATA_TTL,
|
|
3437
|
-
|
|
5001
|
+
DEFAULT_ORDERED_STREAM_CONFIG,
|
|
5002
|
+
DEFAULT_RPC_TIMEOUT,
|
|
5003
|
+
DEFAULT_SHUTDOWN_TIMEOUT,
|
|
5004
|
+
DEFAULT_TRACES,
|
|
3438
5005
|
JETSTREAM_CODEC,
|
|
3439
5006
|
JETSTREAM_CONNECTION,
|
|
3440
|
-
JETSTREAM_EVENT_BUS,
|
|
3441
5007
|
JETSTREAM_OPTIONS,
|
|
3442
5008
|
JetstreamClient,
|
|
3443
5009
|
JetstreamDlqHeader,
|
|
@@ -3447,12 +5013,18 @@ export {
|
|
|
3447
5013
|
JetstreamRecord,
|
|
3448
5014
|
JetstreamRecordBuilder,
|
|
3449
5015
|
JetstreamStrategy,
|
|
5016
|
+
JetstreamTrace,
|
|
3450
5017
|
JsonCodec,
|
|
3451
5018
|
MIN_METADATA_TTL,
|
|
3452
5019
|
MessageKind,
|
|
5020
|
+
MsgpackCodec,
|
|
5021
|
+
NatsErrorCode,
|
|
3453
5022
|
PatternPrefix,
|
|
5023
|
+
PublishKind,
|
|
5024
|
+
RESERVED_HEADERS,
|
|
3454
5025
|
RpcContext,
|
|
3455
5026
|
StreamKind,
|
|
5027
|
+
TRACER_NAME,
|
|
3456
5028
|
TransportEvent,
|
|
3457
5029
|
buildBroadcastSubject,
|
|
3458
5030
|
buildSubject,
|