🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@sentry/server-utils

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sentry/server-utils - npm Package Compare versions

Comparing version
10.60.0
to
10.61.0
+94
build/cjs/tracing-channel.js
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const core = require('@sentry/core');
const debugBuild = require('./debug-build.js');
const NOOP = () => {
};
function bindTracingChannelToSpan(channel, getSpan, opts) {
const handle = bindSpanToChannelStore(channel, getSpan);
const beforeSpanEnd = opts?.beforeSpanEnd;
const getErrorHint = (e) => {
if (typeof opts?.captureError === "function") {
return opts.captureError(e);
}
return {
mechanism: {
type: "auto.diagnostic_channels.bind_span",
handled: false
}
};
};
const subscribers = {
start: NOOP,
asyncStart: NOOP,
end(data) {
if ("error" in data || "result" in data) {
endBoundSpan(data, beforeSpanEnd);
}
},
error(data) {
const span = data._sentrySpan;
if (!span) {
return;
}
if (opts?.captureError) {
core.captureException(data.error, getErrorHint(data.error));
}
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });
},
asyncEnd(data) {
endBoundSpan(data, beforeSpanEnd);
}
};
handle.channel.subscribe(subscribers);
return {
channel: handle.channel,
unbind: () => {
handle.channel.unsubscribe(subscribers);
handle.unbind();
}
};
}
function bindSpanToChannelStore(channel, getSpan) {
const binding = core._INTERNAL_getTracingChannelBinding();
if (!binding) {
debugBuild.DEBUG_BUILD && core.debug.log("[TracingChannel] Could not access async context binding.");
return {
channel,
unbind: NOOP
};
}
const asyncLocalStorage = binding.asyncLocalStorage;
channel.start.bindStore(asyncLocalStorage, (data) => {
const span = getSpan(data);
if (!span) {
return asyncLocalStorage.getStore();
}
data._sentrySpan = span;
return binding.getStoreWithActiveSpan(span);
});
return {
channel,
unbind: () => {
channel.start.unbindStore(asyncLocalStorage);
}
};
}
function endBoundSpan(data, beforeSpanEnd) {
const span = data._sentrySpan;
if (!span) {
return;
}
beforeSpanEnd?.(span, data);
span.end();
}
function getErrorMessage(error) {
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
return error.message;
}
return String(error);
}
exports.bindTracingChannelToSpan = bindTracingChannelToSpan;
//# sourceMappingURL=tracing-channel.js.map
{"version":3,"file":"tracing-channel.js","sources":["../../src/tracing-channel.ts"],"sourcesContent":["import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';\nimport { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\n\nexport type TracingChannelPayloadWithSpan<TData extends object> = TData & {\n _sentrySpan?: Span;\n};\n\n/*\n * A type patch so that we don't have to handle all subscription types.\n */\nexport interface SentryTracingChannel<TData extends object = object> extends Omit<\n TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>,\n 'subscribe' | 'unsubscribe'\n> {\n subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n}\n\nexport interface TracingChannelLifeCycleOptions<TData extends object = object> {\n /**\n * Invoked with the span and the channel context object once the traced operation completes\n * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.\n */\n beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;\n\n /**\n * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.\n * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.\n * Set `true` for instrumentations that own the error boundary, (e.g: route handlers)\n * For database drivers, it is not recommended to set this at all.\n */\n captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);\n}\n\n/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */\nexport interface TracingChannelBindingHandle<TData extends object = object> {\n /**\n * The tracing channel with the span bound into async context.\n */\n channel: SentryTracingChannel<TData>;\n\n /**\n * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.\n * Idempotent, and a no-op when no async context binding was available.\n */\n unbind: () => void;\n}\n\nconst NOOP = (): void => {};\n\n/**\n * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context\n * for the traced operation and is ended when the operation completes.\n *\n * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is\n * tracked, and the active context is left untouched. Use it for events that ride the same channel\n * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent\n * loop's per-step events, where ending a freshly opened span would close the parent prematurely.\n */\nexport function bindTracingChannelToSpan<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n opts?: TracingChannelLifeCycleOptions<TData>,\n): TracingChannelBindingHandle<TData> {\n const handle = bindSpanToChannelStore(channel, getSpan);\n\n const beforeSpanEnd = opts?.beforeSpanEnd;\n const getErrorHint = (e: unknown): ExclusiveEventHintOrCaptureContext => {\n if (typeof opts?.captureError === 'function') {\n return opts.captureError(e);\n }\n\n return {\n mechanism: {\n type: 'auto.diagnostic_channels.bind_span',\n handled: false,\n },\n };\n };\n\n const subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>> = {\n start: NOOP,\n asyncStart: NOOP,\n end(data) {\n // The operation settled synchronously (returned or threw)\n // Presence checks because caller can return `undefined` result or throw a falsy value.\n if ('error' in data || 'result' in data) {\n endBoundSpan(data, beforeSpanEnd);\n }\n },\n error(data) {\n // No span was bound for this payload (`getSpan` returned undefined), so there is nothing to\n // annotate and no instrumentation that owns capturing this error.\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n\n if (opts?.captureError) {\n captureException(data.error, getErrorHint(data.error));\n }\n\n span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });\n },\n asyncEnd(data) {\n endBoundSpan(data, beforeSpanEnd);\n },\n };\n\n handle.channel.subscribe(subscribers);\n\n return {\n channel: handle.channel,\n unbind: () => {\n handle.channel.unsubscribe(subscribers);\n handle.unbind();\n },\n };\n}\n\n/**\n * Bind a span into the channel's async context so it becomes active for the traced operation,\n * without managing its lifecycle. The primitive behind {@link bindTracingChannelToSpan}, which\n * layers span-ending and error handling on top.\n *\n * `getSpan` may return `undefined` to leave the active context untouched for that payload.\n */\nfunction bindSpanToChannelStore<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n): TracingChannelBindingHandle<TData> {\n // Grabs the tracing channel binding defined by the AsyncContext strategy implementation\n const binding = _INTERNAL_getTracingChannelBinding();\n\n // If no binding, then either the implementer doesn't support tracing channels or there is no active strategy\n // Failure mode here means we would still access the channel and potentially subscribe to it, but parenting will be off.\n if (!binding) {\n DEBUG_BUILD && debug.log('[TracingChannel] Could not access async context binding.');\n\n return {\n channel,\n unbind: NOOP,\n };\n }\n\n // Grab the ALS instance, we don't really care what is in it as long as the AsyncContext strategy can use its value to figure out parenting.\n const asyncLocalStorage = binding.asyncLocalStorage as AsyncLocalStorage<TData>;\n\n // bindStore activates the ALS for the traced call; any getStore() inside it returns the value bound for that context.\n // 1. Produce: getStoreWithActiveSpan(span) clones the current scope, plants the span via _INTERNAL_setSpanForScope, and returns { scope, isolationScope }, the active context carrying our span.\n // 2. Bind: the courier hands that opaque value to channel.start.bindStore(asyncLocalStorage, producer), which runs the traced op inside asyncLocalStorage.run(value, …); it never inspects the value.\n // 3. Read: inside the op, Sentry's scope machinery calls getScopes() → asyncStorage.getStore() on that same ALS, so getCurrentScope/getIsolationScope/getActiveSpan resolve to the scope carrying our span.\n // 4. Nest: any child span started in the traced op parents to that active span.\n channel.start.bindStore(asyncLocalStorage, (data: TracingChannelPayloadWithSpan<TData>) => {\n const span = getSpan(data);\n if (!span) {\n // Leave the active context untouched so nested operations keep parenting to the enclosing span.\n return asyncLocalStorage.getStore() as TData;\n }\n data._sentrySpan = span;\n\n return binding.getStoreWithActiveSpan(span) as TData;\n });\n\n return {\n channel,\n unbind: () => {\n // Removes the store\n channel.start.unbindStore(asyncLocalStorage);\n },\n };\n}\n\nfunction endBoundSpan<TData extends object>(\n data: TracingChannelPayloadWithSpan<TData>,\n beforeSpanEnd: TracingChannelLifeCycleOptions<TData>['beforeSpanEnd'],\n): void {\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n beforeSpanEnd?.(span, data);\n span.end();\n}\n\n/** Best-effort short message for a span status: an error-like's `message`, otherwise its string form. */\nfunction getErrorMessage(error: unknown): string {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n"],"names":["captureException","SPAN_STATUS_ERROR","_INTERNAL_getTracingChannelBinding","DEBUG_BUILD","debug"],"mappings":"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"}
import { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core';
import { DEBUG_BUILD } from './debug-build.js';
const NOOP = () => {
};
function bindTracingChannelToSpan(channel, getSpan, opts) {
const handle = bindSpanToChannelStore(channel, getSpan);
const beforeSpanEnd = opts?.beforeSpanEnd;
const getErrorHint = (e) => {
if (typeof opts?.captureError === "function") {
return opts.captureError(e);
}
return {
mechanism: {
type: "auto.diagnostic_channels.bind_span",
handled: false
}
};
};
const subscribers = {
start: NOOP,
asyncStart: NOOP,
end(data) {
if ("error" in data || "result" in data) {
endBoundSpan(data, beforeSpanEnd);
}
},
error(data) {
const span = data._sentrySpan;
if (!span) {
return;
}
if (opts?.captureError) {
captureException(data.error, getErrorHint(data.error));
}
span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });
},
asyncEnd(data) {
endBoundSpan(data, beforeSpanEnd);
}
};
handle.channel.subscribe(subscribers);
return {
channel: handle.channel,
unbind: () => {
handle.channel.unsubscribe(subscribers);
handle.unbind();
}
};
}
function bindSpanToChannelStore(channel, getSpan) {
const binding = _INTERNAL_getTracingChannelBinding();
if (!binding) {
DEBUG_BUILD && debug.log("[TracingChannel] Could not access async context binding.");
return {
channel,
unbind: NOOP
};
}
const asyncLocalStorage = binding.asyncLocalStorage;
channel.start.bindStore(asyncLocalStorage, (data) => {
const span = getSpan(data);
if (!span) {
return asyncLocalStorage.getStore();
}
data._sentrySpan = span;
return binding.getStoreWithActiveSpan(span);
});
return {
channel,
unbind: () => {
channel.start.unbindStore(asyncLocalStorage);
}
};
}
function endBoundSpan(data, beforeSpanEnd) {
const span = data._sentrySpan;
if (!span) {
return;
}
beforeSpanEnd?.(span, data);
span.end();
}
function getErrorMessage(error) {
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
return error.message;
}
return String(error);
}
export { bindTracingChannelToSpan };
//# sourceMappingURL=tracing-channel.js.map
{"version":3,"file":"tracing-channel.js","sources":["../../src/tracing-channel.ts"],"sourcesContent":["import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';\nimport type { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';\nimport { _INTERNAL_getTracingChannelBinding, debug, captureException, SPAN_STATUS_ERROR } from '@sentry/core';\nimport { DEBUG_BUILD } from './debug-build';\n\nexport type TracingChannelPayloadWithSpan<TData extends object> = TData & {\n _sentrySpan?: Span;\n};\n\n/*\n * A type patch so that we don't have to handle all subscription types.\n */\nexport interface SentryTracingChannel<TData extends object = object> extends Omit<\n TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>,\n 'subscribe' | 'unsubscribe'\n> {\n subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;\n}\n\nexport interface TracingChannelLifeCycleOptions<TData extends object = object> {\n /**\n * Invoked with the span and the channel context object once the traced operation completes\n * Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.\n */\n beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;\n\n /**\n * Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.\n * You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.\n * Set `true` for instrumentations that own the error boundary, (e.g: route handlers)\n * For database drivers, it is not recommended to set this at all.\n */\n captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);\n}\n\n/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */\nexport interface TracingChannelBindingHandle<TData extends object = object> {\n /**\n * The tracing channel with the span bound into async context.\n */\n channel: SentryTracingChannel<TData>;\n\n /**\n * Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.\n * Idempotent, and a no-op when no async context binding was available.\n */\n unbind: () => void;\n}\n\nconst NOOP = (): void => {};\n\n/**\n * Bind a span and its lifecycle to a tracing channel so the span becomes the active async context\n * for the traced operation and is ended when the operation completes.\n *\n * `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is\n * tracked, and the active context is left untouched. Use it for events that ride the same channel\n * but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent\n * loop's per-step events, where ending a freshly opened span would close the parent prematurely.\n */\nexport function bindTracingChannelToSpan<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n opts?: TracingChannelLifeCycleOptions<TData>,\n): TracingChannelBindingHandle<TData> {\n const handle = bindSpanToChannelStore(channel, getSpan);\n\n const beforeSpanEnd = opts?.beforeSpanEnd;\n const getErrorHint = (e: unknown): ExclusiveEventHintOrCaptureContext => {\n if (typeof opts?.captureError === 'function') {\n return opts.captureError(e);\n }\n\n return {\n mechanism: {\n type: 'auto.diagnostic_channels.bind_span',\n handled: false,\n },\n };\n };\n\n const subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>> = {\n start: NOOP,\n asyncStart: NOOP,\n end(data) {\n // The operation settled synchronously (returned or threw)\n // Presence checks because caller can return `undefined` result or throw a falsy value.\n if ('error' in data || 'result' in data) {\n endBoundSpan(data, beforeSpanEnd);\n }\n },\n error(data) {\n // No span was bound for this payload (`getSpan` returned undefined), so there is nothing to\n // annotate and no instrumentation that owns capturing this error.\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n\n if (opts?.captureError) {\n captureException(data.error, getErrorHint(data.error));\n }\n\n span.setStatus({ code: SPAN_STATUS_ERROR, message: getErrorMessage(data.error) });\n },\n asyncEnd(data) {\n endBoundSpan(data, beforeSpanEnd);\n },\n };\n\n handle.channel.subscribe(subscribers);\n\n return {\n channel: handle.channel,\n unbind: () => {\n handle.channel.unsubscribe(subscribers);\n handle.unbind();\n },\n };\n}\n\n/**\n * Bind a span into the channel's async context so it becomes active for the traced operation,\n * without managing its lifecycle. The primitive behind {@link bindTracingChannelToSpan}, which\n * layers span-ending and error handling on top.\n *\n * `getSpan` may return `undefined` to leave the active context untouched for that payload.\n */\nfunction bindSpanToChannelStore<TData extends object>(\n channel: TracingChannel<TData, TData>,\n getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined,\n): TracingChannelBindingHandle<TData> {\n // Grabs the tracing channel binding defined by the AsyncContext strategy implementation\n const binding = _INTERNAL_getTracingChannelBinding();\n\n // If no binding, then either the implementer doesn't support tracing channels or there is no active strategy\n // Failure mode here means we would still access the channel and potentially subscribe to it, but parenting will be off.\n if (!binding) {\n DEBUG_BUILD && debug.log('[TracingChannel] Could not access async context binding.');\n\n return {\n channel,\n unbind: NOOP,\n };\n }\n\n // Grab the ALS instance, we don't really care what is in it as long as the AsyncContext strategy can use its value to figure out parenting.\n const asyncLocalStorage = binding.asyncLocalStorage as AsyncLocalStorage<TData>;\n\n // bindStore activates the ALS for the traced call; any getStore() inside it returns the value bound for that context.\n // 1. Produce: getStoreWithActiveSpan(span) clones the current scope, plants the span via _INTERNAL_setSpanForScope, and returns { scope, isolationScope }, the active context carrying our span.\n // 2. Bind: the courier hands that opaque value to channel.start.bindStore(asyncLocalStorage, producer), which runs the traced op inside asyncLocalStorage.run(value, …); it never inspects the value.\n // 3. Read: inside the op, Sentry's scope machinery calls getScopes() → asyncStorage.getStore() on that same ALS, so getCurrentScope/getIsolationScope/getActiveSpan resolve to the scope carrying our span.\n // 4. Nest: any child span started in the traced op parents to that active span.\n channel.start.bindStore(asyncLocalStorage, (data: TracingChannelPayloadWithSpan<TData>) => {\n const span = getSpan(data);\n if (!span) {\n // Leave the active context untouched so nested operations keep parenting to the enclosing span.\n return asyncLocalStorage.getStore() as TData;\n }\n data._sentrySpan = span;\n\n return binding.getStoreWithActiveSpan(span) as TData;\n });\n\n return {\n channel,\n unbind: () => {\n // Removes the store\n channel.start.unbindStore(asyncLocalStorage);\n },\n };\n}\n\nfunction endBoundSpan<TData extends object>(\n data: TracingChannelPayloadWithSpan<TData>,\n beforeSpanEnd: TracingChannelLifeCycleOptions<TData>['beforeSpanEnd'],\n): void {\n const span = data._sentrySpan;\n if (!span) {\n return;\n }\n beforeSpanEnd?.(span, data);\n span.end();\n}\n\n/** Best-effort short message for a span status: an error-like's `message`, otherwise its string form. */\nfunction getErrorMessage(error: unknown): string {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message;\n }\n return String(error);\n}\n"],"names":[],"mappings":"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"}
import { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';
import { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';
export type TracingChannelPayloadWithSpan<TData extends object> = TData & {
_sentrySpan?: Span;
};
export interface SentryTracingChannel<TData extends object = object> extends Pick<TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, Exclude<keyof TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, 'subscribe' | 'unsubscribe'>> {
subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;
unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;
}
export interface TracingChannelLifeCycleOptions<TData extends object = object> {
/**
* Invoked with the span and the channel context object once the traced operation completes
* Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.
*/
beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;
/**
* Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.
* You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.
* Set `true` for instrumentations that own the error boundary, (e.g: route handlers)
* For database drivers, it is not recommended to set this at all.
*/
captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);
}
/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */
export interface TracingChannelBindingHandle<TData extends object = object> {
/**
* The tracing channel with the span bound into async context.
*/
channel: SentryTracingChannel<TData>;
/**
* Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.
* Idempotent, and a no-op when no async context binding was available.
*/
unbind: () => void;
}
/**
* Bind a span and its lifecycle to a tracing channel so the span becomes the active async context
* for the traced operation and is ended when the operation completes.
*
* `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is
* tracked, and the active context is left untouched. Use it for events that ride the same channel
* but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent
* loop's per-step events, where ending a freshly opened span would close the parent prematurely.
*/
export declare function bindTracingChannelToSpan<TData extends object>(channel: TracingChannel<TData, TData>, getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined, opts?: TracingChannelLifeCycleOptions<TData>): TracingChannelBindingHandle<TData>;
//# sourceMappingURL=tracing-channel.d.ts.map
import type { TracingChannel, TracingChannelSubscribers } from 'node:diagnostics_channel';
import type { ExclusiveEventHintOrCaptureContext, Span } from '@sentry/core';
export type TracingChannelPayloadWithSpan<TData extends object> = TData & {
_sentrySpan?: Span;
};
export interface SentryTracingChannel<TData extends object = object> extends Omit<TracingChannel<TData, TracingChannelPayloadWithSpan<TData>>, 'subscribe' | 'unsubscribe'> {
subscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;
unsubscribe(subscribers: Partial<TracingChannelSubscribers<TracingChannelPayloadWithSpan<TData>>>): void;
}
export interface TracingChannelLifeCycleOptions<TData extends object = object> {
/**
* Invoked with the span and the channel context object once the traced operation completes
* Use it to enrich the span from the result/error (branch on `'error' in data` / `'result' in data`) or to run cleanup.
*/
beforeSpanEnd?: (span: Span, data: TracingChannelPayloadWithSpan<TData>) => void;
/**
* Whether a thrown error is captured as a Sentry event. The span is always marked with error status regardless. Defaults to `false`.
* You can alternatively pass a function that sets the ExclusiveEventHintOrCaptureContext on the captured error.
* Set `true` for instrumentations that own the error boundary, (e.g: route handlers)
* For database drivers, it is not recommended to set this at all.
*/
captureError?: boolean | ((e: unknown) => ExclusiveEventHintOrCaptureContext);
}
/** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */
export interface TracingChannelBindingHandle<TData extends object = object> {
/**
* The tracing channel with the span bound into async context.
*/
channel: SentryTracingChannel<TData>;
/**
* Tears down the binding: unsubscribes lifecycle handlers, when present, and unbinds the start store.
* Idempotent, and a no-op when no async context binding was available.
*/
unbind: () => void;
}
/**
* Bind a span and its lifecycle to a tracing channel so the span becomes the active async context
* for the traced operation and is ended when the operation completes.
*
* `getSpan` may return `undefined` to opt a payload out entirely: nothing is bound, no span is
* tracked, and the active context is left untouched. Use it for events that ride the same channel
* but should reuse the enclosing span instead of opening (and ending) their own — e.g. an agent
* loop's per-step events, where ending a freshly opened span would close the parent prematurely.
*/
export declare function bindTracingChannelToSpan<TData extends object>(channel: TracingChannel<TData, TData>, getSpan: (data: TracingChannelPayloadWithSpan<TData>) => Span | undefined, opts?: TracingChannelLifeCycleOptions<TData>): TracingChannelBindingHandle<TData>;
//# sourceMappingURL=tracing-channel.d.ts.map
{"version":3,"file":"tracing-channel.d.ts","sourceRoot":"","sources":["../../src/tracing-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,KAAK,EAAE,kCAAkC,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAI7E,MAAM,MAAM,6BAA6B,CAAC,KAAK,SAAS,MAAM,IAAI,KAAK,GAAG;IACxE,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB,CAAC;AAKF,MAAM,WAAW,oBAAoB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,IAAI,CAC/E,cAAc,CAAC,KAAK,EAAE,6BAA6B,CAAC,KAAK,CAAC,CAAC,EAC3D,WAAW,GAAG,aAAa,CAC5B;IACC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACvG,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,yBAAyB,CAAC,6BAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;CAC1G;AAED,MAAM,WAAW,8BAA8B,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC3E;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,6BAA6B,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAEjF;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,kCAAkC,CAAC,CAAC;CAC/E;AAED,8FAA8F;AAC9F,MAAM,WAAW,2BAA2B,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACxE;;OAEG;IACH,OAAO,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAErC;;;OAGG;IACH,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAID;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,MAAM,EAC3D,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,EACrC,OAAO,EAAE,CAAC,IAAI,EAAE,6BAA6B,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,SAAS,EACzE,IAAI,CAAC,EAAE,8BAA8B,CAAC,KAAK,CAAC,GAC3C,2BAA2B,CAAC,KAAK,CAAC,CAuDpC"}
+2
-0
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const redisDcSubscriber = require('./redis/redis-dc-subscriber.js');
const tracingChannel = require('./tracing-channel.js');

@@ -13,2 +14,3 @@

exports.subscribeRedisDiagnosticChannels = redisDcSubscriber.subscribeRedisDiagnosticChannels;
exports.bindTracingChannelToSpan = tracingChannel.bindTracingChannelToSpan;
//# sourceMappingURL=index.js.map
+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}

@@ -23,2 +23,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });

const spans = /* @__PURE__ */ new WeakMap();
const parentScopes = /* @__PURE__ */ new WeakMap();
queryCh.subscribe({

@@ -46,4 +47,5 @@ start(rawCtx) {

spans.set(rawCtx, span);
const parentSpan = core.getActiveSpan();
if (parentSpan && ctx.arguments.length > 0) {
const scope = core.getCurrentScope();
parentScopes.set(rawCtx, scope);
if (ctx.arguments.length > 0) {
const cbIdx = ctx.arguments.length - 1;

@@ -54,3 +56,3 @@ const orchestrionWrappedCb = ctx.arguments[cbIdx];

ctx.arguments[cbIdx] = function(...args) {
return core.withActiveSpan(parentSpan, () => wrapped.apply(this, args));
return core.withScope(scope, () => wrapped.apply(this, args));
};

@@ -70,2 +72,6 @@ }

if (!span) return;
const parentScope = parentScopes.get(rawCtx);
if (parentScope) {
core.bindScopeToEmitter(result, parentScope);
}
result.on("error", (err) => {

@@ -102,2 +108,3 @@ span.setStatus({

spans.delete(rawCtx);
parentScopes.delete(rawCtx);
}

@@ -104,0 +111,0 @@ }

@@ -1,1 +0,1 @@

{"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Span } from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getActiveSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withActiveSpan,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array the wrapper passes to the wrapped function:\n * orchestrion splices the user's callback out and inserts its own wrapper at\n * the same index before publishing `start`. We mutate that last entry again in\n * our `start` hook so the callback (and any nested `connection.query(...)`)\n * runs inside `withActiveSpan(parent, …)` — mysql v2 loses ALS state when it\n * dispatches callbacks from its socket handler, which would otherwise cause\n * nested queries to begin a fresh root trace.\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Each `context` object is shared across start/end/asyncStart/asyncEnd/error\n // for one call (orchestrion creates one per invocation). We key the span\n // off the same identity. WeakMap so we don't leak if a path never reaches\n // asyncEnd for some reason.\n const spans = new WeakMap<object, Span>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // We end the span on `asyncEnd` for the two callback paths (so the span\n // covers the full network round-trip + callback duration). For the\n // sync-throw path, `end` finishes the span because `ctx.error` is set\n // there. For the streamable no-callback path, `end` finishes by\n // attaching `'end'`/`'error'` listeners to `ctx.result` (the returned\n // `Query` emitter).\n //\n // The discriminator between \"end fired before any error\" and \"end fired\n // after a sync throw\" is whether `ctx.error` is set when `end` runs —\n // orchestrion populates it before publishing `error`. The discriminator\n // between callback and no-callback is whether `ctx.result` is set — only\n // the `wrapPromise` (no-callback) path stores it.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Restore the Sentry/OTel context across mysql's internal callback\n // dispatch. The orchestrion transform has already spliced the user's\n // callback out of `ctx.arguments` and put its own wrapper\n // (`__apm$wrappedCb`) at the same index. mysql v2 drains callbacks\n // from a socket data handler — by the time the response arrives, the\n // AsyncLocalStorage store backing `getActiveSpan()` no longer\n // reflects the caller's context. We re-wrap orchestrion's wrapper so\n // the user's callback (and any nested `connection.query(...)` inside\n // it) runs with the parent span active again.\n //\n // This must happen at `start` (we're synchronously inside the\n // caller's `connection.query` call, so OTel context is still\n // correct). `asyncStart`/`asyncEnd` fire from the same lost context\n // as the callback itself, so they're too late.\n const parentSpan = getActiveSpan();\n if (parentSpan && ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withActiveSpan(parentSpan, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // TODO: streaming spans aren't finished on `connection.destroy()` —\n // mysql guarantees no further events/callbacks for a destroyed\n // connection, so neither `'end'` nor `'error'` fires and the span\n // never ends (it's dropped, never reported). Closing this gap needs\n // connection-level lifecycle hooks, which the per-query channel\n // context doesn't expose here. The `WeakMap` still prevents a leak.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":["DEBUG_BUILD","debug","CHANNELS","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","getActiveSpan","withActiveSpan","SPAN_STATUS_ERROR","defineIntegration"],"mappings":"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"}
{"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Scope, Span } from '@sentry/core';\nimport {\n bindScopeToEmitter,\n debug,\n defineIntegration,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withScope,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array passed to the wrapped function: orchestrion\n * splices the user's callback out and inserts its own wrapper at the same index\n * before publishing `start`. The `start` hook re-wraps that entry to restore the\n * caller's scope across mysql's async callback dispatch (see below).\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Orchestrion creates one `context` object per call, shared across all\n // lifecycle hooks. We key both maps off that identity; `WeakMap` so an\n // unfinished path can't leak its entries.\n const spans = new WeakMap<object, Span>();\n // The scope active when the query was issued, consumed in `end` to bind\n // the streamed `Query` emitter's listeners to it.\n const parentScopes = new WeakMap<object, Scope>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // Where the span closes depends on the path: `asyncEnd` for callbacks (so\n // it spans the full round-trip + callback), or `end` for the sync-throw\n // and streamable paths. The `end` hook tells those apart via `ctx.error`\n // / `ctx.result` — see there.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Capture the scope while we're still synchronously inside the\n // caller's `connection.query` call. mysql v2 drains callbacks and\n // emits streamed-query events from its socket data handler, where the\n // AsyncLocalStorage store backing the active span no longer reflects\n // the caller's context — and `asyncStart`/`asyncEnd` fire from that\n // same lost context, so capturing has to happen now.\n const scope = getCurrentScope();\n parentScopes.set(rawCtx, scope);\n\n // Callback path: orchestrion has spliced the user's callback out of\n // `ctx.arguments` and put its own wrapper (`__apm$wrappedCb`) at the\n // same index. Re-wrap it so the callback — and any nested\n // `connection.query(...)` — runs with the captured scope active.\n if (ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withScope(scope, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // Note: a streamed span never finishes if the connection is destroyed\n // mid-flight — mysql then emits neither `'end'` nor `'error'`, so the\n // span is dropped (the `WeakMap` still prevents a leak). Closing this\n // needs connection-level hooks the per-query context doesn't expose.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n\n // Bind the captured scope to the streamed `Query` emitter: its\n // `'end'`/`'error'`/`'fields'`/… events fire from mysql's socket\n // handler with the caller's context lost, so without this a span\n // started in a user's stream listener would begin a fresh root trace\n // instead of nesting under the parent. `bindScopeToEmitter` patches\n // `on`/`addListener`/… so listeners added after `query()` returns\n // inherit the scope (like OTel's `context.bind`).\n const parentScope = parentScopes.get(rawCtx);\n if (parentScope) {\n bindScopeToEmitter(result, parentScope);\n }\n\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n parentScopes.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":["DEBUG_BUILD","debug","CHANNELS","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","getCurrentScope","withScope","bindScopeToEmitter","SPAN_STATUS_ERROR","defineIntegration"],"mappings":"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"}

@@ -6,2 +6,3 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });

const debugBuild = require('../debug-build.js');
const tracingChannel = require('../tracing-channel.js');

@@ -15,6 +16,5 @@ const REDIS_DC_CHANNEL_COMMAND = "node-redis:command";

const DB_SYSTEM_NAME_VALUE_REDIS = "redis";
const NOOP = () => {
};
let subscribed = false;
let currentResponseHook;
let activeUnbinds = [];
function subscribeRedisDiagnosticChannels(tracingChannel, responseHook) {

@@ -25,11 +25,16 @@ currentResponseHook = responseHook;

try {
setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1));
setupBatchChannel(
tracingChannel,
REDIS_DC_CHANNEL_BATCH,
(data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI"
activeUnbinds.push(
setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)),
setupBatchChannel(
tracingChannel,
REDIS_DC_CHANNEL_BATCH,
(data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI"
),
setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),
// ioredis: args already exclude the command name; no slicing needed. And
// ioredis has no separate batch channel — pipeline/MULTI metadata rides
// on the per-command payload via `batchMode`/`batchSize`.
setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args),
setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT)
);
setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);
setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args);
setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);
} catch {

@@ -39,8 +44,9 @@ debugBuild.DEBUG_BUILD && core.debug.log("Redis node:diagnostics_channel subscription failed.");

}
function setupCommandChannel(tracingChannel, channelName, getCommandArgs) {
const channel = tracingChannel(channelName, (data) => {
const args = getCommandArgs(data);
const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command;
return core.startSpanManual(
{
function setupCommandChannel(tracingChannel$1, channelName, getCommandArgs) {
return tracingChannel.bindTracingChannelToSpan(
tracingChannel$1(channelName),
(data) => {
const args = getCommandArgs(data);
const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command;
return core.startInactiveSpan({
name: `redis-${data.command}`,

@@ -55,30 +61,20 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
const span = data._sentrySpan;
if (!span || data.error) return;
runResponseHook(span, data.command, getCommandArgs(data), data.result);
span.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message });
{
// Command failures are surfaced to (and usually handled by) the caller; only annotate the
// span so we don't emit a duplicate error event for every failed command.
captureError: false,
beforeSpanEnd(span, data) {
if ("error" in data) return;
runResponseHook(span, data.command, getCommandArgs(data), data.result);
}
span.end();
}
});
).unbind;
}
function setupBatchChannel(tracingChannel, channelName, getOperationName) {
const channel = tracingChannel(channelName, (data) => {
return core.startSpanManual(
{
function setupBatchChannel(tracingChannel$1, channelName, getOperationName) {
return tracingChannel.bindTracingChannelToSpan(
tracingChannel$1(channelName),
(data) => {
return core.startInactiveSpan({
name: getOperationName(data),

@@ -95,27 +91,12 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
if (!data.error) data._sentrySpan?.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message });
}
span.end();
}
});
{ captureError: false }
).unbind;
}
function setupConnectChannel(tracingChannel, channelName) {
const channel = tracingChannel(channelName, (data) => {
return core.startSpanManual(
{
function setupConnectChannel(tracingChannel$1, channelName) {
return tracingChannel.bindTracingChannelToSpan(
tracingChannel$1(channelName),
(data) => {
return core.startInactiveSpan({
name: "redis-connect",

@@ -129,22 +110,6 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
if (!data.error) data._sentrySpan?.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: data.error.message });
}
span.end();
}
});
{ captureError: false }
).unbind;
}

@@ -151,0 +116,0 @@ function runResponseHook(span, command, args, result) {

@@ -1,1 +0,1 @@

{"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startSpanManual,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\nconst NOOP = (): void => {};\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Payload type observed by tracing-channel subscribers — the channel payload\n * with `_sentrySpan` stamped on it by the start handler so async/error\n * handlers downstream can read it back.\n */\nexport type RedisTracingChannelContextWithSpan<T> = T & { _sentrySpan?: Span };\n\n/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */\nexport interface RedisTracingChannelSubscribers<T> {\n start: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;\n end: (data: RedisTracingChannelContextWithSpan<T>) => void;\n error: (data: RedisTracingChannelContextWithSpan<T>) => void;\n}\n\n/** Minimal tracing-channel surface the subscriber depends on. */\nexport interface RedisTracingChannel<T extends object> {\n subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;\n}\n\n/**\n * Platform-provided factory that returns a tracing channel for the given\n * channel name. Implementations are responsible for ensuring that, when the\n * channel's `start` event fires, the span returned by `transformStart(data)`\n * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`\n * handlers can read it.\n *\n * - Node passes `@sentry/opentelemetry/tracing-channel` which uses\n * `bindStore` to also propagate the span as the active OTel context.\n * - Deno (and other non-OTel runtimes) pass a portable wrapper around\n * `node:diagnostics_channel.tracingChannel` that just stamps\n * `data._sentrySpan` in `start` without `bindStore`.\n */\nexport type RedisTracingChannelFactory = <T extends object>(\n name: string,\n transformStart: (data: T) => Span,\n) => RedisTracingChannel<T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1));\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n );\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);\n\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args);\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): void {\n const channel = tracingChannel<T>(channelName, data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startSpanManual(\n {\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n const span = data._sentrySpan;\n // Only end here if the error handler isn't going to.\n if (!span || data.error) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n span.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): void {\n const channel = tracingChannel<RedisBatchData>(channelName, data => {\n return startSpanManual(\n {\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): void {\n const channel = tracingChannel<RedisConnectData>(channelName, data => {\n return startSpanManual(\n {\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":["DEBUG_BUILD","debug","startSpanManual","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_OP","DB_SYSTEM_NAME","DB_QUERY_TEXT","SERVER_ADDRESS","SERVER_PORT","SPAN_STATUS_ERROR","DB_OPERATION_BATCH_SIZE"],"mappings":"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"}
{"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import type { TracingChannel } from 'node:diagnostics_channel';\nimport {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport { debug, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { bindTracingChannelToSpan } from '../tracing-channel';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Platform-provided factory that creates a native tracing channel for the given name. The\n * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,\n * which propagates the active span through the runtime's async context.\n *\n * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.\n */\nexport type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\nlet activeUnbinds: Array<() => void> = [];\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n activeUnbinds.push(\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1)),\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n ),\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args),\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT),\n );\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<T>(channelName),\n data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startInactiveSpan({\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n {\n // Command failures are surfaced to (and usually handled by) the caller; only annotate the\n // span so we don't emit a duplicate error event for every failed command.\n captureError: false,\n beforeSpanEnd(span, data) {\n if ('error' in data) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n },\n },\n ).unbind;\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisBatchData>(channelName),\n data => {\n return startInactiveSpan({\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisConnectData>(channelName),\n data => {\n return startInactiveSpan({\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: detach all channel bindings and reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n activeUnbinds.forEach(unbind => unbind());\n activeUnbinds = [];\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":["DEBUG_BUILD","debug","tracingChannel","bindTracingChannelToSpan","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","SEMANTIC_ATTRIBUTE_SENTRY_OP","DB_SYSTEM_NAME","DB_QUERY_TEXT","SERVER_ADDRESS","SERVER_PORT","DB_OPERATION_BATCH_SIZE"],"mappings":"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"}
export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels } from './redis/redis-dc-subscriber.js';
export { bindTracingChannelToSpan } from './tracing-channel.js';
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
import * as diagnosticsChannel from 'node:diagnostics_channel';
import { defineIntegration, debug, SPAN_STATUS_ERROR, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, withActiveSpan } from '@sentry/core';
import { defineIntegration, debug, SPAN_STATUS_ERROR, bindScopeToEmitter, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getCurrentScope, withScope } from '@sentry/core';
import { DEBUG_BUILD } from '../../debug-build.js';

@@ -21,2 +21,3 @@ import { CHANNELS } from '../../orchestrion/channels.js';

const spans = /* @__PURE__ */ new WeakMap();
const parentScopes = /* @__PURE__ */ new WeakMap();
queryCh.subscribe({

@@ -44,4 +45,5 @@ start(rawCtx) {

spans.set(rawCtx, span);
const parentSpan = getActiveSpan();
if (parentSpan && ctx.arguments.length > 0) {
const scope = getCurrentScope();
parentScopes.set(rawCtx, scope);
if (ctx.arguments.length > 0) {
const cbIdx = ctx.arguments.length - 1;

@@ -52,3 +54,3 @@ const orchestrionWrappedCb = ctx.arguments[cbIdx];

ctx.arguments[cbIdx] = function(...args) {
return withActiveSpan(parentSpan, () => wrapped.apply(this, args));
return withScope(scope, () => wrapped.apply(this, args));
};

@@ -68,2 +70,6 @@ }

if (!span) return;
const parentScope = parentScopes.get(rawCtx);
if (parentScope) {
bindScopeToEmitter(result, parentScope);
}
result.on("error", (err) => {

@@ -100,2 +106,3 @@ span.setStatus({

spans.delete(rawCtx);
parentScopes.delete(rawCtx);
}

@@ -102,0 +109,0 @@ }

@@ -1,1 +0,1 @@

{"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Span } from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getActiveSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withActiveSpan,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array the wrapper passes to the wrapped function:\n * orchestrion splices the user's callback out and inserts its own wrapper at\n * the same index before publishing `start`. We mutate that last entry again in\n * our `start` hook so the callback (and any nested `connection.query(...)`)\n * runs inside `withActiveSpan(parent, …)` — mysql v2 loses ALS state when it\n * dispatches callbacks from its socket handler, which would otherwise cause\n * nested queries to begin a fresh root trace.\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Each `context` object is shared across start/end/asyncStart/asyncEnd/error\n // for one call (orchestrion creates one per invocation). We key the span\n // off the same identity. WeakMap so we don't leak if a path never reaches\n // asyncEnd for some reason.\n const spans = new WeakMap<object, Span>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // We end the span on `asyncEnd` for the two callback paths (so the span\n // covers the full network round-trip + callback duration). For the\n // sync-throw path, `end` finishes the span because `ctx.error` is set\n // there. For the streamable no-callback path, `end` finishes by\n // attaching `'end'`/`'error'` listeners to `ctx.result` (the returned\n // `Query` emitter).\n //\n // The discriminator between \"end fired before any error\" and \"end fired\n // after a sync throw\" is whether `ctx.error` is set when `end` runs —\n // orchestrion populates it before publishing `error`. The discriminator\n // between callback and no-callback is whether `ctx.result` is set — only\n // the `wrapPromise` (no-callback) path stores it.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Restore the Sentry/OTel context across mysql's internal callback\n // dispatch. The orchestrion transform has already spliced the user's\n // callback out of `ctx.arguments` and put its own wrapper\n // (`__apm$wrappedCb`) at the same index. mysql v2 drains callbacks\n // from a socket data handler — by the time the response arrives, the\n // AsyncLocalStorage store backing `getActiveSpan()` no longer\n // reflects the caller's context. We re-wrap orchestrion's wrapper so\n // the user's callback (and any nested `connection.query(...)` inside\n // it) runs with the parent span active again.\n //\n // This must happen at `start` (we're synchronously inside the\n // caller's `connection.query` call, so OTel context is still\n // correct). `asyncStart`/`asyncEnd` fire from the same lost context\n // as the callback itself, so they're too late.\n const parentSpan = getActiveSpan();\n if (parentSpan && ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withActiveSpan(parentSpan, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // TODO: streaming spans aren't finished on `connection.destroy()` —\n // mysql guarantees no further events/callbacks for a destroyed\n // connection, so neither `'end'` nor `'error'` fires and the span\n // never ends (it's dropped, never reported). Closing this gap needs\n // connection-level lifecycle hooks, which the per-query channel\n // context doesn't expose here. The `WeakMap` still prevents a leak.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":[],"mappings":"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"}
{"version":3,"file":"mysql.js","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"sourcesContent":["import * as diagnosticsChannel from 'node:diagnostics_channel';\nimport type { IntegrationFn, Scope, Span } from '@sentry/core';\nimport {\n bindScopeToEmitter,\n debug,\n defineIntegration,\n getCurrentScope,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startInactiveSpan,\n withScope,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { CHANNELS } from '../../orchestrion/channels';\n\n// NOTE: this uses the same name as the OTel integration by design.\n// When enabled, OTel 'Mysql' integration is omitted from the default set.\nconst INTEGRATION_NAME = 'Mysql';\n\n// OpenTelemetry \"OLD\" db/net semantic-conventions. We inline them rather than\n// importing `@opentelemetry/semantic-conventions` to keep this integration's\n// dependency surface free of OTel — orchestrion's whole point is to step away\n// from the OTel auto-instrumentation stack.\n//\n// We emit the OLD conventions to match `@opentelemetry/instrumentation-mysql`'s\n// default (it only emits the stable `db.system.name` / `db.query.text` set when\n// `OTEL_SEMCONV_STABILITY_OPT_IN=database` is opted into) and the rest of the\n// Sentry JS SDK, whose `inferDbSpanData` processor renames spans based on\n// `db.statement`.\nconst ATTR_DB_SYSTEM = 'db.system';\nconst ATTR_DB_CONNECTION_STRING = 'db.connection_string';\nconst ATTR_DB_NAME = 'db.name';\nconst ATTR_DB_USER = 'db.user';\nconst ATTR_DB_STATEMENT = 'db.statement';\nconst ATTR_NET_PEER_NAME = 'net.peer.name';\nconst ATTR_NET_PEER_PORT = 'net.peer.port';\n\n/**\n * The shape orchestrion's wrapCallback transform attaches to the tracing-channel\n * `context` object. Documented here rather than imported because orchestrion's\n * runtime doesn't export it — see `node_modules/@apm-js-collab/code-transformer/lib/transforms.js`.\n *\n * `arguments` is the *live* args array passed to the wrapped function: orchestrion\n * splices the user's callback out and inserts its own wrapper at the same index\n * before publishing `start`. The `start` hook re-wraps that entry to restore the\n * caller's scope across mysql's async callback dispatch (see below).\n */\ninterface MysqlQueryChannelContext {\n arguments: unknown[];\n self?: MysqlConnection;\n moduleVersion?: string;\n result?: unknown;\n error?: unknown;\n}\n\ninterface MysqlConnectionConfig {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n // Pool connections nest the real config one level deeper.\n connectionConfig?: MysqlConnectionConfig;\n}\n\ninterface MysqlConnection {\n config?: MysqlConnectionConfig;\n}\n\nconst _mysqlChannelIntegration = (() => {\n return {\n name: INTEGRATION_NAME,\n setupOnce() {\n DEBUG_BUILD && debug.log(`[orchestrion:mysql] subscribing to channel \"${CHANNELS.MYSQL_QUERY}\"`);\n const queryCh = diagnosticsChannel.tracingChannel(CHANNELS.MYSQL_QUERY);\n\n // Orchestrion creates one `context` object per call, shared across all\n // lifecycle hooks. We key both maps off that identity; `WeakMap` so an\n // unfinished path can't leak its entries.\n const spans = new WeakMap<object, Span>();\n // The scope active when the query was issued, consumed in `end` to bind\n // the streamed `Query` emitter's listeners to it.\n const parentScopes = new WeakMap<object, Scope>();\n\n // `subscribe()` requires all five lifecycle hooks. The orchestrion\n // `wrapAuto` transform fires events in one of four orders depending on\n // call shape:\n // - sync throw : start → error → end\n // (NO asyncEnd)\n // - async-callback error : start → end → error →\n // asyncStart → asyncEnd\n // - async-callback success : start → end → asyncStart →\n // asyncEnd\n // - no-callback (streamable Query) : start → end\n // (ctx.result is the Query\n // emitter, no async events)\n //\n // Where the span closes depends on the path: `asyncEnd` for callbacks (so\n // it spans the full round-trip + callback), or `end` for the sync-throw\n // and streamable paths. The `end` hook tells those apart via `ctx.error`\n // / `ctx.result` — see there.\n queryCh.subscribe({\n start(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const sql = extractSql(ctx.arguments[0]);\n const { host, port, database, user } = getConnectionConfig(ctx.self);\n const portNumber = typeof port === 'string' ? parseInt(port, 10) : port;\n const portIsNumber = typeof portNumber === 'number' && !isNaN(portNumber);\n\n const span = startInactiveSpan({\n name: sql ?? 'mysql.query',\n op: 'db',\n attributes: {\n [ATTR_DB_SYSTEM]: 'mysql',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.orchestrion.mysql',\n [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database),\n ...(database ? { [ATTR_DB_NAME]: database } : {}),\n ...(user ? { [ATTR_DB_USER]: user } : {}),\n ...(sql ? { [ATTR_DB_STATEMENT]: sql } : {}),\n ...(host ? { [ATTR_NET_PEER_NAME]: host } : {}),\n ...(portIsNumber ? { [ATTR_NET_PEER_PORT]: portNumber } : {}),\n },\n });\n spans.set(rawCtx, span);\n\n // Capture the scope while we're still synchronously inside the\n // caller's `connection.query` call. mysql v2 drains callbacks and\n // emits streamed-query events from its socket data handler, where the\n // AsyncLocalStorage store backing the active span no longer reflects\n // the caller's context — and `asyncStart`/`asyncEnd` fire from that\n // same lost context, so capturing has to happen now.\n const scope = getCurrentScope();\n parentScopes.set(rawCtx, scope);\n\n // Callback path: orchestrion has spliced the user's callback out of\n // `ctx.arguments` and put its own wrapper (`__apm$wrappedCb`) at the\n // same index. Re-wrap it so the callback — and any nested\n // `connection.query(...)` — runs with the captured scope active.\n if (ctx.arguments.length > 0) {\n const cbIdx = ctx.arguments.length - 1;\n const orchestrionWrappedCb = ctx.arguments[cbIdx];\n if (typeof orchestrionWrappedCb === 'function') {\n const wrapped = orchestrionWrappedCb as (...a: unknown[]) => unknown;\n ctx.arguments[cbIdx] = function (this: unknown, ...args: unknown[]): unknown {\n return withScope(scope, () => wrapped.apply(this, args));\n };\n }\n }\n },\n\n end(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n\n // Sync throw: `end` fires AFTER `error` (both inside the wrapper's\n // `try/catch/finally`), so `ctx.error` is already set. Close the\n // span now since no `asyncEnd` will fire.\n if (ctx.error !== undefined) {\n finishSpan(rawCtx);\n return;\n }\n\n // No-callback (streamable Query) path: orchestrion's `wrapPromise`\n // stores the synchronous return value on `ctx.result` and never\n // fires `asyncStart`/`asyncEnd`. The returned `Query` is an\n // `EventEmitter` that emits `'end'` on success and `'error'` on\n // failure — hook those to close the span.\n // Note: a streamed span never finishes if the connection is destroyed\n // mid-flight — mysql then emits neither `'end'` nor `'error'`, so the\n // span is dropped (the `WeakMap` still prevents a leak). Closing this\n // needs connection-level hooks the per-query context doesn't expose.\n const result = ctx.result;\n if (result && typeof result === 'object' && hasOnMethod(result)) {\n const span = spans.get(rawCtx);\n if (!span) return;\n\n // Bind the captured scope to the streamed `Query` emitter: its\n // `'end'`/`'error'`/`'fields'`/… events fire from mysql's socket\n // handler with the caller's context lost, so without this a span\n // started in a user's stream listener would begin a fresh root trace\n // instead of nesting under the parent. `bindScopeToEmitter` patches\n // `on`/`addListener`/… so listeners added after `query()` returns\n // inherit the scope (like OTel's `context.bind`).\n const parentScope = parentScopes.get(rawCtx);\n if (parentScope) {\n bindScopeToEmitter(result, parentScope);\n }\n\n result.on('error', err => {\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: err instanceof Error ? err.message : 'unknown_error',\n });\n // Defensive: end the span here too in case `'end'` never fires\n // (e.g. abrupt socket destruction). `finishSpan` is idempotent —\n // `spans.delete` makes the subsequent `'end'` listener a no-op.\n finishSpan(rawCtx);\n });\n result.on('end', () => finishSpan(rawCtx));\n return;\n }\n\n // Callback path: `asyncEnd` will close the span. Nothing to do here.\n },\n\n error(rawCtx) {\n const ctx = rawCtx as MysqlQueryChannelContext;\n const span = spans.get(rawCtx);\n if (!span) return;\n span.setStatus({\n code: SPAN_STATUS_ERROR,\n message: ctx.error instanceof Error ? ctx.error.message : 'unknown_error',\n });\n },\n\n asyncStart() {\n // No-op: we end on `asyncEnd` so the span covers the full callback duration.\n },\n\n asyncEnd(rawCtx) {\n finishSpan(rawCtx);\n },\n });\n\n function finishSpan(rawCtx: object): void {\n const span = spans.get(rawCtx);\n if (!span) return;\n span.end();\n spans.delete(rawCtx);\n parentScopes.delete(rawCtx);\n }\n },\n };\n}) satisfies IntegrationFn;\n\nfunction hasOnMethod(obj: object): obj is { on: (event: string, listener: (arg?: unknown) => void) => unknown } {\n return 'on' in obj && typeof (obj as { on?: unknown }).on === 'function';\n}\n\nfunction extractSql(firstArg: unknown): string | undefined {\n if (typeof firstArg === 'string') {\n return firstArg;\n }\n if (firstArg && typeof firstArg === 'object' && 'sql' in firstArg) {\n const sql = (firstArg as { sql?: unknown }).sql;\n return typeof sql === 'string' ? sql : undefined;\n }\n return undefined;\n}\n\nfunction getConnectionConfig(connection: MysqlConnection | undefined): {\n host?: string;\n port?: number | string;\n database?: string;\n user?: string;\n} {\n // Pool connections nest the real config under `.connectionConfig`; single\n // connections expose it directly. Matches `@opentelemetry/instrumentation-mysql`.\n const config = connection?.config?.connectionConfig ?? connection?.config ?? {};\n return {\n host: config.host,\n port: config.port,\n database: config.database,\n user: config.user,\n };\n}\n\nfunction getJDBCString(host: string | undefined, port: number | undefined, database: string | undefined): string {\n let s = `jdbc:mysql://${host || 'localhost'}`;\n if (typeof port === 'number') {\n s += `:${port}`;\n }\n if (database) {\n s += `/${database}`;\n }\n return s;\n}\n\n/**\n * EXPERIMENTAL — orchestrion-driven mysql integration.\n *\n * Subscribes to the `orchestrion:mysql:query` diagnostics_channel that the\n * orchestrion code transform injects into `mysql/lib/Connection.js`'s\n * `Connection.prototype.query`. Requires the orchestrion runtime hook or\n * bundler plugin to be active — wire that up via `_experimentalSetupOrchestrion`.\n */\nexport const mysqlChannelIntegration = defineIntegration(_mysqlChannelIntegration);\n"],"names":[],"mappings":"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"}

@@ -1,1 +0,1 @@

{"type":"module","version":"10.60.0","sideEffects":false}
{"type":"module","version":"10.61.0","sideEffects":false}
import { SERVER_PORT, SERVER_ADDRESS, DB_QUERY_TEXT, DB_SYSTEM_NAME, DB_OPERATION_BATCH_SIZE } from '@sentry/conventions/attributes';
import { debug, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR } from '@sentry/core';
import { debug, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { DEBUG_BUILD } from '../debug-build.js';
import { bindTracingChannelToSpan } from '../tracing-channel.js';

@@ -12,6 +13,5 @@ const REDIS_DC_CHANNEL_COMMAND = "node-redis:command";

const DB_SYSTEM_NAME_VALUE_REDIS = "redis";
const NOOP = () => {
};
let subscribed = false;
let currentResponseHook;
let activeUnbinds = [];
function subscribeRedisDiagnosticChannels(tracingChannel, responseHook) {

@@ -22,11 +22,16 @@ currentResponseHook = responseHook;

try {
setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1));
setupBatchChannel(
tracingChannel,
REDIS_DC_CHANNEL_BATCH,
(data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI"
activeUnbinds.push(
setupCommandChannel(tracingChannel, REDIS_DC_CHANNEL_COMMAND, (data) => data.args.slice(1)),
setupBatchChannel(
tracingChannel,
REDIS_DC_CHANNEL_BATCH,
(data) => data.batchMode === "PIPELINE" ? "PIPELINE" : "MULTI"
),
setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),
// ioredis: args already exclude the command name; no slicing needed. And
// ioredis has no separate batch channel — pipeline/MULTI metadata rides
// on the per-command payload via `batchMode`/`batchSize`.
setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args),
setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT)
);
setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);
setupCommandChannel(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, (data) => data.args);
setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);
} catch {

@@ -37,7 +42,8 @@ DEBUG_BUILD && debug.log("Redis node:diagnostics_channel subscription failed.");

function setupCommandChannel(tracingChannel, channelName, getCommandArgs) {
const channel = tracingChannel(channelName, (data) => {
const args = getCommandArgs(data);
const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command;
return startSpanManual(
{
return bindTracingChannelToSpan(
tracingChannel(channelName),
(data) => {
const args = getCommandArgs(data);
const statement = args.length ? `${data.command} ${args.join(" ")}` : data.command;
return startInactiveSpan({
name: `redis-${data.command}`,

@@ -52,30 +58,20 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
const span = data._sentrySpan;
if (!span || data.error) return;
runResponseHook(span, data.command, getCommandArgs(data), data.result);
span.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });
{
// Command failures are surfaced to (and usually handled by) the caller; only annotate the
// span so we don't emit a duplicate error event for every failed command.
captureError: false,
beforeSpanEnd(span, data) {
if ("error" in data) return;
runResponseHook(span, data.command, getCommandArgs(data), data.result);
}
span.end();
}
});
).unbind;
}
function setupBatchChannel(tracingChannel, channelName, getOperationName) {
const channel = tracingChannel(channelName, (data) => {
return startSpanManual(
{
return bindTracingChannelToSpan(
tracingChannel(channelName),
(data) => {
return startInactiveSpan({
name: getOperationName(data),

@@ -92,27 +88,12 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
if (!data.error) data._sentrySpan?.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });
}
span.end();
}
});
{ captureError: false }
).unbind;
}
function setupConnectChannel(tracingChannel, channelName) {
const channel = tracingChannel(channelName, (data) => {
return startSpanManual(
{
return bindTracingChannelToSpan(
tracingChannel(channelName),
(data) => {
return startInactiveSpan({
name: "redis-connect",

@@ -126,22 +107,6 @@ attributes: {

}
},
(span) => span
);
});
channel.subscribe({
start: NOOP,
asyncStart: NOOP,
end: NOOP,
asyncEnd: (data) => {
if (!data.error) data._sentrySpan?.end();
});
},
error: (data) => {
const span = data._sentrySpan;
if (!span) return;
if (data.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });
}
span.end();
}
});
{ captureError: false }
).unbind;
}

@@ -148,0 +113,0 @@ function runResponseHook(span, command, args, result) {

@@ -1,1 +0,1 @@

{"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SPAN_STATUS_ERROR,\n startSpanManual,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\nconst NOOP = (): void => {};\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Payload type observed by tracing-channel subscribers — the channel payload\n * with `_sentrySpan` stamped on it by the start handler so async/error\n * handlers downstream can read it back.\n */\nexport type RedisTracingChannelContextWithSpan<T> = T & { _sentrySpan?: Span };\n\n/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */\nexport interface RedisTracingChannelSubscribers<T> {\n start: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;\n asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;\n end: (data: RedisTracingChannelContextWithSpan<T>) => void;\n error: (data: RedisTracingChannelContextWithSpan<T>) => void;\n}\n\n/** Minimal tracing-channel surface the subscriber depends on. */\nexport interface RedisTracingChannel<T extends object> {\n subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;\n}\n\n/**\n * Platform-provided factory that returns a tracing channel for the given\n * channel name. Implementations are responsible for ensuring that, when the\n * channel's `start` event fires, the span returned by `transformStart(data)`\n * ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`\n * handlers can read it.\n *\n * - Node passes `@sentry/opentelemetry/tracing-channel` which uses\n * `bindStore` to also propagate the span as the active OTel context.\n * - Deno (and other non-OTel runtimes) pass a portable wrapper around\n * `node:diagnostics_channel.tracingChannel` that just stamps\n * `data._sentrySpan` in `start` without `bindStore`.\n */\nexport type RedisTracingChannelFactory = <T extends object>(\n name: string,\n transformStart: (data: T) => Span,\n) => RedisTracingChannel<T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1));\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n );\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT);\n\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args);\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT);\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): void {\n const channel = tracingChannel<T>(channelName, data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startSpanManual(\n {\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n const span = data._sentrySpan;\n // Only end here if the error handler isn't going to.\n if (!span || data.error) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n span.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): void {\n const channel = tracingChannel<RedisBatchData>(channelName, data => {\n return startSpanManual(\n {\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): void {\n const channel = tracingChannel<RedisConnectData>(channelName, data => {\n return startSpanManual(\n {\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n },\n span => span,\n );\n });\n\n channel.subscribe({\n start: NOOP,\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: data => {\n if (!data.error) data._sentrySpan?.end();\n },\n error: data => {\n const span = data._sentrySpan;\n if (!span) return;\n if (data.error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: data.error.message });\n }\n span.end();\n },\n });\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":[],"mappings":";;;;AAqBO,MAAM,wBAAA,GAA2B;AACjC,MAAM,sBAAA,GAAyB;AAC/B,MAAM,wBAAA,GAA2B;AACjC,MAAM,0BAAA,GAA6B;AACnC,MAAM,0BAAA,GAA6B;AAE1C,MAAM,MAAA,GAAS,kCAAA;AACf,MAAM,0BAAA,GAA6B,OAAA;AAEnC,MAAM,OAAO,MAAY;AAAC,CAAA;AAkH1B,IAAI,UAAA,GAAa,KAAA;AACjB,IAAI,mBAAA;AAcG,SAAS,gCAAA,CACd,gBACA,YAAA,EACM;AACN,EAAA,mBAAA,GAAsB,YAAA;AACtB,EAAA,IAAI,UAAA,EAAY;AAChB,EAAA,UAAA,GAAa,IAAA;AAEb,EAAA,IAAI;AAGF,IAAA,mBAAA,CAAsC,gBAAgB,wBAAA,EAA0B,CAAA,IAAA,KAAQ,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAC1G,IAAA,iBAAA;AAAA,MAAkB,cAAA;AAAA,MAAgB,sBAAA;AAAA,MAAwB,CAAA,IAAA,KACxD,IAAA,CAAK,SAAA,KAAc,UAAA,GAAa,UAAA,GAAa;AAAA,KAC/C;AACA,IAAA,mBAAA,CAAoB,gBAAgB,wBAAwB,CAAA;AAK5D,IAAA,mBAAA,CAAwC,cAAA,EAAgB,0BAAA,EAA4B,CAAA,IAAA,KAAQ,IAAA,CAAK,IAAI,CAAA;AACrG,IAAA,mBAAA,CAAoB,gBAAgB,0BAA0B,CAAA;AAAA,EAChE,CAAA,CAAA,MAAQ;AAGN,IAAA,WAAA,IAAe,KAAA,CAAM,IAAI,qDAAqD,CAAA;AAAA,EAChF;AACF;AAEA,SAAS,mBAAA,CACP,cAAA,EACA,WAAA,EACA,cAAA,EACM;AACN,EAAA,MAAM,OAAA,GAAU,cAAA,CAAkB,WAAA,EAAa,CAAA,IAAA,KAAQ;AAIrD,IAAA,MAAM,IAAA,GAAO,eAAe,IAAI,CAAA;AAChC,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,CAAA,EAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,GAAK,IAAA,CAAK,OAAA;AAC3E,IAAA,OAAO,eAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,CAAA,MAAA,EAAS,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,QAC3B,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,UAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA,UAClB,CAAC,aAAa,GAAG,SAAA;AAAA,UACjB,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACF;AAAA,MACA,CAAA,IAAA,KAAQ;AAAA,KACV;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAA,CAAQ,SAAA,CAAU;AAAA,IAChB,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,IAAA;AAAA,IACZ,GAAA,EAAK,IAAA;AAAA,IACL,UAAU,CAAA,IAAA,KAAQ;AAChB,MAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAElB,MAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,KAAA,EAAO;AACzB,MAAA,eAAA,CAAgB,MAAM,IAAA,CAAK,OAAA,EAAS,eAAe,IAAI,CAAA,EAAG,KAAK,MAAM,CAAA;AACrE,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX,CAAA;AAAA,IACA,OAAO,CAAA,IAAA,KAAQ;AACb,MAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,IAAI,KAAK,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,SAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAAA,MACzE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,GACD,CAAA;AACH;AAEA,SAAS,iBAAA,CACP,cAAA,EACA,WAAA,EACA,gBAAA,EACM;AACN,EAAA,MAAM,OAAA,GAAU,cAAA,CAA+B,WAAA,EAAa,CAAA,IAAA,KAAQ;AAClE,IAAA,OAAO,eAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,iBAAiB,IAAI,CAAA;AAAA,QAC3B,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,UAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA;AAAA;AAAA,UAGlB,GAAI,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,GAAI,CAAA,GAAI,EAAE,CAAC,uBAAuB,GAAG,IAAA,CAAK,SAAA,KAAc,EAAC;AAAA,UAClF,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACF;AAAA,MACA,CAAA,IAAA,KAAQ;AAAA,KACV;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAA,CAAQ,SAAA,CAAU;AAAA,IAChB,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,IAAA;AAAA,IACZ,GAAA,EAAK,IAAA;AAAA,IACL,UAAU,CAAA,IAAA,KAAQ;AAChB,MAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,aAAa,GAAA,EAAI;AAAA,IACzC,CAAA;AAAA,IACA,OAAO,CAAA,IAAA,KAAQ;AACb,MAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,IAAI,KAAK,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,SAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAAA,MACzE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,GACD,CAAA;AACH;AAEA,SAAS,mBAAA,CAAoB,gBAA4C,WAAA,EAA2B;AAClG,EAAA,MAAM,OAAA,GAAU,cAAA,CAAiC,WAAA,EAAa,CAAA,IAAA,KAAQ;AACpE,IAAA,OAAO,eAAA;AAAA,MACL;AAAA,QACE,IAAA,EAAM,eAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,kBAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA,UAClB,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACF;AAAA,MACA,CAAA,IAAA,KAAQ;AAAA,KACV;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAA,CAAQ,SAAA,CAAU;AAAA,IAChB,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,IAAA;AAAA,IACZ,GAAA,EAAK,IAAA;AAAA,IACL,UAAU,CAAA,IAAA,KAAQ;AAChB,MAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,aAAa,GAAA,EAAI;AAAA,IACzC,CAAA;AAAA,IACA,OAAO,CAAA,IAAA,KAAQ;AACb,MAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAClB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,IAAI,KAAK,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,SAAS,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAAA,MACzE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AAAA,IACX;AAAA,GACD,CAAA;AACH;AAEA,SAAS,eAAA,CAAgB,IAAA,EAAY,OAAA,EAAiB,IAAA,EAAgB,MAAA,EAAuB;AAC3F,EAAA,MAAM,IAAA,GAAO,mBAAA;AACb,EAAA,IAAI,CAAC,IAAA,EAAM;AACX,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,MAAM,CAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;;;;"}
{"version":3,"file":"redis-dc-subscriber.js","sources":["../../../src/redis/redis-dc-subscriber.ts"],"sourcesContent":["import type { TracingChannel } from 'node:diagnostics_channel';\nimport {\n DB_OPERATION_BATCH_SIZE,\n DB_QUERY_TEXT,\n DB_SYSTEM_NAME,\n SERVER_ADDRESS,\n SERVER_PORT,\n} from '@sentry/conventions/attributes';\nimport type { Span } from '@sentry/core';\nimport { debug, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { bindTracingChannelToSpan } from '../tracing-channel';\n\n// Channel names published by node-redis >= 5.12.0 and ioredis >= 5.11.0.\n// Hardcoded so the subscriber does not have to import either library — the\n// channels just have to be subscribed to before the user's redis code\n// publishes.\nexport const REDIS_DC_CHANNEL_COMMAND = 'node-redis:command';\nexport const REDIS_DC_CHANNEL_BATCH = 'node-redis:batch';\nexport const REDIS_DC_CHANNEL_CONNECT = 'node-redis:connect';\nexport const IOREDIS_DC_CHANNEL_COMMAND = 'ioredis:command';\nexport const IOREDIS_DC_CHANNEL_CONNECT = 'ioredis:connect';\n\nconst ORIGIN = 'auto.db.redis.diagnostic_channel';\nconst DB_SYSTEM_NAME_VALUE_REDIS = 'redis';\n\n/**\n * Shape of the `node-redis:command` channel payload published by node-redis.\n *\n * Both `command` and `args` are already redacted by node-redis itself (see\n * `sanitizeArgs` in @redis/client) using the OTel `redis-common` rules. The\n * arg array is `[<command>, <safe arg>, ..., '?', ...]` — `?` replaces any\n * value the library considers sensitive. Subscribers can emit `args` directly\n * as `db.query.text` without further serialization.\n */\nexport interface RedisCommandData {\n command: string;\n /** First arg is the command name itself; consumers should slice it off. */\n args: string[];\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/**\n * Shape of the `ioredis:command` channel payload published by ioredis >= 5.11.0.\n *\n * As with node-redis, args are already sanitized by ioredis (`sanitizeArgs` in\n * `lib/tracing.ts`) before publishing. Unlike node-redis, the command name is\n * NOT prepended to `args`.\n */\nexport interface IORedisCommandData {\n command: string;\n args: string[];\n batchMode?: 'MULTI';\n batchSize?: number;\n database?: number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown;\n error?: Error;\n}\n\n/** Shape of the `node-redis:batch` channel payload published by node-redis. */\nexport interface RedisBatchData {\n batchMode?: 'MULTI' | 'PIPELINE';\n batchSize?: number;\n database?: number;\n clientId?: string | number;\n serverAddress?: string;\n serverPort?: number;\n result?: unknown[];\n error?: Error;\n}\n\n/** Shape of the `*:connect` channel payload published by node-redis and ioredis. */\nexport interface RedisConnectData {\n serverAddress?: string;\n serverPort?: number;\n url?: string;\n error?: Error;\n}\n\n/**\n * Optional callback invoked once the redis command response arrives. Useful\n * for attaching response-derived attributes (e.g. cache hit/miss, payload size).\n *\n * Mirrors `@opentelemetry/instrumentation-ioredis`' response hook so existing\n * Sentry node code (`cacheResponseHook`) can be reused unchanged.\n */\nexport type RedisDiagnosticChannelResponseHook = (\n span: Span,\n cmdName: string,\n cmdArgs: string[],\n result: unknown,\n) => void;\n\n/**\n * Platform-provided factory that creates a native tracing channel for the given name. The\n * subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,\n * which propagates the active span through the runtime's async context.\n *\n * Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.\n */\nexport type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;\n\nlet subscribed = false;\nlet currentResponseHook: RedisDiagnosticChannelResponseHook | undefined;\nlet activeUnbinds: Array<() => void> = [];\n\n/**\n * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel\n * events: `node-redis:command`/`:batch`/`:connect` (published by node-redis\n * >= 5.12.0) and `ioredis:command`/`:connect` (published by ioredis >= 5.11.0).\n *\n * On older client versions the channels are never published to, so subscribers\n * are inert — there is no double-instrumentation against any IITM-based\n * patcher gated to those older versions.\n *\n * Idempotent: subsequent calls update the response hook but do not\n * re-subscribe.\n */\nexport function subscribeRedisDiagnosticChannels(\n tracingChannel: RedisTracingChannelFactory,\n responseHook?: RedisDiagnosticChannelResponseHook,\n): void {\n currentResponseHook = responseHook;\n if (subscribed) return;\n subscribed = true;\n\n try {\n // node-redis: command name appears as args[0] in the channel payload, so\n // strip it before the statement and response hook see it.\n activeUnbinds.push(\n setupCommandChannel<RedisCommandData>(tracingChannel, REDIS_DC_CHANNEL_COMMAND, data => data.args.slice(1)),\n setupBatchChannel(tracingChannel, REDIS_DC_CHANNEL_BATCH, data =>\n data.batchMode === 'PIPELINE' ? 'PIPELINE' : 'MULTI',\n ),\n setupConnectChannel(tracingChannel, REDIS_DC_CHANNEL_CONNECT),\n // ioredis: args already exclude the command name; no slicing needed. And\n // ioredis has no separate batch channel — pipeline/MULTI metadata rides\n // on the per-command payload via `batchMode`/`batchSize`.\n setupCommandChannel<IORedisCommandData>(tracingChannel, IOREDIS_DC_CHANNEL_COMMAND, data => data.args),\n setupConnectChannel(tracingChannel, IOREDIS_DC_CHANNEL_CONNECT),\n );\n } catch {\n // The factory may rely on `node:diagnostics_channel`, which isn't always\n // available. Fail closed; the SDK simply won't emit redis spans here.\n DEBUG_BUILD && debug.log('Redis node:diagnostics_channel subscription failed.');\n }\n}\n\nfunction setupCommandChannel<T extends RedisCommandData | IORedisCommandData>(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getCommandArgs: (data: T) => string[],\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<T>(channelName),\n data => {\n // `args` is already sanitized by the publishing library (node-redis /\n // ioredis call their own `sanitizeArgs` before publishing). Join with\n // spaces to mirror the format the libraries themselves intend.\n const args = getCommandArgs(data);\n const statement = args.length ? `${data.command} ${args.join(' ')}` : data.command;\n return startInactiveSpan({\n name: `redis-${data.command}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n [DB_QUERY_TEXT]: statement,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n {\n // Command failures are surfaced to (and usually handled by) the caller; only annotate the\n // span so we don't emit a duplicate error event for every failed command.\n captureError: false,\n beforeSpanEnd(span, data) {\n if ('error' in data) return;\n runResponseHook(span, data.command, getCommandArgs(data), data.result);\n },\n },\n ).unbind;\n}\n\nfunction setupBatchChannel(\n tracingChannel: RedisTracingChannelFactory,\n channelName: string,\n getOperationName: (data: RedisBatchData) => string,\n): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisBatchData>(channelName),\n data => {\n return startInactiveSpan({\n name: getOperationName(data),\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n // should only include batch size greater than 1,\n // or else it isn't properly considered a \"batch\"\n ...(Number(data.batchSize) > 1 ? { [DB_OPERATION_BATCH_SIZE]: data.batchSize } : {}),\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction setupConnectChannel(tracingChannel: RedisTracingChannelFactory, channelName: string): () => void {\n return bindTracingChannelToSpan(\n tracingChannel<RedisConnectData>(channelName),\n data => {\n return startInactiveSpan({\n name: 'redis-connect',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.redis.connect',\n [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_REDIS,\n ...(data.serverAddress != null ? { [SERVER_ADDRESS]: data.serverAddress } : {}),\n ...(data.serverPort != null ? { [SERVER_PORT]: data.serverPort } : {}),\n },\n });\n },\n { captureError: false },\n ).unbind;\n}\n\nfunction runResponseHook(span: Span, command: string, args: string[], result: unknown): void {\n const hook = currentResponseHook;\n if (!hook) return;\n try {\n hook(span, command, args, result);\n } catch {\n // never let user hooks break instrumentation\n }\n}\n\n/** Test-only: detach all channel bindings and reset module-local subscribe state. */\nexport function _resetRedisDiagnosticChannelsForTesting(): void {\n activeUnbinds.forEach(unbind => unbind());\n activeUnbinds = [];\n subscribed = false;\n currentResponseHook = undefined;\n}\n"],"names":[],"mappings":";;;;;AAiBO,MAAM,wBAAA,GAA2B;AACjC,MAAM,sBAAA,GAAyB;AAC/B,MAAM,wBAAA,GAA2B;AACjC,MAAM,0BAAA,GAA6B;AACnC,MAAM,0BAAA,GAA6B;AAE1C,MAAM,MAAA,GAAS,kCAAA;AACf,MAAM,0BAAA,GAA6B,OAAA;AAoFnC,IAAI,UAAA,GAAa,KAAA;AACjB,IAAI,mBAAA;AACJ,IAAI,gBAAmC,EAAC;AAcjC,SAAS,gCAAA,CACd,gBACA,YAAA,EACM;AACN,EAAA,mBAAA,GAAsB,YAAA;AACtB,EAAA,IAAI,UAAA,EAAY;AAChB,EAAA,UAAA,GAAa,IAAA;AAEb,EAAA,IAAI;AAGF,IAAA,aAAA,CAAc,IAAA;AAAA,MACZ,mBAAA,CAAsC,gBAAgB,wBAAA,EAA0B,CAAA,IAAA,KAAQ,KAAK,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,MAC1G,iBAAA;AAAA,QAAkB,cAAA;AAAA,QAAgB,sBAAA;AAAA,QAAwB,CAAA,IAAA,KACxD,IAAA,CAAK,SAAA,KAAc,UAAA,GAAa,UAAA,GAAa;AAAA,OAC/C;AAAA,MACA,mBAAA,CAAoB,gBAAgB,wBAAwB,CAAA;AAAA;AAAA;AAAA;AAAA,MAI5D,mBAAA,CAAwC,cAAA,EAAgB,0BAAA,EAA4B,CAAA,IAAA,KAAQ,KAAK,IAAI,CAAA;AAAA,MACrG,mBAAA,CAAoB,gBAAgB,0BAA0B;AAAA,KAChE;AAAA,EACF,CAAA,CAAA,MAAQ;AAGN,IAAA,WAAA,IAAe,KAAA,CAAM,IAAI,qDAAqD,CAAA;AAAA,EAChF;AACF;AAEA,SAAS,mBAAA,CACP,cAAA,EACA,WAAA,EACA,cAAA,EACY;AACZ,EAAA,OAAO,wBAAA;AAAA,IACL,eAAkB,WAAW,CAAA;AAAA,IAC7B,CAAA,IAAA,KAAQ;AAIN,MAAA,MAAM,IAAA,GAAO,eAAe,IAAI,CAAA;AAChC,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,CAAA,EAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,GAAK,IAAA,CAAK,OAAA;AAC3E,MAAA,OAAO,iBAAA,CAAkB;AAAA,QACvB,IAAA,EAAM,CAAA,MAAA,EAAS,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,QAC3B,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,UAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA,UAClB,CAAC,aAAa,GAAG,SAAA;AAAA,UACjB,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA;AAAA;AAAA;AAAA,MAGE,YAAA,EAAc,KAAA;AAAA,MACd,aAAA,CAAc,MAAM,IAAA,EAAM;AACxB,QAAA,IAAI,WAAW,IAAA,EAAM;AACrB,QAAA,eAAA,CAAgB,MAAM,IAAA,CAAK,OAAA,EAAS,eAAe,IAAI,CAAA,EAAG,KAAK,MAAM,CAAA;AAAA,MACvE;AAAA;AACF,GACF,CAAE,MAAA;AACJ;AAEA,SAAS,iBAAA,CACP,cAAA,EACA,WAAA,EACA,gBAAA,EACY;AACZ,EAAA,OAAO,wBAAA;AAAA,IACL,eAA+B,WAAW,CAAA;AAAA,IAC1C,CAAA,IAAA,KAAQ;AACN,MAAA,OAAO,iBAAA,CAAkB;AAAA,QACvB,IAAA,EAAM,iBAAiB,IAAI,CAAA;AAAA,QAC3B,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,UAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA;AAAA;AAAA,UAGlB,GAAI,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,GAAI,CAAA,GAAI,EAAE,CAAC,uBAAuB,GAAG,IAAA,CAAK,SAAA,KAAc,EAAC;AAAA,UAClF,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,EAAE,cAAc,KAAA;AAAM,GACxB,CAAE,MAAA;AACJ;AAEA,SAAS,mBAAA,CAAoB,gBAA4C,WAAA,EAAiC;AACxG,EAAA,OAAO,wBAAA;AAAA,IACL,eAAiC,WAAW,CAAA;AAAA,IAC5C,CAAA,IAAA,KAAQ;AACN,MAAA,OAAO,iBAAA,CAAkB;AAAA,QACvB,IAAA,EAAM,eAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,UACpC,CAAC,4BAA4B,GAAG,kBAAA;AAAA,UAChC,CAAC,cAAc,GAAG,0BAAA;AAAA,UAClB,GAAI,IAAA,CAAK,aAAA,IAAiB,IAAA,GAAO,EAAE,CAAC,cAAc,GAAG,IAAA,CAAK,aAAA,EAAc,GAAI,EAAC;AAAA,UAC7E,GAAI,IAAA,CAAK,UAAA,IAAc,IAAA,GAAO,EAAE,CAAC,WAAW,GAAG,IAAA,CAAK,UAAA,EAAW,GAAI;AAAC;AACtE,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,EAAE,cAAc,KAAA;AAAM,GACxB,CAAE,MAAA;AACJ;AAEA,SAAS,eAAA,CAAgB,IAAA,EAAY,OAAA,EAAiB,IAAA,EAAgB,MAAA,EAAuB;AAC3F,EAAA,MAAM,IAAA,GAAO,mBAAA;AACb,EAAA,IAAI,CAAC,IAAA,EAAM;AACX,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,MAAM,CAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;;;;"}

@@ -7,3 +7,5 @@ /**

export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels, } from './redis/redis-dc-subscriber';
export { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannel, RedisTracingChannelContextWithSpan, RedisTracingChannelFactory, RedisTracingChannelSubscribers, } from './redis/redis-dc-subscriber';
export { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannelFactory, } from './redis/redis-dc-subscriber';
export { bindTracingChannelToSpan } from './tracing-channel';
export { SentryTracingChannel, TracingChannelLifeCycleOptions, TracingChannelBindingHandle, TracingChannelPayloadWithSpan, } from './tracing-channel';
//# sourceMappingURL=index.d.ts.map

@@ -0,1 +1,2 @@

import { TracingChannel } from 'node:diagnostics_channel';
import { Span } from '@sentry/core';

@@ -71,35 +72,9 @@ export declare const REDIS_DC_CHANNEL_COMMAND = "node-redis:command";

/**
* Payload type observed by tracing-channel subscribers — the channel payload
* with `_sentrySpan` stamped on it by the start handler so async/error
* handlers downstream can read it back.
*/
export type RedisTracingChannelContextWithSpan<T> = T & {
_sentrySpan?: Span;
};
/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */
export interface RedisTracingChannelSubscribers<T> {
start: (data: RedisTracingChannelContextWithSpan<T>) => void;
asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;
asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;
end: (data: RedisTracingChannelContextWithSpan<T>) => void;
error: (data: RedisTracingChannelContextWithSpan<T>) => void;
}
/** Minimal tracing-channel surface the subscriber depends on. */
export interface RedisTracingChannel<T extends object> {
subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;
}
/**
* Platform-provided factory that returns a tracing channel for the given
* channel name. Implementations are responsible for ensuring that, when the
* channel's `start` event fires, the span returned by `transformStart(data)`
* ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`
* handlers can read it.
* Platform-provided factory that creates a native tracing channel for the given name. The
* subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,
* which propagates the active span through the runtime's async context.
*
* - Node passes `@sentry/opentelemetry/tracing-channel` which uses
* `bindStore` to also propagate the span as the active OTel context.
* - Deno (and other non-OTel runtimes) pass a portable wrapper around
* `node:diagnostics_channel.tracingChannel` that just stamps
* `data._sentrySpan` in `start` without `bindStore`.
* Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.
*/
export type RedisTracingChannelFactory = <T extends object>(name: string, transformStart: (data: T) => Span) => RedisTracingChannel<T>;
export type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;
/**

@@ -118,4 +93,4 @@ * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel

export declare function subscribeRedisDiagnosticChannels(tracingChannel: RedisTracingChannelFactory, responseHook?: RedisDiagnosticChannelResponseHook): void;
/** Test-only: reset module-local subscribe state. */
/** Test-only: detach all channel bindings and reset module-local subscribe state. */
export declare function _resetRedisDiagnosticChannelsForTesting(): void;
//# sourceMappingURL=redis-dc-subscriber.d.ts.map

@@ -7,3 +7,5 @@ /**

export { IOREDIS_DC_CHANNEL_COMMAND, IOREDIS_DC_CHANNEL_CONNECT, REDIS_DC_CHANNEL_BATCH, REDIS_DC_CHANNEL_COMMAND, REDIS_DC_CHANNEL_CONNECT, subscribeRedisDiagnosticChannels, } from './redis/redis-dc-subscriber';
export type { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannel, RedisTracingChannelContextWithSpan, RedisTracingChannelFactory, RedisTracingChannelSubscribers, } from './redis/redis-dc-subscriber';
export type { IORedisCommandData, RedisBatchData, RedisCommandData, RedisConnectData, RedisDiagnosticChannelResponseHook, RedisTracingChannelFactory, } from './redis/redis-dc-subscriber';
export { bindTracingChannelToSpan } from './tracing-channel';
export type { SentryTracingChannel, TracingChannelLifeCycleOptions, TracingChannelBindingHandle, TracingChannelPayloadWithSpan, } from './tracing-channel';
//# sourceMappingURL=index.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,kCAAkC,EAClC,mBAAmB,EACnB,kCAAkC,EAClC,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,YAAY,EACV,oBAAoB,EACpB,8BAA8B,EAC9B,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,mBAAmB,CAAC"}

@@ -1,1 +0,1 @@

{"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"names":[],"mappings":"AAkRA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,0CAA8C,CAAC"}
{"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../../../src/integrations/tracing-channel/mysql.ts"],"names":[],"mappings":"AAoRA;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,0CAA8C,CAAC"}

@@ -0,1 +1,2 @@

import type { TracingChannel } from 'node:diagnostics_channel';
import type { Span } from '@sentry/core';

@@ -71,35 +72,9 @@ export declare const REDIS_DC_CHANNEL_COMMAND = "node-redis:command";

/**
* Payload type observed by tracing-channel subscribers — the channel payload
* with `_sentrySpan` stamped on it by the start handler so async/error
* handlers downstream can read it back.
*/
export type RedisTracingChannelContextWithSpan<T> = T & {
_sentrySpan?: Span;
};
/** Subscriber object accepted by {@link RedisTracingChannel.subscribe}. */
export interface RedisTracingChannelSubscribers<T> {
start: (data: RedisTracingChannelContextWithSpan<T>) => void;
asyncStart: (data: RedisTracingChannelContextWithSpan<T>) => void;
asyncEnd: (data: RedisTracingChannelContextWithSpan<T>) => void;
end: (data: RedisTracingChannelContextWithSpan<T>) => void;
error: (data: RedisTracingChannelContextWithSpan<T>) => void;
}
/** Minimal tracing-channel surface the subscriber depends on. */
export interface RedisTracingChannel<T extends object> {
subscribe(subs: Partial<RedisTracingChannelSubscribers<T>>): void;
}
/**
* Platform-provided factory that returns a tracing channel for the given
* channel name. Implementations are responsible for ensuring that, when the
* channel's `start` event fires, the span returned by `transformStart(data)`
* ends up stored on `data._sentrySpan` so the subscriber's `asyncEnd`/`error`
* handlers can read it.
* Platform-provided factory that creates a native tracing channel for the given name. The
* subscriber binds the span and its lifecycle onto the channel via `bindTracingChannelToSpan`,
* which propagates the active span through the runtime's async context.
*
* - Node passes `@sentry/opentelemetry/tracing-channel` which uses
* `bindStore` to also propagate the span as the active OTel context.
* - Deno (and other non-OTel runtimes) pass a portable wrapper around
* `node:diagnostics_channel.tracingChannel` that just stamps
* `data._sentrySpan` in `start` without `bindStore`.
* Both Node and Deno pass `node:diagnostics_channel`'s `tracingChannel` directly.
*/
export type RedisTracingChannelFactory = <T extends object>(name: string, transformStart: (data: T) => Span) => RedisTracingChannel<T>;
export type RedisTracingChannelFactory = <T extends object>(name: string) => TracingChannel<T, T>;
/**

@@ -118,4 +93,4 @@ * Subscribe Sentry span handlers to node-redis and ioredis diagnostics-channel

export declare function subscribeRedisDiagnosticChannels(tracingChannel: RedisTracingChannelFactory, responseHook?: RedisDiagnosticChannelResponseHook): void;
/** Test-only: reset module-local subscribe state. */
/** Test-only: detach all channel bindings and reset module-local subscribe state. */
export declare function _resetRedisDiagnosticChannelsForTesting(): void;
//# sourceMappingURL=redis-dc-subscriber.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"redis-dc-subscriber.d.ts","sourceRoot":"","sources":["../../../src/redis/redis-dc-subscriber.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"redis-dc-subscriber.d.ts","sourceRoot":"","sources":["../../../src/redis/redis-dc-subscriber.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAQ/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AASzC,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAC5D,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAK5D;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kCAAkC,GAAG,CAC/C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,OAAO,KACZ,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAMlG;;;;;;;;;;;GAWG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,EAAE,0BAA0B,EAC1C,YAAY,CAAC,EAAE,kCAAkC,GAChD,IAAI,CAyBN;AA8FD,qFAAqF;AACrF,wBAAgB,uCAAuC,IAAI,IAAI,CAK9D"}
{
"name": "@sentry/server-utils",
"version": "10.60.0",
"version": "10.61.0",
"description": "Server Utilities for all Sentry JavaScript SDKs",

@@ -89,3 +89,3 @@ "repository": "git://github.com/getsentry/sentry-javascript.git",

"@sentry/conventions": "^0.12.0",
"@sentry/core": "10.60.0",
"@sentry/core": "10.61.0",
"magic-string": "~0.30.0"

@@ -92,0 +92,0 @@ },